From d295bff1d73faa5a159fdef8eac7af14c4d9b06b Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Apr 2016 14:47:57 +0200 Subject: [PATCH] add IOpenerService and use it with webview, #3676 --- src/vs/platform/opener/common/opener.ts | 25 +++++++ .../electron-browser/opener.contribution.ts | 12 ++++ .../opener/electron-browser/openerService.ts | 65 +++++++++++++++++++ src/vs/workbench/electron-browser/shell.ts | 3 + .../parts/html/browser/htmlPreviewPart.ts | 15 ++++- .../workbench/parts/html/browser/webview.html | 17 +++++ 6 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 src/vs/platform/opener/common/opener.ts create mode 100644 src/vs/platform/opener/electron-browser/opener.contribution.ts create mode 100644 src/vs/platform/opener/electron-browser/openerService.ts diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts new file mode 100644 index 00000000000..60b3f6c1df0 --- /dev/null +++ b/src/vs/platform/opener/common/opener.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import URI from 'vs/base/common/uri'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {createDecorator} from 'vs/platform/instantiation/common/instantiation'; + +export const IOpenerService = createDecorator('openerService'); + + +export interface IOpenerService { + + serviceId: any; + + /** + * Opens a resource, like a webadress, a document uri, or executes command. + * + * @param resource A resource + * @return A promise that resolves when the opening is done. + */ + open(resource: URI): TPromise; +} diff --git a/src/vs/platform/opener/electron-browser/opener.contribution.ts b/src/vs/platform/opener/electron-browser/opener.contribution.ts new file mode 100644 index 00000000000..6ae5493bc44 --- /dev/null +++ b/src/vs/platform/opener/electron-browser/opener.contribution.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import {registerSingleton} from 'vs/platform/instantiation/common/extensions'; +import {OpenerService} from 'vs/platform/opener/electron-browser/openerService'; +import {IOpenerService} from 'vs/platform/opener/common/opener'; + +registerSingleton(IOpenerService, OpenerService); diff --git a/src/vs/platform/opener/electron-browser/openerService.ts b/src/vs/platform/opener/electron-browser/openerService.ts new file mode 100644 index 00000000000..42fe621a08c --- /dev/null +++ b/src/vs/platform/opener/electron-browser/openerService.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import URI from 'vs/base/common/uri'; +import {Schemas} from 'vs/base/common/network'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {IEditorService} from 'vs/platform/editor/common/editor'; +import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; +import {IOpenerService} from '../common/opener'; + +export class OpenerService implements IOpenerService { + + serviceId: any; + + constructor( + @IEditorService private _editorService: IEditorService, + @IKeybindingService private _keybindingService: IKeybindingService + ) { + // + } + + open(resource: URI): TPromise { + + const {scheme, path, query, fragment} = resource; + let promise: TPromise; + if (scheme === Schemas.http || scheme === Schemas.https) { + // open http + window.open(resource.toString(true)); + + } else if (scheme === 'command' && this._keybindingService.hasCommand(path)) { + // execute as command + let args: any; + try { + args = JSON.parse(query); + } catch (e) { + // + } + promise = this._keybindingService.executeCommand(path, Array.isArray(args) ? args : [args]); + + } else { + promise = this._editorService.resolveEditorModel({ resource }).then(model => { + if (!model) { + return; + } + // support file:///some/file.js#L73 + let selection: { + startLineNumber: number; + startColumn: number; + }; + if (/^L\d+$/.test(fragment)) { + selection = { + startLineNumber: parseInt(fragment.substr(1)), + startColumn: 1 + }; + } + return this._editorService.openEditor({ resource, options: { selection } }); + }); + } + + return TPromise.as(promise).then(undefined, err => { }); // !ignores all errors + } +} diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index 63bcf560c82..f3535ae81d4 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -93,6 +93,9 @@ import {IExtensionsService} from 'vs/workbench/parts/extensions/common/extension import {ExtensionsService} from 'vs/workbench/parts/extensions/node/extensionsService'; import {ReloadWindowAction} from 'vs/workbench/electron-browser/actions'; +// self registering service +import 'vs/platform/opener/electron-browser/opener.contribution'; + /** * Services that we require for the Shell */ diff --git a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts index 07ad0078573..136f42c989a 100644 --- a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts +++ b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts @@ -24,6 +24,7 @@ import {BaseTextEditorModel} from 'vs/workbench/common/editor/textEditorModel'; import {HtmlInput} from 'vs/workbench/parts/html/common/htmlInput'; import {IThemeService} from 'vs/workbench/services/themes/common/themeService'; import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry'; +import {IOpenerService} from 'vs/platform/opener/common/opener'; KeybindingsRegistry.registerCommandDesc({ id: '_webview.openDevTools', @@ -62,7 +63,7 @@ class ManagedWebview { private _ready: TPromise; private _disposables: IDisposable[]; - constructor(private _parent: HTMLElement, private _layoutParent: HTMLElement, private _styleElement) { + constructor(private _parent: HTMLElement, private _layoutParent: HTMLElement, private _styleElement, onDidClickLink:(uri:URI)=>any) { this._webview = document.createElement('webview'); this._webview.style.zIndex = '1'; this._webview.style.position = 'absolute'; @@ -88,6 +89,12 @@ class ManagedWebview { }), addDisposableListener(this._webview, 'crashed', function () { console.error('embedded page crashed'); + }), + addDisposableListener(this._webview, 'ipc-message', (event) => { + if (event.channel === 'did-click-link') { + let [uri] = event.args; + onDidClickLink(URI.parse(uri)); + } }) ]; @@ -190,6 +197,7 @@ export class HtmlPreviewPart extends BaseEditor { private _editorService: IWorkbenchEditorService; private _themeService: IThemeService; + private _openerService: IOpenerService; private _webview: ManagedWebview; private _container: HTMLDivElement; @@ -203,12 +211,14 @@ export class HtmlPreviewPart extends BaseEditor { @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IThemeService themeService: IThemeService, + @IOpenerService openerService: IOpenerService, @IWorkspaceContextService contextService: IWorkspaceContextService ) { super(HtmlPreviewPart.ID, telemetryService); this._editorService = editorService; this._themeService = themeService; + this._openerService = openerService; this._baseUrl = contextService.toResource('/'); } @@ -232,7 +242,8 @@ export class HtmlPreviewPart extends BaseEditor { if (!this._webview) { this._webview = new ManagedWebview(document.getElementById('workbench.main.container'), this._container, - document.querySelector('.monaco-editor-background')); + document.querySelector('.monaco-editor-background'), + uri => this._openerService.open(uri)); this._webview.baseUrl = this._baseUrl && this._baseUrl.toString(); } diff --git a/src/vs/workbench/parts/html/browser/webview.html b/src/vs/workbench/parts/html/browser/webview.html index 12bb4a5b69b..465b1faf6af 100644 --- a/src/vs/workbench/parts/html/browser/webview.html +++ b/src/vs/workbench/parts/html/browser/webview.html @@ -66,6 +66,17 @@ newDocument.head.appendChild(defaultStyles); } + // script to bubble out link-clicks + const defaultScripts = newDocument.createElement('script'); + defaultScripts.innerHTML = ` + document.body.addEventListener('click', function (event) { + if(event.target.tagName === 'A' && event.target.href) { + window.parent.postMessage({ command: 'did-click-link', data: event.target.href }, 'file://'); + event.preventDefault(); + } + });` + newDocument.body.appendChild(defaultScripts); + // write new content onto iframe target.contentDocument.open('text/html', 'replace'); target.contentDocument.write(newDocument.documentElement.innerHTML); @@ -73,6 +84,12 @@ }); + // forward messages from the embedded iframe + window.onmessage = function(message) { + const { command, data} = message.data; + ipcRenderer.sendToHost(command, data); + }; + // signal ready, needs a short timeout for an // unknown reason setTimeout(function() { -- GitLab