From 3338ff4e18cd715e6329b5d8fa7bf5a8027fb9f4 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 6 Nov 2020 13:35:42 -0800 Subject: [PATCH] Fixes navigation commands for webviews Fixes #100536 These commands currently do not work because: - The use the `hasFocus` check in layout.ts - This looks at the active element and checks if the active element has a parent in the editor dom - However webviews are outside of the normal dom flow (since they cannot be reparented without being destroyred) To fix this, this PR adds allows dom node to point to their explicit parent using `setParentFlowTo`. Instead of a normal ancestor check, we then check ancestors while observing the flow to parents of node The webview element is then update to have a parent flow to that points at its editor node --- src/vs/base/browser/dom.ts | 37 +++++++++++++++++++ src/vs/workbench/browser/layout.ts | 4 +- .../webviewPanel/browser/webviewEditor.ts | 7 +++- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 430ef80930f..f16be74d631 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -661,6 +661,43 @@ export function isAncestor(testChild: Node | null, testAncestor: Node | null): b return false; } +const parentFlowToDataKey = 'parentFlowToElementId'; + +/** + * Set an explicit parent to use for nodes that are not part of the + * regular dom structure. + */ +export function setParentFlowTo(fromChildElement: HTMLElement, toParentElement: Element): void { + fromChildElement.dataset[parentFlowToDataKey] = toParentElement.id; +} + +/** + * Check if `testAncestor` is an ancessor of `testChild`, observing the explicit + * parents set by `setParentFlowTo`. + */ +export function isAncestorUsingFlowTo(testChild: Node, testAncestor: Node): boolean { + let node: Node | null = testChild; + while (node) { + if (node === testAncestor) { + return true; + } + + if (node instanceof HTMLElement) { + const flowToParentId = node.dataset[parentFlowToDataKey]; + if (typeof flowToParentId === 'string') { + const flowToParentElement = document.getElementById(flowToParentId); + if (flowToParentElement) { + node = flowToParentElement; + continue; + } + } + } + node = node.parentNode; + } + + return false; +} + export function findParentWithClass(node: HTMLElement, clazz: string, stopAtClazzOrNode?: string | HTMLElement): HTMLElement | null { while (node && node.nodeType === node.ELEMENT_NODE) { if (node.classList.contains(clazz)) { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 5ecd07d5e68..6c5d106c4c4 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, isAncestor, getClientArea, Dimension, position, size, IDimension } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -999,7 +999,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const container = this.getContainer(part); - return !!container && isAncestor(activeElement, container); + return !!container && isAncestorUsingFlowTo(activeElement, container); } focusPart(part: Parts): void { diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts index 1ec3edc79e5..d277dbcbd96 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts @@ -8,18 +8,19 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; +import { generateUuid } from 'vs/base/common/uuid'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; -import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService'; import { EditorInput, EditorOptions, IEditorOpenContext } from 'vs/workbench/common/editor'; import { WebviewOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; +import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput'; export class WebviewEditor extends EditorPane { @@ -54,6 +55,7 @@ export class WebviewEditor extends EditorPane { protected createEditor(parent: HTMLElement): void { const element = document.createElement('div'); this._element = element; + this._element.id = `webview-editor-element-${generateUuid()}`; parent.appendChild(element); } @@ -143,6 +145,7 @@ export class WebviewEditor extends EditorPane { if (this._element) { this._element.setAttribute('aria-flowto', input.webview.container.id); + DOM.setParentFlowTo(input.webview.container, this._element); } this._webviewVisibleDisposables.clear();