Bug 1287658 - Migrate from "mimeTypes.rdf" to "handlers.json". r=mak

The default nsIHandlerService implementation is switched to the one that uses the JSON data store, and any previously configured handlers are imported from the RDF data store if the "gecko.handlerService.migrated" preference is not set.

MozReview-Commit-ID: 7o3JgAtR5Hx

--HG--
extra : rebase_source : a95c783ebb6bb4a7fdd8c34e2ec9ce0ac744b092
This commit is contained in:
Paolo Amadini 2017-04-15 12:16:59 +01:00
parent 744c16f40b
commit d5baf43d3d
7 changed files with 235 additions and 121 deletions

View File

@ -17,6 +17,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
XPCOMUtils.defineLazyServiceGetter(this, "gExternalProtocolService",
"@mozilla.org/uriloader/external-protocol-service;1",
"nsIExternalProtocolService");
XPCOMUtils.defineLazyServiceGetter(this, "gHandlerServiceRDF",
"@mozilla.org/uriloader/handler-service-rdf;1",
"nsIHandlerService");
XPCOMUtils.defineLazyServiceGetter(this, "gMIMEService",
"@mozilla.org/mime;1",
"nsIMIMEService");
@ -43,7 +46,11 @@ HandlerService.prototype = {
dataPostProcessor: this._dataPostProcessor.bind(this),
});
this.__store.ensureDataReady();
this._updateDB();
// 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);
}
return this.__store;
},
@ -56,7 +63,65 @@ HandlerService.prototype = {
};
},
_updateDB() {
/**
* Migrates data from the RDF back-end, returning true if this happened.
*/
_migrateFromRDFIfNeeded() {
try {
if (Services.prefs.getBoolPref("gecko.handlerService.migrated")) {
return false;
}
} catch (ex) {
// If the preference does not exist, we need to import.
}
try {
// Don't initialize the RDF back-end if the file does not exist, improving
// performance on first use for new profiles.
let rdfFile = FileUtils.getFile("ProfD", ["mimeTypes.rdf"]);
if (rdfFile.exists()) {
this._migrateFromRDF();
return true;
}
} catch (ex) {
Cu.reportError(ex);
} finally {
// Don't attempt to import again even if the operation failed.
Services.prefs.setBoolPref("gecko.handlerService.migrated", true);
}
return false;
},
_migrateFromRDF() {
// Initializing the RDF back-end has the side effect of triggering the
// injection of the default protocol handlers. If the version number is
// newer and this happens, then the "enumerate" call in the RDF back-end
// will re-enter the JSON back-end through the MIME service, but this is
// harmless. The injection will not be repeated in the JSON back-end, so we
// rely on the new handlers injected by the RDF back-end.
let handlerInfoEnumerator = gHandlerServiceRDF.enumerate();
while (handlerInfoEnumerator.hasMoreElements()) {
let handlerInfo = handlerInfoEnumerator.getNext()
.QueryInterface(Ci.nsIHandlerInfo);
try {
// If the import from RDF is repeated by flipping the preference, then
// handlerInfo might already include some data from the JSON back-end,
// but any duplication is removed by the "store" method.
gHandlerServiceRDF.fillHandlerInfo(handlerInfo, "");
this.store(handlerInfo);
} catch (ex) {
Cu.reportError(ex);
}
}
},
/**
* Injects new default protocol handlers if the version in the preferences is
* newer than the one in the data store. If we just imported data from the RDF
* back-end, we only need to update the version in the data store.
*/
_injectDefaultProtocolHandlersIfNeeded(alreadyInjected) {
try {
let locale = Services.locale.getAppLocaleAsLangTag();
let prefsDefaultHandlersVersion = Number(Services.prefs.getComplexValue(
@ -66,7 +131,9 @@ HandlerService.prototype = {
let defaultHandlersVersion =
this._store.data.defaultHandlersVersion[locale] || 0;
if (defaultHandlersVersion < prefsDefaultHandlersVersion) {
this._injectNewDefaults();
if (!alreadyInjected) {
this._injectDefaultProtocolHandlers();
}
this._store.data.defaultHandlersVersion[locale] =
prefsDefaultHandlersVersion;
}
@ -75,7 +142,7 @@ HandlerService.prototype = {
}
},
_injectNewDefaults() {
_injectDefaultProtocolHandlers() {
let schemesPrefBranch = Services.prefs.getBranch("gecko.handlerService.schemes.");
let schemePrefList = schemesPrefBranch.getChildList("");

View File

@ -1,2 +1,2 @@
component {220cc253-b60f-41f6-b9cf-fdcb325f970f} nsHandlerService-json.js
contract @mozilla.org/uriloader/handler-service-json;1 {220cc253-b60f-41f6-b9cf-fdcb325f970f} process=main
contract @mozilla.org/uriloader/handler-service;1 {220cc253-b60f-41f6-b9cf-fdcb325f970f} process=main

View File

@ -1,2 +1,2 @@
component {32314cc8-22f7-4f7f-a645-1a45453ba6a6} nsHandlerService.js
contract @mozilla.org/uriloader/handler-service;1 {32314cc8-22f7-4f7f-a645-1a45453ba6a6} process=main
contract @mozilla.org/uriloader/handler-service-rdf;1 {32314cc8-22f7-4f7f-a645-1a45453ba6a6} process=main

View File

@ -21,22 +21,91 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://testing-common/HandlerServiceTestUtils.jsm", this);
Cu.import("resource://testing-common/TestUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gHandlerServiceJSON",
"@mozilla.org/uriloader/handler-service;1",
"nsIHandlerService");
XPCOMUtils.defineLazyServiceGetter(this, "gHandlerServiceRDF",
"@mozilla.org/uriloader/handler-service-rdf;1",
"nsIHandlerService");
HandlerServiceTestUtils.Assert = Assert;
do_get_profile();
let jsonPath = OS.Path.join(OS.Constants.Path.profileDir, "handlers.json");
let rdfFile = FileUtils.getFile("ProfD", ["mimeTypes.rdf"]);
function deleteDatasourceFile() {
if (rdfFile.exists()) {
rdfFile.remove(false);
}
}
/**
* Unloads the nsIHandlerService data store, so the back-end file can be
* accessed or modified, and the new data will be loaded at the next access.
*/
let unloadHandlerStoreJSON = Task.async(function* () {
// If this function is called before the nsIHandlerService instance has been
// initialized for the first time, the observer below will not be registered.
// We have to force initialization to prevent the function from stalling.
gHandlerServiceJSON;
// Delete the existing datasource file, if any, so we start from scratch.
// We also do this after finishing the tests, so there shouldn't be an old
// file lying around, but just in case we delete it here as well.
deleteDatasourceFile();
do_register_cleanup(deleteDatasourceFile);
let promise = TestUtils.topicObserved("handlersvc-json-replace-complete");
Services.obs.notifyObservers(null, "handlersvc-json-replace", null);
yield promise;
});
let unloadHandlerStoreRDF = Task.async(function* () {
// If this function is called before the nsIHandlerService instance has been
// initialized for the first time, the observer below will not be registered.
// We have to force initialization to prevent the function from stalling.
gHandlerServiceRDF;
let promise = TestUtils.topicObserved("handlersvc-rdf-replace-complete");
Services.obs.notifyObservers(null, "handlersvc-rdf-replace", null);
yield promise;
});
/**
* Unloads the data store and deletes it.
*/
let deleteHandlerStoreJSON = Task.async(function* () {
yield unloadHandlerStoreJSON();
yield OS.File.remove(jsonPath, { ignoreAbsent: true });
});
let deleteHandlerStoreRDF = Task.async(function* () {
yield unloadHandlerStoreRDF();
yield OS.File.remove(rdfFile.path, { ignoreAbsent: true });
});
/**
* Unloads the data store and replaces it with the test data file.
*/
let copyTestDataToHandlerStoreJSON = Task.async(function* () {
yield unloadHandlerStoreJSON();
yield OS.File.copy(do_get_file("handlers.json").path, jsonPath);
});
let copyTestDataToHandlerStoreRDF = Task.async(function* () {
yield unloadHandlerStoreRDF();
let fileName = AppConstants.platform == "android" ? "mimeTypes-android.rdf"
: "mimeTypes.rdf";
yield OS.File.copy(do_get_file(fileName).path, rdfFile.path);
});
/**
* Ensures the JSON implementation doesn't migrate entries from the legacy RDF
* data source during the other tests. This is important for both back-ends,
* because the JSON implementation is the default one and is always invoked by
* the MIME service when building new nsIHandlerInfo objects.
*/
add_task(function* test_initialize() {
// We don't need to reset this preference when the tests end, because it's
// irrelevant for any other test in the tree.
Services.prefs.setBoolPref("gecko.handlerService.migrated", true);
});
/**
* Ensures the files are removed and the services unloaded when the tests end.
*/
do_register_cleanup(function* test_terminate() {
yield deleteHandlerStoreJSON();
yield deleteHandlerStoreRDF();
});

View File

@ -70,17 +70,11 @@ function run_test() {
//if (!executable.exists())
// executable.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o755);
var localHandler = {
name: "Local Handler",
executable: executable,
interfaces: [Ci.nsIHandlerApp, Ci.nsILocalHandlerApp, Ci.nsISupports],
QueryInterface: function(iid) {
if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
throw Cr.NS_ERROR_NO_INTERFACE;
return this;
}
};
var localHandler = Cc["@mozilla.org/uriloader/local-handler-app;1"].
createInstance(Ci.nsILocalHandlerApp);
localHandler.name = "Local Handler";
localHandler.executable = executable;
var webHandler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
createInstance(Ci.nsIWebHandlerApp);
webHandler.name = "Web Handler";
@ -429,30 +423,13 @@ function run_test() {
lolHandler.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
lolHandler.preferredApplicationHandler = localHandler;
lolHandler.alwaysAskBeforeHandling = false;
lolHandler.appendExtension("lolcat");
// store the handler
do_check_false(handlerSvc.exists(lolHandler));
handlerSvc.store(lolHandler);
do_check_true(handlerSvc.exists(lolHandler));
// Get a file:// string pointing to mimeTypes.rdf
var fileHandler = ioService.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler);
var rdfFileURI = fileHandler.getURLSpecFromFile(rdfFile);
// Assign a file extenstion to the handler. handlerSvc.store() doesn't
// actually store any file extensions added with setFileExtensions(), you
// have to wade into RDF muck to do so.
// Based on toolkit/mozapps/downloads/content/helperApps.js :: addExtension()
var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
var mimeSource = gRDF.GetUnicodeResource("urn:mimetype:application/lolcat");
var valueProperty = gRDF.GetUnicodeResource("http://home.netscape.com/NC-rdf#fileExtensions");
var mimeLiteral = gRDF.GetLiteral("lolcat");
var DS = gRDF.GetDataSourceBlocking(rdfFileURI);
DS.Assert(mimeSource, valueProperty, mimeLiteral, true);
// test now-existent extension
lolType = handlerSvc.getTypeFromExtension("lolcat");
do_check_eq(lolType, "application/lolcat");

View File

@ -5,42 +5,10 @@
* Tests the nsIHandlerService interface using the JSON backend.
*/
XPCOMUtils.defineLazyServiceGetter(this, "gHandlerService",
"@mozilla.org/uriloader/handler-service-json;1",
"nsIHandlerService");
/**
* Unloads the nsIHandlerService data store, so the back-end file can be
* accessed or modified, and the new data will be loaded at the next access.
*/
let unloadHandlerStore = Task.async(function* () {
// If this function is called before the nsIHandlerService instance has been
// initialized for the first time, the observer below will not be registered.
// We have to force initialization to prevent the function from stalling.
gHandlerService;
let promise = TestUtils.topicObserved("handlersvc-json-replace-complete");
Services.obs.notifyObservers(null, "handlersvc-json-replace");
yield promise;
});
/**
* Unloads the data store and deletes it.
*/
let deleteHandlerStore = Task.async(function* () {
yield unloadHandlerStore();
yield OS.File.remove(jsonPath, { ignoreAbsent: true });
});
/**
* Unloads the data store and replaces it with the test data file.
*/
let copyTestDataToHandlerStore = Task.async(function* () {
yield unloadHandlerStore();
yield OS.File.copy(do_get_file("handlers.json").path, jsonPath);
});
let gHandlerService = gHandlerServiceJSON;
let unloadHandlerStore = unloadHandlerStoreJSON;
let deleteHandlerStore = deleteHandlerStoreJSON;
let copyTestDataToHandlerStore = copyTestDataToHandlerStoreJSON;
var scriptFile = do_get_file("common_test_handlerService.js");
Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec);
@ -63,3 +31,70 @@ add_task(function* test_store_keeps_unknown_properties() {
do_check_eq(data.mimeTypes["example/type.handleinternally"].unknownProperty,
"preserved");
});
/**
* Tests the migration from an existing RDF data source.
*/
add_task(function* test_migration_rdf_present() {
// Perform the most common migration, with the JSON file missing.
yield deleteHandlerStore();
yield copyTestDataToHandlerStoreRDF();
Services.prefs.setBoolPref("gecko.handlerService.migrated", false);
yield assertAllHandlerInfosMatchTestData();
do_check_true(Services.prefs.getBoolPref("gecko.handlerService.migrated"));
// Repeat the migration with the JSON file present.
yield unloadHandlerStore();
yield unloadHandlerStoreRDF();
Services.prefs.setBoolPref("gecko.handlerService.migrated", false);
yield assertAllHandlerInfosMatchTestData();
do_check_true(Services.prefs.getBoolPref("gecko.handlerService.migrated"));
});
/**
* Tests that new entries are preserved if migration is triggered manually.
*/
add_task(function* test_migration_rdf_present_keeps_new_data() {
yield deleteHandlerStore();
let handlerInfo = getKnownHandlerInfo("example/new");
gHandlerService.store(handlerInfo);
// Perform the migration with the JSON file present.
yield unloadHandlerStore();
yield copyTestDataToHandlerStoreRDF();
Services.prefs.setBoolPref("gecko.handlerService.migrated", false);
let actualHandlerInfo = HandlerServiceTestUtils.getHandlerInfo("example/new");
HandlerServiceTestUtils.assertHandlerInfoMatches(actualHandlerInfo, {
type: "example/new",
preferredAction: Ci.nsIHandlerInfo.saveToDisk,
alwaysAskBeforeHandling: false,
});
do_check_true(Services.prefs.getBoolPref("gecko.handlerService.migrated"));
});
/**
* Tests the injection of default protocol handlers when the RDF does not exist.
*/
add_task(function* test_migration_rdf_absent() {
if (!Services.prefs.getPrefType("gecko.handlerService.defaultHandlersVersion")) {
do_print("This platform or locale does not have default handlers.");
return;
}
// Perform the most common migration, with the JSON file missing.
yield deleteHandlerStore();
yield deleteHandlerStoreRDF();
Services.prefs.setBoolPref("gecko.handlerService.migrated", false);
yield assertAllHandlerInfosMatchDefaultHandlers();
do_check_true(Services.prefs.getBoolPref("gecko.handlerService.migrated"));
// Repeat the migration with the JSON file present.
yield unloadHandlerStore();
yield unloadHandlerStoreRDF();
Services.prefs.setBoolPref("gecko.handlerService.migrated", false);
yield assertAllHandlerInfosMatchDefaultHandlers();
do_check_true(Services.prefs.getBoolPref("gecko.handlerService.migrated"));
});

View File

@ -2,47 +2,13 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Tests the nsIHandlerService interface using the JSON backend.
* Tests the nsIHandlerService interface using the RDF backend.
*/
XPCOMUtils.defineLazyServiceGetter(this, "gHandlerService",
"@mozilla.org/uriloader/handler-service;1",
"nsIHandlerService");
/**
* Unloads the nsIHandlerService data store, so the back-end file can be
* accessed or modified, and the new data will be loaded at the next access.
*/
let unloadHandlerStore = Task.async(function* () {
// If this function is called before the nsIHandlerService instance has been
// initialized for the first time, the observer below will not be registered.
// We have to force initialization to prevent the function from stalling.
gHandlerService;
let promise = TestUtils.topicObserved("handlersvc-rdf-replace-complete");
Services.obs.notifyObservers(null, "handlersvc-rdf-replace");
yield promise;
});
/**
* Unloads the data store and deletes it.
*/
let deleteHandlerStore = Task.async(function* () {
yield unloadHandlerStore();
yield OS.File.remove(rdfFile.path, { ignoreAbsent: true });
});
/**
* Unloads the data store and replaces it with the test data file.
*/
let copyTestDataToHandlerStore = Task.async(function* () {
yield unloadHandlerStore();
let fileName = AppConstants.platform == "android" ? "mimeTypes-android.rdf"
: "mimeTypes.rdf";
yield OS.File.copy(do_get_file(fileName).path, rdfFile.path);
});
let gHandlerService = gHandlerServiceRDF;
let unloadHandlerStore = unloadHandlerStoreRDF;
let deleteHandlerStore = deleteHandlerStoreRDF;
let copyTestDataToHandlerStore = copyTestDataToHandlerStoreRDF;
var scriptFile = do_get_file("common_test_handlerService.js");
Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec);