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:
Alexandra Borovova 2023-09-05 13:12:38 +00:00
Родитель f54d247bf6
Коммит 813b9799be
8 изменённых файлов: 424 добавлений и 420 удалений

Просмотреть файл

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