зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1162871 - Introduce the TabStateFlusher for async flushing r=billm
This commit is contained in:
Родитель
9a6f81f509
Коммит
9c05c58af3
|
@ -158,6 +158,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "TabState",
|
|||
"resource:///modules/sessionstore/TabState.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
|
||||
"resource:///modules/sessionstore/TabStateCache.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TabStateFlusher",
|
||||
"resource:///modules/sessionstore/TabStateFlusher.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
|
||||
"resource:///modules/sessionstore/Utils.jsm");
|
||||
|
||||
|
@ -653,6 +655,19 @@ let SessionStoreInternal = {
|
|||
TabState.update(browser, aMessage.data);
|
||||
this.saveStateDelayed(win);
|
||||
|
||||
if (aMessage.data.isFinal) {
|
||||
// If this the final message we need to resolve all pending flush
|
||||
// requests for the given browser as they might have been sent too
|
||||
// late and will never respond. If they have been sent shortly after
|
||||
// switching a browser's remoteness there isn't too much data to skip.
|
||||
TabStateFlusher.resolveAll(browser);
|
||||
} else if (aMessage.data.flushID) {
|
||||
// This is an update kicked off by an async flush request. Notify the
|
||||
// TabStateFlusher so that it can finish the request and notify its
|
||||
// consumer that's waiting for the flush to be done.
|
||||
TabStateFlusher.resolve(browser, aMessage.data.flushID);
|
||||
}
|
||||
|
||||
// Handle any updates sent by the child after the tab was closed. This
|
||||
// might be the final update as sent by the "unload" handler but also
|
||||
// any async update message that was sent before the child unloaded.
|
||||
|
@ -1622,6 +1637,10 @@ let SessionStoreInternal = {
|
|||
let tab = aWindow.gBrowser.getTabForBrowser(aBrowser);
|
||||
this._resetLocalTabRestoringState(tab);
|
||||
}
|
||||
|
||||
// The browser crashed so we might never receive flush responses.
|
||||
// Resolve all pending flush requests for the crashed browser.
|
||||
TabStateFlusher.resolveAll(aBrowser);
|
||||
},
|
||||
|
||||
// Clean up data that has been closed a long time ago.
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/* 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);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves the flush request with the given flush ID.
|
||||
*/
|
||||
resolve(browser, flushID) {
|
||||
TabStateFlusherInternal.resolve(browser, flushID);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
resolveAll(browser) {
|
||||
TabStateFlusherInternal.resolveAll(browser);
|
||||
}
|
||||
});
|
||||
|
||||
let 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);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves the flush request with the given flush ID.
|
||||
*/
|
||||
resolve(browser, flushID) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Resolve the request with the given id.
|
||||
let resolve = perBrowserRequests.get(flushID);
|
||||
perBrowserRequests.delete(flushID);
|
||||
resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
resolveAll(browser) {
|
||||
// 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);
|
||||
|
||||
// Resolve all requests.
|
||||
for (let resolve of perBrowserRequests.values()) {
|
||||
resolve();
|
||||
}
|
||||
|
||||
// Clear active requests.
|
||||
perBrowserRequests.clear();
|
||||
}
|
||||
};
|
|
@ -106,6 +106,7 @@ let MessageListener = {
|
|||
"SessionStore:restoreHistory",
|
||||
"SessionStore:restoreTabContent",
|
||||
"SessionStore:resetRestore",
|
||||
"SessionStore:flush",
|
||||
],
|
||||
|
||||
init: function () {
|
||||
|
@ -137,6 +138,9 @@ let MessageListener = {
|
|||
case "SessionStore:resetRestore":
|
||||
gContentRestore.resetRestore();
|
||||
break;
|
||||
case "SessionStore:flush":
|
||||
this.flush(data);
|
||||
break;
|
||||
default:
|
||||
debug("received unknown message '" + name + "'");
|
||||
break;
|
||||
|
@ -192,6 +196,11 @@ let MessageListener = {
|
|||
// Pretend that the load succeeded so that event handlers fire correctly.
|
||||
sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch});
|
||||
}
|
||||
},
|
||||
|
||||
flush({id}) {
|
||||
// Flush the message queue, send the latest updates.
|
||||
MessageQueue.send({flushID: id});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -651,6 +660,8 @@ let MessageQueue = {
|
|||
* @param options (object)
|
||||
* {id: 123} to override the update ID used to accumulate data to send.
|
||||
* {sync: true} to send data to the parent process synchronously.
|
||||
* {flushID: 123} to specify that this is a flush
|
||||
* {isFinal: true} to signal this is the final message sent on unload
|
||||
*/
|
||||
send: function (options = {}) {
|
||||
// Looks like we have been called off a timeout after the tab has been
|
||||
|
@ -667,6 +678,7 @@ let MessageQueue = {
|
|||
|
||||
let sync = options && options.sync;
|
||||
let startID = (options && options.id) || this._id;
|
||||
let flushID = (options && options.flushID) || 0;
|
||||
|
||||
// We use sendRpcMessage in the sync case because we may have been called
|
||||
// through a CPOW. RPC messages are the only synchronous messages that the
|
||||
|
@ -702,7 +714,7 @@ let MessageQueue = {
|
|||
|
||||
// Send all data to the parent process.
|
||||
sendMessage("SessionStore:update", {
|
||||
id: this._id, data, telemetry,
|
||||
id: this._id, data, telemetry, flushID,
|
||||
isFinal: options.isFinal || false,
|
||||
epoch: gCurrentEpoch
|
||||
});
|
||||
|
|
|
@ -44,6 +44,7 @@ EXTRA_JS_MODULES.sessionstore = [
|
|||
'TabAttributes.jsm',
|
||||
'TabState.jsm',
|
||||
'TabStateCache.jsm',
|
||||
'TabStateFlusher.jsm',
|
||||
'Utils.jsm',
|
||||
]
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче