Backed out changeset 86cbcf2195f9 (bug 1356036) for browser-chrome failures in browser_all_files_referenced.js CLOSED TREE

This commit is contained in:
Noemi Erli 2019-01-09 16:59:28 +02:00
Родитель e490d97127
Коммит d3e491db1d
8 изменённых файлов: 681 добавлений и 0 удалений

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

@ -154,6 +154,9 @@ var whitelist = [
{file: "resource://gre/modules/PerfMeasurement.jsm"}, {file: "resource://gre/modules/PerfMeasurement.jsm"},
// Bug 1356045 // Bug 1356045
{file: "chrome://global/content/test-ipc.xul"}, {file: "chrome://global/content/test-ipc.xul"},
// Bug 1356036
{file: "resource://gre/modules/PerformanceWatcher-content.js"},
{file: "resource://gre/modules/PerformanceWatcher.jsm"},
// Bug 1378173 (warning: still used by devtools) // Bug 1378173 (warning: still used by devtools)
{file: "resource://gre/modules/Promise.jsm"}, {file: "resource://gre/modules/Promise.jsm"},
// Still used by WebIDE, which is going away but not entirely gone. // Still used by WebIDE, which is going away but not entirely gone.

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

@ -17,6 +17,9 @@ var EXPORTED_SYMBOLS = ["PerformanceStats"];
* *
* Data is collected by "Performance Group". Typically, a Performance Group * Data is collected by "Performance Group". Typically, a Performance Group
* is a frame, or the internals of the application. * is a frame, or the internals of the application.
*
* Generally, if you have the choice between PerformanceStats and PerformanceWatcher,
* you should favor PerformanceWatcher.
*/ */
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this); ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", this);

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

@ -0,0 +1,46 @@
/* 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";
/**
* An API for being informed of slow tabs (content process scripts).
*/
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
/**
* `true` if this is a content process, `false` otherwise.
*/
let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
if (isContent) {
const { PerformanceWatcher } = ChromeUtils.import("resource://gre/modules/PerformanceWatcher.jsm", {});
let toMsg = function(alerts) {
let result = [];
for (let {source, details} of alerts) {
// Convert xpcom values to serializable data.
let serializableSource = {};
for (let k of ["groupId", "name", "windowId", "isSystem", "processId", "isContentProcess"]) {
serializableSource[k] = source[k];
}
let serializableDetails = {};
for (let k of ["reason", "highestJank", "highestCPOW"]) {
serializableDetails[k] = details[k];
}
result.push({source: serializableSource, details: serializableDetails});
}
return result;
};
PerformanceWatcher.addPerformanceListener({windowId: 0}, alerts => {
Services.cpmm.sendAsyncMessage("performancewatcher-propagate-notifications",
{windows: toMsg(alerts)}
);
});
}

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

@ -0,0 +1,325 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* 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";
/**
* An API for being informed of slow tabs.
*
* Generally, this API is both more CPU-efficient and more battery-efficient
* than PerformanceStats. As PerformanceStats, this API does not provide any
* information during the startup or shutdown of Firefox.
*
* = Example =
*
* Example use: reporting whenever any webpage slows down Firefox.
* let listener = function(alerts) {
* // This listener is triggered whenever any window causes Firefox to miss
* // frames. FieldArgument `source` contains information about the source of the
* // slowdown (including the process in which it happens), while `details`
* // contains performance statistics.
* for (let {source, details} of alerts) {
* console.log(`Oops, window ${source.windowId} seems to be slowing down Firefox.`, details);
* };
* // Special windowId 0 lets us to listen to all webpages.
* PerformanceWatcher.addPerformanceListener({windowId: 0}, listener);
*
*
* = How this works =
*
* This high-level API is based on the lower-level nsIPerformanceStatsService.
* At the end of each event (including micro-tasks), the nsIPerformanceStatsService
* updates its internal performance statistics and determines whether any
* window in the current process has exceeded the jank threshold.
*
* The PerformanceWatcher maintains low-level performance observers in each
* process and forwards alerts to the main process. Internal observers collate
* low-level main process alerts and children process alerts and notify clients
* of this API.
*/
var EXPORTED_SYMBOLS = ["PerformanceWatcher"];
let { PerformanceStats, performanceStatsService } = ChromeUtils.import("resource://gre/modules/PerformanceStats.jsm", {});
let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm", {});
// `true` if the code is executed in content, `false` otherwise
let isContent = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
if (!isContent) {
// Initialize communication with children.
//
// To keep the protocol simple, the children inform the parent whenever a slow
// tab is detected. We do not attempt to implement thresholds.
Services.ppmm.loadProcessScript("resource://gre/modules/PerformanceWatcher-content.js",
true/* including future processes*/);
Services.ppmm.addMessageListener("performancewatcher-propagate-notifications",
(...args) => ChildManager.notifyObservers(...args)
);
}
// Configure the performance stats service to inform us in case of jank.
performanceStatsService.jankAlertThreshold = 64000 /* us */;
/**
* Handle communications with child processes. Handle listening to
* a single window id (including the special window id 0, which is
* notified for all windows).
*
* Acquire through `ChildManager.getWindow`.
*/
function ChildManager(map, key) {
this.key = key;
this._map = map;
this._listeners = new Set();
}
ChildManager.prototype = {
/**
* Add a listener, which will be notified whenever a child process
* reports a slow performance alert for this window.
*/
addListener(listener) {
this._listeners.add(listener);
},
/**
* Remove a listener.
*/
removeListener(listener) {
let deleted = this._listeners.delete(listener);
if (!deleted) {
throw new Error("Unknown listener");
}
},
listeners() {
return this._listeners.values();
},
};
/**
* Dispatch child alerts to observers.
*
* Triggered by messages from content processes.
*/
ChildManager.notifyObservers = function({data: {windows}}) {
if (windows && windows.length > 0) {
// Dispatch the entire list to universal listeners
this._notify(ChildManager.getWindow(0).listeners(), windows);
// Dispatch individual alerts to individual listeners
for (let {source, details} of windows) {
this._notify(ChildManager.getWindow(source.windowId).listeners(), source, details);
}
}
};
ChildManager._notify = function(targets, ...args) {
for (let target of targets) {
target(...args);
}
};
ChildManager.getWindow = function(key) {
return this._get(this._windows, key);
};
ChildManager._windows = new Map();
ChildManager._get = function(map, key) {
let result = map.get(key);
if (!result) {
result = new ChildManager(map, key);
map.set(key, result);
}
return result;
};
/**
* An object in charge of managing all the observables for a single
* target (window/all windows).
*
* In a content process, a target is represented by a single observable.
* The situation is more sophisticated in a parent process, as a target
* has both an in-process observable and several observables across children
* processes.
*
* This class abstracts away the difference to simplify the work of
* (un)registering observers for targets.
*
* @param {object} target The target being observed, as an object
* with one of the following fields:
* - {xul:tab} tab A single tab. It must already be initialized.
* - {number} windowId Either 0 for the universal window observer
* or the outer window id of the window.
*/
function Observable(target) {
// A mapping from `listener` (function) to `Observer`.
this._observers = new Map();
if ("tab" in target || "windowId" in target) {
let windowId;
if ("tab" in target) {
windowId = target.tab.linkedBrowser.outerWindowID;
// By convention, outerWindowID may not be 0.
} else if ("windowId" in target) {
windowId = target.windowId;
}
if (windowId == undefined || windowId == null) {
throw new TypeError(`No outerWindowID. Perhaps the target is a tab that is not initialized yet.`);
}
this._key = `tab-windowId: ${windowId}`;
this._process = performanceStatsService.getObservableWindow(windowId);
this._children = isContent ? null : ChildManager.getWindow(windowId);
this._isBuffered = windowId == 0;
} else {
throw new TypeError("Unexpected target");
}
}
Observable.prototype = {
addJankObserver(listener) {
if (this._observers.has(listener)) {
throw new TypeError(`Listener already registered for target ${this._key}`);
}
if (this._children) {
this._children.addListener(listener);
}
let observer = this._isBuffered ? new BufferedObserver(listener)
: new Observer(listener);
// Store the observer to be able to call `this._process.removeJankObserver`.
this._observers.set(listener, observer);
this._process.addJankObserver(observer);
},
removeJankObserver(listener) {
let observer = this._observers.get(listener);
if (!observer) {
throw new TypeError(`No listener for target ${this._key}`);
}
this._observers.delete(listener);
if (this._children) {
this._children.removeListener(listener);
}
this._process.removeJankObserver(observer);
observer.dispose();
},
};
/**
* Get a cached observable for a given target.
*/
Observable.get = function(target) {
let key;
if ("tab" in target) {
// We do not want to use a tab as a key, as this would prevent it from
// being garbage-collected.
key = target.tab.linkedBrowser.outerWindowID;
} else if ("windowId" in target) {
key = target.windowId;
}
if (key == null) {
throw new TypeError(`Could not extract a key from ${JSON.stringify(target)}. Could the target be an unitialized tab?`);
}
let observable = this._cache.get(key);
if (!observable) {
observable = new Observable(target);
this._cache.set(key, observable);
}
return observable;
};
Observable._cache = new Map();
/**
* Wrap a listener callback as an unbuffered nsIPerformanceObserver.
*
* Each observation is propagated immediately to the listener.
*/
function Observer(listener) {
// Make sure that monitoring stays alive (in all processes) at least as
// long as the observer.
this._monitor = PerformanceStats.getMonitor(["jank", "cpow"]);
this._listener = listener;
}
Observer.prototype = {
observe(...args) {
this._listener(...args);
},
dispose() {
this._monitor.dispose();
this.observe = function poison() {
throw new Error("Internal error: I should have stopped receiving notifications");
};
},
};
/**
* Wrap a listener callback as an buffered nsIPerformanceObserver.
*
* Observations are buffered and dispatch in the next tick to the listener.
*/
function BufferedObserver(listener) {
Observer.call(this, listener);
this._buffer = [];
this._isDispatching = false;
this._pending = null;
}
BufferedObserver.prototype = Object.create(Observer.prototype);
BufferedObserver.prototype.observe = function(source, details) {
this._buffer.push({source, details});
if (!this._isDispatching) {
this._isDispatching = true;
Services.tm.dispatchToMainThread(() => {
// Grab buffer, in case something in the listener could modify it.
let buffer = this._buffer;
this._buffer = [];
// As of this point, any further observations need to use the new buffer
// and a new dispatcher.
this._isDispatching = false;
this._listener(buffer);
});
}
};
var PerformanceWatcher = {
/**
* Add a listener informed whenever we receive a slow performance alert
* in the application.
*
* @param {object} target An object with one of the following fields:
* - {number} windowId Either 0 to observe all windows or an outer window ID
* to observe a single tab.
* - {xul:browser} tab To observe a single tab.
* @param {function} listener A function that will be triggered whenever
* the target causes a slow performance notification. The notification may
* have originated in any process of the application.
*
* If the listener listens to a single webpage, it is triggered with
* the following arguments:
* source: {groupId, name, windowId, isSystem, processId}
* Information on the source of the notification.
* details: {reason, highestJank, highestCPOW} Information on the
* notification.
*
* If the listener listens to all webpages, it is triggered with
* an array of {source, details}, as described above.
*/
addPerformanceListener(target, listener) {
if (typeof listener != "function") {
throw new TypeError();
}
let observable = Observable.get(target);
observable.addJankObserver(listener);
},
removePerformanceListener(target, listener) {
if (typeof listener != "function") {
throw new TypeError();
}
let observable = Observable.get(target);
observable.removeJankObserver(listener);
},
};

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

@ -14,6 +14,8 @@ XPIDL_MODULE = 'toolkit_perfmonitoring'
EXTRA_JS_MODULES += [ EXTRA_JS_MODULES += [
'PerformanceStats-content.js', 'PerformanceStats-content.js',
'PerformanceStats.jsm', 'PerformanceStats.jsm',
'PerformanceWatcher-content.js',
'PerformanceWatcher.jsm',
] ]
XPIDL_SOURCES += [ XPIDL_SOURCES += [

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

@ -8,3 +8,4 @@ support-files =
[browser_compartments.js] [browser_compartments.js]
skip-if = (os == "linux" && !debug && e10s) || (os == "win" && os_version == "10.0") # Bug 1230018, Bug 1409631 skip-if = (os == "linux" && !debug && e10s) || (os == "win" && os_version == "10.0") # Bug 1230018, Bug 1409631
[browser_webpagePerformanceAlerts.js]

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

@ -0,0 +1,114 @@
/* eslint-disable mozilla/no-arbitrary-setTimeout */
"use strict";
/**
* Tests for PerformanceWatcher watching slow web pages.
*/
/**
* Simulate a slow webpage.
*/
function WebpageBurner() {
CPUBurner.call(this, "http://example.com/browser/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html?test=" + Math.random(), 300000);
}
WebpageBurner.prototype = Object.create(CPUBurner.prototype);
WebpageBurner.prototype.promiseBurnContentCPU = function() {
return promiseContentResponse(this._browser, "test-performance-watcher:burn-content-cpu", {});
};
function WebpageListener(windowId, accept) {
info(`Creating WebpageListener for ${windowId}`);
AlertListener.call(this, accept, {
register: () => PerformanceWatcher.addPerformanceListener({windowId}, this.listener),
unregister: () => PerformanceWatcher.removePerformanceListener({windowId}, this.listener),
});
}
WebpageListener.prototype = Object.create(AlertListener.prototype);
add_task(async function init() {
// Get rid of buffering.
let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].getService(
Ci.nsIPerformanceStatsService);
let oldDelay = service.jankAlertBufferingDelay;
service.jankAlertBufferingDelay = 0 /* ms */;
registerCleanupFunction(() => {
info("Cleanup");
service.jankAlertBufferingDelay = oldDelay;
});
});
add_task(async function test_open_window_then_watch_it() {
let burner = new WebpageBurner();
await burner.promiseInitialized;
await burner.promiseBurnContentCPU();
info(`Check that burning CPU triggers the real listener, but not the fake listener`);
let realListener = new WebpageListener(burner.windowId, (group, details) => {
info(`test: realListener for ${burner.tab.linkedBrowser.outerWindowID}: ${group}, ${details}\n`);
Assert.equal(group.windowId, burner.windowId, "We should not receive data meant for another group");
return details;
}); // This listener should be triggered.
info(`Creating fake burner`);
let otherTab = BrowserTestUtils.addTab(gBrowser);
await BrowserTestUtils.browserLoaded(otherTab.linkedBrowser);
info(`Check that burning CPU triggers the real listener, but not the fake listener`);
let fakeListener = new WebpageListener(otherTab.linkedBrowser.outerWindowID, group => group.windowId == burner.windowId); // This listener should never be triggered.
let universalListener = new WebpageListener(0, alerts =>
alerts.find(alert => alert.source.windowId == burner.windowId)
);
// Waiting a little – listeners are buffered.
await new Promise(resolve => setTimeout(resolve, 100));
await burner.run("promiseBurnContentCPU", 20, realListener);
Assert.ok(realListener.triggered, `1. The real listener was triggered`);
Assert.ok(universalListener.triggered, `1. The universal listener was triggered`);
Assert.ok(!fakeListener.triggered, `1. The fake listener was not triggered`);
if (realListener.result) {
Assert.ok(realListener.result.highestJank >= 300, `1. jank is at least 300ms (${realListener.result.highestJank}ms)`);
}
info(`Attempting to remove a performance listener incorrectly, check that this does not hurt our real listener`);
Assert.throws(() => PerformanceWatcher.removePerformanceListener({windowId: burner.windowId}, () => {}),
/No listener for target/, "should throw an error for a different listener");
Assert.throws(() => PerformanceWatcher.removePerformanceListener({windowId: burner.windowId + "-unbound-id-" + Math.random()}, realListener.listener),
/No listener for target/, "should throw an error for a different window id");
// Waiting a little – listeners are buffered.
await new Promise(resolve => setTimeout(resolve, 100));
await burner.run("promiseBurnContentCPU", 20, realListener);
// Waiting a little – listeners are buffered.
await new Promise(resolve => setTimeout(resolve, 100));
Assert.ok(realListener.triggered, `2. The real listener was triggered`);
Assert.ok(universalListener.triggered, `2. The universal listener was triggered`);
Assert.ok(!fakeListener.triggered, `2. The fake listener was not triggered`);
if (realListener.result) {
Assert.ok(realListener.result.highestJank >= 300, `2. jank is at least 300ms (${realListener.jank}ms)`);
}
info(`Attempting to remove correctly, check if the listener is still triggered`);
// Waiting a little – listeners are buffered.
await new Promise(resolve => setTimeout(resolve, 100));
realListener.unregister();
// Waiting a little – listeners are buffered.
await new Promise(resolve => setTimeout(resolve, 100));
await burner.run("promiseBurnContentCPU", 3, realListener);
Assert.ok(!realListener.triggered, `3. After being unregistered, the real listener was not triggered`);
Assert.ok(universalListener.triggered, `3. The universal listener is still triggered`);
universalListener.unregister();
// Waiting a little – listeners are buffered.
await new Promise(resolve => setTimeout(resolve, 100));
await burner.run("promiseBurnContentCPU", 3, realListener);
Assert.ok(!universalListener.triggered, `4. After being unregistered, the universal listener is not triggered`);
fakeListener.unregister();
burner.dispose();
gBrowser.removeTab(otherTab);
});

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

@ -4,9 +4,115 @@
/* eslint-env mozilla/frame-script */ /* eslint-env mozilla/frame-script */
ChromeUtils.import("resource://gre/modules/AddonManager.jsm", this); ChromeUtils.import("resource://gre/modules/AddonManager.jsm", this);
ChromeUtils.import("resource://gre/modules/PerformanceWatcher.jsm", this);
ChromeUtils.import("resource://gre/modules/Services.jsm", this); ChromeUtils.import("resource://gre/modules/Services.jsm", this);
ChromeUtils.import("resource://testing-common/ContentTaskUtils.jsm", this); ChromeUtils.import("resource://testing-common/ContentTaskUtils.jsm", this);
/**
* Base class for simulating slow addons/webpages.
*/
function CPUBurner(url, jankThreshold) {
info(`CPUBurner: Opening tab for ${url}\n`);
this.url = url;
this.tab = BrowserTestUtils.addTab(gBrowser, url);
this.jankThreshold = jankThreshold;
let browser = this.tab.linkedBrowser;
this._browser = browser;
ContentTask.spawn(this._browser, null, CPUBurner.frameScript);
this.promiseInitialized = BrowserTestUtils.browserLoaded(browser);
}
CPUBurner.prototype = {
get windowId() {
return this._browser.outerWindowID;
},
/**
* Burn CPU until it triggers a listener with the specified jank threshold.
*/
async run(burner, max, listener) {
listener.reset();
for (let i = 0; i < max; ++i) {
await new Promise(resolve => setTimeout(resolve, 50));
try {
await this[burner]();
} catch (ex) {
return false;
}
if (listener.triggered && listener.result >= this.jankThreshold) {
return true;
}
}
return false;
},
dispose() {
info(`CPUBurner: Closing tab for ${this.url}\n`);
gBrowser.removeTab(this.tab);
},
};
// This function is injected in all frames
CPUBurner.frameScript = function() {
try {
"use strict";
let sandboxes = new Map();
let getSandbox = function(addonId) {
let sandbox = sandboxes.get(addonId);
if (!sandbox) {
sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), { addonId });
sandboxes.set(addonId, sandbox);
}
return sandbox;
};
let burnCPU = function() {
var start = Date.now();
var ignored = [];
while (Date.now() - start < 500) {
ignored[ignored.length % 2] = ignored.length;
}
};
let burnCPUInSandbox = function(addonId) {
let sandbox = getSandbox(addonId);
Cu.evalInSandbox(burnCPU.toSource() + "()", sandbox);
};
{
let topic = "test-performance-watcher:burn-content-cpu";
addMessageListener(topic, function(msg) {
try {
if (msg.data && msg.data.addonId) {
burnCPUInSandbox(msg.data.addonId);
} else {
burnCPU();
}
sendAsyncMessage(topic, {});
} catch (ex) {
dump(`This is the content attempting to burn CPU: error ${ex}\n`);
dump(`${ex.stack}\n`);
}
});
}
// Bind the function to the global context or it might be GC'd during test
// causing failures (bug 1230027)
this.burnCPOWInSandbox = function(addonId) {
try {
burnCPUInSandbox(addonId);
} catch (ex) {
dump(`This is the addon attempting to burn CPOW: error ${ex}\n`);
dump(`${ex.stack}\n`);
}
};
sendAsyncMessage("test-performance-watcher:cpow-init", {}, {
burnCPOWInSandbox: this.burnCPOWInSandbox,
});
} catch (ex) {
Cu.reportError("This is the addon: error " + ex);
Cu.reportError(ex.stack);
}
};
/** /**
* Base class for listening to slow group alerts * Base class for listening to slow group alerts
*/ */
@ -49,6 +155,87 @@ AlertListener.prototype = {
}, },
}; };
/**
* Simulate a slow add-on.
*/
function AddonBurner(addonId = "fake add-on id: " + Math.random()) {
this.jankThreshold = 200000;
CPUBurner.call(this, `http://example.com/?uri=${addonId}`, this.jankThreshold);
this._addonId = addonId;
this._sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal(), { addonId: this._addonId });
this._CPOWBurner = null;
this._promiseCPOWBurner = new Promise(resolve => {
this._browser.messageManager.addMessageListener("test-performance-watcher:cpow-init", msg => {
// Note that we cannot resolve Promises with CPOWs now that they
// have been outlawed in bug 1233497, so we stash it in the
// AddonBurner instance instead.
this._CPOWBurner = msg.objects.burnCPOWInSandbox;
resolve();
});
});
}
AddonBurner.prototype = Object.create(CPUBurner.prototype);
Object.defineProperty(AddonBurner.prototype, "addonId", {
get() {
return this._addonId;
},
});
/**
* Simulate slow code being executed by the add-on in the chrome.
*/
AddonBurner.prototype.burnCPU = function() {
Cu.evalInSandbox(AddonBurner.burnCPU.toSource() + "()", this._sandbox);
};
/**
* Simulate slow code being executed by the add-on in a CPOW.
*/
AddonBurner.prototype.promiseBurnCPOW = async function() {
await this._promiseCPOWBurner;
ok(this._CPOWBurner, "Got the CPOW burner");
let burner = this._CPOWBurner;
info("Parent: Preparing to burn CPOW");
try {
await burner(this._addonId);
info("Parent: Done burning CPOW");
} catch (ex) {
info(`Parent: Error burning CPOW: ${ex}\n`);
info(ex.stack + "\n");
}
};
/**
* Simulate slow code being executed by the add-on in the content.
*/
AddonBurner.prototype.promiseBurnContentCPU = function() {
return promiseContentResponse(this._browser, "test-performance-watcher:burn-content-cpu", {addonId: this._addonId});
};
AddonBurner.burnCPU = function() {
var start = Date.now();
var ignored = [];
while (Date.now() - start < 500) {
ignored[ignored.length % 2] = ignored.length;
}
};
function AddonListener(addonId, accept) {
let target = {addonId};
AlertListener.call(this, accept, {
register: () => {
info(`AddonListener: registering ${JSON.stringify(target, null, "\t")}`);
PerformanceWatcher.addPerformanceListener({addonId}, this.listener);
},
unregister: () => {
info(`AddonListener: unregistering ${JSON.stringify(target, null, "\t")}`);
PerformanceWatcher.removePerformanceListener({addonId}, this.listener);
},
});
}
AddonListener.prototype = Object.create(AlertListener.prototype);
function promiseContentResponse(browser, name, message) { function promiseContentResponse(browser, name, message) {
let mm = browser.messageManager; let mm = browser.messageManager;
let promise = new Promise(resolve => { let promise = new Promise(resolve => {