зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1824221 - [remote] Move prompt observer listening logic to PromptListener. r=webdriver-reviewers,whimboo
Depends on D186790 Differential Revision: https://phabricator.services.mozilla.com/D186791
This commit is contained in:
Родитель
f54d247bf6
Коммит
813b9799be
|
@ -33,6 +33,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
permissions: "chrome://remote/content/marionette/permissions.sys.mjs",
|
||||
pprint: "chrome://remote/content/shared/Format.sys.mjs",
|
||||
print: "chrome://remote/content/shared/PDF.sys.mjs",
|
||||
PromptListener:
|
||||
"chrome://remote/content/shared/listeners/PromptListener.sys.mjs",
|
||||
quit: "chrome://remote/content/shared/Browser.sys.mjs",
|
||||
reftest: "chrome://remote/content/marionette/reftest.sys.mjs",
|
||||
registerCommandsActor:
|
||||
|
@ -123,9 +125,9 @@ export function GeckoDriver(server) {
|
|||
// Use content context by default
|
||||
this.context = lazy.Context.Content;
|
||||
|
||||
// used for modal dialogs or tab modal alerts
|
||||
// used for modal dialogs
|
||||
this.dialog = null;
|
||||
this.dialogObserver = null;
|
||||
this.promptListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -197,20 +199,20 @@ GeckoDriver.prototype.QueryInterface = ChromeUtils.generateQI([
|
|||
]);
|
||||
|
||||
/**
|
||||
* Callback used to observe the creation of new modal or tab modal dialogs
|
||||
* Callback used to observe the closing of modal dialogs
|
||||
* during the session's lifetime.
|
||||
*/
|
||||
GeckoDriver.prototype.handleModalDialog = function (action, dialog) {
|
||||
if (!this.currentSession) {
|
||||
return;
|
||||
}
|
||||
GeckoDriver.prototype.handleClosedModalDialog = function () {
|
||||
this.dialog = null;
|
||||
};
|
||||
|
||||
if (action === lazy.modal.ACTION_OPENED) {
|
||||
this.dialog = dialog;
|
||||
this.getActor().notifyDialogOpened();
|
||||
} else if (action === lazy.modal.ACTION_CLOSED) {
|
||||
this.dialog = null;
|
||||
}
|
||||
/**
|
||||
* Callback used to observe the creation of new modal dialogs
|
||||
* during the session's lifetime.
|
||||
*/
|
||||
GeckoDriver.prototype.handleOpenModalDialog = function (eventName, data) {
|
||||
this.dialog = data.prompt;
|
||||
this.getActor().notifyDialogOpened();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -448,10 +450,10 @@ GeckoDriver.prototype.newSession = async function (cmd) {
|
|||
this.mainFrame = appWin;
|
||||
|
||||
// Setup observer for modal dialogs
|
||||
this.dialogObserver = new lazy.modal.DialogObserver(
|
||||
() => this.curBrowser
|
||||
);
|
||||
this.dialogObserver.add(this.handleModalDialog.bind(this));
|
||||
this.promptListener = new lazy.PromptListener(() => this.curBrowser);
|
||||
this.promptListener.on("closed", this.handleClosedModalDialog.bind(this));
|
||||
this.promptListener.on("opened", this.handleOpenModalDialog.bind(this));
|
||||
this.promptListener.startListening();
|
||||
|
||||
for (let win of lazy.windowManager.windows) {
|
||||
this.registerWindow(win, { registerBrowsers: true });
|
||||
|
@ -2393,9 +2395,9 @@ GeckoDriver.prototype.deleteSession = function () {
|
|||
// reset to the top-most frame
|
||||
this.mainFrame = null;
|
||||
|
||||
if (this.dialogObserver) {
|
||||
this.dialogObserver.cleanup();
|
||||
this.dialogObserver = null;
|
||||
if (this.promptListener) {
|
||||
this.promptListener.stopListening();
|
||||
this.promptListener = null;
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -2696,7 +2698,7 @@ GeckoDriver.prototype.fullscreenWindow = async function () {
|
|||
};
|
||||
|
||||
/**
|
||||
* Dismisses a currently displayed tab modal, or returns no such alert if
|
||||
* Dismisses a currently displayed modal dialogs, or returns no such alert if
|
||||
* no modal is displayed.
|
||||
*
|
||||
* @throws {NoSuchAlertError}
|
||||
|
@ -2708,7 +2710,7 @@ GeckoDriver.prototype.dismissDialog = async function () {
|
|||
lazy.assert.open(this.getBrowsingContext({ top: true }));
|
||||
this._checkIfAlertIsPresent();
|
||||
|
||||
const dialogClosed = this.dialogObserver.dialogClosed();
|
||||
const dialogClosed = this.promptListener.dialogClosed();
|
||||
this.dialog.dismiss();
|
||||
await dialogClosed;
|
||||
|
||||
|
@ -2717,7 +2719,7 @@ GeckoDriver.prototype.dismissDialog = async function () {
|
|||
};
|
||||
|
||||
/**
|
||||
* Accepts a currently displayed tab modal, or returns no such alert if
|
||||
* Accepts a currently displayed dialog modal, or returns no such alert if
|
||||
* no modal is displayed.
|
||||
*
|
||||
* @throws {NoSuchAlertError}
|
||||
|
@ -2729,7 +2731,7 @@ GeckoDriver.prototype.acceptDialog = async function () {
|
|||
lazy.assert.open(this.getBrowsingContext({ top: true }));
|
||||
this._checkIfAlertIsPresent();
|
||||
|
||||
const dialogClosed = this.dialogObserver.dialogClosed();
|
||||
const dialogClosed = this.promptListener.dialogClosed();
|
||||
this.dialog.accept();
|
||||
await dialogClosed;
|
||||
|
||||
|
@ -2758,7 +2760,7 @@ GeckoDriver.prototype.getTextFromDialog = async function () {
|
|||
*
|
||||
* Sends keys to the input field of a currently displayed modal, or
|
||||
* returns a no such alert error if no modal is currently displayed. If
|
||||
* a tab modal is currently displayed but has no means for text input,
|
||||
* a modal dialog is currently displayed but has no means for text input,
|
||||
* an element not visible error is returned.
|
||||
*
|
||||
* @param {object} cmd
|
||||
|
|
|
@ -9,7 +9,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
EventDispatcher:
|
||||
"chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
|
||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||
modal: "chrome://remote/content/shared/Prompt.sys.mjs",
|
||||
PageLoadStrategy:
|
||||
"chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
|
||||
ProgressListener: "chrome://remote/content/shared/Navigate.sys.mjs",
|
||||
|
@ -256,11 +255,9 @@ navigate.waitForNavigationCompleted = async function waitForNavigationCompleted(
|
|||
}
|
||||
};
|
||||
|
||||
const onDialogOpened = action => {
|
||||
if (action === lazy.modal.ACTION_OPENED) {
|
||||
lazy.logger.trace("Canceled page load listener because a dialog opened");
|
||||
checkDone({ finished: true });
|
||||
}
|
||||
const onPromptOpened = action => {
|
||||
lazy.logger.trace("Canceled page load listener because a dialog opened");
|
||||
checkDone({ finished: true });
|
||||
};
|
||||
|
||||
const onTimer = timer => {
|
||||
|
@ -367,7 +364,7 @@ navigate.waitForNavigationCompleted = async function waitForNavigationCompleted(
|
|||
"XULFrameLoaderCreated",
|
||||
onBrowsingContextChanged
|
||||
);
|
||||
driver.dialogObserver.add(onDialogOpened);
|
||||
driver.promptListener.on("opened", onPromptOpened);
|
||||
Services.obs.addObserver(
|
||||
onBrowsingContextDiscarded,
|
||||
"browsing-context-discarded"
|
||||
|
@ -418,7 +415,7 @@ navigate.waitForNavigationCompleted = async function waitForNavigationCompleted(
|
|||
"XULFrameLoaderCreated",
|
||||
onBrowsingContextChanged
|
||||
);
|
||||
driver.dialogObserver?.remove(onDialogOpened);
|
||||
driver.promptListener?.off(onPromptOpened);
|
||||
unloadTimer?.cancel();
|
||||
|
||||
lazy.EventDispatcher.off("page-load", onNavigation);
|
||||
|
|
|
@ -7,7 +7,6 @@ const lazy = {};
|
|||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
|
||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
||||
});
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
|
||||
|
@ -93,225 +92,6 @@ modal.findPrompt = function (context) {
|
|||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Observer for modal and tab modal dialogs.
|
||||
*
|
||||
* @param {Function} curBrowserFn
|
||||
* Function that returns the current |browser.Context| or undefined.
|
||||
* If undefined is returned, execute the callback for the events from all contexts.
|
||||
*
|
||||
* @returns {modal.DialogObserver}
|
||||
* Returns instance of the DialogObserver class.
|
||||
*/
|
||||
modal.DialogObserver = class {
|
||||
constructor(curBrowserFn) {
|
||||
this._curBrowserFn = curBrowserFn;
|
||||
|
||||
this.callbacks = new Set();
|
||||
this.register();
|
||||
}
|
||||
|
||||
register() {
|
||||
Services.obs.addObserver(this, "common-dialog-loaded");
|
||||
Services.obs.addObserver(this, "domwindowopened");
|
||||
Services.obs.addObserver(this, "geckoview-prompt-show");
|
||||
|
||||
// Register event listener for all already open windows
|
||||
for (let win of Services.wm.getEnumerator(null)) {
|
||||
win.addEventListener("DOMModalDialogClosed", this);
|
||||
}
|
||||
}
|
||||
|
||||
unregister() {
|
||||
Services.obs.removeObserver(this, "common-dialog-loaded");
|
||||
Services.obs.removeObserver(this, "domwindowopened");
|
||||
Services.obs.removeObserver(this, "geckoview-prompt-show");
|
||||
|
||||
// Unregister event listener for all open windows
|
||||
for (let win of Services.wm.getEnumerator(null)) {
|
||||
win.removeEventListener("DOMModalDialogClosed", this);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.callbacks.clear();
|
||||
this.unregister();
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
lazy.logger.trace(`Received event ${event.type}`);
|
||||
|
||||
const chromeWin = event.target.opener
|
||||
? event.target.opener.ownerGlobal
|
||||
: event.target.ownerGlobal;
|
||||
const curBrowser = this._curBrowserFn();
|
||||
|
||||
if (curBrowser && chromeWin != curBrowser.window) {
|
||||
return;
|
||||
}
|
||||
|
||||
let contentBrowser;
|
||||
if (lazy.AppInfo.isAndroid) {
|
||||
const tabBrowser = lazy.TabManager.getTabBrowser(event.target);
|
||||
// Since on Android we always have only one tab we can just check
|
||||
// the selected tab.
|
||||
const tab = tabBrowser.selectedTab;
|
||||
contentBrowser = lazy.TabManager.getBrowserForTab(tab);
|
||||
} else {
|
||||
contentBrowser = event.target;
|
||||
}
|
||||
|
||||
this.callbacks.forEach(callback =>
|
||||
callback(modal.ACTION_CLOSED, event.detail, contentBrowser)
|
||||
);
|
||||
}
|
||||
|
||||
observe(subject, topic) {
|
||||
lazy.logger.trace(`Received observer notification ${topic}`);
|
||||
|
||||
let curBrowser = this._curBrowserFn();
|
||||
switch (topic) {
|
||||
case "common-dialog-loaded":
|
||||
if (curBrowser) {
|
||||
if (
|
||||
!this.#hasCommonDialog(
|
||||
curBrowser.contentBrowser,
|
||||
curBrowser.window,
|
||||
subject
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const chromeWin = subject.opener
|
||||
? subject.opener.ownerGlobal
|
||||
: subject.ownerGlobal;
|
||||
|
||||
for (const tab of lazy.TabManager.getTabsForWindow(chromeWin)) {
|
||||
const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
|
||||
const window = lazy.TabManager.getWindowForTab(tab);
|
||||
|
||||
if (this.#hasCommonDialog(contentBrowser, window, subject)) {
|
||||
curBrowser = {
|
||||
contentBrowser,
|
||||
window,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.callbacks.forEach(callback =>
|
||||
callback(
|
||||
modal.ACTION_OPENED,
|
||||
new modal.Dialog(() => curBrowser, subject),
|
||||
curBrowser.contentBrowser
|
||||
)
|
||||
);
|
||||
break;
|
||||
|
||||
case "domwindowopened":
|
||||
subject.addEventListener("DOMModalDialogClosed", this);
|
||||
break;
|
||||
|
||||
case "geckoview-prompt-show":
|
||||
for (let win of Services.wm.getEnumerator(null)) {
|
||||
const prompt = win.prompts().find(item => item.id == subject.id);
|
||||
if (prompt) {
|
||||
const tabBrowser = lazy.TabManager.getTabBrowser(win);
|
||||
// Since on Android we always have only one tab we can just check
|
||||
// the selected tab.
|
||||
const tab = tabBrowser.selectedTab;
|
||||
const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
|
||||
const window = lazy.TabManager.getWindowForTab(tab);
|
||||
|
||||
// Do not send the event if the curBrowser is specified,
|
||||
// and it's different from prompt browser.
|
||||
if (curBrowser && contentBrowser !== curBrowser.contentBrowser) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.callbacks.forEach(callback =>
|
||||
callback(
|
||||
modal.ACTION_OPENED,
|
||||
new modal.Dialog(
|
||||
() => ({
|
||||
contentBrowser,
|
||||
window,
|
||||
}),
|
||||
prompt
|
||||
),
|
||||
contentBrowser
|
||||
)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dialog handler by function reference.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* The handler to be added.
|
||||
*/
|
||||
add(callback) {
|
||||
if (this.callbacks.has(callback)) {
|
||||
return;
|
||||
}
|
||||
this.callbacks.add(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove dialog handler by function reference.
|
||||
*
|
||||
* @param {Function} callback
|
||||
* The handler to be removed.
|
||||
*/
|
||||
remove(callback) {
|
||||
if (!this.callbacks.has(callback)) {
|
||||
return;
|
||||
}
|
||||
this.callbacks.delete(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that waits for the dialog to be closed.
|
||||
*/
|
||||
async dialogClosed() {
|
||||
return new Promise(resolve => {
|
||||
const dialogClosed = (action, dialog) => {
|
||||
if (action == modal.ACTION_CLOSED) {
|
||||
this.remove(dialogClosed);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
this.add(dialogClosed);
|
||||
});
|
||||
}
|
||||
|
||||
#hasCommonDialog(contentBrowser, window, prompt) {
|
||||
const modalType = prompt.Dialog.args.modalType;
|
||||
if (
|
||||
modalType === Services.prompt.MODAL_TYPE_TAB ||
|
||||
modalType === Services.prompt.MODAL_TYPE_CONTENT
|
||||
) {
|
||||
// Find the container of the dialog in the parent document, and ensure
|
||||
// it is a descendant of the same container as the content browser.
|
||||
const container = contentBrowser.closest(".browserSidebarContainer");
|
||||
|
||||
return container.contains(prompt.docShell.chromeEventHandler);
|
||||
}
|
||||
|
||||
return prompt.ownerGlobal == window || prompt.opener?.ownerGlobal == window;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a modal dialog.
|
||||
*
|
||||
|
|
|
@ -5,9 +5,15 @@
|
|||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
|
||||
EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
|
||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||
modal: "chrome://remote/content/shared/Prompt.sys.mjs",
|
||||
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
||||
});
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
|
||||
|
||||
/**
|
||||
* The PromptListener listens to the DialogObserver events.
|
||||
*
|
||||
|
@ -43,59 +49,237 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
* The user text specified in a prompt.
|
||||
*/
|
||||
export class PromptListener {
|
||||
#observer;
|
||||
#curBrowserFn;
|
||||
#listening;
|
||||
|
||||
constructor() {
|
||||
constructor(curBrowserFn) {
|
||||
lazy.EventEmitter.decorate(this);
|
||||
|
||||
// curBrowserFn is used only for Marionette (WebDriver classic).
|
||||
this.#curBrowserFn = curBrowserFn;
|
||||
this.#listening = false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.stopListening();
|
||||
}
|
||||
|
||||
startListening() {
|
||||
if (this.#observer) {
|
||||
/**
|
||||
* Waits for the prompt to be closed.
|
||||
*
|
||||
* @returns {Promise}
|
||||
* Promise that resolves when the prompt is closed.
|
||||
*/
|
||||
async dialogClosed() {
|
||||
return new Promise(resolve => {
|
||||
const dialogClosed = () => {
|
||||
this.off("closed", dialogClosed);
|
||||
resolve();
|
||||
};
|
||||
|
||||
this.on("closed", dialogClosed);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles `DOMModalDialogClosed` events.
|
||||
*/
|
||||
handleEvent(event) {
|
||||
lazy.logger.trace(`Received event ${event.type}`);
|
||||
|
||||
const chromeWin = event.target.opener
|
||||
? event.target.opener.ownerGlobal
|
||||
: event.target.ownerGlobal;
|
||||
const curBrowser = this.#curBrowserFn && this.#curBrowserFn();
|
||||
|
||||
// For Marionette (WebDriver classic) we only care about events which come
|
||||
// the currently selected browser.
|
||||
if (curBrowser && chromeWin != curBrowser.window) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#observer = new lazy.modal.DialogObserver(() => {});
|
||||
this.#observer.add(this.#onEvent);
|
||||
let contentBrowser;
|
||||
if (lazy.AppInfo.isAndroid) {
|
||||
const tabBrowser = lazy.TabManager.getTabBrowser(event.target);
|
||||
// Since on Android we always have only one tab we can just check
|
||||
// the selected tab.
|
||||
const tab = tabBrowser.selectedTab;
|
||||
contentBrowser = lazy.TabManager.getBrowserForTab(tab);
|
||||
} else {
|
||||
contentBrowser = event.target;
|
||||
}
|
||||
|
||||
const detail = {};
|
||||
|
||||
// The event details are present now only on Desktop.
|
||||
// See the bug 1849621 for Android.
|
||||
if (event.detail) {
|
||||
const { areLeaving, value } = event.detail;
|
||||
// `areLeaving` returns undefined for alerts, for confirms and prompts
|
||||
// it returns true if a user prompt was accepted and false if it was dismissed.
|
||||
detail.accepted = areLeaving === undefined ? true : areLeaving;
|
||||
if (value) {
|
||||
detail.userText = value;
|
||||
}
|
||||
}
|
||||
|
||||
this.emit("closed", {
|
||||
contentBrowser,
|
||||
detail,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Observes the following notifications:
|
||||
* `common-dialog-loaded` - when a modal dialog loaded on desktop,
|
||||
* `domwindowopened` - when a new chrome window opened,
|
||||
* `geckoview-prompt-show` - when a modal dialog opened on Android.
|
||||
*/
|
||||
observe(subject, topic) {
|
||||
lazy.logger.trace(`Received observer notification ${topic}`);
|
||||
|
||||
let curBrowser = this.#curBrowserFn && this.#curBrowserFn();
|
||||
switch (topic) {
|
||||
case "common-dialog-loaded":
|
||||
if (curBrowser) {
|
||||
if (
|
||||
!this.#hasCommonDialog(
|
||||
curBrowser.contentBrowser,
|
||||
curBrowser.window,
|
||||
subject
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const chromeWin = subject.opener
|
||||
? subject.opener.ownerGlobal
|
||||
: subject.ownerGlobal;
|
||||
|
||||
for (const tab of lazy.TabManager.getTabsForWindow(chromeWin)) {
|
||||
const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
|
||||
const window = lazy.TabManager.getWindowForTab(tab);
|
||||
|
||||
if (this.#hasCommonDialog(contentBrowser, window, subject)) {
|
||||
curBrowser = {
|
||||
contentBrowser,
|
||||
window,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.emit("opened", {
|
||||
contentBrowser: curBrowser.contentBrowser,
|
||||
prompt: new lazy.modal.Dialog(() => curBrowser, subject),
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case "domwindowopened":
|
||||
subject.addEventListener("DOMModalDialogClosed", this);
|
||||
break;
|
||||
|
||||
case "geckoview-prompt-show":
|
||||
for (let win of Services.wm.getEnumerator(null)) {
|
||||
const prompt = win.prompts().find(item => item.id == subject.id);
|
||||
if (prompt) {
|
||||
const tabBrowser = lazy.TabManager.getTabBrowser(win);
|
||||
// Since on Android we always have only one tab we can just check
|
||||
// the selected tab.
|
||||
const tab = tabBrowser.selectedTab;
|
||||
const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
|
||||
const window = lazy.TabManager.getWindowForTab(tab);
|
||||
|
||||
// Do not send the event if the curBrowser is specified,
|
||||
// and it's different from prompt browser.
|
||||
if (curBrowser && contentBrowser !== curBrowser.contentBrowser) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.emit("opened", {
|
||||
contentBrowser,
|
||||
prompt: new lazy.modal.Dialog(
|
||||
() => ({
|
||||
contentBrowser,
|
||||
window,
|
||||
}),
|
||||
prompt
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
startListening() {
|
||||
if (this.#listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#register();
|
||||
this.#listening = true;
|
||||
}
|
||||
|
||||
stopListening() {
|
||||
if (!this.#observer) {
|
||||
if (!this.#listening) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#observer.cleanup();
|
||||
this.#observer = null;
|
||||
this.#unregister();
|
||||
this.#listening = false;
|
||||
}
|
||||
|
||||
#onEvent = async (action, data, contentBrowser) => {
|
||||
if (action === lazy.modal.ACTION_OPENED) {
|
||||
this.emit("opened", {
|
||||
contentBrowser,
|
||||
prompt: data,
|
||||
});
|
||||
} else if (action === lazy.modal.ACTION_CLOSED) {
|
||||
const detail = {};
|
||||
#hasCommonDialog(contentBrowser, window, prompt) {
|
||||
const modalType = prompt.Dialog.args.modalType;
|
||||
if (
|
||||
modalType === Services.prompt.MODAL_TYPE_TAB ||
|
||||
modalType === Services.prompt.MODAL_TYPE_CONTENT
|
||||
) {
|
||||
// Find the container of the dialog in the parent document, and ensure
|
||||
// it is a descendant of the same container as the content browser.
|
||||
const container = contentBrowser.closest(".browserSidebarContainer");
|
||||
|
||||
// The event details are present now only on Desktop.
|
||||
// See the bug 1849621 for Android.
|
||||
if (data) {
|
||||
const { areLeaving, value } = data;
|
||||
// `areLeaving` returns undefined for alerts, for confirms and prompts
|
||||
// it returns true if a user prompt was accepted and false if it was dismissed.
|
||||
detail.accepted = areLeaving === undefined ? true : areLeaving;
|
||||
if (value) {
|
||||
detail.userText = value;
|
||||
}
|
||||
}
|
||||
|
||||
this.emit("closed", {
|
||||
contentBrowser,
|
||||
detail,
|
||||
});
|
||||
return container.contains(prompt.docShell.chromeEventHandler);
|
||||
}
|
||||
};
|
||||
|
||||
return prompt.ownerGlobal == window || prompt.opener?.ownerGlobal == window;
|
||||
}
|
||||
|
||||
#register() {
|
||||
Services.obs.addObserver(this, "common-dialog-loaded");
|
||||
Services.obs.addObserver(this, "domwindowopened");
|
||||
Services.obs.addObserver(this, "geckoview-prompt-show");
|
||||
|
||||
// Register event listener and save already open prompts for all already open windows.
|
||||
for (const win of Services.wm.getEnumerator(null)) {
|
||||
win.addEventListener("DOMModalDialogClosed", this);
|
||||
}
|
||||
}
|
||||
|
||||
#unregister() {
|
||||
const removeObserver = observerName => {
|
||||
try {
|
||||
Services.obs.removeObserver(this, observerName);
|
||||
} catch (e) {
|
||||
lazy.logger.debug(`Failed to remove observer "${observerName}"`);
|
||||
}
|
||||
};
|
||||
|
||||
for (const observerName of [
|
||||
"common-dialog-loaded",
|
||||
"domwindowopened",
|
||||
"geckoview-prompt-show",
|
||||
]) {
|
||||
removeObserver(observerName);
|
||||
}
|
||||
|
||||
// Unregister event listener for all open windows
|
||||
for (const win of Services.wm.getEnumerator(null)) {
|
||||
win.removeEventListener("DOMModalDialogClosed", this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,3 +12,4 @@ prefs =
|
|||
[browser_ConsoleListener.js]
|
||||
[browser_ConsoleListener_cached_messages.js]
|
||||
[browser_NetworkListener.js]
|
||||
[browser_PromptListener.js]
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
/* 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/. */
|
||||
|
||||
const { PromptListener } = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/shared/listeners/PromptListener.sys.mjs"
|
||||
);
|
||||
|
||||
add_task(async function test_without_curBrowser() {
|
||||
const listener = new PromptListener();
|
||||
const opened = listener.once("opened");
|
||||
const closed = listener.once("closed");
|
||||
|
||||
listener.startListening();
|
||||
|
||||
const dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
|
||||
await createScriptNode(`setTimeout(() => window.confirm('test'))`);
|
||||
const dialogWin = await dialogPromise;
|
||||
|
||||
const openedEvent = await opened;
|
||||
|
||||
is(openedEvent.prompt.window, dialogWin, "Received expected prompt");
|
||||
|
||||
dialogWin.document.querySelector("dialog").acceptDialog();
|
||||
|
||||
const closedEvent = await closed;
|
||||
|
||||
is(closedEvent.detail.accepted, true, "Received correct event details");
|
||||
|
||||
listener.destroy();
|
||||
});
|
||||
|
||||
add_task(async function test_with_curBrowser() {
|
||||
const listener = new PromptListener(() => ({
|
||||
contentBrowser: gBrowser.selectedBrowser,
|
||||
window,
|
||||
}));
|
||||
const opened = listener.once("opened");
|
||||
const closed = listener.once("closed");
|
||||
|
||||
listener.startListening();
|
||||
|
||||
const dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
|
||||
await createScriptNode(`setTimeout(() => window.confirm('test'))`);
|
||||
const dialogWin = await dialogPromise;
|
||||
|
||||
const openedEvent = await opened;
|
||||
|
||||
is(openedEvent.prompt.window, dialogWin, "Received expected prompt");
|
||||
|
||||
dialogWin.document.querySelector("dialog").acceptDialog();
|
||||
|
||||
const closedEvent = await closed;
|
||||
|
||||
is(closedEvent.detail.accepted, true, "Received correct event details");
|
||||
|
||||
listener.destroy();
|
||||
});
|
||||
|
||||
add_task(async function test_close_event_details() {
|
||||
const listener = new PromptListener();
|
||||
let closed = listener.once("closed");
|
||||
|
||||
listener.startListening();
|
||||
|
||||
let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
|
||||
await createScriptNode(`setTimeout(() => window.prompt('Enter your name:'))`);
|
||||
let dialogWin = await dialogPromise;
|
||||
|
||||
dialogWin.document.getElementById("loginTextbox").value = "Test";
|
||||
dialogWin.document.querySelector("dialog").acceptDialog();
|
||||
|
||||
let closedEvent = await closed;
|
||||
|
||||
is(
|
||||
closedEvent.detail.accepted,
|
||||
true,
|
||||
"Received correct `accepted` value in event details"
|
||||
);
|
||||
is(
|
||||
closedEvent.detail.userText,
|
||||
"Test",
|
||||
"Received correct `userText` value in event details"
|
||||
);
|
||||
|
||||
closed = listener.once("closed");
|
||||
|
||||
dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
|
||||
await createScriptNode(`setTimeout(() => window.prompt('Enter your name:'))`);
|
||||
dialogWin = await dialogPromise;
|
||||
|
||||
dialogWin.document.getElementById("loginTextbox").value = "Test";
|
||||
dialogWin.document.querySelector("dialog").cancelDialog();
|
||||
|
||||
closedEvent = await closed;
|
||||
|
||||
is(
|
||||
closedEvent.detail.accepted,
|
||||
false,
|
||||
"Received correct `accepted` value in event details"
|
||||
);
|
||||
is(
|
||||
closedEvent.detail.userText,
|
||||
undefined,
|
||||
"Received correct `userText` value in event details"
|
||||
);
|
||||
|
||||
listener.destroy();
|
||||
});
|
||||
|
||||
add_task(async function test_dialogClosed() {
|
||||
const listener = new PromptListener();
|
||||
|
||||
listener.startListening();
|
||||
|
||||
let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
|
||||
await createScriptNode(`setTimeout(() => window.alert('test'))`);
|
||||
let dialogWin = await dialogPromise;
|
||||
let closed = listener.dialogClosed();
|
||||
|
||||
dialogWin.document.querySelector("dialog").acceptDialog();
|
||||
|
||||
await closed;
|
||||
|
||||
is(true, true, "Close promise got resolved");
|
||||
|
||||
dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
|
||||
await createScriptNode(`setTimeout(() => window.alert('test'))`);
|
||||
dialogWin = await dialogPromise;
|
||||
closed = listener.dialogClosed();
|
||||
|
||||
dialogWin.document.querySelector("dialog").cancelDialog();
|
||||
|
||||
await closed;
|
||||
|
||||
is(true, true, "Close promise got resolved");
|
||||
|
||||
listener.destroy();
|
||||
});
|
||||
|
||||
add_task(async function test_events_in_another_browser() {
|
||||
const win = await BrowserTestUtils.openNewBrowserWindow();
|
||||
const selectedBrowser = win.gBrowser.selectedBrowser;
|
||||
const listener = new PromptListener(() => ({
|
||||
contentBrowser: selectedBrowser,
|
||||
window: selectedBrowser.ownerGlobal,
|
||||
}));
|
||||
const events = [];
|
||||
const onEvent = (name, data) => events.push(data);
|
||||
listener.on("opened", onEvent);
|
||||
listener.on("closed", onEvent);
|
||||
|
||||
listener.startListening();
|
||||
|
||||
const dialogPromise = BrowserTestUtils.promiseAlertDialogOpen();
|
||||
await createScriptNode(`setTimeout(() => window.confirm('test'))`);
|
||||
const dialogWin = await dialogPromise;
|
||||
|
||||
ok(events.length === 0, "No event was received");
|
||||
|
||||
dialogWin.document.querySelector("dialog").acceptDialog();
|
||||
|
||||
// Wait a bit to make sure that the event didn't come.
|
||||
await new Promise(resolve => {
|
||||
// eslint-disable-next-line mozilla/no-arbitrary-setTimeout
|
||||
setTimeout(resolve, 500);
|
||||
});
|
||||
|
||||
ok(events.length === 0, "No event was received");
|
||||
|
||||
listener.destroy();
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
});
|
|
@ -1,132 +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";
|
||||
|
||||
const { modal } = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/shared/Prompt.sys.mjs"
|
||||
);
|
||||
|
||||
const chromeWindow = {};
|
||||
|
||||
const mockModalDialog = {
|
||||
docShell: {
|
||||
chromeEventHandler: null,
|
||||
},
|
||||
opener: {
|
||||
ownerGlobal: chromeWindow,
|
||||
},
|
||||
Dialog: {
|
||||
args: {
|
||||
modalType: Services.prompt.MODAL_TYPE_WINDOW,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockContentBrowser = {};
|
||||
|
||||
const mockCurBrowser = {
|
||||
window: chromeWindow,
|
||||
contentBrowser: mockContentBrowser,
|
||||
};
|
||||
|
||||
const mockDialogObject = new modal.Dialog(
|
||||
() => mockCurBrowser,
|
||||
mockModalDialog
|
||||
);
|
||||
|
||||
add_task(function test_addCallback() {
|
||||
let observer = new modal.DialogObserver(() => mockCurBrowser);
|
||||
let cb1 = () => true;
|
||||
let cb2 = () => false;
|
||||
|
||||
equal(observer.callbacks.size, 0);
|
||||
observer.add(cb1);
|
||||
equal(observer.callbacks.size, 1);
|
||||
observer.add(cb1);
|
||||
equal(observer.callbacks.size, 1);
|
||||
observer.add(cb2);
|
||||
equal(observer.callbacks.size, 2);
|
||||
});
|
||||
|
||||
add_task(function test_removeCallback() {
|
||||
let observer = new modal.DialogObserver(() => mockCurBrowser);
|
||||
let cb1 = () => true;
|
||||
let cb2 = () => false;
|
||||
|
||||
equal(observer.callbacks.size, 0);
|
||||
observer.add(cb1);
|
||||
observer.add(cb2);
|
||||
|
||||
equal(observer.callbacks.size, 2);
|
||||
observer.remove(cb1);
|
||||
equal(observer.callbacks.size, 1);
|
||||
observer.remove(cb1);
|
||||
equal(observer.callbacks.size, 1);
|
||||
observer.remove(cb2);
|
||||
equal(observer.callbacks.size, 0);
|
||||
});
|
||||
|
||||
add_task(function test_registerDialogClosedEventHandler() {
|
||||
let observer = new modal.DialogObserver(() => mockCurBrowser);
|
||||
let mockChromeWindow = {
|
||||
addEventListener(event, cb) {
|
||||
equal(
|
||||
event,
|
||||
"DOMModalDialogClosed",
|
||||
"registered event for closing modal"
|
||||
);
|
||||
equal(cb, observer, "set itself as handler");
|
||||
},
|
||||
};
|
||||
|
||||
observer.observe(mockChromeWindow, "domwindowopened");
|
||||
});
|
||||
|
||||
add_task(function test_handleCallbackOpenModalDialog() {
|
||||
let observer = new modal.DialogObserver(() => mockCurBrowser);
|
||||
|
||||
observer.add((action, dialog, contentBrowser) => {
|
||||
equal(action, modal.ACTION_OPENED, "'opened' action has been passed");
|
||||
equal(
|
||||
dialog.curBrowser_,
|
||||
mockDialogObject.curBrowser_,
|
||||
"dialog has been passed"
|
||||
);
|
||||
equal(contentBrowser, mockContentBrowser, "contentBrowser has been passed");
|
||||
});
|
||||
observer.observe(mockModalDialog, "common-dialog-loaded");
|
||||
});
|
||||
|
||||
add_task(function test_handleCallbackCloseModalDialog() {
|
||||
let observer = new modal.DialogObserver(() => mockCurBrowser);
|
||||
const detail = {
|
||||
areLeaving: true,
|
||||
value: "text",
|
||||
};
|
||||
|
||||
observer.add((action, eventDetail, contentBrowser) => {
|
||||
equal(action, modal.ACTION_CLOSED, "'closed' action has been passed");
|
||||
equal(detail, eventDetail, "dialog has been passed");
|
||||
equal(contentBrowser, mockContentBrowser, "contentBrowser has been passed");
|
||||
});
|
||||
observer.handleEvent({
|
||||
type: "DOMModalDialogClosed",
|
||||
target: mockContentBrowser,
|
||||
detail,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_dialogClosed() {
|
||||
let observer = new modal.DialogObserver(() => mockCurBrowser);
|
||||
|
||||
const dialogClosed = observer.dialogClosed();
|
||||
|
||||
observer.handleEvent({
|
||||
type: "DOMModalDialogClosed",
|
||||
target: mockModalDialog,
|
||||
});
|
||||
|
||||
await dialogClosed;
|
||||
});
|
|
@ -9,7 +9,6 @@ head = head.js
|
|||
[test_DOM.js]
|
||||
[test_Format.js]
|
||||
[test_Navigate.js]
|
||||
[test_Prompt.js]
|
||||
[test_Realm.js]
|
||||
[test_RecommendedPreferences.js]
|
||||
[test_Stack.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче