Source code
Revision control
Copy as Markdown
Other Tools
/* 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
let lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
HomePage: "resource:///modules/HomePage.sys.mjs",
NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
clearTimeout: "resource://gre/modules/Timer.sys.mjs",
setTimeout: "resource://gre/modules/Timer.sys.mjs",
});
/**
* AboutHomeStartupCache is responsible for reading and writing the
* initial about:home document from the HTTP cache as a startup
* performance optimization. It only works when the "privileged about
* content process" is enabled and when ENABLED_PREF is set to true.
*
* for further details.
*/
export var AboutHomeStartupCache = {
ABOUT_HOME_URI_STRING: "about:home",
SCRIPT_EXTENSION: "script",
ENABLED_PREF: "browser.startup.homepage.abouthome_cache.enabled",
PRELOADED_NEWTAB_PREF: "browser.newtab.preload",
LOG_LEVEL_PREF: "browser.startup.homepage.abouthome_cache.loglevel",
// It's possible that the layout of about:home will change such that
// we want to invalidate any pre-existing caches. We do this by setting
// this meta key in the nsICacheEntry for the page.
//
// The version is currently set to the build ID, meaning that the cache
// is invalidated after every upgrade (like the main startup cache).
CACHE_VERSION_META_KEY: "version",
LOG_NAME: "AboutHomeStartupCache",
// These messages are used to request the "privileged about content process"
// to create the cached document, and then to receive that document.
CACHE_REQUEST_MESSAGE: "AboutHomeStartupCache:CacheRequest",
CACHE_RESPONSE_MESSAGE: "AboutHomeStartupCache:CacheResponse",
CACHE_USAGE_RESULT_MESSAGE: "AboutHomeStartupCache:UsageResult",
// When a "privileged about content process" is launched, this message is
// sent to give it some nsIInputStream's for the about:home document they
// should load.
SEND_STREAMS_MESSAGE: "AboutHomeStartupCache:InputStreams",
// This time in ms is used to debounce messages that are broadcast to
// all about:newtab's, or the preloaded about:newtab. We use those
// messages as a signal that it's likely time to refresh the cache.
CACHE_DEBOUNCE_RATE_MS: 5000,
// This is how long we'll block the AsyncShutdown while waiting for
// the cache to write. If we fail to write within that time, we will
// allow the shutdown to proceed.
SHUTDOWN_CACHE_WRITE_TIMEOUT_MS: 1000,
// The following values are as possible values for the
// browser.startup.abouthome_cache_result scalar. Keep these in sync with the
// scalar definition in Scalars.yaml and the matching Glean metric in
// browser/components/metrics.yaml. See setDeferredResult for more
// information.
CACHE_RESULT_SCALARS: {
UNSET: 0,
DOES_NOT_EXIST: 1,
CORRUPT_PAGE: 2,
CORRUPT_SCRIPT: 3,
INVALIDATED: 4,
LATE: 5,
VALID_AND_USED: 6,
DISABLED: 7,
NOT_LOADING_ABOUTHOME: 8,
PRELOADING_DISABLED: 9,
},
// This will be set to one of the values of CACHE_RESULT_SCALARS
// once it is determined which result best suits what occurred.
_cacheDeferredResultScalar: -1,
// A reference to the nsICacheEntry to read from and write to.
_cacheEntry: null,
// These nsIPipe's are sent down to the "privileged about content process"
// immediately after the process launches. This allows us to race the loading
// of the cache entry in the parent process with the load of the about:home
// page in the content process, since we'll connect the InputStream's to
// the pipes as soon as the nsICacheEntry is available.
//
// The page pipe is for the HTML markup for the page.
_pagePipe: null,
// The script pipe is for the JavaScript that the HTML markup loads
// to set its internal state.
_scriptPipe: null,
_cacheDeferred: null,
_enabled: false,
_initted: false,
_hasWrittenThisSession: false,
_finalized: false,
_firstPrivilegedProcessCreated: false,
init() {
if (this._initted) {
throw new Error("AboutHomeStartupCache already initted.");
}
this.setDeferredResult(this.CACHE_RESULT_SCALARS.UNSET);
this._enabled = Services.prefs.getBoolPref(
"browser.startup.homepage.abouthome_cache.enabled"
);
if (!this._enabled) {
this.recordResult(this.CACHE_RESULT_SCALARS.DISABLED);
return;
}
this.log = console.createInstance({
prefix: this.LOG_NAME,
maxLogLevelPref: this.LOG_LEVEL_PREF,
});
this.log.trace("Initting.");
// If the user is not configured to load about:home at startup, then
// let's not bother with the cache - loading it needlessly is more likely
// to hinder what we're actually trying to load.
let willLoadAboutHome =
!lazy.HomePage.overridden &&
Services.prefs.getIntPref("browser.startup.page") === 1;
if (!willLoadAboutHome) {
this.log.trace("Not configured to load about:home by default.");
this.recordResult(this.CACHE_RESULT_SCALARS.NOT_LOADING_ABOUTHOME);
return;
}
if (!Services.prefs.getBoolPref(this.PRELOADED_NEWTAB_PREF, false)) {
this.log.trace("Preloaded about:newtab disabled.");
this.recordResult(this.CACHE_RESULT_SCALARS.PRELOADING_DISABLED);
return;
}
Services.obs.addObserver(this, "ipc:content-created");
Services.obs.addObserver(this, "process-type-set");
Services.obs.addObserver(this, "ipc:content-shutdown");
Services.obs.addObserver(this, "intl:app-locales-changed");
this.log.trace("Constructing pipes.");
this._pagePipe = this.makePipe();
this._scriptPipe = this.makePipe();
this._cacheEntryPromise = new Promise(resolve => {
this._cacheEntryResolver = resolve;
});
let lci = Services.loadContextInfo.default;
let storage = Services.cache2.diskCacheStorage(lci);
try {
storage.asyncOpenURI(
this.aboutHomeURI,
"",
Ci.nsICacheStorage.OPEN_PRIORITY,
this
);
} catch (e) {
this.log.error("Failed to open about:home cache entry", e);
}
this._cacheTask = new lazy.DeferredTask(async () => {
await this.cacheNow();
}, this.CACHE_DEBOUNCE_RATE_MS);
lazy.AsyncShutdown.quitApplicationGranted.addBlocker(
"AboutHomeStartupCache: Writing cache",
async () => {
await this.onShutdown();
},
() => this._cacheProgress
);
this._cacheDeferred = null;
this._initted = true;
this.log.trace("Initialized.");
},
get initted() {
return this._initted;
},
uninit() {
if (!this._enabled) {
return;
}
try {
Services.obs.removeObserver(this, "ipc:content-created");
Services.obs.removeObserver(this, "process-type-set");
Services.obs.removeObserver(this, "ipc:content-shutdown");
Services.obs.removeObserver(this, "intl:app-locales-changed");
} catch (e) {
// If we failed to initialize and register for these observer
// notifications, then attempting to remove them will throw.
// It's fine to ignore that case on shutdown.
}
if (this._cacheTask) {
this._cacheTask.disarm();
this._cacheTask = null;
}
this._pagePipe = null;
this._scriptPipe = null;
this._initted = false;
this._cacheEntry = null;
this._hasWrittenThisSession = false;
this._cacheEntryPromise = null;
this._cacheEntryResolver = null;
this._cacheDeferredResultScalar = -1;
if (this.log) {
this.log.trace("Uninitialized.");
this.log = null;
}
this._procManager = null;
this._procManagerID = null;
this._appender = null;
this._cacheDeferred = null;
this._finalized = false;
this._firstPrivilegedProcessCreated = false;
},
_aboutHomeURI: null,
get aboutHomeURI() {
if (this._aboutHomeURI) {
return this._aboutHomeURI;
}
this._aboutHomeURI = Services.io.newURI(this.ABOUT_HOME_URI_STRING);
return this._aboutHomeURI;
},
// For the AsyncShutdown blocker, this is used to populate the progress
// value.
_cacheProgress: "Not yet begun",
/**
* Called by the AsyncShutdown blocker on quit-application-granted
* to potentially flush the most recent cache to disk. If one was
* never written during the session, one is generated and written
* before the async function resolves.
*
* @param {boolean} withTimeout
* Whether or not the timeout mechanism should be used. Defaults
* to true.
* @returns {Promise<boolean>}
* If a cache has never been written, or a cache write is in
* progress, resolves true when the cache has been written. Also
* resolves to true if a cache didn't need to be written.
*
* Resolves to false if a cache write unexpectedly timed out.
*/
async onShutdown(withTimeout = true) {
// If we never wrote this session, arm the task so that the next
// step can finalize.
if (!this._hasWrittenThisSession) {
this.log.trace("Never wrote a cache this session. Arming cache task.");
this._cacheTask.arm();
}
Glean.browserStartup.abouthomeCacheShutdownwrite.set(
this._cacheTask.isArmed
);
if (this._cacheTask.isArmed) {
this.log.trace("Finalizing cache task on shutdown");
this._finalized = true;
// To avoid hanging shutdowns, we'll ensure that we wait a maximum of
// SHUTDOWN_CACHE_WRITE_TIMEOUT_MS millseconds before giving up.
const TIMED_OUT = Symbol();
let timeoutID = 0;
let timeoutPromise = new Promise(resolve => {
timeoutID = lazy.setTimeout(
() => resolve(TIMED_OUT),
this.SHUTDOWN_CACHE_WRITE_TIMEOUT_MS
);
});
let promises = [this._cacheTask.finalize()];
if (withTimeout) {
this.log.trace("Using timeout mechanism.");
promises.push(timeoutPromise);
} else {
this.log.trace("Skipping timeout mechanism.");
}
let result = await Promise.race(promises);
this.log.trace("Done blocking shutdown.");
lazy.clearTimeout(timeoutID);
if (result === TIMED_OUT) {
this.log.error("Timed out getting cache streams. Skipping cache task.");
return false;
}
}
this.log.trace("onShutdown is exiting");
return true;
},
/**
* Called by the _cacheTask DeferredTask to actually do the work of
* caching the about:home document.
*
* @returns {Promise<undefined>}
* Resolves when a fresh version of the cache has been written.
*/
async cacheNow() {
this.log.trace("Caching now.");
this._cacheProgress = "Getting cache streams";
let { pageInputStream, scriptInputStream } = await this.requestCache();
if (!pageInputStream || !scriptInputStream) {
this.log.trace("Failed to get cache streams.");
this._cacheProgress = "Failed to get streams";
return;
}
this.log.trace("Got cache streams.");
this._cacheProgress = "Writing to cache";
try {
this.log.trace("Populating cache.");
await this.populateCache(pageInputStream, scriptInputStream);
} catch (e) {
this._cacheProgress = "Failed to populate cache";
this.log.error("Populating the cache failed: ", e);
return;
}
this._cacheProgress = "Done";
this.log.trace("Done writing to cache.");
this._hasWrittenThisSession = true;
},
/**
* Requests the cached document streams from the "privileged about content
* process".
*
* @returns {Promise<object>}
* Resolves with an Object with the following properties:
*
* pageInputStream (nsIInputStream)
* The page content to write to the cache, or null if request the streams
* failed.
*
* scriptInputStream (nsIInputStream)
* The script content to write to the cache, or null if request the streams
* failed.
*/
requestCache() {
this.log.trace("Parent is requesting Activity Stream state object.");
if (!this._procManager) {
this.log.error("requestCache called with no _procManager!");
return { pageInputStream: null, scriptInputStream: null };
}
if (
this._procManager.remoteType != lazy.E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE
) {
this.log.error("Somehow got the wrong process type.");
return { pageInputStream: null, scriptInputStream: null };
}
let state = lazy.AboutNewTab.activityStream.store.getState();
return new Promise(resolve => {
this._cacheDeferred = resolve;
this.log.trace("Parent is requesting cache streams.");
this._procManager.sendAsyncMessage(this.CACHE_REQUEST_MESSAGE, { state });
});
},
/**
* Helper function that returns a newly constructed nsIPipe instance.
*
* @returns {nsIPipe}
*/
makePipe() {
let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
pipe.init(
true /* non-blocking input */,
true /* non-blocking output */,
0 /* segment size */,
0 /* max segments */
);
return pipe;
},
get pagePipe() {
return this._pagePipe;
},
get scriptPipe() {
return this._scriptPipe;
},
/**
* Called when the nsICacheEntry has been accessed. If the nsICacheEntry
* has content that we want to send down to the "privileged about content
* process", then we connect that content to the nsIPipe's that may or
* may not have already been sent down to the process.
*
* In the event that the nsICacheEntry doesn't contain anything usable,
* the nsInputStreams on the nsIPipe's are closed.
*/
connectToPipes() {
this.log.trace(`Connecting nsICacheEntry to pipes.`);
// If the cache doesn't yet exist, we'll know because the version metadata
// won't exist yet.
let version;
try {
this.log.trace("");
version = this._cacheEntry.getMetaDataElement(
this.CACHE_VERSION_META_KEY
);
} catch (e) {
if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
this.log.debug("Cache meta data does not exist. Closing streams.");
this.pagePipe.outputStream.close();
this.scriptPipe.outputStream.close();
this.setDeferredResult(this.CACHE_RESULT_SCALARS.DOES_NOT_EXIST);
return;
}
throw e;
}
this.log.info("Version retrieved is", version);
if (version != Services.appinfo.appBuildID) {
this.log.info("Version does not match! Dooming and closing streams.\n");
// This cache is no good - doom it, and prepare for a new one.
this.clearCache();
this.pagePipe.outputStream.close();
this.scriptPipe.outputStream.close();
this.setDeferredResult(this.CACHE_RESULT_SCALARS.INVALIDATED);
return;
}
let cachePageInputStream;
try {
cachePageInputStream = this._cacheEntry.openInputStream(0);
} catch (e) {
this.log.error("Failed to open main input stream for cache entry", e);
this.pagePipe.outputStream.close();
this.scriptPipe.outputStream.close();
this.setDeferredResult(this.CACHE_RESULT_SCALARS.CORRUPT_PAGE);
return;
}
this.log.trace("Connecting page stream to pipe.");
lazy.NetUtil.asyncCopy(
cachePageInputStream,
this.pagePipe.outputStream,
() => {
this.log.info("Page stream connected to pipe.");
}
);
let cacheScriptInputStream;
try {
this.log.trace("Connecting script stream to pipe.");
cacheScriptInputStream =
this._cacheEntry.openAlternativeInputStream("script");
lazy.NetUtil.asyncCopy(
cacheScriptInputStream,
this.scriptPipe.outputStream,
() => {
this.log.info("Script stream connected to pipe.");
}
);
} catch (e) {
if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
// For some reason, the script was not available. We'll close the pipe
// without sending anything into it. The privileged about content process
// will notice that there's nothing available in the pipe, and fall back
// to dynamically generating the page.
this.log.error("Script stream not available! Closing pipe.");
this.scriptPipe.outputStream.close();
this.setDeferredResult(this.CACHE_RESULT_SCALARS.CORRUPT_SCRIPT);
} else {
throw e;
}
}
this.setDeferredResult(this.CACHE_RESULT_SCALARS.VALID_AND_USED);
this.log.trace("Streams connected to pipes.");
},
/**
* Called when we have received a the cache values from the "privileged
* about content process". The page and script streams are written to
* the nsICacheEntry.
*
* This writing is asynchronous, and if a write happens to already be
* underway when this function is called, that latter call will be
* ignored.
*
* @param {nsIInputStream} pageInputStream
* A stream containing the HTML markup to be saved to the cache.
* @param {nsIInputStream} scriptInputStream
* A stream containing the JS hydration script to be saved to the cache.
* @returns {Promise<undefined, Error>}
* When the cache has been successfully written to.
* Rejects with a JS Error if writing any part of the cache happens to
* fail.
*/
async populateCache(pageInputStream, scriptInputStream) {
await this.ensureCacheEntry();
await new Promise((resolve, reject) => {
// Doom the old cache entry, so we can start writing to a new one.
this.log.trace("Populating the cache. Dooming old entry.");
this.clearCache();
this.log.trace("Opening the page output stream.");
let pageOutputStream;
try {
pageOutputStream = this._cacheEntry.openOutputStream(0, -1);
} catch (e) {
reject(e);
return;
}
this.log.info("Writing the page cache.");
lazy.NetUtil.asyncCopy(pageInputStream, pageOutputStream, pageResult => {
if (!Components.isSuccessCode(pageResult)) {
this.log.error("Failed to write page. Result: " + pageResult);
reject(new Error(pageResult));
return;
}
this.log.trace(
"Writing the page data is complete. Now opening the " +
"script output stream."
);
let scriptOutputStream;
try {
scriptOutputStream = this._cacheEntry.openAlternativeOutputStream(
"script",
-1
);
} catch (e) {
reject(e);
return;
}
this.log.info("Writing the script cache.");
lazy.NetUtil.asyncCopy(
scriptInputStream,
scriptOutputStream,
scriptResult => {
if (!Components.isSuccessCode(scriptResult)) {
this.log.error("Failed to write script. Result: " + scriptResult);
reject(new Error(scriptResult));
return;
}
this.log.trace(
"Writing the script cache is done. Setting version."
);
try {
this._cacheEntry.setMetaDataElement(
"version",
Services.appinfo.appBuildID
);
} catch (e) {
this.log.error("Failed to write version.");
reject(e);
return;
}
this.log.trace(`Version is set to ${Services.appinfo.appBuildID}.`);
this.log.info("Caching of page and script is done.");
resolve();
}
);
});
});
this.log.trace("populateCache has finished.");
},
/**
* Returns a Promise that resolves once the nsICacheEntry for the cache
* is available to write to and read from.
*
* @returns {Promise<nsICacheEntry, string>}
* Resolves once the cache entry has become available.
*
* Rejects with an error message if getting the cache entry is attempted
* before the AboutHomeStartupCache component has been initialized.
*/
ensureCacheEntry() {
if (!this._initted) {
return Promise.reject(
"Cannot ensureCacheEntry - AboutHomeStartupCache is not initted"
);
}
return this._cacheEntryPromise;
},
/**
* Clears the contents of the cache.
*/
clearCache() {
this.log.trace("Clearing the cache.");
this._cacheEntry = this._cacheEntry.recreate();
this._cacheEntryPromise = new Promise(resolve => {
resolve(this._cacheEntry);
});
this._hasWrittenThisSession = false;
},
/**
* Called when a content process is created. If this is the "privileged
* about content process", then the cache streams will be sent to it.
*
* @param {number} childID
* The unique ID for the content process that was created, as passed by
* ipc:content-created.
* @param {ProcessMessageManager} procManager
* The ProcessMessageManager for the created content process.
* @param {nsIDOMProcessParent} processParent
* The nsIDOMProcessParent for the tab.
*/
onContentProcessCreated(childID, procManager, processParent) {
if (procManager.remoteType == lazy.E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE) {
if (this._finalized) {
this.log.trace(
"Ignoring privileged about content process launch after finalization."
);
return;
}
if (this._firstPrivilegedProcessCreated) {
this.log.trace(
"Ignoring non-first privileged about content processes."
);
return;
}
this.log.trace(
`A privileged about content process is launching with ID ${childID}.`
);
this.log.info("Sending input streams down to content process.");
let actor = processParent.getActor("BrowserProcess");
actor.sendAsyncMessage(this.SEND_STREAMS_MESSAGE, {
pageInputStream: this.pagePipe.inputStream,
scriptInputStream: this.scriptPipe.inputStream,
});
procManager.addMessageListener(this.CACHE_RESPONSE_MESSAGE, this);
procManager.addMessageListener(this.CACHE_USAGE_RESULT_MESSAGE, this);
this._procManager = procManager;
this._procManagerID = childID;
this._firstPrivilegedProcessCreated = true;
}
},
/**
* Called when a content process is destroyed. Either it shut down normally,
* or it crashed. If this is the "privileged about content process", then some
* internal state is cleared.
*
* @param {number} childID
* The unique ID for the content process that was created, as passed by
* ipc:content-shutdown.
*/
onContentProcessShutdown(childID) {
this.log.info(`Content process shutdown: ${childID}`);
if (this._procManagerID == childID) {
this.log.info("It was the current privileged about process.");
if (this._cacheDeferred) {
this.log.error(
"A privileged about content process shut down while cache streams " +
"were still en route."
);
// The crash occurred while we were waiting on cache input streams to
// be returned to us. Resolve with null streams instead.
this._cacheDeferred({ pageInputStream: null, scriptInputStream: null });
this._cacheDeferred = null;
}
this._procManager.removeMessageListener(
this.CACHE_RESPONSE_MESSAGE,
this
);
this._procManager.removeMessageListener(
this.CACHE_USAGE_RESULT_MESSAGE,
this
);
this._procManager = null;
this._procManagerID = null;
}
},
/**
* Called externally by ActivityStreamMessageChannel anytime
* a message is broadcast to all about:newtabs, or sent to the
* preloaded about:newtab. This is used to determine if we need
* to refresh the cache.
*/
onPreloadedNewTabMessage() {
if (!this._initted || !this._enabled) {
return;
}
if (this._finalized) {
this.log.trace("Ignoring preloaded newtab update after finalization.");
return;
}
this.log.trace("Preloaded about:newtab was updated.");
this._cacheTask.disarm();
this._cacheTask.arm();
},
/**
* Stores the CACHE_RESULT_SCALARS value that most accurately represents
* the current notion of how the cache has operated so far. It is stored
* temporarily like this because we need to hear from the privileged
* about content process to hear whether or not retrieving the cache
* actually worked on that end. The success state reported back from
* the privileged about content process will be compared against the
* deferred result scalar to compute what will be recorded to
* Telemetry.
*
* Note that this value will only be recorded if its value is GREATER
* than the currently recorded value. This is because it's possible for
* certain functions that record results to re-enter - but we want to record
* the _first_ condition that caused the cache to not be read from.
*
* @param {number} result
* One of the CACHE_RESULT_SCALARS values. If this value is less than
* the currently recorded value, it is ignored.
*/
setDeferredResult(result) {
if (this._cacheDeferredResultScalar < result) {
this._cacheDeferredResultScalar = result;
}
},
/**
* Records the final result of how the cache operated for the user
* during this session to Telemetry.
*
* @param {number} result
* One of the result constants from CACHE_RESULT_SCALARS.
*/
recordResult(result) {
// Note: this can be called very early on in the lifetime of
// AboutHomeStartupCache, so things like this.log might not exist yet.
Glean.browserStartup.abouthomeCacheResult.set(result);
},
/**
* Called when the parent process receives a message from the privileged
* about content process saying whether or not reading from the cache
* was successful.
*
* @param {boolean} success
* True if reading from the cache succeeded.
*/
onUsageResult(success) {
this.log.trace(`Received usage result. Success = ${success}`);
if (success) {
if (
this._cacheDeferredResultScalar !=
this.CACHE_RESULT_SCALARS.VALID_AND_USED
) {
this.log.error(
"Somehow got a success result despite having never " +
"successfully sent down the cache streams"
);
this.recordResult(this._cacheDeferredResultScalar);
} else {
this.recordResult(this.CACHE_RESULT_SCALARS.VALID_AND_USED);
}
return;
}
if (
this._cacheDeferredResultScalar ==
this.CACHE_RESULT_SCALARS.VALID_AND_USED
) {
// We failed to read from the cache despite having successfully
// sent it down to the content process. We presume then that the
// streams just didn't provide any bytes in time.
this.recordResult(this.CACHE_RESULT_SCALARS.LATE);
} else {
// We failed to read the cache, but already knew why. We can
// now record that value.
this.recordResult(this._cacheDeferredResultScalar);
}
},
QueryInterface: ChromeUtils.generateQI([
"nsICacheEntryOpenallback",
"nsIObserver",
]),
/* MessageListener */
receiveMessage(message) {
// Only the privileged about content process can write to the cache.
if (
message.target.remoteType != lazy.E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE
) {
this.log.error(
"Received a message from a non-privileged content process!"
);
return;
}
switch (message.name) {
case this.CACHE_RESPONSE_MESSAGE: {
this.log.trace("Parent received cache streams.");
if (!this._cacheDeferred) {
this.log.error("Parent doesn't have _cacheDeferred set up!");
return;
}
this._cacheDeferred(message.data);
this._cacheDeferred = null;
break;
}
case this.CACHE_USAGE_RESULT_MESSAGE: {
this.onUsageResult(message.data.success);
break;
}
}
},
/* nsIObserver */
observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "intl:app-locales-changed": {
this.clearCache();
break;
}
case "process-type-set":
// Intentional fall-through
case "ipc:content-created": {
let childID = aData;
let procManager = aSubject
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIMessageSender);
let pp = aSubject.QueryInterface(Ci.nsIDOMProcessParent);
this.onContentProcessCreated(childID, procManager, pp);
break;
}
case "ipc:content-shutdown": {
let childID = aData;
this.onContentProcessShutdown(childID);
break;
}
}
},
/* nsICacheEntryOpenCallback */
onCacheEntryCheck() {
return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
},
onCacheEntryAvailable(aEntry) {
this.log.trace("Cache entry is available.");
this._cacheEntry = aEntry;
this.connectToPipes();
this._cacheEntryResolver(this._cacheEntry);
},
};