Bug 1389443 - Load handlers.json asynchronously r=florian,Paolo

Asynchronously load handlers.json for nsHandlerService-json.js in
order to avoid blocking IO early on.

MozReview-Commit-ID: F3THSxvXR7I

--HG--
extra : rebase_source : 95d5ccb545cd7d8a09f80586fc2bf2dcf1ea5ab7
This commit is contained in:
Doug Thayer 2017-08-15 14:57:32 -07:00
parent a4a6ffdd99
commit 21ef26ac40
7 changed files with 151 additions and 35 deletions

View File

@ -483,6 +483,16 @@ BrowserGlue.prototype = {
case "sync-ui-state:update":
this._updateFxaBadges();
break;
case "handlersvc-store-initialized":
// Initialize PdfJs when running in-process and remote. This only
// happens once since PdfJs registers global hooks. If the PdfJs
// extension is installed the init method below will be overridden
// leaving initialization to the extension.
// parent only: configure default prefs, set up pref observers, register
// pdf content handler, and initializes parent side message manager
// shim for privileged api access.
PdfJs.init(true);
break;
}
},
@ -518,6 +528,7 @@ BrowserGlue.prototype = {
os.addObserver(this, "flash-plugin-hang");
os.addObserver(this, "xpi-signature-changed");
os.addObserver(this, "sync-ui-state:update");
os.addObserver(this, "handlersvc-store-initialized");
this._flashHangCount = 0;
this._firstWindowReady = new Promise(resolve => this._firstWindowLoaded = resolve);
@ -857,20 +868,12 @@ BrowserGlue.prototype = {
// the first browser window has finished initializing
_onFirstWindowLoaded: function BG__onFirstWindowLoaded(aWindow) {
// Initialize PdfJs when running in-process and remote. This only
// happens once since PdfJs registers global hooks. If the PdfJs
// extension is installed the init method below will be overridden
// leaving initialization to the extension.
// parent only: configure default prefs, set up pref observers, register
// pdf content handler, and initializes parent side message manager
// shim for privileged api access.
PdfJs.init(true);
// child only: similar to the call above for parent - register content
// handler and init message manager child shim for privileged api access.
// With older versions of the extension installed, this load will fail
// passively.
// Set up listeners and, if PdfJs is enabled, register the PDF stream converter.
// We delay all of the parent's initialization other than stream converter
// registration, because it requires file IO from nsHandlerService-json.js
Services.ppmm.loadProcessScript("resource://pdf.js/pdfjschildbootstrap.js", true);
if (PdfJs.enabled) {
PdfJs.ensureRegistered();
Services.ppmm.loadProcessScript("resource://pdf.js/pdfjschildbootstrap-enabled.js", true);
}
@ -1151,6 +1154,12 @@ BrowserGlue.prototype = {
Services.tm.idleDispatchToMainThread(Services.startup.trackStartupCrashEnd);
}, STARTUP_CRASHES_END_DELAY_MS);
});
Services.tm.idleDispatchToMainThread(() => {
let handlerService = Cc["@mozilla.org/uriloader/handler-service;1"].
getService(Ci.nsIHandlerService);
handlerService.asyncInit();
});
},
/**

View File

@ -30,6 +30,10 @@ const PREF_PREVIOUS_ACTION = PREF_PREFIX + ".previousHandler.preferredAction";
const PREF_PREVIOUS_ASK = PREF_PREFIX +
".previousHandler.alwaysAskBeforeHandling";
const PREF_DISABLED_PLUGIN_TYPES = "plugin.disable_full_page_plugin_for_types";
const PREF_ENABLED_CACHE_STATE = PREF_PREFIX + ".enabledCache.state";
const PREF_ENABLED_CACHE_INITIALIZED = PREF_PREFIX +
".enabledCache.initialized";
const PREF_APP_UPDATE_POSTUPDATE = "app.update.postupdate";
const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged";
const TOPIC_PLUGINS_LIST_UPDATED = "plugins-list-updated";
const TOPIC_PLUGIN_INFO_UPDATED = "plugin-info-updated";
@ -190,7 +194,7 @@ var PdfJs = {
},
updateRegistration: function updateRegistration() {
if (this.enabled) {
if (this.checkEnabled()) {
this.ensureRegistered();
} else {
this.ensureUnregistered();
@ -269,26 +273,7 @@ var PdfJs = {
false);
},
// nsIObserver
observe: function observe(aSubject, aTopic, aData) {
if (Services.appinfo.processType !==
Services.appinfo.PROCESS_TYPE_DEFAULT) {
throw new Error("Only the parent process should be observing PDF " +
"handler changes.");
}
this.updateRegistration();
let jsm = "resource://pdf.js/PdfjsChromeUtils.jsm";
let PdfjsChromeUtils = Components.utils.import(jsm, {}).PdfjsChromeUtils;
PdfjsChromeUtils.notifyChildOfSettingsChange(this.enabled);
},
/**
* pdf.js is only enabled if it is both selected as the pdf viewer and if the
* global switch enabling it is true.
* @return {boolean} Whether or not it's enabled.
*/
get enabled() {
_isEnabled: function _isEnabled() {
var disabled = getBoolPref(PREF_DISABLED, true);
if (disabled) {
return false;
@ -328,6 +313,47 @@ var PdfJs = {
return !enabledPluginFound;
},
checkEnabled: function checkEnabled() {
let isEnabled = this._isEnabled();
// This will be updated any time we observe a dependency changing, since
// updateRegistration internally calls enabled.
Services.prefs.setBoolPref(PREF_ENABLED_CACHE_STATE, isEnabled);
return isEnabled;
},
// nsIObserver
observe: function observe(aSubject, aTopic, aData) {
if (Services.appinfo.processType !==
Services.appinfo.PROCESS_TYPE_DEFAULT) {
throw new Error("Only the parent process should be observing PDF " +
"handler changes.");
}
this.updateRegistration();
let jsm = "resource://pdf.js/PdfjsChromeUtils.jsm";
let PdfjsChromeUtils = Components.utils.import(jsm, {}).PdfjsChromeUtils;
PdfjsChromeUtils.notifyChildOfSettingsChange(this.enabled);
},
/**
* pdf.js is only enabled if it is both selected as the pdf viewer and if the
* global switch enabling it is true.
* @return {boolean} Whether or not it's enabled.
*/
get enabled() {
if (!Services.prefs.getBoolPref(PREF_ENABLED_CACHE_INITIALIZED, false)) {
// If we just updated, and the cache hasn't been initialized, then we
// can't assume a default state, and need to synchronously initialize
// PdfJs
if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_POSTUPDATE)) {
this.checkEnabled();
}
Services.prefs.setBoolPref(PREF_ENABLED_CACHE_INITIALIZED, true);
}
return Services.prefs.getBoolPref(PREF_ENABLED_CACHE_STATE, true);
},
ensureRegistered: function ensureRegistered() {
if (this._registered) {
return;
@ -350,4 +376,3 @@ var PdfJs = {
this._registered = false;
},
};

View File

@ -117,6 +117,11 @@ ContentHandlerService::~ContentHandlerService()
{
}
NS_IMETHODIMP ContentHandlerService::AsyncInit()
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP ContentHandlerService::Enumerate(nsISimpleEnumerator * *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;

View File

@ -44,14 +44,27 @@ HandlerService.prototype = {
path: OS.Path.join(OS.Constants.Path.profileDir, "handlers.json"),
dataPostProcessor: this._dataPostProcessor.bind(this),
});
}
// Always call this even if this.__store was set, since it may have been
// set by asyncInit, which might not have completed yet.
this._ensureStoreInitialized();
return this.__store;
},
__storeInitialized: false,
_ensureStoreInitialized() {
if (!this.__storeInitialized) {
this.__storeInitialized = true;
this.__store.ensureDataReady();
// We have to inject new default protocol handlers only if we haven't
// already done this when migrating data from the RDF back-end.
let alreadyInjected = this._migrateFromRDFIfNeeded();
this._injectDefaultProtocolHandlersIfNeeded(alreadyInjected);
Services.obs.notifyObservers(null, "handlersvc-store-initialized");
}
return this.__store;
},
_dataPostProcessor(data) {
@ -218,6 +231,7 @@ HandlerService.prototype = {
await this.__store.finalize();
}
this.__store = null;
this.__storeInitialized = false;
})().catch(Cu.reportError);
},
@ -232,6 +246,22 @@ HandlerService.prototype = {
});
},
// nsIHandlerService
asyncInit() {
if (!this.__store) {
this.__store = new JSONFile({
path: OS.Path.join(OS.Constants.Path.profileDir, "handlers.json"),
dataPostProcessor: this._dataPostProcessor.bind(this),
});
this.__store.load().then(() => {
// __store can be null if we called _onDBChange in the mean time.
if (this.__store) {
this._ensureStoreInitialized();
}
}).catch(Cu.reportError);
}
},
// nsIHandlerService
enumerate() {
let handlers = Cc["@mozilla.org/array;1"]

View File

@ -302,6 +302,9 @@ HandlerService.prototype = {
//**************************************************************************//
// nsIHandlerService
asyncInit: function HS_asyncInit() {
// noop
},
enumerate: function HS_enumerate() {
var handlers = Cc["@mozilla.org/array;1"].

View File

@ -10,6 +10,12 @@ interface nsISimpleEnumerator;
[scriptable, uuid(53f0ad17-ec62-46a1-adbc-efccc06babcd)]
interface nsIHandlerService : nsISupports
{
/**
* Asynchronously performs any IO that the nsIHandlerService needs to do
* before it can be of use.
*/
void asyncInit();
/**
* Retrieve a list of all handlers in the datastore. This list is not
* guaranteed to be in any particular order, and callers should not assume

View File

@ -32,6 +32,44 @@ add_task(async function test_store_keeps_unknown_properties() {
"preserved");
});
/**
* Runs the asyncInit method, ensuring that it successfully inits the store
* and calls the handlersvc-store-initialized topic.
*/
add_task(async function test_async_init() {
await deleteHandlerStore();
await copyTestDataToHandlerStore();
gHandlerService.asyncInit();
await TestUtils.topicObserved("handlersvc-store-initialized");
await assertAllHandlerInfosMatchTestData();
await unloadHandlerStore();
});
/**
* Races the asyncInit method against the sync init (implicit in enumerate),
* to ensure that the store will be synchronously initialized without any
* ill effects.
*/
add_task(async function test_race_async_init() {
await deleteHandlerStore();
await copyTestDataToHandlerStore();
let storeInitialized = false;
// Pass a callback to synchronously observe the topic, as a promise would
// resolve asynchronously
TestUtils.topicObserved("handlersvc-store-initialized", () => {
storeInitialized = true;
return true;
});
gHandlerService.asyncInit();
do_check_false(storeInitialized);
gHandlerService.enumerate();
do_check_true(storeInitialized);
await assertAllHandlerInfosMatchTestData();
await unloadHandlerStore();
});
/**
* Tests the migration from an existing RDF data source.
*/