gecko-dev/testing/marionette/modal.js

224 строки
5.7 KiB
JavaScript

/* 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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { Log } = ChromeUtils.import("chrome://marionette/content/log.js");
XPCOMUtils.defineLazyGetter(this, "logger", Log.get);
this.EXPORTED_SYMBOLS = ["modal"];
const COMMON_DIALOG = "chrome://global/content/commonDialog.xul";
const isFirefox = () =>
Services.appinfo.ID == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
/** @namespace */
this.modal = {
ACTION_CLOSED: "closed",
ACTION_OPENED: "opened",
};
/**
* Check for already existing modal or tab modal dialogs
*
* @param {browser.Context} context
* Reference to the browser context to check for existent dialogs.
*
* @return {modal.Dialog}
* Returns instance of the Dialog class, or `null` if no modal dialog
* is present.
*/
modal.findModalDialogs = function(context) {
// First check if there is a modal dialog already present for the
// current browser window.
for (let win of Services.wm.getEnumerator(null)) {
// TODO: Use BrowserWindowTracker.getTopWindow for modal dialogs without
// an opener.
if (
win.document.documentURI === COMMON_DIALOG &&
win.opener &&
win.opener === context.window
) {
return new modal.Dialog(() => context, Cu.getWeakReference(win));
}
}
// If no modal dialog has been found, also check if there is an open
// tab modal dialog present for the current tab.
// TODO: Find an adequate implementation for Fennec.
if (context.tab && context.tabBrowser.getTabModalPromptBox) {
let contentBrowser = context.contentBrowser;
let promptManager = context.tabBrowser.getTabModalPromptBox(contentBrowser);
let prompts = promptManager.listPrompts();
if (prompts.length) {
return new modal.Dialog(() => context, null);
}
}
return null;
};
/**
* Observer for modal and tab modal dialogs.
*
* @return {modal.DialogObserver}
* Returns instance of the DialogObserver class.
*/
modal.DialogObserver = class {
constructor() {
this.callbacks = new Set();
this.register();
}
register() {
Services.obs.addObserver(this, "common-dialog-loaded");
Services.obs.addObserver(this, "tabmodal-dialog-loaded");
Services.obs.addObserver(this, "toplevel-window-ready");
// 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, "tabmodal-dialog-loaded");
Services.obs.removeObserver(this, "toplevel-window-ready");
// 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) {
logger.trace(`Received event ${event.type}`);
let chromeWin = event.target.opener
? event.target.opener.ownerGlobal
: event.target.ownerGlobal;
let targetRef = Cu.getWeakReference(event.target);
this.callbacks.forEach(callback => {
callback(modal.ACTION_CLOSED, targetRef, chromeWin);
});
}
observe(subject, topic) {
logger.trace(`Received observer notification ${topic}`);
switch (topic) {
case "common-dialog-loaded":
case "tabmodal-dialog-loaded":
let chromeWin = subject.opener
? subject.opener.ownerGlobal
: subject.ownerGlobal;
// Always keep a weak reference to the current dialog
let targetRef = Cu.getWeakReference(subject);
this.callbacks.forEach(callback => {
callback(modal.ACTION_OPENED, targetRef, chromeWin);
});
break;
case "toplevel-window-ready":
subject.addEventListener("DOMModalDialogClosed", this);
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);
}
};
/**
* Represents a modal dialog.
*
* @param {function(): browser.Context} curBrowserFn
* Function that returns the current |browser.Context|.
* @param {nsIWeakReference=} winRef
* A weak reference to the current |ChromeWindow|.
*/
modal.Dialog = class {
constructor(curBrowserFn, winRef = undefined) {
this.curBrowserFn_ = curBrowserFn;
this.win_ = winRef;
}
get curBrowser_() {
return this.curBrowserFn_();
}
/**
* Returns the ChromeWindow associated with an open dialog window if
* it is currently attached to the DOM.
*/
get window() {
if (this.win_) {
let win = this.win_.get();
if (win && win.parent) {
return win;
}
}
return null;
}
get tabModal() {
let win = this.window;
if (win) {
return win.Dialog;
}
return this.curBrowser_.getTabModal();
}
get args() {
let tm = this.tabModal;
return tm ? tm.args : null;
}
get ui() {
let tm = this.tabModal;
return tm ? tm.ui : null;
}
};