diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index a3430a44354..143a15e1c6c 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -17,6 +17,29 @@ import { FileAccess, RemoteAuthorities, Schemas } from 'vs/base/common/network'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; +export const { registerWindow, getWindows, onDidCreateWindow } = (function () { + const windows: Window[] = []; + const onDidCreateWindow = new event.Emitter<{ window: Window; disposableStore: DisposableStore }>(); + return { + onDidCreateWindow: onDidCreateWindow.event, + registerWindow(window: Window): IDisposable { + windows.push(window); + const disposableStore = new DisposableStore(); + disposableStore.add(toDisposable(() => { + const index = windows.indexOf(window); + if (index !== -1) { + windows.splice(index, 1); + } + })); + onDidCreateWindow.fire({ window, disposableStore }); + return disposableStore; + }, + getWindows(): Window[] { + return windows; + } + }; +})(); + export function clearNode(node: HTMLElement): void { while (node.firstChild) { node.firstChild.remove(); @@ -282,34 +305,37 @@ export function addDisposableThrottledListener(node: } export function getComputedStyle(el: HTMLElement): CSSStyleDeclaration { - return document.defaultView!.getComputedStyle(el, null); + return el.ownerDocument.defaultView!.getComputedStyle(el, null); } export function getClientArea(element: HTMLElement): Dimension { + const elDocument = element.ownerDocument; + const elWindow = elDocument.defaultView?.window; + // Try with DOM clientWidth / clientHeight - if (element !== document.body) { + if (element !== elDocument.body) { return new Dimension(element.clientWidth, element.clientHeight); } // If visual view port exits and it's on mobile, it should be used instead of window innerWidth / innerHeight, or document.body.clientWidth / document.body.clientHeight - if (platform.isIOS && window.visualViewport) { - return new Dimension(window.visualViewport.width, window.visualViewport.height); + if (platform.isIOS && elWindow?.visualViewport) { + return new Dimension(elWindow.visualViewport.width, elWindow.visualViewport.height); } // Try innerWidth / innerHeight - if (window.innerWidth && window.innerHeight) { - return new Dimension(window.innerWidth, window.innerHeight); + if (elWindow?.innerWidth && elWindow.innerHeight) { + return new Dimension(elWindow.innerWidth, elWindow.innerHeight); } // Try with document.body.clientWidth / document.body.clientHeight - if (document.body && document.body.clientWidth && document.body.clientHeight) { - return new Dimension(document.body.clientWidth, document.body.clientHeight); + if (elDocument.body && elDocument.body.clientWidth && elDocument.body.clientHeight) { + return new Dimension(elDocument.body.clientWidth, elDocument.body.clientHeight); } // Try with document.documentElement.clientWidth / document.documentElement.clientHeight - if (document.documentElement && document.documentElement.clientWidth && document.documentElement.clientHeight) { - return new Dimension(document.documentElement.clientWidth, document.documentElement.clientHeight); + if (elDocument.documentElement && elDocument.documentElement.clientWidth && elDocument.documentElement.clientHeight) { + return new Dimension(elDocument.documentElement.clientWidth, elDocument.documentElement.clientHeight); } throw new Error('Unable to figure out browser width and height'); @@ -431,8 +457,8 @@ export function getTopLeftOffset(element: HTMLElement): IDomPosition { while ( (element = element.parentNode) !== null - && element !== document.body - && element !== document.documentElement + && element !== element.ownerDocument.body + && element !== element.ownerDocument.documentElement ) { top -= element.scrollTop; const c = isShadowRoot(element) ? null : getComputedStyle(element); @@ -498,8 +524,8 @@ export function position(element: HTMLElement, top: number, right?: number, bott export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePosition { const bb = domNode.getBoundingClientRect(); return { - left: bb.left + window.scrollX, - top: bb.top + window.scrollY, + left: bb.left + (domNode.ownerDocument.defaultView?.scrollX ?? 0), + top: bb.top + (domNode.ownerDocument.defaultView?.scrollY ?? 0), width: bb.width, height: bb.height }; @@ -518,7 +544,7 @@ export function getDomNodeZoomLevel(domNode: HTMLElement): number { } testElement = testElement.parentElement; - } while (testElement !== null && testElement !== document.documentElement); + } while (testElement !== null && testElement !== testElement.ownerDocument.documentElement); return zoom; } @@ -602,7 +628,7 @@ export function setParentFlowTo(fromChildElement: HTMLElement, toParentElement: function getParentFlowToElement(node: HTMLElement): HTMLElement | null { const flowToParentId = node.dataset[parentFlowToDataKey]; if (typeof flowToParentId === 'string') { - return document.getElementById(flowToParentId); + return node.ownerDocument.getElementById(flowToParentId); } return null; } @@ -671,7 +697,7 @@ export function isInShadowDOM(domNode: Node): boolean { export function getShadowRoot(domNode: Node): ShadowRoot | null { while (domNode.parentNode) { - if (domNode === document.body) { + if (domNode === domNode.ownerDocument?.body) { // reached the body return null; } @@ -680,8 +706,12 @@ export function getShadowRoot(domNode: Node): ShadowRoot | null { return isShadowRoot(domNode) ? domNode : null; } +/** + * Returns the active element across all child windows. + * Use this instead of `document.activeElement` to handle multiple windows. + */ export function getActiveElement(): Element | null { - let result = document.activeElement; + let result = getActiveDocument().activeElement; while (result?.shadowRoot) { result = result.shadowRoot.activeElement; @@ -690,6 +720,15 @@ export function getActiveElement(): Element | null { return result; } +/** + * Returns the active document across all child windows. + * Use this instead of `document` when reacting to dom events to handle multiple windows. + */ +export function getActiveDocument(): Document { + const documents = [document, ...getWindows().map(w => w.document)]; + return documents.find(doc => doc.hasFocus()) ?? document; +} + export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0], beforeAppend?: (style: HTMLStyleElement) => void): HTMLStyleElement { const style = document.createElement('style'); style.type = 'text/css'; @@ -875,15 +914,19 @@ class FocusTracker extends Disposable implements IFocusTracker { private _refreshStateHandler: () => void; - private static hasFocusWithin(element: HTMLElement): boolean { - const shadowRoot = getShadowRoot(element); - const activeElement = (shadowRoot ? shadowRoot.activeElement : document.activeElement); - return isAncestor(activeElement, element); + private static hasFocusWithin(element: HTMLElement | Window): boolean { + if (isHTMLElement(element)) { + const shadowRoot = getShadowRoot(element); + const activeElement = (shadowRoot ? shadowRoot.activeElement : element.ownerDocument.activeElement); + return isAncestor(activeElement, element); + } else { + return isAncestor(window.document.activeElement, window.document); + } } constructor(element: HTMLElement | Window) { super(); - let hasFocus = FocusTracker.hasFocusWithin(element); + let hasFocus = FocusTracker.hasFocusWithin(element); let loosingFocus = false; const onFocus = () => { @@ -1092,7 +1135,7 @@ export function removeTabIndexAndUpdateFocus(node: HTMLElement): void { // standard DOM behavior is to move focus to the element. We // typically never want that, rather put focus to the closest element // in the hierarchy of the parent DOM nodes. - if (document.activeElement === node) { + if (node.ownerDocument.activeElement === node) { const parentFocusable = findParentWithAttribute(node.parentElement, 'tabIndex'); parentFocusable?.focus(); } diff --git a/src/vs/base/browser/mouseEvent.ts b/src/vs/base/browser/mouseEvent.ts index db8ba364a86..bdfbf2ed661 100644 --- a/src/vs/base/browser/mouseEvent.ts +++ b/src/vs/base/browser/mouseEvent.ts @@ -69,8 +69,8 @@ export class StandardMouseEvent implements IMouseEvent { this.posy = e.pageY; } else { // Probably hit by MSGestureEvent - this.posx = e.clientX + document.body.scrollLeft + document.documentElement!.scrollLeft; - this.posy = e.clientY + document.body.scrollTop + document.documentElement!.scrollTop; + this.posx = e.clientX + this.target.ownerDocument.body.scrollLeft + this.target.ownerDocument.documentElement.scrollLeft; + this.posy = e.clientY + this.target.ownerDocument.body.scrollTop + this.target.ownerDocument.documentElement.scrollTop; } // Find the position of the iframe this code is executing in relative to the iframe where the event was captured. diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 5bfec5deba4..c2cc7511079 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -98,7 +98,7 @@ export class MouseHandler extends ViewEventHandler { // remove this listener if (!this._mouseLeaveMonitor) { - this._mouseLeaveMonitor = dom.addDisposableListener(document, 'mousemove', (e) => { + this._mouseLeaveMonitor = dom.addDisposableListener(this.viewHelper.viewDomNode.ownerDocument, 'mousemove', (e) => { if (!this.viewHelper.viewDomNode.contains(e.target as Node | null)) { // went outside the editor! this._onMouseLeave(new EditorMouseEvent(e, false, this.viewHelper.viewDomNode)); diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index b39b8f95ba6..4c57874ba42 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -336,7 +336,7 @@ export class HitTestContext { } private static _findAttribute(element: Element, attr: string, stopAt: Element): string | null { - while (element && element !== document.body) { + while (element && element !== element.ownerDocument.body) { if (element.hasAttribute && element.hasAttribute(attr)) { return element.getAttribute(attr); } @@ -917,7 +917,7 @@ export class MouseTargetFactory { range = (shadowRoot).caretRangeFromPoint(coords.clientX, coords.clientY); } } else { - range = (document).caretRangeFromPoint(coords.clientX, coords.clientY); + range = (ctx.viewDomNode.ownerDocument).caretRangeFromPoint(coords.clientX, coords.clientY); } if (!range || !range.startContainer) { @@ -959,7 +959,7 @@ export class MouseTargetFactory { * Most probably Gecko */ private static _doHitTestWithCaretPositionFromPoint(ctx: HitTestContext, coords: ClientCoordinates): HitTestResult { - const hitResult: { offsetNode: Node; offset: number } = (document).caretPositionFromPoint(coords.clientX, coords.clientY); + const hitResult: { offsetNode: Node; offset: number } = (ctx.viewDomNode.ownerDocument).caretPositionFromPoint(coords.clientX, coords.clientY); if (hitResult.offsetNode.nodeType === hitResult.offsetNode.TEXT_NODE) { // offsetNode is expected to be the token text @@ -1011,9 +1011,9 @@ export class MouseTargetFactory { private static _doHitTest(ctx: HitTestContext, request: BareHitTestRequest): HitTestResult { let result: HitTestResult = new UnknownHitTestResult(); - if (typeof (document).caretRangeFromPoint === 'function') { + if (typeof (ctx.viewDomNode.ownerDocument).caretRangeFromPoint === 'function') { result = this._doHitTestWithCaretRangeFromPoint(ctx, request); - } else if ((document).caretPositionFromPoint) { + } else if ((ctx.viewDomNode.ownerDocument).caretPositionFromPoint) { result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates()); } if (result.type === HitTestResultType.Content) { diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index d1db497e636..8be2c4e4baa 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -391,7 +391,7 @@ export class TextAreaHandler extends ViewPart { const distanceToModelLineStart = startModelPosition.column - 1 - visibleBeforeCharCount; const hiddenLineTextBefore = lineTextBeforeSelection.substring(0, lineTextBeforeSelection.length - visibleBeforeCharCount); const { tabSize } = this._context.viewModel.model.getOptions(); - const widthOfHiddenTextBefore = measureText(hiddenLineTextBefore, this._fontInfo, tabSize); + const widthOfHiddenTextBefore = measureText(this.textArea.domNode.ownerDocument, hiddenLineTextBefore, this._fontInfo, tabSize); return { distanceToModelLineStart, widthOfHiddenTextBefore }; })(); @@ -927,7 +927,7 @@ interface IRenderData { strikethrough?: boolean; } -function measureText(text: string, fontInfo: FontInfo, tabSize: number): number { +function measureText(document: Document, text: string, fontInfo: FontInfo, tabSize: number): number { if (text.length === 0) { return 0; } diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 36da11ef183..929df3c5d84 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -110,6 +110,8 @@ export interface ICompleteTextAreaWrapper extends ITextAreaWrapper { readonly onBlur: Event; readonly onSyntheticTap: Event; + readonly ownerDocument: Document; + setIgnoreSelectionChangeTime(reason: string): void; getIgnoreSelectionChangeTime(): number; resetSelectionChangeTime(): void; @@ -494,7 +496,7 @@ export class TextAreaInput extends Disposable { // `selectionchange` events often come multiple times for a single logical change // so throttle multiple `selectionchange` events that burst in a short period of time. let previousSelectionChangeEventTime = 0; - return dom.addDisposableListener(document, 'selectionchange', (e) => { + return dom.addDisposableListener(this._textArea.ownerDocument, 'selectionchange', (e) => {//todo inputLatency.onSelectionChange(); if (!this._hasFocus) { @@ -701,6 +703,10 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap public readonly onFocus = this._register(new DomEmitter(this._actual, 'focus')).event; public readonly onBlur = this._register(new DomEmitter(this._actual, 'blur')).event; + public get ownerDocument(): Document { + return this._actual.ownerDocument; + } + private _onSyntheticTap = this._register(new Emitter()); public readonly onSyntheticTap: Event = this._onSyntheticTap.event; @@ -725,7 +731,7 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap if (shadowRoot) { return shadowRoot.activeElement === this._actual; } else if (dom.isInDOM(this._actual)) { - return document.activeElement === this._actual; + return this._actual.ownerDocument.activeElement === this._actual; } else { return false; } @@ -775,7 +781,7 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap if (shadowRoot) { activeElement = shadowRoot.activeElement; } else { - activeElement = document.activeElement; + activeElement = textArea.ownerDocument.activeElement; } const currentIsFocused = (activeElement === textArea); diff --git a/src/vs/editor/browser/coreCommands.ts b/src/vs/editor/browser/coreCommands.ts index 7a3452d8461..44fc8eb9ab9 100644 --- a/src/vs/editor/browser/coreCommands.ts +++ b/src/vs/editor/browser/coreCommands.ts @@ -29,6 +29,7 @@ import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/co import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IViewModel } from 'vs/editor/common/viewModel'; import { ISelection } from 'vs/editor/common/core/selection'; +import { getActiveElement } from 'vs/base/browser/dom'; const CORE_WEIGHT = KeybindingWeight.EditorCore; @@ -315,9 +316,9 @@ abstract class EditorOrNativeTextInputCommand { // 2. handle case when focus is in some other `input` / `textarea`. target.addImplementation(1000, 'generic-dom-input-textarea', (accessor: ServicesAccessor, args: unknown) => { // Only if focused on an element that allows for entering text - const activeElement = document.activeElement; + const activeElement = getActiveElement(); if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { - this.runDOMCommand(); + this.runDOMCommand(activeElement); return true; } return false; @@ -343,7 +344,7 @@ abstract class EditorOrNativeTextInputCommand { return true; } - public abstract runDOMCommand(): void; + public abstract runDOMCommand(activeElement: Element): void; public abstract runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: unknown): void | Promise; } @@ -1875,13 +1876,13 @@ export namespace CoreNavigationCommands { constructor() { super(SelectAllCommand); } - public runDOMCommand(): void { + public runDOMCommand(activeElement: Element): void { if (isFirefox) { - (document.activeElement).focus(); - (document.activeElement).select(); + (activeElement).focus(); + (activeElement).select(); } - document.execCommand('selectAll'); + activeElement.ownerDocument.execCommand('selectAll'); } public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: unknown): void { const viewModel = editor._getViewModel(); @@ -2090,8 +2091,8 @@ export namespace CoreEditingCommands { constructor() { super(UndoCommand); } - public runDOMCommand(): void { - document.execCommand('undo'); + public runDOMCommand(activeElement: Element): void { + activeElement.ownerDocument.execCommand('undo'); } public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: unknown): void | Promise { if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) { @@ -2105,8 +2106,8 @@ export namespace CoreEditingCommands { constructor() { super(RedoCommand); } - public runDOMCommand(): void { - document.execCommand('redo'); + public runDOMCommand(activeElement: Element): void { + activeElement.ownerDocument.execCommand('redo'); } public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: unknown): void | Promise { if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) { diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index 4af00e67b13..60379467426 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -241,7 +241,7 @@ export class GlobalEditorPointerMoveMonitor extends Disposable { // Add a <> keydown event listener that will cancel the monitoring // if something other than a modifier key is pressed - this._keydownListener = dom.addStandardDisposableListener(document, 'keydown', (e) => { + this._keydownListener = dom.addStandardDisposableListener(initialElement.ownerDocument, 'keydown', (e) => { const chord = e.toKeyCodeChord(); if (chord.isModifierKey()) { // Allow modifier keys diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 75562c53821..f84a017b141 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -24,6 +24,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { IDisposable } from 'vs/base/common/lifecycle'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ILogService } from 'vs/platform/log/common/log'; +import { getActiveElement } from 'vs/base/browser/dom'; export type ServicesAccessor = InstantiationServicesAccessor; export type EditorContributionCtor = IConstructorSignature; @@ -219,7 +220,7 @@ export class MultiCommand extends Command { logService.trace(`Executing Command '${this.id}' which has ${this._implementations.length} bound.`); for (const impl of this._implementations) { if (impl.when) { - const context = contextKeyService.getContext(document.activeElement); + const context = contextKeyService.getContext(getActiveElement()); const value = impl.when.evaluate(context); if (!value) { continue; diff --git a/src/vs/editor/browser/view/viewPart.ts b/src/vs/editor/browser/view/viewPart.ts index 14a46ce44fe..ad88b9f9551 100644 --- a/src/vs/editor/browser/view/viewPart.ts +++ b/src/vs/editor/browser/view/viewPart.ts @@ -57,7 +57,7 @@ export class PartFingerprints { const result: PartFingerprint[] = []; let resultLen = 0; - while (child && child !== document.body) { + while (child && child !== child.ownerDocument.body) { if (child === stopAt) { break; } diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index b494ba60cc2..3112c19c324 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -266,9 +266,11 @@ class Widget { } private _getMaxWidth(): number { + const elDocument = this.domNode.domNode.ownerDocument; + const elWindow = elDocument.defaultView; return ( this.allowEditorOverflow - ? window.innerWidth || document.documentElement!.offsetWidth || document.body.offsetWidth + ? elWindow?.innerWidth || elDocument.documentElement.offsetWidth || elDocument.body.offsetWidth : this._contentWidth ); } @@ -326,7 +328,9 @@ class Widget { const MIN_LIMIT = Math.max(LEFT_PADDING, domNodePosition.left - width); const MAX_LIMIT = Math.min(domNodePosition.left + domNodePosition.width + width, windowSize.width - RIGHT_PADDING); - let absoluteLeft = domNodePosition.left + left - window.scrollX; + const elDocument = this._viewDomNode.domNode.ownerDocument; + const elWindow = elDocument.defaultView; + let absoluteLeft = domNodePosition.left + left - (elWindow?.scrollX ?? 0); if (absoluteLeft + width > MAX_LIMIT) { const delta = absoluteLeft - (MAX_LIMIT - width); @@ -348,10 +352,12 @@ class Widget { const belowTop = anchor.top + anchor.height; const domNodePosition = dom.getDomNodePagePosition(this._viewDomNode.domNode); - const absoluteAboveTop = domNodePosition.top + aboveTop - window.scrollY; - const absoluteBelowTop = domNodePosition.top + belowTop - window.scrollY; + const elDocument = this._viewDomNode.domNode.ownerDocument; + const elWindow = elDocument.defaultView; + const absoluteAboveTop = domNodePosition.top + aboveTop - (elWindow?.scrollY ?? 0); + const absoluteBelowTop = domNodePosition.top + belowTop - (elWindow?.scrollY ?? 0); - const windowSize = dom.getClientArea(document.body); + const windowSize = dom.getClientArea(elDocument.body); const [left, absoluteAboveLeft] = this._layoutHorizontalSegmentInPage(windowSize, domNodePosition, anchor.left - ctx.scrollLeft + this._contentLeft, width); // Leave some clearance to the top/bottom diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts index 9ccc307b2cd..f17051a0364 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditor.contribution.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getActiveElement } from 'vs/base/browser/dom'; import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; @@ -290,10 +291,11 @@ export function findFocusedDiffEditor(accessor: ServicesAccessor): IDiffEditor | } } - if (document.activeElement) { + const activeElement = getActiveElement(); + if (activeElement) { for (const d of diffEditors) { const container = d.getContainerDomNode(); - if (isElementOrParentOf(container, document.activeElement)) { + if (isElementOrParentOf(container, activeElement)) { return d; } } diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index 420abfa16a4..1aa2c9f3611 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as browser from 'vs/base/browser/browser'; +import { getActiveDocument } from 'vs/base/browser/dom'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; import { CopyOptions, InMemoryClipboardMetadataManager } from 'vs/editor/browser/controller/textAreaInput'; @@ -179,7 +180,7 @@ class ExecCommandCopyWithSyntaxHighlightingAction extends EditorAction { CopyOptions.forceCopyWithSyntaxHighlighting = true; editor.focus(); - document.execCommand('copy'); + editor.getContainerDomNode().ownerDocument.execCommand('copy'); CopyOptions.forceCopyWithSyntaxHighlighting = false; } } @@ -200,7 +201,7 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman if (selection && selection.isEmpty() && !emptySelectionClipboard) { return true; } - document.execCommand(browserCommand); + focusedEditor.getContainerDomNode().ownerDocument.execCommand(browserCommand); return true; } return false; @@ -208,7 +209,7 @@ function registerExecCommandImpl(target: MultiCommand | undefined, browserComman // 2. (default) handle case when focus is somewhere else. target.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: any) => { - document.execCommand(browserCommand); + getActiveDocument().execCommand(browserCommand); return true; }); } @@ -225,7 +226,7 @@ if (PasteAction) { // Only if editor text focus (i.e. not if editor has widget focus). const focusedEditor = codeEditorService.getFocusedCodeEditor(); if (focusedEditor && focusedEditor.hasTextFocus()) { - const result = document.execCommand('paste'); + const result = focusedEditor.getContainerDomNode().ownerDocument.execCommand('paste'); // Use the clipboard service if document.execCommand('paste') was not successful if (!result && platform.isWeb) { return (async () => { @@ -256,7 +257,7 @@ if (PasteAction) { // 2. Paste: (default) handle case when focus is somewhere else. PasteAction.addImplementation(0, 'generic-dom', (accessor: ServicesAccessor, args: any) => { - document.execCommand('paste'); + getActiveDocument().execCommand('paste'); return true; }); } diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts index 2f25409976f..5e245dfb4a2 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts @@ -264,7 +264,7 @@ class SaturationBox extends Disposable { this.monitor.startMonitoring(e.target, e.pointerId, e.buttons, event => this.onDidChangePosition(event.pageX - origin.left, event.pageY - origin.top), () => null); - const pointerUpListener = dom.addDisposableListener(document, dom.EventType.POINTER_UP, () => { + const pointerUpListener = dom.addDisposableListener(e.target.ownerDocument, dom.EventType.POINTER_UP, () => { this._onColorFlushed.fire(); pointerUpListener.dispose(); if (this.monitor) { @@ -387,7 +387,7 @@ abstract class Strip extends Disposable { monitor.startMonitoring(e.target, e.pointerId, e.buttons, event => this.onDidChangeTop(event.pageY - origin.top), () => null); - const pointerUpListener = dom.addDisposableListener(document, dom.EventType.POINTER_UP, () => { + const pointerUpListener = dom.addDisposableListener(e.target.ownerDocument, dom.EventType.POINTER_UP, () => { this._onColorFlushed.fire(); pointerUpListener.dispose(); monitor.stopMonitoring(true); diff --git a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts index f85e595c4ff..08ad5897812 100644 --- a/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableListener } from 'vs/base/browser/dom'; +import { addDisposableListener, getActiveDocument } from 'vs/base/browser/dom'; import { coalesce } from 'vs/base/common/arrays'; import { CancelablePromise, createCancelablePromise, raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -99,7 +99,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi this._editor.focus(); try { this._pasteAsActionContext = { preferredId }; - document.execCommand('paste'); + getActiveDocument().execCommand('paste'); } finally { this._pasteAsActionContext = undefined; } diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index c06e6bac4a7..00b575b5c89 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -675,7 +675,7 @@ export class ContentHoverWidget extends ResizableContentWidget { ); if (overflowing || this._hover.containerDomNode.clientWidth < initialWidth) { - const bodyBoxWidth = dom.getClientArea(document.body).width; + const bodyBoxWidth = dom.getClientArea(this._hover.containerDomNode.ownerDocument.body).width; const horizontalPadding = 14; return bodyBoxWidth - horizontalPadding; } else { diff --git a/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts b/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts index 0243d9e88bf..72736b8ab6e 100644 --- a/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts +++ b/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts @@ -78,7 +78,7 @@ export abstract class ResizableContentWidget extends Disposable implements ICont return; } const editorBox = dom.getDomNodePagePosition(editorDomNode); - const bodyBox = dom.getClientArea(document.body); + const bodyBox = dom.getClientArea(editorDomNode.ownerDocument.body); const mouseBottom = editorBox.top + mouseBox.top + mouseBox.height; return bodyBox.height - mouseBottom - BOTTOM_HEIGHT; } diff --git a/src/vs/editor/contrib/message/browser/messageController.ts b/src/vs/editor/contrib/message/browser/messageController.ts index e4466162dc4..6e13775d651 100644 --- a/src/vs/editor/contrib/message/browser/messageController.ts +++ b/src/vs/editor/contrib/message/browser/messageController.ts @@ -82,7 +82,7 @@ export class MessageController implements IEditorContribution { return; // override when mouse over message } - if (this._messageWidget.value && dom.isAncestor(document.activeElement, this._messageWidget.value.getDomNode())) { + if (this._messageWidget.value && dom.isAncestor(dom.getActiveElement(), this._messageWidget.value.getDomNode())) { return; // override when focus is inside the message } diff --git a/src/vs/editor/contrib/rename/browser/renameInputField.ts b/src/vs/editor/contrib/rename/browser/renameInputField.ts index 2ea6da1d7f8..e00d68cd368 100644 --- a/src/vs/editor/contrib/rename/browser/renameInputField.ts +++ b/src/vs/editor/contrib/rename/browser/renameInputField.ts @@ -192,7 +192,7 @@ export class RenameInputField implements IContentWidget { }; disposeOnDone.add(token.onCancellationRequested(() => this.cancelInput(true))); - disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!document.hasFocus()))); + disposeOnDone.add(this._editor.onDidBlurEditorWidget(() => this.cancelInput(!this._domNode?.ownerDocument.hasFocus()))); this._show(); diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index 6a03536715e..b86e2fd94ef 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -787,7 +787,7 @@ export class SuggestWidget implements IDisposable { return; } - const bodyBox = dom.getClientArea(document.body); + const bodyBox = dom.getClientArea(this.element.domNode.ownerDocument.body); const info = this.getLayoutInfo(); if (!size) { diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts index d6e95dd5a71..7f4646be053 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidgetDetails.ts @@ -372,7 +372,7 @@ export class SuggestDetailsOverlay implements IOverlayWidget { } _placeAtAnchor(anchorBox: dom.IDomNodePagePosition, size: dom.Dimension, preferAlignAtTop: boolean) { - const bodyBox = dom.getClientArea(document.body); + const bodyBox = dom.getClientArea(this.getDomNode().ownerDocument.body); const info = this.widget.getLayoutInfo(); diff --git a/src/vs/editor/test/browser/controller/textAreaInput.test.ts b/src/vs/editor/test/browser/controller/textAreaInput.test.ts index f66d1bbaa65..6874020e739 100644 --- a/src/vs/editor/test/browser/controller/textAreaInput.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaInput.test.ts @@ -91,6 +91,8 @@ suite('TextAreaInput', () => { private _state: IRecordedTextareaState; private _currDispatchingEvent: IRecordedEvent | null; + public ownerDocument = document; + constructor() { super(); this._state = { diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 28a17729768..418f0b990c9 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -100,6 +100,7 @@ export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor { class TestEditorDomElement { parentElement: IContextKeyServiceTarget | null = null; + ownerDocument = document; setAttribute(attr: string, value: string): void { } removeAttribute(attr: string): void { } hasAttribute(attr: string): boolean { return false; } diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index e6112182d25..10e58f90efe 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -18,7 +18,7 @@ import { UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { Keybinding, KeyCodeChord, ResolvedKeybinding, ScanCodeChord } from 'vs/base/common/keybindings'; import { IMMUTABLE_CODE_TO_KEY_CODE, KeyCode, KeyCodeUtils, KeyMod, ScanCode, ScanCodeUtils } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import { isMacintosh, OperatingSystem, OS } from 'vs/base/common/platform'; import { dirname } from 'vs/base/common/resources'; @@ -237,28 +237,9 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { this.updateKeybindingsJsonSchema(); this._register(extensionService.onDidRegisterExtensions(() => this.updateKeybindingsJsonSchema())); - // for standard keybindings - this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { - this.isComposingGlobalContextKey.set(e.isComposing); - const keyEvent = new StandardKeyboardEvent(e); - this._log(`/ Received keydown event - ${printKeyboardEvent(e)}`); - this._log(`| Converted keydown event - ${printStandardKeyboardEvent(keyEvent)}`); - const shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target); - if (shouldPreventDefault) { - keyEvent.preventDefault(); - } - this.isComposingGlobalContextKey.set(false); - })); - - // for single modifier chord keybindings (e.g. shift shift) - this._register(dom.addDisposableListener(window, dom.EventType.KEY_UP, (e: KeyboardEvent) => { - this.isComposingGlobalContextKey.set(e.isComposing); - const keyEvent = new StandardKeyboardEvent(e); - const shouldPreventDefault = this._singleModifierDispatch(keyEvent, keyEvent.target); - if (shouldPreventDefault) { - keyEvent.preventDefault(); - } - this.isComposingGlobalContextKey.set(false); + this._register(this._registerKeyListeners(window)); + this._register(dom.onDidCreateWindow(({ window, disposableStore }) => { + disposableStore.add(this._registerKeyListeners(window)); })); this._register(browser.onDidChangeFullscreen(() => { @@ -280,6 +261,36 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { })); } + private _registerKeyListeners(window: Window): IDisposable { + const disposables = new DisposableStore(); + + // for standard keybindings + disposables.add(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + this.isComposingGlobalContextKey.set(e.isComposing); + const keyEvent = new StandardKeyboardEvent(e); + this._log(`/ Received keydown event - ${printKeyboardEvent(e)}`); + this._log(`| Converted keydown event - ${printStandardKeyboardEvent(keyEvent)}`); + const shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target); + if (shouldPreventDefault) { + keyEvent.preventDefault(); + } + this.isComposingGlobalContextKey.set(false); + })); + + // for single modifier chord keybindings (e.g. shift shift) + disposables.add(dom.addDisposableListener(window, dom.EventType.KEY_UP, (e: KeyboardEvent) => { + this.isComposingGlobalContextKey.set(e.isComposing); + const keyEvent = new StandardKeyboardEvent(e); + const shouldPreventDefault = this._singleModifierDispatch(keyEvent, keyEvent.target); + if (shouldPreventDefault) { + keyEvent.preventDefault(); + } + this.isComposingGlobalContextKey.set(false); + })); + + return disposables; + } + public registerSchemaContribution(contribution: KeybindingsSchemaContribution): void { this._contributions.push(contribution); if (contribution.onDidChange) {