gecko-dev/browser/components/sessionstore/TabStateFlusher.jsm
Mike Conley c9f1c242db Bug 1177310 - TabStateFlusher Promises should always resolve. r=Yoric
They'll always resolve, but might receive a negative success state
and a message. We're doing this so that we can maintain a Set of
in-flight flushes that we can call Promise.all on (which will
fast-fail if any Promise rejects, or will just never resolve if
one or more of the Promises never resolve).

--HG--
extra : commitid : AkIM83IZCxK
extra : rebase_source : 4fb8eb059589299c87fcbd3c4e519a81a8b93def
2015-11-24 18:41:32 -05:00

185 lines
6.2 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";
this.EXPORTED_SYMBOLS = ["TabStateFlusher"];
const Cu = Components.utils;
Cu.import("resource://gre/modules/Promise.jsm", this);
/**
* A module that enables async flushes. Updates from frame scripts are
* throttled to be sent only once per second. If an action wants a tab's latest
* state without waiting for a second then it can request an async flush and
* wait until the frame scripts reported back. At this point the parent has the
* latest data and the action can continue.
*/
this.TabStateFlusher = Object.freeze({
/**
* Requests an async flush for the given browser. Returns a promise that will
* resolve when we heard back from the content process and the parent has
* all the latest data.
*/
flush(browser) {
return TabStateFlusherInternal.flush(browser);
},
/**
* Requests an async flush for all browsers of a given window. Returns a Promise
* that will resolve when we've heard back from all browsers.
*/
flushWindow(window) {
return TabStateFlusherInternal.flushWindow(window);
},
/**
* Resolves the flush request with the given flush ID.
*
* @param browser (<xul:browser>)
* The browser for which the flush is being resolved.
* @param flushID (int)
* The ID of the flush that was sent to the browser.
* @param success (bool, optional)
* Whether or not the flush succeeded.
* @param message (string, optional)
* An error message that will be sent to the Console in the
* event that a flush failed.
*/
resolve(browser, flushID, success=true, message="") {
TabStateFlusherInternal.resolve(browser, flushID, success, message);
},
/**
* Resolves all active flush requests for a given browser. This should be
* used when the content process crashed or the final update message was
* seen. In those cases we can't guarantee to ever hear back from the frame
* script so we just resolve all requests instead of discarding them.
*
* @param browser (<xul:browser>)
* The browser for which all flushes are being resolved.
* @param success (bool, optional)
* Whether or not the flushes succeeded.
* @param message (string, optional)
* An error message that will be sent to the Console in the
* event that the flushes failed.
*/
resolveAll(browser, success=true, message="") {
TabStateFlusherInternal.resolveAll(browser, success, message);
}
});
var TabStateFlusherInternal = {
// Stores the last request ID.
_lastRequestID: 0,
// A map storing all active requests per browser.
_requests: new WeakMap(),
/**
* Requests an async flush for the given browser. Returns a promise that will
* resolve when we heard back from the content process and the parent has
* all the latest data.
*/
flush(browser) {
let id = ++this._lastRequestID;
let mm = browser.messageManager;
mm.sendAsyncMessage("SessionStore:flush", {id});
// Retrieve active requests for given browser.
let permanentKey = browser.permanentKey;
let perBrowserRequests = this._requests.get(permanentKey) || new Map();
return new Promise(resolve => {
// Store resolve() so that we can resolve the promise later.
perBrowserRequests.set(id, resolve);
// Update the flush requests stored per browser.
this._requests.set(permanentKey, perBrowserRequests);
});
},
/**
* Requests an async flush for all browsers of a given window. Returns a Promise
* that will resolve when we've heard back from all browsers.
*/
flushWindow(window) {
let browsers = window.gBrowser.browsers;
let promises = browsers.map((browser) => this.flush(browser));
return Promise.all(promises);
},
/**
* Resolves the flush request with the given flush ID.
*
* @param browser (<xul:browser>)
* The browser for which the flush is being resolved.
* @param flushID (int)
* The ID of the flush that was sent to the browser.
* @param success (bool, optional)
* Whether or not the flush succeeded.
* @param message (string, optional)
* An error message that will be sent to the Console in the
* event that a flush failed.
*/
resolve(browser, flushID, success=true, message="") {
// Nothing to do if there are no pending flushes for the given browser.
if (!this._requests.has(browser.permanentKey)) {
return;
}
// Retrieve active requests for given browser.
let perBrowserRequests = this._requests.get(browser.permanentKey);
if (!perBrowserRequests.has(flushID)) {
return;
}
if (!success) {
Cu.reportError("Failed to flush browser: " + message);
}
// Resolve the request with the given id.
let resolve = perBrowserRequests.get(flushID);
perBrowserRequests.delete(flushID);
resolve(success);
},
/**
* Resolves all active flush requests for a given browser. This should be
* used when the content process crashed or the final update message was
* seen. In those cases we can't guarantee to ever hear back from the frame
* script so we just resolve all requests instead of discarding them.
*
* @param browser (<xul:browser>)
* The browser for which all flushes are being resolved.
* @param success (bool, optional)
* Whether or not the flushes succeeded.
* @param message (string, optional)
* An error message that will be sent to the Console in the
* event that the flushes failed.
*/
resolveAll(browser, success=true, message="") {
// Nothing to do if there are no pending flushes for the given browser.
if (!this._requests.has(browser.permanentKey)) {
return;
}
// Retrieve active requests for given browser.
let perBrowserRequests = this._requests.get(browser.permanentKey);
if (!success) {
Cu.reportError("Failed to flush browser: " + message);
}
// Resolve all requests.
for (let resolve of perBrowserRequests.values()) {
resolve(success);
}
// Clear active requests.
perBrowserRequests.clear();
}
};