From 5c5196104e502b3074035a5ccfd953b51dde7eba Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Tue, 4 May 2021 17:01:48 -0700 Subject: [PATCH] browser(firefox-stable): cherry pick recent changes from browser_patches/firefox (#6409) --- browser_patches/firefox-stable/BUILD_NUMBER | 4 +- .../firefox-stable/juggler/TargetRegistry.js | 3 +- .../juggler/content/FrameTree.js | 77 +++++++++-- .../juggler/content/PageAgent.js | 126 +++--------------- .../juggler/protocol/BrowserHandler.js | 23 +++- .../screencast/nsIScreencastService.idl | 2 +- .../screencast/nsScreencastService.cpp | 10 +- 7 files changed, 112 insertions(+), 133 deletions(-) diff --git a/browser_patches/firefox-stable/BUILD_NUMBER b/browser_patches/firefox-stable/BUILD_NUMBER index fefde9c950..bc10ed2b79 100644 --- a/browser_patches/firefox-stable/BUILD_NUMBER +++ b/browser_patches/firefox-stable/BUILD_NUMBER @@ -1,2 +1,2 @@ -1244 -Changed: lushnikov@chromium.org Tue 20 Apr 2021 04:48:52 PM PDT +1245 +Changed: dgozman@gmail.com Tue May 4 16:09:13 PDT 2021 diff --git a/browser_patches/firefox-stable/juggler/TargetRegistry.js b/browser_patches/firefox-stable/juggler/TargetRegistry.js index fe80fb23ed..0e646c83f2 100644 --- a/browser_patches/firefox-stable/juggler/TargetRegistry.js +++ b/browser_patches/firefox-stable/juggler/TargetRegistry.js @@ -504,7 +504,8 @@ class PageTarget { // Exclude address bar and navigation control from the video. const rect = this.linkedBrowser().getBoundingClientRect(); const devicePixelRatio = this._window.devicePixelRatio; - const videoSessionId = screencast.startVideoRecording(docShell, file, width, height, scale || 0, devicePixelRatio * rect.top); + const viewport = this._viewportSize || this._browserContext.defaultViewportSize || {width: 0, height: 0}; + const videoSessionId = screencast.startVideoRecording(docShell, file, width, height, viewport.width, viewport.height, scale || 0, devicePixelRatio * rect.top); this._screencastInfo = { videoSessionId, file }; this.emit(PageTarget.Events.ScreencastStarted); } diff --git a/browser_patches/firefox-stable/juggler/content/FrameTree.js b/browser_patches/firefox-stable/juggler/content/FrameTree.js index 1dd040fbf2..4380dc7f8e 100644 --- a/browser_patches/firefox-stable/juggler/content/FrameTree.js +++ b/browser_patches/firefox-stable/juggler/content/FrameTree.js @@ -23,6 +23,7 @@ class FrameTree { this._browsingContextGroup.__jugglerFrameTrees = new Set(); this._browsingContextGroup.__jugglerFrameTrees.add(this); this._scriptsToEvaluateOnNewDocument = new Map(); + this._isolatedWorlds = new Map(); this._webSocketEventService = Cc[ "@mozilla.org/websocketevent/service;1" @@ -72,6 +73,25 @@ class FrameTree { return this._runtime; } + addScriptToEvaluateOnNewDocument(script, worldName) { + const scriptId = helper.generateId(); + if (worldName) { + this._isolatedWorlds.set(scriptId, {script, worldName}); + for (const frame of this.frames()) + frame.createIsolatedWorld(worldName); + } else { + this._scriptsToEvaluateOnNewDocument.set(scriptId, script); + } + return {scriptId}; + } + + removeScriptToEvaluateOnNewDocument(scriptId) { + if (this._isolatedWorlds.has(scriptId)) + this._isolatedWorlds.delete(scriptId); + else + this._scriptsToEvaluateOnNewDocument.delete(scriptId); + } + _frameForWorker(workerDebugger) { if (workerDebugger.type !== Ci.nsIWorkerDebugger.TYPE_DEDICATED) return null; @@ -86,7 +106,6 @@ class FrameTree { if (!frame) return; frame._onGlobalObjectCleared(); - this.emit(FrameTree.Events.GlobalObjectCreated, { frame, window }); } _onWorkerCreated(workerDebugger) { @@ -129,16 +148,6 @@ class FrameTree { return true; } - addScriptToEvaluateOnNewDocument(script) { - const scriptId = helper.generateId(); - this._scriptsToEvaluateOnNewDocument.set(scriptId, script); - return scriptId; - } - - removeScriptToEvaluateOnNewDocument(scriptId) { - this._scriptsToEvaluateOnNewDocument.delete(scriptId); - } - addBinding(name, script) { this._bindings.set(name, script); for (const frame of this.frames()) @@ -291,7 +300,6 @@ FrameTree.Events = { BindingCalled: 'bindingcalled', FrameAttached: 'frameattached', FrameDetached: 'framedetached', - GlobalObjectCreated: 'globalobjectcreated', WorkerCreated: 'workercreated', WorkerDestroyed: 'workerdestroyed', WebSocketCreated: 'websocketcreated', @@ -330,6 +338,9 @@ class Frame { this._textInputProcessor = null; this._executionContext = null; + this._isolatedWorlds = new Map(); + this._initialNavigationDone = false; + this._webSocketListenerInnerWindowId = 0; // WebSocketListener calls frameReceived event before webSocketOpened. // To avoid this, serialize event reporting. @@ -415,7 +426,36 @@ class Frame { }; } + createIsolatedWorld(name) { + const principal = [this.domWindow()]; // extended principal + const sandbox = Cu.Sandbox(principal, { + sandboxPrototype: this.domWindow(), + wantComponents: false, + wantExportHelpers: false, + wantXrays: true, + }); + const world = this._runtime.createExecutionContext(this.domWindow(), sandbox, { + frameId: this.id(), + name, + }); + this._isolatedWorlds.set(world.id(), world); + return world; + } + + unsafeObject(objectId) { + const contexts = [this.executionContext(), ...this._isolatedWorlds.values()]; + for (const context of contexts) { + const result = context.unsafeObject(objectId); + if (result) + return result.object; + } + throw new Error('Cannot find object with id = ' + objectId); + } + dispose() { + for (const world of this._isolatedWorlds.values()) + this._runtime.destroyExecutionContext(world); + this._isolatedWorlds.clear(); if (this._executionContext) this._runtime.destroyExecutionContext(this._executionContext); this._executionContext = null; @@ -458,6 +498,19 @@ class Frame { dump(`ERROR: ${e.message}\n${e.stack}\n`); } } + + for (const world of this._isolatedWorlds.values()) + this._runtime.destroyExecutionContext(world); + this._isolatedWorlds.clear(); + for (const {script, worldName} of this._frameTree._isolatedWorlds.values()) { + const context = worldName ? this.createIsolatedWorld(worldName) : this.executionContext(); + try { + let result = context.evaluateScript(script); + if (result && result.objectId) + context.disposeObject(result.objectId); + } catch (e) { + } + } } executionContext() { diff --git a/browser_patches/firefox-stable/juggler/content/PageAgent.js b/browser_patches/firefox-stable/juggler/content/PageAgent.js index bb278e5f27..25205bcaa6 100644 --- a/browser_patches/firefox-stable/juggler/content/PageAgent.js +++ b/browser_patches/firefox-stable/juggler/content/PageAgent.js @@ -49,65 +49,6 @@ class WorkerData { } } -class FrameData { - constructor(agent, runtime, frame) { - this._agent = agent; - this._runtime = runtime; - this._frame = frame; - this._isolatedWorlds = new Map(); - this._initialNavigationDone = false; - this.reset(); - } - - reset() { - for (const world of this._isolatedWorlds.values()) - this._runtime.destroyExecutionContext(world); - this._isolatedWorlds.clear(); - - for (const {script, worldName} of this._agent._isolatedWorlds.values()) { - const context = worldName ? this.createIsolatedWorld(worldName) : this._frame.executionContext(); - try { - let result = context.evaluateScript(script); - if (result && result.objectId) - context.disposeObject(result.objectId); - } catch (e) { - } - } - } - - createIsolatedWorld(name) { - const principal = [this._frame.domWindow()]; // extended principal - const sandbox = Cu.Sandbox(principal, { - sandboxPrototype: this._frame.domWindow(), - wantComponents: false, - wantExportHelpers: false, - wantXrays: true, - }); - const world = this._runtime.createExecutionContext(this._frame.domWindow(), sandbox, { - frameId: this._frame.id(), - name, - }); - this._isolatedWorlds.set(world.id(), world); - return world; - } - - unsafeObject(objectId) { - const contexts = [this._frame.executionContext(), ...this._isolatedWorlds.values()]; - for (const context of contexts) { - const result = context.unsafeObject(objectId); - if (result) - return result.object; - } - throw new Error('Cannot find object with id = ' + objectId); - } - - dispose() { - for (const world of this._isolatedWorlds.values()) - this._runtime.destroyExecutionContext(world); - this._isolatedWorlds.clear(); - } -} - class PageAgent { constructor(messageManager, browserChannel, frameTree) { this._messageManager = messageManager; @@ -116,10 +57,7 @@ class PageAgent { this._frameTree = frameTree; this._runtime = frameTree.runtime(); - this._frameData = new Map(); this._workerData = new Map(); - this._scriptsToEvaluateOnNewDocument = new Map(); - this._isolatedWorlds = new Map(); const docShell = frameTree.mainFrame().docShell(); this._docShell = docShell; @@ -169,7 +107,6 @@ class PageAgent { helper.on(this._frameTree, 'bindingcalled', this._onBindingCalled.bind(this)), helper.on(this._frameTree, 'frameattached', this._onFrameAttached.bind(this)), helper.on(this._frameTree, 'framedetached', this._onFrameDetached.bind(this)), - helper.on(this._frameTree, 'globalobjectcreated', this._onGlobalObjectCreated.bind(this)), helper.on(this._frameTree, 'navigationstarted', this._onNavigationStarted.bind(this)), helper.on(this._frameTree, 'navigationcommitted', this._onNavigationCommitted.bind(this)), helper.on(this._frameTree, 'navigationaborted', this._onNavigationAborted.bind(this)), @@ -198,7 +135,7 @@ class PageAgent { this._runtime.events.onExecutionContextDestroyed(this._onExecutionContextDestroyed.bind(this)), browserChannel.register('page', { addBinding: ({ name, script }) => this._frameTree.addBinding(name, script), - addScriptToEvaluateOnNewDocument: this._addScriptToEvaluateOnNewDocument.bind(this), + addScriptToEvaluateOnNewDocument: ({script, worldName}) => this._frameTree.addScriptToEvaluateOnNewDocument(script, worldName), adoptNode: this._adoptNode.bind(this), crash: this._crash.bind(this), describeNode: this._describeNode.bind(this), @@ -213,7 +150,7 @@ class PageAgent { insertText: this._insertText.bind(this), navigate: this._navigate.bind(this), reload: this._reload.bind(this), - removeScriptToEvaluateOnNewDocument: this._removeScriptToEvaluateOnNewDocument.bind(this), + removeScriptToEvaluateOnNewDocument: ({scriptId}) => this._frameTree.removeScriptToEvaluateOnNewDocument(scriptId), screenshot: this._screenshot.bind(this), scrollIntoViewIfNeeded: this._scrollIntoViewIfNeeded.bind(this), setCacheDisabled: this._setCacheDisabled.bind(this), @@ -227,27 +164,6 @@ class PageAgent { ]; } - _addScriptToEvaluateOnNewDocument({script, worldName}) { - if (worldName) - return this._createIsolatedWorld({script, worldName}); - return {scriptId: this._frameTree.addScriptToEvaluateOnNewDocument(script)}; - } - - _createIsolatedWorld({script, worldName}) { - const scriptId = helper.generateId(); - this._isolatedWorlds.set(scriptId, {script, worldName}); - for (const frameData of this._frameData.values()) - frameData.createIsolatedWorld(worldName); - return {scriptId}; - } - - _removeScriptToEvaluateOnNewDocument({scriptId}) { - if (this._isolatedWorlds.has(scriptId)) - this._isolatedWorlds.delete(scriptId); - else - this._frameTree.removeScriptToEvaluateOnNewDocument(scriptId); - } - _setCacheDisabled({cacheDisabled}) { const enable = Ci.nsIRequest.LOAD_NORMAL; const disable = Ci.nsIRequest.LOAD_BYPASS_CACHE | @@ -333,16 +249,16 @@ class PageAgent { _filePickerShown(inputElement) { if (inputElement.ownerGlobal.docShell !== this._docShell) return; - const frameData = this._findFrameForNode(inputElement); + const frame = this._findFrameForNode(inputElement); this._browserPage.emit('pageFileChooserOpened', { - executionContextId: frameData._frame.executionContext().id(), - element: frameData._frame.executionContext().rawValueToRemoteObject(inputElement) + executionContextId: frame.executionContext().id(), + element: frame.executionContext().rawValueToRemoteObject(inputElement) }); } _findFrameForNode(node) { - return Array.from(this._frameData.values()).find(data => { - const doc = data._frame.domWindow().document; + return this._frameTree.frames().find(frame => { + const doc = frame.domWindow().document; return node === doc || node.ownerDocument === doc; }); } @@ -404,10 +320,9 @@ class PageAgent { navigationId, errorText, }); - const frameData = this._frameData.get(frame); - if (!frameData._initialNavigationDone && frame !== this._frameTree.mainFrame()) + if (!frame._initialNavigationDone && frame !== this._frameTree.mainFrame()) this._emitAllEvents(frame); - frameData._initialNavigationDone = true; + frame._initialNavigationDone = true; } _onSameDocumentNavigation(frame) { @@ -424,11 +339,7 @@ class PageAgent { url: frame.url(), name: frame.name(), }); - this._frameData.get(frame)._initialNavigationDone = true; - } - - _onGlobalObjectCreated({ frame }) { - this._frameData.get(frame).reset(); + frame._initialNavigationDone = true; } _onFrameAttached(frame) { @@ -436,11 +347,9 @@ class PageAgent { frameId: frame.id(), parentFrameId: frame.parentFrame() ? frame.parentFrame().id() : undefined, }); - this._frameData.set(frame, new FrameData(this, this._runtime, frame)); } _onFrameDetached(frame) { - this._frameData.delete(frame); this._browserPage.emit('pageFrameDetached', { frameId: frame.id(), }); @@ -458,9 +367,6 @@ class PageAgent { for (const workerData of this._workerData.values()) workerData.dispose(); this._workerData.clear(); - for (const frameData of this._frameData.values()) - frameData.dispose(); - this._frameData.clear(); helper.removeListeners(this._eventListeners); } @@ -525,7 +431,7 @@ class PageAgent { const frame = this._frameTree.frame(frameId); if (!frame) throw new Error('Failed to find frame with id = ' + frameId); - const unsafeObject = this._frameData.get(frame).unsafeObject(objectId); + const unsafeObject = frame.unsafeObject(objectId); const context = this._runtime.findExecutionContext(executionContextId); const fromPrincipal = unsafeObject.nodePrincipal; const toFrame = this._frameTree.frame(context.auxData().frameId); @@ -539,7 +445,7 @@ class PageAgent { const frame = this._frameTree.frame(frameId); if (!frame) throw new Error('Failed to find frame with id = ' + frameId); - const unsafeObject = this._frameData.get(frame).unsafeObject(objectId); + const unsafeObject = frame.unsafeObject(objectId); if (!unsafeObject) throw new Error('Object is not input!'); const nsFiles = await Promise.all(files.map(filePath => File.createFromFileName(filePath))); @@ -550,7 +456,7 @@ class PageAgent { const frame = this._frameTree.frame(frameId); if (!frame) throw new Error('Failed to find frame with id = ' + frameId); - const unsafeObject = this._frameData.get(frame).unsafeObject(objectId); + const unsafeObject = frame.unsafeObject(objectId); if (!unsafeObject.getBoxQuads) throw new Error('RemoteObject is not a node'); const quads = unsafeObject.getBoxQuads({relativeTo: this._frameTree.mainFrame().domWindow().document}).map(quad => { @@ -568,7 +474,7 @@ class PageAgent { const frame = this._frameTree.frame(frameId); if (!frame) throw new Error('Failed to find frame with id = ' + frameId); - const unsafeObject = this._frameData.get(frame).unsafeObject(objectId); + const unsafeObject = frame.unsafeObject(objectId); const browsingContextGroup = frame.docShell().browsingContext.group; const frames = this._frameTree.allFramesInBrowsingContextGroup(browsingContextGroup); let contentFrame; @@ -590,7 +496,7 @@ class PageAgent { const frame = this._frameTree.frame(frameId); if (!frame) throw new Error('Failed to find frame with id = ' + frameId); - const unsafeObject = this._frameData.get(frame).unsafeObject(objectId); + const unsafeObject = frame.unsafeObject(objectId); if (!unsafeObject.isConnected) throw new Error('Node is detached from document'); if (!rect) @@ -877,7 +783,7 @@ class PageAgent { async _getFullAXTree({objectId}) { let unsafeObject = null; if (objectId) { - unsafeObject = this._frameData.get(this._frameTree.mainFrame()).unsafeObject(objectId); + unsafeObject = this._frameTree.mainFrame().unsafeObject(objectId); if (!unsafeObject) throw new Error(`No object found for id "${objectId}"`); } diff --git a/browser_patches/firefox-stable/juggler/protocol/BrowserHandler.js b/browser_patches/firefox-stable/juggler/protocol/BrowserHandler.js index a084b3389c..92dea19214 100644 --- a/browser_patches/firefox-stable/juggler/protocol/BrowserHandler.js +++ b/browser_patches/firefox-stable/juggler/protocol/BrowserHandler.js @@ -138,7 +138,12 @@ class BrowserHandler { "navigator:browser" ); if (browserWindow && browserWindow.gBrowserInit) { - await browserWindow.gBrowserInit.idleTasksFinishedPromise; + // idleTasksFinishedPromise does not resolve when the window + // is closed early enough, so we race against window closure. + await Promise.race([ + browserWindow.gBrowserInit.idleTasksFinishedPromise, + waitForWindowClosed(browserWindow), + ]); } // Try to fully initialize browser before closing. // See comment in `Browser.enable`. @@ -281,6 +286,22 @@ async function waitForAddonManager() { }); } +async function waitForWindowClosed(browserWindow) { + if (browserWindow.closed) + return; + await new Promise((resolve => { + const listener = { + onCloseWindow: window => { + if (window === browserWindow) { + Services.wm.removeListener(listener); + resolve(); + } + }, + }; + Services.wm.addListener(listener); + })); +} + function nullToUndefined(value) { return value === null ? undefined : value; } diff --git a/browser_patches/firefox-stable/juggler/screencast/nsIScreencastService.idl b/browser_patches/firefox-stable/juggler/screencast/nsIScreencastService.idl index d3a82a7cda..b7edfe3c1a 100644 --- a/browser_patches/firefox-stable/juggler/screencast/nsIScreencastService.idl +++ b/browser_patches/firefox-stable/juggler/screencast/nsIScreencastService.idl @@ -12,7 +12,7 @@ interface nsIDocShell; [scriptable, uuid(d8c4d9e0-9462-445e-9e43-68d3872ad1de)] interface nsIScreencastService : nsISupports { - AString startVideoRecording(in nsIDocShell docShell, in ACString fileName, in uint32_t width, in uint32_t height, in double scale, in int32_t offset_top); + AString startVideoRecording(in nsIDocShell docShell, in ACString fileName, in uint32_t width, in uint32_t height, in uint32_t viewportWidth, in uint32_t viewportHeight, in double scale, in int32_t offset_top); /** * Will emit 'juggler-screencast-stopped' when the video file is saved. diff --git a/browser_patches/firefox-stable/juggler/screencast/nsScreencastService.cpp b/browser_patches/firefox-stable/juggler/screencast/nsScreencastService.cpp index 36e4c69e4d..cbebd9fbc1 100644 --- a/browser_patches/firefox-stable/juggler/screencast/nsScreencastService.cpp +++ b/browser_patches/firefox-stable/juggler/screencast/nsScreencastService.cpp @@ -140,7 +140,7 @@ nsScreencastService::nsScreencastService() = default; nsScreencastService::~nsScreencastService() { } -nsresult nsScreencastService::StartVideoRecording(nsIDocShell* aDocShell, const nsACString& aFileName, uint32_t width, uint32_t height, double scale, int32_t offsetTop, nsAString& sessionId) { +nsresult nsScreencastService::StartVideoRecording(nsIDocShell* aDocShell, const nsACString& aFileName, uint32_t width, uint32_t height, uint32_t viewportWidth, uint32_t viewportHeight, double scale, int32_t offsetTop, nsAString& sessionId) { MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Screencast service must be started on the Main thread."); PresShell* presShell = aDocShell->GetPresShell(); @@ -164,15 +164,13 @@ nsresult nsScreencastService::StartVideoRecording(nsIDocShell* aDocShell, const maybeScale = Some(scale); gfx::IntMargin margin; - // On GTK the bottom of the client rect is below the bounds and - // client size is actually equal to the size of the bounds so - // we don't need an adjustment. -#ifndef MOZ_WIDGET_GTK auto bounds = widget->GetScreenBounds().ToUnknownRect(); auto clientBounds = widget->GetClientBounds().ToUnknownRect(); + // The browser window has a minimum size, so it might be larger than the viewport size. + clientBounds.width = std::min((int)viewportWidth, clientBounds.width); + clientBounds.height = std::min((int)viewportHeight, clientBounds.height); // Crop the image to exclude frame (if any). margin = bounds - clientBounds; -#endif // Crop the image to exclude controls. margin.top += offsetTop;