Bug 1162871 - Introduce the TabStateFlusher for async flushing r=billm

This commit is contained in:
Tim Taubert 2015-05-08 10:59:38 +02:00
Родитель 9a6f81f509
Коммит 9c05c58af3
4 изменённых файлов: 155 добавлений и 1 удалений

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

@ -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',
]