Bug 1533949 - Make BrowserChild functions Fission-compatible, and move to BrowserElementChild. r=NeilDeakin
Differential Revision: https://phabricator.services.mozilla.com/D30725
This commit is contained in:
@@ -41,6 +41,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
PageThumbs: "resource://gre/modules/PageThumbs.jsm",
|
||||
PanelMultiView: "resource:///modules/PanelMultiView.jsm",
|
||||
PanelView: "resource:///modules/PanelMultiView.jsm",
|
||||
PermitUnloader: "resource://gre/actors/BrowserElementParent.jsm",
|
||||
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
|
||||
PlacesUIUtils: "resource:///modules/PlacesUIUtils.jsm",
|
||||
PlacesTransactions: "resource://gre/modules/PlacesTransactions.jsm",
|
||||
|
||||
@@ -44,7 +44,6 @@ window._gBrowser = {
|
||||
let messageManager = window.getGroupMessageManager("browsers");
|
||||
if (gMultiProcessBrowser) {
|
||||
messageManager.addMessageListener("DOMTitleChanged", this);
|
||||
messageManager.addMessageListener("DOMWindowClose", this);
|
||||
window.messageManager.addMessageListener("contextmenu", this);
|
||||
messageManager.addMessageListener("Browser:Init", this);
|
||||
} else {
|
||||
@@ -2868,9 +2867,10 @@ window._gBrowser = {
|
||||
|
||||
_hasBeforeUnload(aTab) {
|
||||
let browser = aTab.linkedBrowser;
|
||||
return browser.isRemoteBrowser && browser.frameLoader &&
|
||||
browser.frameLoader.remoteTab &&
|
||||
browser.frameLoader.remoteTab.hasBeforeUnload;
|
||||
if (browser.isRemoteBrowser && browser.frameLoader) {
|
||||
return PermitUnloader.hasBeforeUnload(browser.frameLoader);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_beginRemoveTab(aTab, {
|
||||
@@ -4335,25 +4335,6 @@ window._gBrowser = {
|
||||
tab.setAttribute("titlechanged", "true");
|
||||
break;
|
||||
}
|
||||
case "DOMWindowClose":
|
||||
{
|
||||
if (this.tabs.length == 1) {
|
||||
// We already did PermitUnload in the content process
|
||||
// for this tab (the only one in the window). So we don't
|
||||
// need to do it again for any tabs.
|
||||
window.skipNextCanClose = true;
|
||||
window.close();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let tab = this.getTabForBrowser(browser);
|
||||
if (tab) {
|
||||
// Skip running PermitUnload since it already happened in
|
||||
// the content process.
|
||||
this.removeTab(tab, { skipPermitUnload: true });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "contextmenu":
|
||||
{
|
||||
openContextMenu(aMessage);
|
||||
@@ -4540,25 +4521,48 @@ window._gBrowser = {
|
||||
});
|
||||
|
||||
this.addEventListener("DOMWindowClose", (event) => {
|
||||
if (!event.isTrusted)
|
||||
return;
|
||||
let browser = event.target;
|
||||
if (!browser.isRemoteBrowser) {
|
||||
if (!event.isTrusted) {
|
||||
// If the browser is not remote, then we expect the event to be trusted.
|
||||
// In the remote case, the DOMWindowClose event is captured in content,
|
||||
// a message is sent to the parent, and another DOMWindowClose event
|
||||
// is re-dispatched on the actual browser node. In that case, the event
|
||||
// won't be marked as trusted, since it's synthesized by JavaScript.
|
||||
return;
|
||||
}
|
||||
// In the parent-process browser case, it's possible that the browser
|
||||
// that fired DOMWindowClose is actually a child of another browser. We
|
||||
// want to find the top-most browser to determine whether or not this is
|
||||
// for a tab or not. The chromeEventHandler will be the top-most browser.
|
||||
browser = event.target.docShell.chromeEventHandler;
|
||||
}
|
||||
|
||||
if (this.tabs.length == 1) {
|
||||
// We already did PermitUnload in nsGlobalWindow::Close
|
||||
// for this tab. There are no other tabs we need to do
|
||||
// PermitUnload for.
|
||||
// We already did PermitUnload in the content process
|
||||
// for this tab (the only one in the window). So we don't
|
||||
// need to do it again for any tabs.
|
||||
window.skipNextCanClose = true;
|
||||
// In the parent-process browser case, the nsCloseEvent will actually take
|
||||
// care of tearing down the window, but we need to do this ourselves in the
|
||||
// content-process browser case. Doing so in both cases doesn't appear to
|
||||
// hurt.
|
||||
window.close();
|
||||
return;
|
||||
}
|
||||
|
||||
let browser = event.target.docShell.chromeEventHandler;
|
||||
let tab = this.getTabForBrowser(browser);
|
||||
if (tab) {
|
||||
// Skip running PermitUnload since it already happened.
|
||||
// Skip running PermitUnload since it already happened in
|
||||
// the content process.
|
||||
this.removeTab(tab, { skipPermitUnload: true });
|
||||
// If we don't preventDefault on the DOMWindowClose event, then
|
||||
// in the parent-process browser case, we're telling the platform
|
||||
// to close the entire window. Calling preventDefault is our way of
|
||||
// saying we took care of this close request by closing the tab.
|
||||
event.preventDefault();
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
|
||||
this.addEventListener("DOMWillOpenModalDialog", (event) => {
|
||||
if (!event.isTrusted)
|
||||
|
||||
@@ -1227,6 +1227,7 @@ pref("editor.positioning.offset", 0);
|
||||
|
||||
// Scripts & Windows prefs
|
||||
pref("dom.disable_beforeunload", false);
|
||||
pref("dom.beforeunload_timeout_ms", 1000);
|
||||
pref("dom.disable_window_flip", false);
|
||||
pref("dom.disable_window_move_resize", false);
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["BrowserChild"];
|
||||
|
||||
const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "BrowserUtils",
|
||||
"resource://gre/modules/BrowserUtils.jsm");
|
||||
|
||||
class BrowserChild extends ActorChild {
|
||||
handleEvent(event) {
|
||||
if (event.type == "DOMWindowClose") {
|
||||
this.mm.sendAsyncMessage("DOMWindowClose");
|
||||
}
|
||||
}
|
||||
|
||||
receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "Browser:CreateAboutBlank":
|
||||
if (!this.content.document || this.content.document.documentURI != "about:blank") {
|
||||
throw new Error("Can't create a content viewer unless on about:blank");
|
||||
}
|
||||
let principal = message.data;
|
||||
principal = BrowserUtils.principalWithMatchingOA(principal, this.content.document.nodePrincipal);
|
||||
this.mm.docShell.createAboutBlankContentViewer(principal);
|
||||
break;
|
||||
|
||||
case "InPermitUnload":
|
||||
let inPermitUnload = this.mm.docShell.contentViewer && this.mm.docShell.contentViewer.inPermitUnload;
|
||||
this.mm.sendAsyncMessage("InPermitUnload", {id: message.data.id, inPermitUnload});
|
||||
break;
|
||||
|
||||
case "PermitUnload":
|
||||
this.mm.sendAsyncMessage("PermitUnload", {id: message.data.id, kind: "start"});
|
||||
|
||||
let permitUnload = true;
|
||||
if (this.mm.docShell && this.mm.docShell.contentViewer) {
|
||||
permitUnload = this.mm.docShell.contentViewer.permitUnload(message.data.aPermitUnloadFlags);
|
||||
}
|
||||
|
||||
this.mm.sendAsyncMessage("PermitUnload", {id: message.data.id, kind: "end", permitUnload});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
toolkit/actors/BrowserElementChild.jsm
Normal file
35
toolkit/actors/BrowserElementChild.jsm
Normal file
@@ -0,0 +1,35 @@
|
||||
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["BrowserElementChild"];
|
||||
|
||||
class BrowserElementChild extends JSWindowActorChild {
|
||||
handleEvent(event) {
|
||||
if (event.type == "DOMWindowClose" &&
|
||||
!this.manager.browsingContext.parent) {
|
||||
this.sendAsyncMessage("DOMWindowClose", {});
|
||||
}
|
||||
}
|
||||
|
||||
receiveMessage(message) {
|
||||
let context = this.manager.browsingContext;
|
||||
let docShell = context.docShell;
|
||||
|
||||
switch (message.name) {
|
||||
case "PermitUnload": {
|
||||
this.sendAsyncMessage("Running", {});
|
||||
|
||||
let permitUnload = true;
|
||||
if (docShell && docShell.contentViewer) {
|
||||
permitUnload = docShell.contentViewer.permitUnload(message.data.flags);
|
||||
}
|
||||
|
||||
this.sendAsyncMessage("Done", { permitUnload });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
383
toolkit/actors/BrowserElementParent.jsm
Normal file
383
toolkit/actors/BrowserElementParent.jsm
Normal file
@@ -0,0 +1,383 @@
|
||||
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = ["BrowserElementParent", "PermitUnloader"];
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
let {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "unloadTimeoutMs",
|
||||
"dom.beforeunload_timeout_ms");
|
||||
|
||||
// Out-of-process subframes might be in the following states following a request
|
||||
// to permit closing:
|
||||
//
|
||||
// We're waiting to hear back from the frame on whether or not we should permit
|
||||
// unloading.
|
||||
const STATE_WAITING = 0;
|
||||
// The out-of-process iframe is running its permitUnload routine. The frame stays
|
||||
// in this state when the permit unload modal dialog is being displayed.
|
||||
const STATE_RUNNING = 1;
|
||||
// The permitUnload routine has completed, and any modal dialogs for that subframe
|
||||
// have been cleared. This frame, at this point, has answered the questions of whether
|
||||
// or not it wants to be unloaded.
|
||||
const STATE_DONE = 2;
|
||||
|
||||
/**
|
||||
* The BrowserElementParent is for performing actions on one or more subframes of
|
||||
* a <xul:browser> from the browser element binding.
|
||||
*/
|
||||
class BrowserElementParent extends JSWindowActorParent {
|
||||
/**
|
||||
* NOTE: It is expected that this function is only called from PermitUnloader.
|
||||
* Callers who want to check if a <xul:browser> wants to allow being unloaded
|
||||
* should use PermitUnloader instead.
|
||||
*
|
||||
* Sends a request to a subframe to run the docShell's permitUnload routine
|
||||
* with the passed flags.
|
||||
*
|
||||
* @param {Number} flags See nsIContentViewer.idl for the types of flags that
|
||||
* can be passed.
|
||||
*/
|
||||
sendPermitUnload(flags) {
|
||||
this.sendAsyncMessage("PermitUnload", { flags });
|
||||
}
|
||||
|
||||
receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "Running": {
|
||||
PermitUnloader._transitionFrameState(this.manager, STATE_RUNNING);
|
||||
break;
|
||||
}
|
||||
case "Done": {
|
||||
let permitUnload = message.data.permitUnload;
|
||||
if (!permitUnload) {
|
||||
PermitUnloader._doNotPermitUnload(this.manager);
|
||||
} else {
|
||||
PermitUnloader._transitionFrameState(this.manager, STATE_DONE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "DOMWindowClose": {
|
||||
// This message is sent whenever window.close() is called within a window
|
||||
// that had originally been opened via window.open. Double-check that this is
|
||||
// coming from a top-level frame, and then dispatch the DOMWindowClose event
|
||||
// on the browser so that the front-end code can do the right thing with the
|
||||
// request to close.
|
||||
if (!this.manager.browsingContext.parent) {
|
||||
let browser = this.manager.browsingContext.embedderElement;
|
||||
let win = browser.ownerGlobal;
|
||||
// If this is a non-remote browser, the DOMWindowClose event will bubble
|
||||
// up naturally, and doesn't need to be re-dispatched.
|
||||
if (browser.isRemoteBrowser) {
|
||||
browser.dispatchEvent(new win.CustomEvent("DOMWindowClose", {
|
||||
bubbles: true,
|
||||
}));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PermitUnloader is a parent-process singleton that manages checking to see whether
|
||||
* or not <xul:browser> elements would prefer to be unloaded or not.
|
||||
*
|
||||
* PermitUnloader works by first finding the root nodes of process-contiguous trees
|
||||
* of frames. This means that if a document has the following frame structure:
|
||||
*
|
||||
* a
|
||||
* / \
|
||||
* b c
|
||||
* / \ / \
|
||||
* b d c c
|
||||
* / \
|
||||
* d d
|
||||
*
|
||||
* where each letter represents the process that each frame runs in, then we consider
|
||||
* process-contiguous trees of frames to be ones where all frames are directly connected
|
||||
* and running in the same process. Process-contiguous subtrees are denoted in the following
|
||||
* graph with shared numbers.
|
||||
*
|
||||
* a1
|
||||
* / \
|
||||
* b2 c3
|
||||
* / \ / \
|
||||
* b2 d4 c3 c3
|
||||
* / \
|
||||
* d4 d5
|
||||
*
|
||||
* Specifically, note that the d5 leaf node, while belonging to the same process as the
|
||||
* d4 nodes are not directly connected, so it's not process-contiguous.
|
||||
*
|
||||
* Messaging the roots of these process-contiguous subtrees concurrently allows us to ask
|
||||
* each of these subtrees to simultaneously run their permit unload checks in
|
||||
* breadth-first order. This appears to be Chromium's algorithm.
|
||||
*/
|
||||
var PermitUnloader = {
|
||||
// Maps a frameLoader to the state of a permitUnload request. If the frameLoader
|
||||
// doesn't exist in the map, then either permitUnload has never been called on
|
||||
// the frameLoader, or a previous permitUnload check has already completed.
|
||||
//
|
||||
// See permitUnload for the mapping structure.
|
||||
inProgressPermitUnload: new WeakMap(),
|
||||
|
||||
/* Public methods */
|
||||
|
||||
/**
|
||||
* Returns true if the frameLoader associated with a tab is still determining
|
||||
* whether unloading it is preferred. This might mean that beforeunload events
|
||||
* are still running, or the modal UI to prevent unloading is waiting on the
|
||||
* user to respond.
|
||||
*
|
||||
* @param {FrameLoader} frameLoader the frameLoader associated with a <xul:browser>
|
||||
* that might still be determining unload-ability.
|
||||
* @return {Boolean} true if unload-ability is still being determined.
|
||||
*/
|
||||
inPermitUnload(frameLoader) {
|
||||
return this.inProgressPermitUnload.has(frameLoader);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an Object indicating whether or not a <xul:browser> wants to be
|
||||
* unloaded or not.
|
||||
*
|
||||
* Note: this function spins a nested event loop while it waits for a response
|
||||
* from each process-contiguous subtree, and will throw to avoid re-entry for
|
||||
* the same frameLoader.
|
||||
*
|
||||
* @param {FrameLoader} frameLoader the frameLoader that should be checked for
|
||||
* unload-ability.
|
||||
* @param {Number} flags See nsIContentViewer.idl for the types of flags that
|
||||
* can be passed.
|
||||
*
|
||||
* @return {Object} an Object with the following structure:
|
||||
*
|
||||
* {
|
||||
* permitUnload: Boolean (true if all frames prefer to be unloaded.)
|
||||
* timedOut: Boolean (true if contacting all frames timed out.)
|
||||
* }
|
||||
*/
|
||||
permitUnload(frameLoader, flags) {
|
||||
// Don't allow re-entry for the same tab
|
||||
if (this.inPermitUnload(frameLoader)) {
|
||||
throw new Error("permitUnload is already running for this tab.");
|
||||
}
|
||||
|
||||
let frameStates = new Map();
|
||||
|
||||
// Until JS Window Actor teardown methods are implemented, we'll use the
|
||||
// message-manager-close observer notification to notice if the top-level
|
||||
// context has gone away.
|
||||
let mm = frameLoader.messageManager;
|
||||
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
let roots = this.getProcessContiguousRoots(frameLoader.browsingContext);
|
||||
|
||||
let state = {
|
||||
frameStates,
|
||||
timedOut: false,
|
||||
permitUnload: true,
|
||||
timer,
|
||||
waitingCount: roots.length,
|
||||
};
|
||||
|
||||
let observer = subject => {
|
||||
if (subject == mm) {
|
||||
this._finish(state);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
this.inProgressPermitUnload.set(frameLoader, state);
|
||||
Services.obs.addObserver(observer, "message-manager-close");
|
||||
|
||||
for (let windowGlobalParent of roots) {
|
||||
let actor = windowGlobalParent.getActor("BrowserElement");
|
||||
frameStates.set(actor.manager, STATE_WAITING);
|
||||
actor.sendPermitUnload(flags);
|
||||
}
|
||||
|
||||
timer.initWithCallback(() => {
|
||||
this._onTimeout(frameLoader);
|
||||
}, unloadTimeoutMs, timer.TYPE_ONE_SHOT);
|
||||
|
||||
Services.tm.spinEventLoopUntilOrShutdown(() => {
|
||||
return this._finishedPermitUnload(frameStates);
|
||||
});
|
||||
} finally {
|
||||
this._finish(state);
|
||||
this.inProgressPermitUnload.delete(frameLoader);
|
||||
Services.obs.removeObserver(observer, "message-manager-close");
|
||||
}
|
||||
|
||||
return {
|
||||
permitUnload: state.permitUnload,
|
||||
timedOut: state.timedOut,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Given a BrowsingContext, returns that BrowsingContext's WindowGlobalParent
|
||||
* along with all of the WindowGlobalParents for the roots of the process
|
||||
* contiguous subtrees under the passed BrowsingContext. See the documentation
|
||||
* for PermitUnloader for a detailed illustration of what roots of
|
||||
* process-contiguous subtrees are.
|
||||
*
|
||||
* @param {BrowsingContext} browsingContext the BrowsingContext whose descendants
|
||||
* should be searched. The WindowGlobalParent of this BrowsingContext will be
|
||||
* included in the returned Array.
|
||||
*
|
||||
* @return {Array<WindowGlobalParent>} The WindowGlobalParent's for all roots of
|
||||
* process-contiguous subtrees of the passed in BrowsingContext, along with the
|
||||
* WindowGlobalParent of the passed in BrowsingContext.
|
||||
*/
|
||||
getProcessContiguousRoots(browsingContext) {
|
||||
let contextsToWalk = [browsingContext];
|
||||
let roots = [];
|
||||
|
||||
while (contextsToWalk.length) {
|
||||
let currentContext = contextsToWalk.pop();
|
||||
let windowGlobal = currentContext.currentWindowGlobal;
|
||||
|
||||
if (!windowGlobal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (windowGlobal.isProcessRoot) {
|
||||
roots.push(windowGlobal);
|
||||
}
|
||||
|
||||
contextsToWalk.push(...currentContext.getChildren());
|
||||
}
|
||||
|
||||
return roots;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if any of the subframes associated with the frameLoader
|
||||
* have a beforeunload event handler set. If this returns false, there's
|
||||
* no point in running permitUnload on the frameLoader, as we know that
|
||||
* no frames will prevent it.
|
||||
*
|
||||
* @param {FrameLoader} frameLoader the frameLoader that should be checked for
|
||||
* beforeunload event handlers.
|
||||
*
|
||||
* @return {Boolean} true if there's at least one beforeunload event handler
|
||||
* set in any of the frameLoader's frames.
|
||||
*/
|
||||
hasBeforeUnload(frameLoader) {
|
||||
if (frameLoader.remoteTab) {
|
||||
return frameLoader.remoteTab.hasBeforeUnload;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Private methods - the following methods are only expected to be called
|
||||
* from PermitUnloader or BrowserElementParent.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is called when the BrowserElementParent receives a message from the
|
||||
* process-contiguous subtree root alerting it that it is either starting to
|
||||
* run the permitUnload routine, or has completed running the permitUnload
|
||||
* routine.
|
||||
*
|
||||
* @param {WindowGlobalParent} windowGlobal the WindowGlobalParent for the
|
||||
* root of the process-contiguous subtree that we're receiving the state
|
||||
* update from.
|
||||
* @param Number newFrameState one of STATE_RUNNING or STATE_DONE.
|
||||
*/
|
||||
_transitionFrameState(windowGlobal, newFrameState) {
|
||||
let frameLoader = windowGlobal.rootFrameLoader;
|
||||
let state = this.inProgressPermitUnload.get(frameLoader);
|
||||
let oldFrameState = state.frameStates.get(windowGlobal);
|
||||
|
||||
if (oldFrameState == STATE_WAITING) {
|
||||
state.waitingCount--;
|
||||
|
||||
if (!state.waitingCount) {
|
||||
state.timer.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
state.frameStates.set(windowGlobal, newFrameState);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called from the permitUnload nested event loop to check whether we've
|
||||
* gotten enough information to exit that loop. Enough information means
|
||||
* either that we've heard from all process-contiguous subtrees, or we've
|
||||
* gotten an answer from one of the process-contiguous subtrees saying that
|
||||
* we shouldn't unload the <xul:browser>, or that we've timed out waiting
|
||||
* for all responses to come back.
|
||||
*
|
||||
* @param {Map} frameStates the mapping of process-contiguous subtree roots
|
||||
* to their loading states as set by permitUnload.
|
||||
*
|
||||
* @return {Boolean} true if the nested event loop should exit.
|
||||
*/
|
||||
_finishedPermitUnload(frameStates) {
|
||||
for (let [, state] of frameStates) {
|
||||
if (state != STATE_DONE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when we no longer want to run the nested event loop, and no longer want
|
||||
* to fire the messaging timeout function.
|
||||
*
|
||||
* @param {Object} state the state for the frameLoader that we're currently checking
|
||||
* for unload-ability.
|
||||
*/
|
||||
_finish(state) {
|
||||
state.frameStates.clear();
|
||||
state.timer.cancel();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the timeout for checking for unload-ability has fired. This means
|
||||
* we haven't heard back from all of the process-contiguous subtrees in time,
|
||||
* in which case, we assume that it's safe to unload the <xul:browser>.
|
||||
*
|
||||
* @param {FrameLoader} frameLoader the frameLoader that took too long to detect
|
||||
* unload-ability.
|
||||
*/
|
||||
_onTimeout(frameLoader) {
|
||||
let state = this.inProgressPermitUnload.get(frameLoader);
|
||||
state.timedOut = true;
|
||||
this._finish(state);
|
||||
// Dispatch something to ensure that the main thread wakes up.
|
||||
Services.tm.dispatchToMainThread(function() {});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called by the BrowserElementParent if a process-contiguous subtree reports that
|
||||
* it shouldn't be unloaded.
|
||||
*
|
||||
* @param {WindowGlobalParent} windowGlobal the WindowGlobalParent for the
|
||||
* root of the process-contiguous subtree that requested that unloading not
|
||||
* occur.
|
||||
*/
|
||||
_doNotPermitUnload(windowGlobal) {
|
||||
let frameLoader = windowGlobal.rootFrameLoader;
|
||||
let state = this.inProgressPermitUnload.get(frameLoader);
|
||||
// We might have already heard from a previous process-contiguous subtree
|
||||
// that unload should not be permitted, in which case this state will have
|
||||
// been cleared. In that case, we'll just ignore the message.
|
||||
if (state) {
|
||||
this._finish(state);
|
||||
state.permitUnload = false;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -21,7 +21,8 @@ TESTING_JS_MODULES += [
|
||||
FINAL_TARGET_FILES.actors += [
|
||||
'AudioPlaybackChild.jsm',
|
||||
'AutoplayChild.jsm',
|
||||
'BrowserChild.jsm',
|
||||
'BrowserElementChild.jsm',
|
||||
'BrowserElementParent.jsm',
|
||||
'ControllersChild.jsm',
|
||||
'DateTimePickerChild.jsm',
|
||||
'ExtFindChild.jsm',
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
/* eslint-env mozilla/frame-script */
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "BrowserUtils",
|
||||
"resource://gre/modules/BrowserUtils.jsm");
|
||||
|
||||
const {WebProgressChild} = ChromeUtils.import("resource://gre/modules/WebProgressChild.jsm");
|
||||
|
||||
this.WebProgress = new WebProgressChild(this);
|
||||
@@ -28,6 +31,21 @@ addEventListener("ImageContentLoaded", function(aEvent) {
|
||||
}
|
||||
}, false);
|
||||
|
||||
// This is here for now until we find a better way of forcing an about:blank load
|
||||
// with a particular principal that doesn't involve the message manager. We can't
|
||||
// do this with JS Window Actors for now because JS Window Actors are tied to the
|
||||
// document principals themselves, so forcing the load with a new principal is
|
||||
// self-destructive in that case.
|
||||
addMessageListener("BrowserElement:CreateAboutBlank", message => {
|
||||
if (!content.document || content.document.documentURI != "about:blank") {
|
||||
throw new Error("Can't create a content viewer unless on about:blank");
|
||||
}
|
||||
let principal = message.data;
|
||||
principal = BrowserUtils.principalWithMatchingOA(principal,
|
||||
content.document.nodePrincipal);
|
||||
docShell.createAboutBlankContentViewer(principal);
|
||||
});
|
||||
|
||||
// We may not get any responses to Browser:Init if the browser element
|
||||
// is torn down too quickly.
|
||||
var outerWindowID = docShell.outerWindowID;
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
{
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let LazyModules = {};
|
||||
|
||||
ChromeUtils.defineModuleGetter(LazyModules, "PermitUnloader",
|
||||
"resource://gre/actors/BrowserElementParent.jsm");
|
||||
|
||||
const elementsToDestroyOnUnload = new Set();
|
||||
|
||||
window.addEventListener("unload", () => {
|
||||
@@ -306,8 +311,6 @@ class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) {
|
||||
this._autoScrollScrollId = null;
|
||||
|
||||
this._autoScrollPresShellId = null;
|
||||
|
||||
this._permitUnloadId = 0;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
@@ -1430,8 +1433,20 @@ class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) {
|
||||
BrowserUtils.principalWithMatchingOA(aPrincipal, this.contentPrincipal);
|
||||
this.frameLoader.remoteTab.transmitPermissionsForPrincipal(permissionPrincipal);
|
||||
|
||||
// Create the about blank content viewer in the content process
|
||||
this.messageManager.sendAsyncMessage("Browser:CreateAboutBlank", aPrincipal);
|
||||
// This still uses the message manager, for the following reasons:
|
||||
//
|
||||
// 1. Due to bug 1523638, it's virtually certain that, if we've just created
|
||||
// this <xul:browser>, that the WindowGlobalParent for the top-level frame
|
||||
// of this browser doesn't exist yet, so it's not possible to get at a
|
||||
// JS Window Actor for it.
|
||||
//
|
||||
// 2. JS Window Actors are tied to the principals for the frames they're running
|
||||
// in - switching principals is therefore self-destructive and unexpected.
|
||||
//
|
||||
// So we'll continue to use the message manager until we come up with a better
|
||||
// solution.
|
||||
this.messageManager.sendAsyncMessage("BrowserElement:CreateAboutBlank",
|
||||
aPrincipal);
|
||||
return;
|
||||
}
|
||||
let principal = BrowserUtils.principalWithMatchingOA(aPrincipal, this.contentPrincipal);
|
||||
@@ -1754,16 +1769,13 @@ class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) {
|
||||
|
||||
getInPermitUnload(aCallback) {
|
||||
if (this.isRemoteBrowser) {
|
||||
let id = this._permitUnloadId++;
|
||||
let mm = this.messageManager;
|
||||
mm.sendAsyncMessage("InPermitUnload", { id });
|
||||
mm.addMessageListener("InPermitUnload", function listener(msg) {
|
||||
if (msg.data.id != id) {
|
||||
return;
|
||||
}
|
||||
mm.removeMessageListener("InPermitUnload", listener);
|
||||
aCallback(msg.data.inPermitUnload);
|
||||
});
|
||||
let { remoteTab } = this.frameLoader;
|
||||
if (!remoteTab) {
|
||||
// If we're crashed, we're definitely not in this state anymore.
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
aCallback(LazyModules.PermitUnloader.inPermitUnload(this.frameLoader));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1776,68 +1788,11 @@ class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) {
|
||||
|
||||
permitUnload(aPermitUnloadFlags) {
|
||||
if (this.isRemoteBrowser) {
|
||||
let { remoteTab } = this.frameLoader;
|
||||
|
||||
if (!remoteTab.hasBeforeUnload) {
|
||||
if (!LazyModules.PermitUnloader.hasBeforeUnload(this.frameLoader)) {
|
||||
return { permitUnload: true, timedOut: false };
|
||||
}
|
||||
|
||||
const kTimeout = 1000;
|
||||
|
||||
let finished = false;
|
||||
let responded = false;
|
||||
let permitUnload;
|
||||
let id = this._permitUnloadId++;
|
||||
let mm = this.messageManager;
|
||||
let {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
let msgListener = msg => {
|
||||
if (msg.data.id != id) {
|
||||
return;
|
||||
}
|
||||
if (msg.data.kind == "start") {
|
||||
responded = true;
|
||||
return;
|
||||
}
|
||||
done(msg.data.permitUnload);
|
||||
};
|
||||
|
||||
let observer = subject => {
|
||||
if (subject == mm) {
|
||||
done(true);
|
||||
}
|
||||
};
|
||||
|
||||
function done(result) {
|
||||
finished = true;
|
||||
permitUnload = result;
|
||||
mm.removeMessageListener("PermitUnload", msgListener);
|
||||
Services.obs.removeObserver(observer, "message-manager-close");
|
||||
}
|
||||
|
||||
mm.sendAsyncMessage("PermitUnload", { id, aPermitUnloadFlags });
|
||||
mm.addMessageListener("PermitUnload", msgListener);
|
||||
Services.obs.addObserver(observer, "message-manager-close");
|
||||
|
||||
let timedOut = false;
|
||||
|
||||
function timeout() {
|
||||
if (!responded) {
|
||||
timedOut = true;
|
||||
}
|
||||
|
||||
// Dispatch something to ensure that the main thread wakes up.
|
||||
Services.tm.dispatchToMainThread(function() {});
|
||||
}
|
||||
|
||||
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
timer.initWithCallback(timeout, kTimeout, timer.TYPE_ONE_SHOT);
|
||||
|
||||
while (!finished && !timedOut) {
|
||||
Services.tm.currentThread.processNextEvent(true);
|
||||
}
|
||||
|
||||
return { permitUnload, timedOut };
|
||||
return LazyModules.PermitUnloader.permitUnload(this.frameLoader, aPermitUnloadFlags);
|
||||
}
|
||||
|
||||
if (!this.docShell || !this.docShell.contentViewer) {
|
||||
|
||||
@@ -101,6 +101,20 @@ const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
const {DefaultMap} = ExtensionUtils;
|
||||
|
||||
let ACTORS = {
|
||||
BrowserElement: {
|
||||
parent: {
|
||||
moduleURI: "resource://gre/actors/BrowserElementParent.jsm",
|
||||
},
|
||||
|
||||
child: {
|
||||
moduleURI: "resource://gre/actors/BrowserElementChild.jsm",
|
||||
events: {
|
||||
"DOMWindowClose": {},
|
||||
},
|
||||
},
|
||||
|
||||
allFrames: true,
|
||||
},
|
||||
};
|
||||
|
||||
let LEGACY_ACTORS = {
|
||||
@@ -125,21 +139,6 @@ let LEGACY_ACTORS = {
|
||||
},
|
||||
},
|
||||
|
||||
Browser: {
|
||||
child: {
|
||||
module: "resource://gre/actors/BrowserChild.jsm",
|
||||
events: {
|
||||
"DOMWindowClose": {},
|
||||
},
|
||||
|
||||
messages: [
|
||||
"Browser:CreateAboutBlank",
|
||||
"InPermitUnload",
|
||||
"PermitUnload",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
Controllers: {
|
||||
child: {
|
||||
module: "resource://gre/actors/ControllersChild.jsm",
|
||||
|
||||
Reference in New Issue
Block a user