зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1416163 - Implement EveryWindow.jsm to run arbitrary per-window code. r=johannh
Differential Revision: https://phabricator.services.mozilla.com/D26947 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
7f4b636f4f
Коммит
0f3e51207d
|
@ -126,6 +126,10 @@ var whitelist = [
|
|||
{file: "resource://app/modules/translation/GoogleTranslator.jsm"},
|
||||
{file: "resource://app/modules/translation/YandexTranslator.jsm"},
|
||||
|
||||
// Used in Firefox Monitor, which is an extension - we don't check
|
||||
// files inside the XPI.
|
||||
{file: "resource://app/modules/EveryWindow.jsm"},
|
||||
|
||||
// Starting from here, files in the whitelist are bugs that need fixing.
|
||||
// Bug 1339424 (wontfix?)
|
||||
{file: "chrome://browser/locale/taskbar.properties",
|
||||
|
|
|
@ -27,7 +27,6 @@ FINAL_TARGET_FILES.features['fxmonitor@mozilla.org']['privileged'] += [
|
|||
]
|
||||
|
||||
FINAL_TARGET_FILES.features['fxmonitor@mozilla.org']['privileged']['subscripts'] += [
|
||||
'privileged/subscripts/EveryWindow.jsm',
|
||||
'privileged/subscripts/Globals.jsm'
|
||||
]
|
||||
|
||||
|
|
|
@ -98,13 +98,10 @@ this.FirefoxMonitor = {
|
|||
|
||||
this._delayedInited = true;
|
||||
|
||||
/* globals Preferences, RemoteSettings, fetch, btoa, XUL_NS */
|
||||
/* globals EveryWindow, Preferences, RemoteSettings, fetch, btoa, XUL_NS */
|
||||
Services.scriptloader.loadSubScript(
|
||||
this.getURL("privileged/subscripts/Globals.jsm"));
|
||||
|
||||
/* globals EveryWindow */
|
||||
Services.scriptloader.loadSubScript(
|
||||
this.getURL("privileged/subscripts/EveryWindow.jsm"));
|
||||
|
||||
// Expire our telemetry on November 1, at which time
|
||||
// we should redo data-review.
|
||||
|
|
|
@ -1,62 +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/. */
|
||||
|
||||
/* globals Services */
|
||||
|
||||
this.EveryWindow = {
|
||||
_callbacks: new Map(),
|
||||
_initialized: false,
|
||||
|
||||
registerCallback: function EW_registerCallback(id, init, uninit) {
|
||||
if (this._callbacks.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._callForEveryWindow(init);
|
||||
this._callbacks.set(id, {id, init, uninit});
|
||||
|
||||
if (!this._initialized) {
|
||||
Services.obs.addObserver(this._onOpenWindow.bind(this),
|
||||
"browser-delayed-startup-finished");
|
||||
this._initialized = true;
|
||||
}
|
||||
},
|
||||
|
||||
unregisterCallback: function EW_unregisterCallback(aId, aCallUninit = true) {
|
||||
if (!this._callbacks.has(aId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aCallUninit) {
|
||||
this._callForEveryWindow(this._callbacks.get(aId).uninit);
|
||||
}
|
||||
|
||||
this._callbacks.delete(aId);
|
||||
},
|
||||
|
||||
_callForEveryWindow(aFunction) {
|
||||
let windowList = Services.wm.getEnumerator("navigator:browser");
|
||||
while (windowList.hasMoreElements()) {
|
||||
let win = windowList.getNext();
|
||||
win.delayedStartupPromise.then(() => { aFunction(win); });
|
||||
}
|
||||
},
|
||||
|
||||
_onOpenWindow(aWindow) {
|
||||
for (let c of this._callbacks.values()) {
|
||||
c.init(aWindow);
|
||||
}
|
||||
|
||||
aWindow.addEventListener("unload",
|
||||
this._onWindowClosing.bind(this),
|
||||
{ once: true });
|
||||
},
|
||||
|
||||
_onWindowClosing(aEvent) {
|
||||
let win = aEvent.target;
|
||||
for (let c of this._callbacks.values()) {
|
||||
c.uninit(win, true);
|
||||
}
|
||||
},
|
||||
};
|
|
@ -10,6 +10,8 @@ ChromeUtils.defineModuleGetter(this, "PluralForm",
|
|||
"resource://gre/modules/PluralForm.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "RemoteSettings",
|
||||
"resource://services-settings/remote-settings.js");
|
||||
ChromeUtils.defineModuleGetter(this, "EveryWindow",
|
||||
"resource:///modules/EveryWindow.jsm");
|
||||
const {setTimeout, clearTimeout} = ChromeUtils.import("resource://gre/modules/Timer.jsm", {});
|
||||
Cu.importGlobalProperties(["fetch", "btoa"]);
|
||||
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
/* 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 = ["EveryWindow"];
|
||||
|
||||
/*
|
||||
* This module enables consumers to register callbacks on every
|
||||
* current and future browser window.
|
||||
*
|
||||
* Usage: EveryWindow.registerCallback(id, init, uninit);
|
||||
* EveryWindow.unregisterCallback(id);
|
||||
*
|
||||
* id is expected to be a unique value that identifies the
|
||||
* consumer, to be used for unregistration. If the id is already
|
||||
* in use, registerCallback returns false without doing anything.
|
||||
*
|
||||
* Each callback will receive the window for which it is presently
|
||||
* being called as the first argument.
|
||||
*
|
||||
* init is called on every existing window at the time of registration,
|
||||
* and on all future windows at browser-delayed-startup-finished.
|
||||
*
|
||||
* uninit is called on every existing window if requested at the time
|
||||
* of unregistration, and at the time of domwindowclosed.
|
||||
* If the window is closing, a second argument is passed with value `true`.
|
||||
*/
|
||||
|
||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var initialized = false;
|
||||
var callbacks = new Map();
|
||||
|
||||
function callForEveryWindow(callback) {
|
||||
let windowList = Services.wm.getEnumerator("navigator:browser");
|
||||
for (let win of windowList) {
|
||||
win.delayedStartupPromise.then(() => { callback(win); });
|
||||
}
|
||||
}
|
||||
|
||||
this.EveryWindow = {
|
||||
/**
|
||||
* Registers init and uninit functions to be called on every window.
|
||||
*
|
||||
* @param {string} id A unique identifier for the consumer, to be
|
||||
* used for unregistration.
|
||||
* @param {function} init The function to be called on every currently
|
||||
* existing window and every future window after delayed startup.
|
||||
* @param {function} uninit The function to be called on every window
|
||||
* at the time of callback unregistration or after domwindowclosed.
|
||||
* @returns {boolean} Returns false if the id was taken, else true.
|
||||
*/
|
||||
registerCallback: function EW_registerCallback(id, init, uninit) {
|
||||
if (callbacks.has(id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
let addUnloadListener = (win) => {
|
||||
function observer(subject, topic, data) {
|
||||
if (topic == "domwindowclosed" && subject === win) {
|
||||
Services.ww.unregisterNotification(observer);
|
||||
for (let c of callbacks.values()) {
|
||||
c.uninit(win, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Services.ww.registerNotification(observer);
|
||||
};
|
||||
|
||||
Services.obs.addObserver(win => {
|
||||
for (let c of callbacks.values()) {
|
||||
c.init(win);
|
||||
}
|
||||
addUnloadListener(win);
|
||||
}, "browser-delayed-startup-finished");
|
||||
|
||||
callForEveryWindow(addUnloadListener);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
callForEveryWindow(init);
|
||||
callbacks.set(id, {id, init, uninit});
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregisters a previously registered consumer.
|
||||
*
|
||||
* @param {string} id The id to unregister.
|
||||
* @param {boolean} [callUninit=true] Whether to call the registered uninit
|
||||
* function on every window.
|
||||
*/
|
||||
unregisterCallback: function EW_unregisterCallback(id, callUninit = true) {
|
||||
if (!callbacks.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (callUninit) {
|
||||
callForEveryWindow(callbacks.get(id).uninit);
|
||||
}
|
||||
|
||||
callbacks.delete(id);
|
||||
},
|
||||
};
|
|
@ -61,6 +61,9 @@ with Files("ContentCrashHandlers.jsm"):
|
|||
with Files("ContentSearch.jsm"):
|
||||
BUG_COMPONENT = ("Firefox", "Search")
|
||||
|
||||
with Files("EveryWindow.jsm"):
|
||||
BUG_COMPONENT = ("Firefox", "General")
|
||||
|
||||
with Files("ExtensionsUI.jsm"):
|
||||
BUG_COMPONENT = ("WebExtensions", "General")
|
||||
|
||||
|
@ -136,6 +139,7 @@ EXTRA_JS_MODULES += [
|
|||
'ContentObservers.js',
|
||||
'ContentSearch.jsm',
|
||||
'Discovery.jsm',
|
||||
'EveryWindow.jsm',
|
||||
'ExtensionsUI.jsm',
|
||||
'FaviconLoader.jsm',
|
||||
'FormValidationHandler.jsm',
|
||||
|
|
|
@ -14,6 +14,7 @@ support-files =
|
|||
!/browser/components/search/test/browser/head.js
|
||||
!/browser/components/search/test/browser/testEngine.xml
|
||||
testEngine_chromeicon.xml
|
||||
[browser_EveryWindow.js]
|
||||
[browser_LiveBookmarkMigrator.js]
|
||||
[browser_PageActions.js]
|
||||
[browser_PermissionUI.js]
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
/* 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";
|
||||
|
||||
/* eslint-disable mozilla/no-arbitrary-setTimeout */
|
||||
|
||||
const {EveryWindow} = ChromeUtils.import("resource:///modules/EveryWindow.jsm");
|
||||
|
||||
async function windowInited(aId, aWin) {
|
||||
// TestUtils.topicObserved returns [subject, data]. We return the
|
||||
// subject, which in this case is the window.
|
||||
return (await TestUtils.topicObserved(`${aId}:init`, (win) => {
|
||||
return aWin ? win == aWin : true;
|
||||
}))[0];
|
||||
}
|
||||
|
||||
function windowUninited(aId, aWin, aClosing) {
|
||||
return TestUtils.topicObserved(`${aId}:uninit`, (win, closing) => {
|
||||
if (aWin && aWin != win) {
|
||||
return false;
|
||||
}
|
||||
if (!aWin) {
|
||||
return true;
|
||||
}
|
||||
if (!!aClosing != !!closing) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function registerEWCallback(id) {
|
||||
EveryWindow.registerCallback(
|
||||
id,
|
||||
(win) => {
|
||||
Services.obs.notifyObservers(win, `${id}:init`);
|
||||
},
|
||||
(win, closing) => {
|
||||
Services.obs.notifyObservers(win, `${id}:uninit`, closing);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function unregisterEWCallback(id, aCallUninit) {
|
||||
EveryWindow.unregisterCallback(id, aCallUninit);
|
||||
}
|
||||
|
||||
add_task(async function test_stuff() {
|
||||
let win2 = await BrowserTestUtils.openNewBrowserWindow();
|
||||
let win3 = await BrowserTestUtils.openNewBrowserWindow();
|
||||
|
||||
let callbackId1 = "EveryWindow:test:1";
|
||||
let callbackId2 = "EveryWindow:test:2";
|
||||
|
||||
let initPromise = Promise.all([windowInited(callbackId1, window),
|
||||
windowInited(callbackId1, win2),
|
||||
windowInited(callbackId1, win3),
|
||||
windowInited(callbackId2, window),
|
||||
windowInited(callbackId2, win2),
|
||||
windowInited(callbackId2, win3)]);
|
||||
|
||||
registerEWCallback(callbackId1);
|
||||
registerEWCallback(callbackId2);
|
||||
|
||||
await initPromise;
|
||||
ok(true, "Init called for all existing windows for all registered consumers");
|
||||
|
||||
let uninitPromise = Promise.all([windowUninited(callbackId1, window, false),
|
||||
windowUninited(callbackId1, win2, false),
|
||||
windowUninited(callbackId1, win3, false),
|
||||
windowUninited(callbackId2, window, false),
|
||||
windowUninited(callbackId2, win2, false),
|
||||
windowUninited(callbackId2, win3, false)]);
|
||||
|
||||
unregisterEWCallback(callbackId1);
|
||||
unregisterEWCallback(callbackId2);
|
||||
await uninitPromise;
|
||||
ok(true, "Uninit called for all existing windows");
|
||||
|
||||
initPromise = Promise.all([windowInited(callbackId1, window),
|
||||
windowInited(callbackId1, win2),
|
||||
windowInited(callbackId1, win3),
|
||||
windowInited(callbackId2, window),
|
||||
windowInited(callbackId2, win2),
|
||||
windowInited(callbackId2, win3)]);
|
||||
|
||||
registerEWCallback(callbackId1);
|
||||
registerEWCallback(callbackId2);
|
||||
|
||||
await initPromise;
|
||||
ok(true, "Init called for all existing windows for all registered consumers");
|
||||
|
||||
uninitPromise = Promise.all([windowUninited(callbackId1, win2, true),
|
||||
windowUninited(callbackId2, win2, true)]);
|
||||
await BrowserTestUtils.closeWindow(win2);
|
||||
await uninitPromise;
|
||||
ok(true, "Uninit called with closing=true for win2 for all registered consumers");
|
||||
|
||||
uninitPromise = Promise.all([windowUninited(callbackId1, win3, true),
|
||||
windowUninited(callbackId2, win3, true)]);
|
||||
await BrowserTestUtils.closeWindow(win3);
|
||||
await uninitPromise;
|
||||
ok(true, "Uninit called with closing=true for win3 for all registered consumers");
|
||||
|
||||
initPromise = windowInited(callbackId1);
|
||||
let initPromise2 = windowInited(callbackId2);
|
||||
win2 = await BrowserTestUtils.openNewBrowserWindow();
|
||||
is(await initPromise, win2, "Init called for new window for callback 1");
|
||||
is(await initPromise2, win2, "Init called for new window for callback 2");
|
||||
|
||||
uninitPromise = Promise.all([windowUninited(callbackId1, win2, true),
|
||||
windowUninited(callbackId2, win2, true)]);
|
||||
await BrowserTestUtils.closeWindow(win2);
|
||||
await uninitPromise;
|
||||
ok(true, "Uninit called with closing=true for win2 for all registered consumers");
|
||||
|
||||
uninitPromise = windowUninited(callbackId1, window, false);
|
||||
unregisterEWCallback(callbackId1);
|
||||
await uninitPromise;
|
||||
ok(true, "Uninit called for main window without closing flag for the unregistered consumer");
|
||||
|
||||
uninitPromise = windowUninited(callbackId2, window, false);
|
||||
let timeoutPromise = new Promise(resolve => setTimeout(resolve, 500));
|
||||
unregisterEWCallback(callbackId2, false);
|
||||
let result = await Promise.race([uninitPromise, timeoutPromise]);
|
||||
is(result, undefined, "Uninit not called when unregistering a consumer with aCallUninit=false");
|
||||
});
|
Загрузка…
Ссылка в новой задаче