browser(firefox-stable): cherry pick recent changes from browser_patches/firefox (#6409)

This commit is contained in:
Dmitry Gozman 2021-05-04 17:01:48 -07:00 committed by GitHub
parent 14ebcfdf0b
commit 5c5196104e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 133 deletions

View File

@ -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

View File

@ -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);
}

View File

@ -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() {

View File

@ -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}"`);
}

View File

@ -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;
}

View File

@ -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.

View File

@ -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;