diff --git a/resources/web/code-web.js b/resources/web/code-web.js index c38826c0847..464ac2f639d 100644 --- a/resources/web/code-web.js +++ b/resources/web/code-web.js @@ -60,6 +60,7 @@ if (args.help) { ' --host Remote host\n' + ' --port Remote/Local port\n' + ' --local_port Local port override\n' + + ' --secondary-port Secondary port\n' + ' --extension Path of an extension to include\n' + ' --github-auth Github authentication token\n' + ' --verbose Print out more information\n' + @@ -72,6 +73,7 @@ if (args.help) { const PORT = args.port || process.env.PORT || 8080; const LOCAL_PORT = args.local_port || process.env.LOCAL_PORT || PORT; +const SECONDARY_PORT = args['secondary-port'] || (parseInt(PORT, 10) + 1); const SCHEME = args.scheme || process.env.VSCODE_SCHEME || 'http'; const HOST = args.host || 'localhost'; const AUTHORITY = process.env.VSCODE_AUTHORITY || `${HOST}:${PORT}`; @@ -207,7 +209,11 @@ const commandlineProvidedExtensionsPromise = getCommandlineProvidedExtensionInfo const mapCallbackUriToRequestId = new Map(); -const server = http.createServer((req, res) => { +/** + * @param req {http.IncomingMessage} + * @param res {http.ServerResponse} + */ +const requestHandler = (req, res) => { const parsedUrl = url.parse(req.url, true); const pathname = parsedUrl.pathname; @@ -252,16 +258,25 @@ const server = http.createServer((req, res) => { return serveError(req, res, 500, 'Internal Server Error.'); } -}); +}; +const server = http.createServer(requestHandler); server.listen(LOCAL_PORT, () => { if (LOCAL_PORT !== PORT) { - console.log(`Operating location at http://0.0.0.0:${LOCAL_PORT}`); + console.log(`Operating location at http://0.0.0.0:${LOCAL_PORT}`); } - console.log(`Web UI available at ${SCHEME}://${AUTHORITY}`); + console.log(`Web UI available at ${SCHEME}://${AUTHORITY}`); +}); +server.on('error', err => { + console.error(`Error occurred in server:`); + console.error(err); }); -server.on('error', err => { +const secondaryServer = http.createServer(requestHandler); +secondaryServer.listen(SECONDARY_PORT, () => { + console.log(`Secondary server available at ${SCHEME}://${HOST}:${SECONDARY_PORT}`); +}); +secondaryServer.on('error', err => { console.error(`Error occurred in server:`); console.error(err); }); @@ -366,6 +381,7 @@ async function handleRoot(req, res) { folderUri: folderUri, staticExtensions, enableSyncByDefault: args['enable-sync'], + webWorkerExtensionHostIframeSrc: `${SCHEME}://${HOST}:${SECONDARY_PORT}/static/out/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html` }; if (args['wrap-iframe']) { webConfigJSON._wrapWebWorkerExtHostInIframe = true; diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 53dc899d48d..333e5b24b05 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -51,6 +51,7 @@ export interface IProductConfiguration { readonly downloadUrl?: string; readonly updateUrl?: string; + readonly webEndpointUrl?: string; readonly target?: string; readonly settingsSearchBuildId?: number; diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts index 026e286977d..fd2d6597f37 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts @@ -16,7 +16,6 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as platform from 'vs/base/common/platform'; -import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { URI } from 'vs/base/common/uri'; import { IExtensionHost, ExtensionHostLogFileName, ExtensionHostKind } from 'vs/workbench/services/extensions/common/extensions'; @@ -28,7 +27,6 @@ import { IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output import { localize } from 'vs/nls'; import { generateUuid } from 'vs/base/common/uuid'; import { canceled, onUnexpectedError } from 'vs/base/common/errors'; -import { WEB_WORKER_IFRAME } from 'vs/workbench/services/extensions/common/webWorkerIframe'; import { Barrier } from 'vs/base/common/async'; import { FileAccess } from 'vs/base/common/network'; @@ -81,10 +79,38 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost return true; } + private _webWorkerExtensionHostIframeSrc(): string | null { + if (this._environmentService.options && this._environmentService.options.webWorkerExtensionHostIframeSrc) { + return this._environmentService.options.webWorkerExtensionHostIframeSrc; + } + if (this._productService.webEndpointUrl) { + const forceHTTPS = (location.protocol === 'https:'); + let baseUrl = this._productService.webEndpointUrl; + if (this._productService.quality) { + baseUrl += `/${this._productService.quality}`; + } + if (this._productService.commit) { + baseUrl += `/${this._productService.commit}`; + } + return ( + forceHTTPS + ? `${baseUrl}/out/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html` + : `${baseUrl}/out/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html` + ); + } + return null; + } + public async start(): Promise { if (!this._protocolPromise) { - if (platform.isWeb && !browser.isSafari && this._wrapInIframe()) { - this._protocolPromise = this._startInsideIframe(); + if (platform.isWeb) { + const webWorkerExtensionHostIframeSrc = this._webWorkerExtensionHostIframeSrc(); + if (webWorkerExtensionHostIframeSrc && this._wrapInIframe()) { + this._protocolPromise = this._startInsideIframe(webWorkerExtensionHostIframeSrc); + } else { + console.warn(`The web worker extension host is started without an iframe sandbox!`); + this._protocolPromise = this._startOutsideIframe(); + } } else { this._protocolPromise = this._startOutsideIframe(); } @@ -93,40 +119,41 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost return this._protocolPromise; } - private async _startInsideIframe(): Promise { + private async _startInsideIframe(webWorkerExtensionHostIframeSrc: string): Promise { const emitter = this._register(new Emitter()); const iframe = document.createElement('iframe'); iframe.setAttribute('class', 'web-worker-ext-host-iframe'); - iframe.setAttribute('sandbox', 'allow-scripts'); + iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin'); iframe.style.display = 'none'; const vscodeWebWorkerExtHostId = generateUuid(); - const workerUrl = FileAccess.asBrowserUri('../worker/extensionHostWorkerMain.js', require).toString(true); - const workerSrc = getWorkerBootstrapUrl(workerUrl, 'WorkerExtensionHost', true); - const escapeAttribute = (value: string): string => { - return value.replace(/"/g, '"'); - }; - const forceHTTPS = (location.protocol === 'https:'); - const html = ` - - - - - - - - - -`; - const iframeContent = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`; - iframe.setAttribute('src', iframeContent); - const timeout = setTimeout(() => { - this._onDidExit.fire([ExtensionHostExitCode.StartTimeout10s, 'The Web Worker Extension Host did not start in 10s']); - }, 10000); + iframe.setAttribute('src', `${webWorkerExtensionHostIframeSrc}?vscodeWebWorkerExtHostId=${vscodeWebWorkerExtHostId}`); const barrier = new Barrier(); let port!: MessagePort; + let barrierError: Error | null = null; + let barrierHasError = false; + let startTimeout: any = null; + + const rejectBarrier = (exitCode: number, error: Error) => { + barrierError = error; + barrierHasError = true; + onUnexpectedError(barrierError); + clearTimeout(startTimeout); + this._onDidExit.fire([ExtensionHostExitCode.UnexpectedError, barrierError.message]); + barrier.open(); + }; + + const resolveBarrier = (messagePort: MessagePort) => { + port = messagePort; + clearTimeout(startTimeout); + barrier.open(); + }; + + startTimeout = setTimeout(() => { + rejectBarrier(ExtensionHostExitCode.StartTimeout10s, new Error('The Web Worker Extension Host did not start in 10s')); + }, 10000); this._register(dom.addDisposableListener(window, 'message', (event) => { if (event.source !== iframe.contentWindow) { @@ -141,21 +168,15 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost err.message = message; err.name = name; err.stack = stack; - onUnexpectedError(err); - clearTimeout(timeout); - this._onDidExit.fire([ExtensionHostExitCode.UnexpectedError, err.message]); - return; + return rejectBarrier(ExtensionHostExitCode.UnexpectedError, err); } const { data } = event.data; if (barrier.isOpen() || !(data instanceof MessagePort)) { console.warn('UNEXPECTED message', event); - clearTimeout(timeout); - this._onDidExit.fire([ExtensionHostExitCode.UnexpectedError, 'UNEXPECTED message']); - return; + const err = new Error('UNEXPECTED message'); + return rejectBarrier(ExtensionHostExitCode.UnexpectedError, err); } - port = data; - clearTimeout(timeout); - barrier.open(); + resolveBarrier(data); })); document.body.appendChild(iframe); @@ -165,6 +186,10 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost // with the worker extension host await barrier.wait(); + if (barrierHasError) { + throw barrierError; + } + port.onmessage = (event) => { const { data } = event; if (!(data instanceof ArrayBuffer)) { diff --git a/src/vs/workbench/services/extensions/common/webWorkerIframe.ts b/src/vs/workbench/services/extensions/common/webWorkerIframe.ts deleted file mode 100644 index 7b354788d46..00000000000 --- a/src/vs/workbench/services/extensions/common/webWorkerIframe.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export const WEB_WORKER_IFRAME = { - sha: 'sha256-r24mDVsMuFEo8ChaY9ppVJKbY3CUM4I12Aw/yscWZbg=', - js: ` -(function() { - const workerSrc = document.getElementById('vscode-worker-src').getAttribute('data-value'); - const worker = new Worker(workerSrc, { name: 'WorkerExtensionHost' }); - const vscodeWebWorkerExtHostId = document.getElementById('vscode-web-worker-ext-host-id').getAttribute('data-value'); - - worker.onmessage = (event) => { - const { data } = event; - if (!(data instanceof MessagePort)) { - console.warn('Unknown data received', event); - window.parent.postMessage({ - vscodeWebWorkerExtHostId, - error: { - name: 'Error', - message: 'Unknown data received', - stack: [] - } - }, '*'); - return; - } - window.parent.postMessage({ - vscodeWebWorkerExtHostId, - data: data - }, '*', [data]); - }; - - worker.onerror = (event) => { - console.error(event.message, event.error); - window.parent.postMessage({ - vscodeWebWorkerExtHostId, - error: { - name: event.error ? event.error.name : '', - message: event.error ? event.error.message : '', - stack: event.error ? event.error.stack : [] - } - }, '*'); - }; -})(); -` -}; diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts index 6a54c82b1af..dd554377b19 100644 --- a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts @@ -45,9 +45,9 @@ self.addEventListener = () => console.trace(`'addEventListener' has been blocked (self)['webkitResolveLocalFileSystemSyncURL'] = undefined; (self)['webkitResolveLocalFileSystemURL'] = undefined; -if (location.protocol === 'data:') { +if ((self).Worker) { // make sure new Worker(...) always uses data: - const _Worker = Worker; + const _Worker = (self).Worker; Worker = function (stringUrl: string | URL, options?: WorkerOptions) { const js = `importScripts('${stringUrl}');`; options = options || {}; diff --git a/src/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html b/src/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html new file mode 100644 index 00000000000..a99d3df29df --- /dev/null +++ b/src/vs/workbench/services/extensions/worker/httpWebWorkerExtensionHostIframe.html @@ -0,0 +1,50 @@ + + + + + + + + + diff --git a/src/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html b/src/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html new file mode 100644 index 00000000000..27b6114a13b --- /dev/null +++ b/src/vs/workbench/services/extensions/worker/httpsWebWorkerExtensionHostIframe.html @@ -0,0 +1,50 @@ + + + + + + + + + diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 16accfae70b..68906ff6fa7 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -277,6 +277,11 @@ interface IWorkbenchConstructionOptions { */ readonly webviewEndpoint?: string; + /** + * An URL pointing to the web worker extension host