зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1631362 - Bind prompts to JSWindowActor lifetime instead of closing them on pagehide. r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D73882
This commit is contained in:
Родитель
c85367f9d5
Коммит
225f10d76a
|
@ -1,20 +0,0 @@
|
|||
/* 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 = ["PromptChild"];
|
||||
|
||||
class PromptChild extends JSWindowActorChild {
|
||||
constructor(dispatcher) {
|
||||
super(dispatcher);
|
||||
}
|
||||
|
||||
handleEvent(aEvent) {
|
||||
if (aEvent.type !== "pagehide") {
|
||||
return;
|
||||
}
|
||||
this.sendAsyncMessage("Prompt:OnPageHide", {});
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@ class PromptParent extends JSWindowActorParent {
|
|||
didDestroy() {
|
||||
// In the event that the subframe or tab crashed, make sure that
|
||||
// we close any active Prompts.
|
||||
this.forceClosePrompts(this.browsingContext);
|
||||
this.forceClosePrompts();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,8 +47,6 @@ class PromptParent extends JSWindowActorParent {
|
|||
* We need to track a Prompt so that we can, for example, force-close the
|
||||
* TabModalPrompt if the originating subframe or tab unloads or crashes.
|
||||
*
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* The BrowsingContext from which the request to open the Prompt came.
|
||||
* @param {Object} tabModalPrompt
|
||||
* The TabModalPrompt that will be shown to the user.
|
||||
* @param {string} id
|
||||
|
@ -59,11 +57,11 @@ class PromptParent extends JSWindowActorParent {
|
|||
* Resolves with the arguments returned from the TabModalPrompt when it
|
||||
* is dismissed.
|
||||
*/
|
||||
registerPrompt(browsingContext, tabModalPrompt, id) {
|
||||
let prompts = gBrowserPrompts.get(browsingContext);
|
||||
registerPrompt(tabModalPrompt, id) {
|
||||
let prompts = gBrowserPrompts.get(this.browsingContext);
|
||||
if (!prompts) {
|
||||
prompts = new Map();
|
||||
gBrowserPrompts.set(browsingContext, prompts);
|
||||
gBrowserPrompts.set(this.browsingContext, prompts);
|
||||
}
|
||||
|
||||
let promise = new Promise(resolve => {
|
||||
|
@ -80,45 +78,22 @@ class PromptParent extends JSWindowActorParent {
|
|||
* Removes a Prompt for a BrowsingContext with a particular ID from the registry.
|
||||
* This needs to be done to avoid leaking <xul:browser>'s.
|
||||
*
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* The BrowsingContext from which the request to open the Prompt came.
|
||||
* @param {string} id
|
||||
* A unique ID to differentiate multiple Prompts coming from the same
|
||||
* BrowsingContext.
|
||||
*/
|
||||
unregisterPrompt(browsingContext, id) {
|
||||
let prompts = gBrowserPrompts.get(browsingContext);
|
||||
unregisterPrompt(id) {
|
||||
let prompts = gBrowserPrompts.get(this.browsingContext);
|
||||
if (prompts) {
|
||||
prompts.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatically closes a Prompt, without waiting for the TabModalPrompt to
|
||||
* return with any arguments.
|
||||
*
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* The BrowsingContext from which the request to open the Prompt came.
|
||||
* @param {string} id
|
||||
* A unique ID to differentiate multiple Prompts coming from the same
|
||||
* BrowsingContext.
|
||||
* Programmatically closes all Prompts for the current BrowsingContext.
|
||||
*/
|
||||
forceClosePrompt(browsingContext, id) {
|
||||
let prompts = gBrowserPrompts.get(browsingContext);
|
||||
let prompt = prompts.get(id);
|
||||
if (prompt && prompt.tabModalPrompt) {
|
||||
prompt.tabModalPrompt.abortPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatically closes all Prompts for a BrowsingContext.
|
||||
*
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* The BrowsingContext from which the request to open the Prompts came.
|
||||
*/
|
||||
forceClosePrompts(browsingContext) {
|
||||
let prompts = gBrowserPrompts.get(browsingContext) || [];
|
||||
forceClosePrompts() {
|
||||
let prompts = gBrowserPrompts.get(this.browsingContext) || [];
|
||||
|
||||
for (let [, prompt] of prompts) {
|
||||
prompt.tabModalPrompt && prompt.tabModalPrompt.abortPrompt();
|
||||
|
@ -127,27 +102,14 @@ class PromptParent extends JSWindowActorParent {
|
|||
|
||||
receiveMessage(message) {
|
||||
let args = message.data;
|
||||
let browsingContext = args.browsingContext || this.browsingContext;
|
||||
let id = args._remoteId;
|
||||
|
||||
switch (message.name) {
|
||||
case "Prompt:Open": {
|
||||
let topPrincipal =
|
||||
browsingContext.top.currentWindowGlobal.documentPrincipal;
|
||||
args.showAlertOrigin = topPrincipal.equals(args.promptPrincipal);
|
||||
if (args.modalType === Ci.nsIPrompt.MODAL_TYPE_WINDOW) {
|
||||
return this.openWindowPrompt(args, browsingContext);
|
||||
return this.openWindowPrompt(args);
|
||||
}
|
||||
return this.openTabPrompt(args, browsingContext, id);
|
||||
}
|
||||
case "Prompt:ForceClose": {
|
||||
this.forceClosePrompt(browsingContext, id);
|
||||
break;
|
||||
}
|
||||
case "Prompt:OnPageHide": {
|
||||
// User navigates away, close all non window prompts
|
||||
this.forceClosePrompts(browsingContext);
|
||||
break;
|
||||
return this.openTabPrompt(args, id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,8 +123,6 @@ class PromptParent extends JSWindowActorParent {
|
|||
* @param {Object} args
|
||||
* The arguments passed up from the BrowsingContext to be passed directly
|
||||
* to the TabModalPrompt.
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* The BrowsingContext from which the request to open the Prompts came.
|
||||
* @param {string} id
|
||||
* A unique ID to differentiate multiple Prompts coming from the same
|
||||
* BrowsingContext.
|
||||
|
@ -171,8 +131,8 @@ class PromptParent extends JSWindowActorParent {
|
|||
* @resolves {Object}
|
||||
* The arguments returned from the TabModalPrompt.
|
||||
*/
|
||||
openTabPrompt(args, browsingContext = this.browsingContext, id) {
|
||||
let browser = browsingContext.top.embedderElement;
|
||||
openTabPrompt(args, id) {
|
||||
let browser = this.browsingContext.top.embedderElement;
|
||||
if (!browser) {
|
||||
throw new Error("Cannot tab-prompt without a browser!");
|
||||
}
|
||||
|
@ -181,8 +141,15 @@ class PromptParent extends JSWindowActorParent {
|
|||
let newPrompt;
|
||||
let needRemove = false;
|
||||
|
||||
let onPromptClose = forceCleanup => {
|
||||
let promptData = gBrowserPrompts.get(browsingContext);
|
||||
// If the page which called the prompt is different from the the top context
|
||||
// where we show the prompt, ask the prompt implementation to display the origin.
|
||||
// For example, this can happen if a cross origin subframe shows a prompt.
|
||||
args.showCallerOrigin =
|
||||
args.promptPrincipal &&
|
||||
!browser.contentPrincipal.equals(args.promptPrincipal);
|
||||
|
||||
let onPromptClose = () => {
|
||||
let promptData = gBrowserPrompts.get(this.browsingContext);
|
||||
if (!promptData || !promptData.has(id)) {
|
||||
throw new Error(
|
||||
"Failed to close a prompt since it wasn't registered for some reason."
|
||||
|
@ -200,7 +167,7 @@ class PromptParent extends JSWindowActorParent {
|
|||
needRemove = true;
|
||||
}
|
||||
|
||||
this.unregisterPrompt(browsingContext, id);
|
||||
this.unregisterPrompt(id);
|
||||
|
||||
PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
|
||||
resolver(args);
|
||||
|
@ -224,7 +191,7 @@ class PromptParent extends JSWindowActorParent {
|
|||
args.promptActive = true;
|
||||
|
||||
newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
|
||||
let promise = this.registerPrompt(browsingContext, newPrompt, id);
|
||||
let promise = this.registerPrompt(newPrompt, id);
|
||||
|
||||
if (needRemove) {
|
||||
tabPrompt.removePrompt(newPrompt);
|
||||
|
@ -246,30 +213,33 @@ class PromptParent extends JSWindowActorParent {
|
|||
* @param {Object} args
|
||||
* The arguments passed up from the BrowsingContext to be passed
|
||||
* directly to the modal window.
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* The BrowsingContext from which the request to open the window-modal
|
||||
* prompt came.
|
||||
* @return {Promise}
|
||||
* Resolves when the window prompt is dismissed.
|
||||
* @resolves {Object}
|
||||
* The arguments returned from the window prompt.
|
||||
*/
|
||||
async openWindowPrompt(args, browsingContext = this.browsingContext) {
|
||||
async openWindowPrompt(args) {
|
||||
const COMMON_DIALOG = "chrome://global/content/commonDialog.xhtml";
|
||||
const SELECT_DIALOG = "chrome://global/content/selectDialog.xhtml";
|
||||
let uri = args.promptType == "select" ? SELECT_DIALOG : COMMON_DIALOG;
|
||||
|
||||
let browser = browsingContext.top.embedderElement;
|
||||
// If can't get the browser, because the BC does not have an embedder element,
|
||||
// use window associated with the BC.
|
||||
// This happens if we are passed a browsingContext of a chrome window.
|
||||
let win = (browser && browser.ownerGlobal) || browsingContext.top.window;
|
||||
let browsingContext = this.browsingContext.top;
|
||||
|
||||
let browser = browsingContext.embedderElement;
|
||||
let win;
|
||||
|
||||
// If we are a chrome actor we can use the associated chrome win.
|
||||
if (!browsingContext.isContent && browsingContext.window) {
|
||||
win = browsingContext.window;
|
||||
} else {
|
||||
win = browser?.ownerGlobal;
|
||||
}
|
||||
|
||||
// There's a requirement for prompts to be blocked if a window is
|
||||
// passed and that window is hidden (eg, auth prompts are suppressed if the
|
||||
// passed window is the hidden window).
|
||||
// See bug 875157 comment 30 for more..
|
||||
if (win && win.winUtils && !win.winUtils.isParentWindowMainWidgetVisible) {
|
||||
if (win?.winUtils && !win.winUtils.isParentWindowMainWidgetVisible) {
|
||||
throw new Error("Cannot call openModalWindow on a hidden window");
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,6 @@ FINAL_TARGET_FILES.actors += [
|
|||
'PageStyleParent.jsm',
|
||||
'PluginChild.jsm',
|
||||
'PluginParent.jsm',
|
||||
'PromptChild.jsm',
|
||||
'PromptParent.jsm',
|
||||
'RFPHelperChild.jsm',
|
||||
'RFPHelperParent.jsm',
|
||||
|
|
|
@ -43,7 +43,6 @@ const whitelist = {
|
|||
"resource:///actors/BrowserTabChild.jsm",
|
||||
"resource:///actors/LinkHandlerChild.jsm",
|
||||
"resource:///actors/SearchTelemetryChild.jsm",
|
||||
"resource:///actors/PromptChild.jsm",
|
||||
"resource://gre/actors/AutoCompleteChild.jsm",
|
||||
"resource://gre/modules/ActorManagerChild.jsm",
|
||||
"resource://gre/modules/E10SUtils.jsm",
|
||||
|
|
|
@ -437,14 +437,6 @@ let ACTORS = {
|
|||
parent: {
|
||||
moduleURI: "resource:///actors/PromptParent.jsm",
|
||||
},
|
||||
child: {
|
||||
moduleURI: "resource:///actors/PromptChild.jsm",
|
||||
events: {
|
||||
pagehide: {
|
||||
capture: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
includeChrome: true,
|
||||
allFrames: true,
|
||||
},
|
||||
|
|
|
@ -242,9 +242,12 @@ var TabModalPrompt = class {
|
|||
this.Dialog = new tmp.CommonDialog(args, this.ui);
|
||||
this.Dialog.onLoad(null);
|
||||
|
||||
// Display the tabprompt title that shows the prompt origin when
|
||||
// For content prompts display the tabprompt title that shows the prompt origin when
|
||||
// the prompt origin is not the same as that of the top window.
|
||||
if (!args.showAlertOrigin) {
|
||||
if (
|
||||
args.modalType == Ci.nsIPrompt.MODAL_TYPE_CONTENT &&
|
||||
args.showCallerOrigin
|
||||
) {
|
||||
this.ui.infoTitle.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
|
|
|
@ -1011,21 +1011,12 @@ class ModalPrompter {
|
|||
if (browsingContext && domWin) {
|
||||
throw new Error("Pass either browsingContext or domWin");
|
||||
}
|
||||
this.browsingContext = browsingContext;
|
||||
this._domWin = domWin;
|
||||
|
||||
if (this._domWin) {
|
||||
if (domWin) {
|
||||
// We have a domWin, get the associated browsing context
|
||||
this.browsingContext = BrowsingContext.getFromWindow(this._domWin);
|
||||
} else if (this.browsingContext) {
|
||||
// We have a browsingContext, get the associated dom window
|
||||
if (this.browsingContext.window) {
|
||||
this._domWin = this.browsingContext.window;
|
||||
} else {
|
||||
this._domWin =
|
||||
this.browsingContext.embedderElement &&
|
||||
this.browsingContext.embedderElement.ownerGlobal;
|
||||
}
|
||||
this.browsingContext = BrowsingContext.getFromWindow(domWin);
|
||||
} else {
|
||||
this.browsingContext = browsingContext;
|
||||
}
|
||||
|
||||
// Use given modal type or fallback to default
|
||||
|
@ -1048,16 +1039,12 @@ class ModalPrompter {
|
|||
return;
|
||||
}
|
||||
|
||||
// If we have a chrome window and the browsing context isn't embedded
|
||||
// in a browser, we can't use tab/content prompts.
|
||||
// Or if we don't allow tab or content prompts, override modalType
|
||||
// argument to use window prompts
|
||||
// We can't use content / tab prompts if they are disabled by pref,
|
||||
// or we are not given a parent.
|
||||
if (
|
||||
!ModalPrompter.tabModalEnabled ||
|
||||
!this.browsingContext ||
|
||||
!this._domWin ||
|
||||
(this._domWin.isChromeWindow &&
|
||||
!this.browsingContext.top.embedderElement) ||
|
||||
!ModalPrompter.tabModalEnabled
|
||||
!this.browsingContext.isContent
|
||||
) {
|
||||
modalType = Ci.nsIPrompt.MODAL_TYPE_WINDOW;
|
||||
|
||||
|
@ -1116,44 +1103,59 @@ class ModalPrompter {
|
|||
args.modalType = this.modalType;
|
||||
}
|
||||
|
||||
args.browsingContext = this.browsingContext;
|
||||
const IS_CONTENT =
|
||||
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
|
||||
|
||||
let actor;
|
||||
try {
|
||||
actor = this._domWin.windowGlobalChild.getActor("Prompt");
|
||||
} catch (error) {
|
||||
Cu.reportError(error);
|
||||
if (IS_CONTENT) {
|
||||
// When in the content, get the PromptChild actor.
|
||||
actor = this.browsingContext.window.windowGlobalChild.getActor(
|
||||
"Prompt"
|
||||
);
|
||||
} else {
|
||||
// When in the parent, get the PromptParent actor.
|
||||
actor = this.browsingContext.currentWindowGlobal.getActor("Prompt");
|
||||
}
|
||||
} catch (_) {
|
||||
// We can't get the prompt actor, fallback to window prompt.
|
||||
this.openWindowPrompt(this._domWin, args);
|
||||
let parentWin;
|
||||
// If given a chrome BC we can try to get its window
|
||||
if (!this.browsingContext.isContent && this.browsingContext.window) {
|
||||
parentWin = this.browsingContext.window;
|
||||
} else {
|
||||
// Try to get the window which is the browsers parent
|
||||
parentWin = this.browsingContext.top?.embedderElement?.ownerGlobal;
|
||||
}
|
||||
this.openWindowPrompt(parentWin, args);
|
||||
return args;
|
||||
}
|
||||
|
||||
let docShell =
|
||||
(this.browsingContext && this.browsingContext.docShell) ||
|
||||
this._domWin.docShell;
|
||||
let inPermitUnload =
|
||||
docShell.contentViewer && docShell.contentViewer.inPermitUnload;
|
||||
let eventDetail = Cu.cloneInto(
|
||||
{
|
||||
tabPrompt: this.modalType != Ci.nsIPrompt.MODAL_TYPE_WINDOW,
|
||||
inPermitUnload,
|
||||
},
|
||||
this._domWin
|
||||
);
|
||||
PromptUtils.fireDialogEvent(
|
||||
this._domWin,
|
||||
"DOMWillOpenModalDialog",
|
||||
null,
|
||||
eventDetail
|
||||
);
|
||||
if (IS_CONTENT) {
|
||||
args.promptPrincipal = this.browsingContext.window?.document.nodePrincipal;
|
||||
|
||||
let windowUtils =
|
||||
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT &&
|
||||
this._domWin.windowUtils;
|
||||
let docShell = this.browsingContext.docShell;
|
||||
let inPermitUnload = docShell?.contentViewer?.inPermitUnload;
|
||||
args.inPermitUnload = inPermitUnload;
|
||||
let eventDetail = Cu.cloneInto(
|
||||
{
|
||||
tabPrompt: this.modalType != Ci.nsIPrompt.MODAL_TYPE_WINDOW,
|
||||
inPermitUnload,
|
||||
},
|
||||
this.browsingContext.window
|
||||
);
|
||||
PromptUtils.fireDialogEvent(
|
||||
this.browsingContext.window,
|
||||
"DOMWillOpenModalDialog",
|
||||
null,
|
||||
eventDetail
|
||||
);
|
||||
|
||||
// Put content windows in the modal state while the prompt is open.
|
||||
if (windowUtils) {
|
||||
windowUtils.enterModalState();
|
||||
// Put content window in the modal state while the prompt is open.
|
||||
let windowUtils = this.browsingContext.window?.windowUtils;
|
||||
if (windowUtils) {
|
||||
windowUtils.enterModalState();
|
||||
}
|
||||
}
|
||||
|
||||
// It is technically possible for multiple prompts to be sent from a single
|
||||
|
@ -1166,25 +1168,40 @@ class ModalPrompter {
|
|||
.generateUUID()
|
||||
.toString();
|
||||
|
||||
args.promptPrincipal = this._domWin.document.nodePrincipal;
|
||||
args.inPermitUnload = inPermitUnload;
|
||||
args._remoteId = id;
|
||||
|
||||
let returnedArgs;
|
||||
|
||||
try {
|
||||
returnedArgs = await actor.sendQuery("Prompt:Open", args);
|
||||
if (returnedArgs && returnedArgs.promptAborted) {
|
||||
if (IS_CONTENT) {
|
||||
// If we're in the content process, send a message to the PromptParent
|
||||
// window actor.
|
||||
returnedArgs = await actor.sendQuery("Prompt:Open", args);
|
||||
} else {
|
||||
// If we're in the parent process we already have the parent actor.
|
||||
// We can call its message handler directly.
|
||||
returnedArgs = await actor.receiveMessage({
|
||||
name: "Prompt:Open",
|
||||
data: args,
|
||||
});
|
||||
}
|
||||
|
||||
if (returnedArgs?.promptAborted) {
|
||||
throw Components.Exception(
|
||||
"prompt aborted by user",
|
||||
Cr.NS_ERROR_NOT_AVAILABLE
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (windowUtils) {
|
||||
windowUtils.leaveModalState();
|
||||
if (IS_CONTENT) {
|
||||
let windowUtils = this.browsingContext.window?.windowUtils;
|
||||
if (windowUtils) {
|
||||
windowUtils.leaveModalState();
|
||||
}
|
||||
PromptUtils.fireDialogEvent(
|
||||
this.browsingContext.window,
|
||||
"DOMModalDialogClosed"
|
||||
);
|
||||
}
|
||||
PromptUtils.fireDialogEvent(this._domWin, "DOMModalDialogClosed");
|
||||
}
|
||||
return returnedArgs;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче