Skip to content

Commit

Permalink
Fix drag and dropping of editors for webviews
Browse files Browse the repository at this point in the history
Fixes microsoft#25854

This change does the following:

- Have the webview editor create an `EditorDropTarget`. This is required because the webview itself is not part of the normal editor dom (it exists as a top level node in the workbench so that we never reparent it). This means that the standard `EditorDropTarget` does not work.

- Make webviews set `pointer-events: none` while a drag is happening. When a drag happens on Electron's webviews or a normal iframe, no  drag and drop events seem to get generated. The fix is to set `pointer-events: none` while the drag is happening. But it's also difficult to detect when the drag is happening because the  `EditorDropTargets` class eats the drop event itself. The only reliable seeming way I could find to determine when a drag starts and ends is looking at global events on the window.

This workaround is pretty ugly. I'm not sure if there's some better approach that would work with webviews
  • Loading branch information
mjbvz committed Oct 19, 2019
1 parent bd200ea commit 424973a
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 21 deletions.
7 changes: 6 additions & 1 deletion src/vs/workbench/browser/parts/editor/editorDropTarget.ts
Expand Up @@ -460,6 +460,10 @@ class DropOverlay extends Themable {
}
}

export interface EditorDropTargetDelegate {
groupContainsPredicate?(groupView: IEditorGroupView): boolean;
}

export class EditorDropTarget extends Themable {

private _overlay?: DropOverlay;
Expand All @@ -472,6 +476,7 @@ export class EditorDropTarget extends Themable {
constructor(
private accessor: IEditorGroupsAccessor,
private container: HTMLElement,
private readonly delegate: EditorDropTargetDelegate,
@IThemeService themeService: IThemeService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
Expand Down Expand Up @@ -545,7 +550,7 @@ export class EditorDropTarget extends Themable {

private findTargetGroupView(child: HTMLElement): IEditorGroupView | undefined {
const groups = this.accessor.groups;
return find(groups, groupView => isAncestor(child, groupView.element));
return find(groups, groupView => isAncestor(child, groupView.element) || this.delegate.groupContainsPredicate?.(groupView));
}

private updateContainer(isDraggedOver: boolean): void {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/browser/parts/editor/editorPart.ts
Expand Up @@ -822,7 +822,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
this.centeredLayoutWidget = this._register(new CenteredViewLayout(this.container, this.gridWidgetView, this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY]));

// Drop support
this._register(this.instantiationService.createInstance(EditorDropTarget, this, this.container));
this._register(this.instantiationService.createInstance(EditorDropTarget, this, this.container, {}));

return this.container;
}
Expand Down
15 changes: 15 additions & 0 deletions src/vs/workbench/contrib/webview/browser/baseWebviewElement.ts
Expand Up @@ -272,4 +272,19 @@ export abstract class BaseWebview<T extends HTMLElement> extends Disposable {
// And re-dispatch
window.dispatchEvent(emulatedKeyboardEvent);
}

windowDidDragStart(): void {
// Webview break drag and droping around the main window (no events are generated when you are over them)
// Work around this by disabling pointer events during the drag.
// https://github.com/electron/electron/issues/18226
if (this.element) {
this.element.style.pointerEvents = 'none';
}
}

windowDidDragEnd(): void {
if (this.element) {
this.element.style.pointerEvents = '';
}
}
}
Expand Up @@ -198,4 +198,12 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd
f(this._webview.value);
}
}

windowDidDragStart() {
this.withWebview(webview => webview.windowDidDragStart());
}

windowDidDragEnd() {
this.withWebview(webview => webview.windowDidDragEnd());
}
}
3 changes: 3 additions & 0 deletions src/vs/workbench/contrib/webview/browser/webview.ts
Expand Up @@ -82,6 +82,9 @@ export interface Webview extends IDisposable {
showFind(): void;
hideFind(): void;
runFindAction(previous: boolean): void;

windowDidDragStart(): void;
windowDidDragEnd(): void;
}

export interface WebviewElement extends Webview {
Expand Down
69 changes: 50 additions & 19 deletions src/vs/workbench/contrib/webview/browser/webviewEditor.ts
Expand Up @@ -6,19 +6,22 @@
import * as DOM from 'vs/base/browser/dom';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { isWeb } from 'vs/base/common/platform';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
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 { IHostService } from 'vs/workbench/services/host/browser/host';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorOptions, EditorInput } from 'vs/workbench/common/editor';
import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTarget';
import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart';
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { isWeb } from 'vs/base/common/platform';
import { IHostService } from 'vs/workbench/services/host/browser/host';

export class WebviewEditor extends BaseEditor {

Expand All @@ -30,7 +33,7 @@ export class WebviewEditor extends BaseEditor {
private _content?: HTMLElement;
private _dimension?: DOM.Dimension;

private readonly _webviewFocusTrackerDisposables = this._register(new DisposableStore());
private readonly _webviewVisibleDisposables = this._register(new DisposableStore());
private readonly _onFocusWindowHandler = this._register(new MutableDisposable());

private readonly _onDidFocusWebview = this._register(new Emitter<void>());
Expand All @@ -39,10 +42,12 @@ export class WebviewEditor extends BaseEditor {
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IEditorService private readonly _editorService: IEditorService,
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
@IHostService private readonly _hostService: IHostService,
@IStorageService storageService: IStorageService
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super(WebviewEditor.ID, telemetryService, themeService, storageService);

Expand Down Expand Up @@ -117,23 +122,22 @@ export class WebviewEditor extends BaseEditor {
}

protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void {
const webview = this.input && (this.input as WebviewInput).webview;
if (webview) {
if (this.input instanceof WebviewInput) {
const webview = this.input.webview;
if (visible) {
webview.claim(this);
} else {
webview.release(this);
}
this.claimWebview(this.input as WebviewInput);
this.claimWebview(this.input);
}

super.setEditorVisible(visible, group);
}

public clearInput() {
if (this.input && this.input instanceof WebviewInput) {
this.input.webview.release(this);
this._webviewFocusTrackerDisposables.clear();
this._webviewVisibleDisposables.clear();
}

super.clearInput();
Expand Down Expand Up @@ -178,8 +182,33 @@ export class WebviewEditor extends BaseEditor {
this._content.setAttribute('aria-flowto', input.webview.container.id);
}

this._webviewVisibleDisposables.clear();

// Webviews are not part of the normal editor dom, so we have to register our own drag and drop handler on them.
this._webviewVisibleDisposables.add(this.instantiationService.createInstance(EditorDropTarget, this._editorGroupsService as EditorPart, input.webview.container, {
groupContainsPredicate: (group) => this.group?.id === group.group.id
}));

this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_START, () => {
if (this.input instanceof WebviewInput) {
this.input.webview.windowDidDragStart();
}
}));

const onDragEnd = () => {
if (this.input instanceof WebviewInput) {
this.input.webview.windowDidDragEnd();
}
};
this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.DRAG_END, onDragEnd));
this._webviewVisibleDisposables.add(DOM.addDisposableListener(window, DOM.EventType.MOUSE_MOVE, currentEvent => {
if (currentEvent.buttons === 0) {
onDragEnd();
}
}));

this.synchronizeWebviewContainerDimensions(input.webview);
this.trackFocus(input.webview);
this._webviewVisibleDisposables.add(this.trackFocus(input.webview));
}

private synchronizeWebviewContainerDimensions(webview: WebviewEditorOverlay, dimension?: DOM.Dimension) {
Expand All @@ -188,15 +217,17 @@ export class WebviewEditor extends BaseEditor {
}
}

private trackFocus(webview: WebviewEditorOverlay): void {
this._webviewFocusTrackerDisposables.clear();
private trackFocus(webview: WebviewEditorOverlay): IDisposable {
const store = new DisposableStore();

// Track focus in webview content
const webviewContentFocusTracker = DOM.trackFocus(webview.container);
this._webviewFocusTrackerDisposables.add(webviewContentFocusTracker);
this._webviewFocusTrackerDisposables.add(webviewContentFocusTracker.onDidFocus(() => this._onDidFocusWebview.fire()));
store.add(webviewContentFocusTracker);
store.add(webviewContentFocusTracker.onDidFocus(() => this._onDidFocusWebview.fire()));

// Track focus in webview element
this._webviewFocusTrackerDisposables.add(webview.onDidFocus(() => this._onDidFocusWebview.fire()));
store.add(webview.onDidFocus(() => this._onDidFocusWebview.fire()));

return store;
}
}

0 comments on commit 424973a

Please sign in to comment.