Bug 1355585 - Streamline the format of "handlers.json", align the implementation, and reorganize tests. r=mak

This patch significantly improves the test coverage for both the JSON and RDF back-ends. There is a clearer separation between tests using predefined data and tests for the injection of default handlers. The predefined data includes more significant property combinations, and the JSON is now formatted. Helper functions are renamed for clarity.

Functions like "exists" that have different paths for MIME types and protocols are now tested with both, while behaviors that have a single path are now only tested with MIME types for efficiency.

The file format is redesigned to be more compact, and all the data is normalized when saving instead of when loading. Duplicates are now handled correctly when saving.

MozReview-Commit-ID: JI4I1M0N3lq

--HG--
extra : rebase_source : 06920d9be56830f81e3e01cbc97a02983f50c264
extra : source : 3c4b8028ac594ee24760feb96e93bfdab9704e98
This commit is contained in:
Paolo Amadini 2017-04-24 11:29:46 +01:00
parent d457a3002d
commit 2723e3cda4
10 changed files with 996 additions and 736 deletions

View File

@ -39,8 +39,7 @@ HandlerService.prototype = {
get _store() {
if (!this.__store) {
this.__store = new JSONFile({
path: OS.Path.join(OS.Constants.Path.profileDir,
"handlers.json"),
path: OS.Path.join(OS.Constants.Path.profileDir, "handlers.json"),
dataPostProcessor: this._dataPostProcessor.bind(this),
});
this.__store.ensureDataReady();
@ -50,21 +49,26 @@ HandlerService.prototype = {
},
_dataPostProcessor(data) {
return data.schemes ? data : { version: {}, mimetypes: {}, schemes: {} };
return data.defaultHandlersVersion ? data : {
defaultHandlersVersion: {},
mimeTypes: {},
schemes: {},
};
},
_updateDB() {
try {
let locale = Services.locale.getAppLocaleAsLangTag();
let prefsDefaultHandlersVersion = Number(Services.prefs.getComplexValue(
"gecko.handlerService.defaultHandlersVersion",
Ci.nsIPrefLocalizedString).data);
let defaultHandlersVersion = this._store.data.version[locale] || 0;
if (defaultHandlersVersion < prefsDefaultHandlersVersion ) {
let defaultHandlersVersion =
this._store.data.defaultHandlersVersion[locale] || 0;
if (defaultHandlersVersion < prefsDefaultHandlersVersion) {
this._injectNewDefaults();
this._store.data.version[locale] = prefsDefaultHandlersVersion;
this._store.data.defaultHandlersVersion[locale] =
prefsDefaultHandlersVersion;
}
} catch (ex) {
Cu.reportError(ex);
@ -121,26 +125,15 @@ HandlerService.prototype = {
for (let handlerNumber of Object.keys(schemes[scheme])) {
let handlerApp = this.handlerAppFromSerializable(schemes[scheme][handlerNumber]);
if (!this._isInHandlerArray(possibleHandlers, handlerApp)) {
possibleHandlers.appendElement(handlerApp);
}
// If there is already a handler registered with the same template
// URL, the newly added one will be ignored when saving.
possibleHandlers.appendElement(handlerApp, false);
}
this.store(protoInfo);
}
},
_isInHandlerArray(array, handler) {
let enumerator = array.enumerate();
while (enumerator.hasMoreElements()) {
let handlerApp = enumerator.getNext().QueryInterface(Ci.nsIHandlerApp);
if (handlerApp.equals(handler)) {
return true;
}
}
return false;
},
_onDBChange() {
return Task.spawn(function* () {
if (this.__store) {
@ -163,9 +156,9 @@ HandlerService.prototype = {
// nsIHandlerService
enumerate() {
let handlers = Cc["@mozilla.org/array;1"].
createInstance(Ci.nsIMutableArray);
for (let type of Object.keys(this._store.data.mimetypes)) {
let handlers = Cc["@mozilla.org/array;1"]
.createInstance(Ci.nsIMutableArray);
for (let type of Object.keys(this._store.data.mimeTypes)) {
let handler = gMIMEService.getFromTypeAndExtension(type, null);
handlers.appendElement(handler);
}
@ -178,46 +171,76 @@ HandlerService.prototype = {
// nsIHandlerService
store(handlerInfo) {
let handlerObj = {
action: handlerInfo.preferredAction,
askBeforeHandling: handlerInfo.alwaysAskBeforeHandling,
};
let handlerList = this._getHandlerListByHandlerInfoType(handlerInfo);
let preferredHandler = handlerInfo.preferredApplicationHandler;
if (preferredHandler) {
let serializable = this.handlerAppToSerializable(preferredHandler);
if (serializable) {
handlerObj.preferredHandler = serializable;
// Retrieve an existing entry if present, instead of creating a new one, so
// that we preserve unknown properties for forward compatibility.
let storedHandlerInfo = handlerList[handlerInfo.type];
if (!storedHandlerInfo) {
storedHandlerInfo = {};
handlerList[handlerInfo.type] = storedHandlerInfo;
}
// Only a limited number of preferredAction values is allowed.
if (handlerInfo.preferredAction == Ci.nsIHandlerInfo.saveToDisk ||
handlerInfo.preferredAction == Ci.nsIHandlerInfo.useSystemDefault ||
handlerInfo.preferredAction == Ci.nsIHandlerInfo.handleInternally) {
storedHandlerInfo.action = handlerInfo.preferredAction;
} else {
storedHandlerInfo.action = Ci.nsIHandlerInfo.useHelperApp;
}
if (handlerInfo.alwaysAskBeforeHandling) {
storedHandlerInfo.ask = true;
} else {
delete storedHandlerInfo.ask;
}
// Build a list of unique nsIHandlerInfo instances to process later.
let handlers = [];
if (handlerInfo.preferredApplicationHandler) {
handlers.push(handlerInfo.preferredApplicationHandler);
}
let enumerator = handlerInfo.possibleApplicationHandlers.enumerate();
while (enumerator.hasMoreElements()) {
let handler = enumerator.getNext().QueryInterface(Ci.nsIHandlerApp);
// If the caller stored duplicate handlers, we save them only once.
if (!handlers.some(h => h.equals(handler))) {
handlers.push(handler);
}
}
let apps = handlerInfo.possibleApplicationHandlers.enumerate();
let possibleHandlers = [];
while (apps.hasMoreElements()) {
let handler = apps.getNext().QueryInterface(Ci.nsIHandlerApp);
let serializable = this.handlerAppToSerializable(handler);
if (serializable) {
possibleHandlers.push(serializable);
// If any of the nsIHandlerInfo instances cannot be serialized, it is not
// included in the final list. The first element is always the preferred
// handler, or null if there is none.
let serializableHandlers =
handlers.map(h => this.handlerAppToSerializable(h)).filter(h => h);
if (serializableHandlers.length) {
if (!handlerInfo.preferredApplicationHandler) {
serializableHandlers.unshift(null);
}
}
if (possibleHandlers.length) {
handlerObj.possibleHandlers = possibleHandlers;
storedHandlerInfo.handlers = serializableHandlers;
} else {
delete storedHandlerInfo.handlers;
}
if (this._isMIMEInfo(handlerInfo)) {
let extEnumerator = handlerInfo.getFileExtensions();
let extensions = [];
let extensions = storedHandlerInfo.extensions || [];
while (extEnumerator.hasMore()) {
let extension = extEnumerator.getNext();
let extension = extEnumerator.getNext().toLowerCase();
// If the caller stored duplicate extensions, we save them only once.
if (!extensions.includes(extension)) {
extensions.push(extension);
}
}
if (extensions.length) {
handlerObj.fileExtensions = extensions;
storedHandlerInfo.extensions = extensions;
} else {
delete storedHandlerInfo.extensions;
}
}
this._getHandlerListByHandlerInfoType(handlerInfo)[handlerInfo.type] = handlerObj;
this._store.saveSoon();
},
@ -230,44 +253,28 @@ HandlerService.prototype = {
Cr.NS_ERROR_NOT_AVAILABLE);
}
// logic from _retrievePreferredAction of nsHandlerService.js
if (storedHandlerInfo.action == Ci.nsIHandlerInfo.saveToDisk ||
storedHandlerInfo.action == Ci.nsIHandlerInfo.useSystemDefault ||
storedHandlerInfo.action == Ci.nsIHandlerInfo.handleInternally) {
handlerInfo.preferredAction = storedHandlerInfo.action;
} else {
handlerInfo.preferredAction = Ci.nsIHandlerInfo.useHelperApp;
}
handlerInfo.alwaysAskBeforeHandling = !!storedHandlerInfo.ask;
let preferHandler = null;
if (storedHandlerInfo.preferredHandler) {
preferHandler = this.handlerAppFromSerializable(storedHandlerInfo.preferredHandler);
}
handlerInfo.preferredApplicationHandler = preferHandler;
if (preferHandler) {
handlerInfo.possibleApplicationHandlers.appendElement(preferHandler);
}
if (storedHandlerInfo.possibleHandlers) {
for (let handler of storedHandlerInfo.possibleHandlers) {
let possibleHandler = this.handlerAppFromSerializable(handler);
if (possibleHandler && (!preferHandler ||
!possibleHandler.equals(preferHandler))) {
handlerInfo.possibleApplicationHandlers.appendElement(possibleHandler);
// If the first item is not null, it is also the preferred handler. Since
// we cannot modify the stored array, use a boolean to keep track of this.
let isFirstItem = true;
for (let handler of storedHandlerInfo.handlers || [null]) {
let handlerApp = this.handlerAppFromSerializable(handler || {});
if (isFirstItem) {
isFirstItem = false;
handlerInfo.preferredApplicationHandler = handlerApp;
}
if (handlerApp) {
handlerInfo.possibleApplicationHandlers.appendElement(handlerApp);
}
}
// We always store "askBeforeHandling" in the JSON file. Just use this value.
handlerInfo.alwaysAskBeforeHandling = storedHandlerInfo.askBeforeHandling;
if (this._isMIMEInfo(handlerInfo)) {
if (storedHandlerInfo.fileExtensions) {
for (let extension of storedHandlerInfo.fileExtensions) {
if (this._isMIMEInfo(handlerInfo) && storedHandlerInfo.extensions) {
for (let extension of storedHandlerInfo.extensions) {
handlerInfo.appendExtension(extension);
}
}
}
},
/**
@ -313,19 +320,19 @@ HandlerService.prototype = {
if (!file.exists()) {
return null;
}
handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
createInstance(Ci.nsILocalHandlerApp);
handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]
.createInstance(Ci.nsILocalHandlerApp);
handlerApp.executable = file;
} catch (ex) {
return null;
}
} else if ("uriTemplate" in handlerObj) {
handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"].
createInstance(Ci.nsIWebHandlerApp);
handlerApp = Cc["@mozilla.org/uriloader/web-handler-app;1"]
.createInstance(Ci.nsIWebHandlerApp);
handlerApp.uriTemplate = handlerObj.uriTemplate;
} else if ("service" in handlerObj) {
handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"].
createInstance(Ci.nsIDBusHandlerApp);
handlerApp = Cc["@mozilla.org/uriloader/dbus-handler-app;1"]
.createInstance(Ci.nsIDBusHandlerApp);
handlerApp.service = handlerObj.service;
handlerApp.method = handlerObj.method;
handlerApp.objectPath = handlerObj.objectPath;
@ -339,11 +346,11 @@ HandlerService.prototype = {
},
/**
* The function return a reference to the "mimetypes" or "schemes" object
* The function returns a reference to the "mimeTypes" or "schemes" object
* based on which type of handlerInfo is provided.
*/
_getHandlerListByHandlerInfoType(handlerInfo) {
return this._isMIMEInfo(handlerInfo) ? this._store.data.mimetypes
return this._isMIMEInfo(handlerInfo) ? this._store.data.mimeTypes
: this._store.data.schemes;
},
@ -372,16 +379,15 @@ HandlerService.prototype = {
// nsIHandlerService
getTypeFromExtension(fileExtension) {
let extension = fileExtension.toLowerCase();
let mimeTypes = this._store.data.mimetypes;
let mimeTypes = this._store.data.mimeTypes;
for (let type of Object.keys(mimeTypes)) {
if (mimeTypes[type].fileExtensions &&
mimeTypes[type].fileExtensions.includes(extension)) {
if (mimeTypes[type].extensions &&
mimeTypes[type].extensions.includes(extension)) {
return type;
}
}
return "";
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HandlerService]);

View File

@ -709,12 +709,7 @@ HandlerService.prototype = {
var handler = aHandlerInfo.preferredApplicationHandler;
if (handler) {
// If the handlerApp is an unknown type, ignore it.
// Android default application handler is the case.
if (this._handlerAppIsUnknownType(handler)) {
return;
}
if (handler && !this._handlerAppIsUnknownType(handler)) {
this._storeHandlerApp(handlerID, handler);
// Make this app be the preferred app for the handler info.
@ -728,8 +723,9 @@ HandlerService.prototype = {
this._setResource(infoID, NC_PREFERRED_APP, handlerID);
}
else {
// There isn't a preferred handler. Remove the existing record for it,
// if any.
// There isn't a preferred handler or the handler cannot be serialized,
// for example the Android default application handler. Remove the
// existing record for it, if any.
this._removeTarget(infoID, NC_PREFERRED_APP);
this._removeAssertions(handlerID);
}

View File

@ -95,6 +95,14 @@ this.HandlerServiceTestUtils = {
// that may have been imported from the default nsIHandlerService instance
// and is not overwritten by fillHandlerInfo later.
let handlerInfo = gMIMEService.getFromTypeAndExtension(type, null);
if (AppConstants.platform == "android") {
// On Android, the first handler application is always the internal one.
while (handlerInfo.possibleApplicationHandlers.length > 1) {
handlerInfo.possibleApplicationHandlers.removeElementAt(1);
}
} else {
handlerInfo.possibleApplicationHandlers.clear();
}
handlerInfo.setFileExtensions("");
// Populate the object from the handler service instance under testing.
if (this.handlerService.exists(handlerInfo)) {
@ -173,9 +181,12 @@ this.HandlerServiceTestUtils = {
: Ci.nsIHandlerInfo;
Assert.ok(handlerInfo instanceof expectedInterface);
Assert.equal(handlerInfo.type, expected.type);
if (!expected.preferredActionOSDependent) {
Assert.equal(handlerInfo.preferredAction, expected.preferredAction);
Assert.equal(handlerInfo.alwaysAskBeforeHandling,
expected.alwaysAskBeforeHandling);
}
if (expectedInterface == Ci.nsIMIMEInfo) {
let fileExtensionsEnumerator = handlerInfo.getFileExtensions();

File diff suppressed because it is too large Load Diff

View File

@ -1 +1,90 @@
{"version":{"en-US":999},"mimetypes":{"nonexistent/type":{"action":3,"askBeforeHandling":false,"fileExtensions":["pdf"]}},"schemes":{"webcal":{"action":1,"askBeforeHandling":true,"preferredHandler":{"name":"30 Boxes","uriTemplate":"http://30boxes.com/external/widget?refer=ff&url=%s"},"possibleHandlers":[{"name":"30 Boxes","uriTemplate":"https://30boxes.com/external/widget?refer=ff&url=%s"}]},"ircs":{"action":1,"askBeforeHandling":true,"fileExtensions":[],"possibleHandlers":[{"name":"Mibbit","uriTemplate":"https://www.mibbit.com/?url=%s"}]},"mailto":{"action":4,"askBeforeHandling":false,"possibleHandlers":[{"name":"Yahoo! Mail","uriTemplate":"https://compose.mail.yahoo.com/?To=%s"},{"name":"Gmail","uriTemplate":"https://mail.google.com/mail/?extsrc=mailto&url=%s"}]},"irc":{"action":1,"askBeforeHandling":true,"possibleHandlers":[{"name":"Mibbit","uriTemplate":"https://www.mibbit.com/?url=%s"}]}}}
{
"defaultHandlersVersion": {
"en-US": 999
},
"mimeTypes": {
"example/type.handleinternally": {
"unknownProperty": "preserved",
"action": 3,
"extensions": [
"example_one"
]
},
"example/type.savetodisk": {
"action": 0,
"ask": true,
"handlers": [
{
"name": "Example Default Handler",
"uriTemplate": "https://www.example.com/?url=%s"
}
],
"extensions": [
"example_two",
"example_three"
]
},
"example/type.usehelperapp": {
"action": 2,
"ask": true,
"handlers": [
{
"name": "Example Default Handler",
"uriTemplate": "https://www.example.com/?url=%s"
},
{
"name": "Example Possible Handler One",
"uriTemplate": "http://www.example.com/?id=1&url=%s"
},
{
"name": "Example Possible Handler Two",
"uriTemplate": "http://www.example.com/?id=2&url=%s"
}
],
"extensions": [
"example_two",
"example_three"
]
},
"example/type.usesystemdefault": {
"action": 4,
"handlers": [
null,
{
"name": "Example Possible Handler",
"uriTemplate": "http://www.example.com/?url=%s"
}
]
}
},
"schemes": {
"examplescheme.usehelperapp": {
"action": 2,
"ask": true,
"handlers": [
{
"name": "Example Default Handler",
"uriTemplate": "https://www.example.com/?url=%s"
},
{
"name": "Example Possible Handler One",
"uriTemplate": "http://www.example.com/?id=1&url=%s"
},
{
"name": "Example Possible Handler Two",
"uriTemplate": "http://www.example.com/?id=2&url=%s"
}
]
},
"examplescheme.usesystemdefault": {
"action": 4,
"handlers": [
null,
{
"name": "Example Possible Handler",
"uriTemplate": "http://www.example.com/?url=%s"
}
]
}
}
}

View File

@ -11,6 +11,7 @@
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@ -26,8 +27,7 @@ do_get_profile();
let jsonPath = OS.Path.join(OS.Constants.Path.profileDir, "handlers.json");
let rdfFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
rdfFile.append("mimeTypes.rdf")
let rdfFile = FileUtils.getFile("ProfD", ["mimeTypes.rdf"]);
function deleteDatasourceFile() {
if (rdfFile.exists()) {

View File

@ -1,78 +1,100 @@
<?xml version="1.0"?>
<RDF:RDF xmlns:NC="http://home.netscape.com/NC-rdf#"
xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<RDF:Description RDF:about="urn:mimetype:handler:webcal"
NC:alwaysAsk="true">
<NC:externalApplication RDF:resource="urn:mimetype:externalApplication:webcal"/>
<NC:possibleApplication RDF:resource="urn:handler:web:http://30boxes.com/external/widget?refer=ff&amp;url=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:https://30boxes.com/external/widget?refer=ff&amp;url=%s"/>
</RDF:Description>
<RDF:Description RDF:about="urn:mimetype:nonexistent/type"
NC:value="nonexistent/type"
NC:fileExtensions="pdf">
<NC:handlerProp RDF:resource="urn:mimetype:handler:nonexistent/type"/>
</RDF:Description>
<RDF:Description RDF:about="urn:mimetype:ircs"
NC:value="ircs">
<NC:handlerProp RDF:resource="urn:mimetype:handler:ircs"/>
</RDF:Description>
<RDF:Description RDF:about="urn:handler:web:https://compose.mail.yahoo.com/?To=%s"
NC:prettyName="Yahoo! Mail"
NC:uriTemplate="https://compose.mail.yahoo.com/?To=%s" />
<RDF:Description RDF:about="urn:handler:web:https://30boxes.com/external/widget?refer=ff&amp;url=%s"
NC:prettyName="30 Boxes"
NC:uriTemplate="https://30boxes.com/external/widget?refer=ff&amp;url=%s" />
<RDF:Seq RDF:about="urn:mimetypes:root">
<RDF:li RDF:resource="urn:mimetype:irc"/>
<RDF:li RDF:resource="urn:mimetype:ircs"/>
<RDF:li RDF:resource="urn:mimetype:mailto"/>
<RDF:li RDF:resource="urn:mimetype:nonexistent/type"/>
<RDF:li RDF:resource="urn:mimetype:webcal"/>
<RDF:li RDF:resource="urn:mimetype:example/type.handleinternally"/>
<RDF:li RDF:resource="urn:mimetype:example/type.savetodisk"/>
<RDF:li RDF:resource="urn:mimetype:example/type.usehelperapp"/>
<RDF:li RDF:resource="urn:mimetype:example/type.usesystemdefault"/>
<RDF:li RDF:resource="urn:mimetype:examplescheme.usehelperapp"/>
<RDF:li RDF:resource="urn:mimetype:examplescheme.usesystemdefault"/>
</RDF:Seq>
<RDF:Description RDF:about="urn:mimetype:handler:nonexistent/type"
<RDF:Description RDF:about="urn:mimetype:example/type.usehelperapp"
NC:value="example/type.usehelperapp">
<NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.usehelperapp"/>
<NC:fileExtensions>example_two</NC:fileExtensions>
<NC:fileExtensions>example_three</NC:fileExtensions>
</RDF:Description>
<RDF:Description RDF:about="urn:handler:web:http://www.example.com/?id=2&amp;url=%s"
NC:prettyName="Example Possible Handler Two"
NC:uriTemplate="http://www.example.com/?id=2&amp;url=%s" />
<RDF:Description RDF:about="urn:mimetype:handler:example/type.handleinternally"
NC:handleInternal="true"
NC:alwaysAsk="false" />
<RDF:Description RDF:about="urn:handler:web:https://www.mibbit.com/?url=%s"
NC:prettyName="Mibbit"
NC:uriTemplate="https://www.mibbit.com/?url=%s" />
<RDF:Description RDF:about="urn:mimetype:externalApplication:webcal"
NC:prettyName="30 Boxes"
NC:uriTemplate="http://30boxes.com/external/widget?refer=ff&amp;url=%s" />
<RDF:Description RDF:about="urn:mimetype:mailto"
NC:value="mailto">
<NC:handlerProp RDF:resource="urn:mimetype:handler:mailto"/>
<RDF:Description RDF:about="urn:mimetype:examplescheme.usesystemdefault"
NC:value="examplescheme.usesystemdefault">
<NC:handlerProp RDF:resource="urn:mimetype:handler:examplescheme.usesystemdefault"/>
</RDF:Description>
<RDF:Description RDF:about="urn:mimetype:handler:mailto"
<RDF:Description RDF:about="urn:mimetype:externalApplication:example/type.usehelperapp"
NC:prettyName="Example Default Handler"
NC:uriTemplate="https://www.example.com/?url=%s" />
<RDF:Description RDF:about="urn:mimetype:externalApplication:example/type.savetodisk"
NC:prettyName="Example Default Handler"
NC:uriTemplate="https://www.example.com/?url=%s" />
<RDF:Description RDF:about="urn:mimetype:example/type.usesystemdefault"
NC:value="example/type.usesystemdefault">
<NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.usesystemdefault"/>
</RDF:Description>
<RDF:Description RDF:about="urn:mimetype:examplescheme.usehelperapp"
NC:value="examplescheme.usehelperapp">
<NC:handlerProp RDF:resource="urn:mimetype:handler:examplescheme.usehelperapp"/>
</RDF:Description>
<RDF:Description RDF:about="urn:handler:web:http://www.example.com/?url=%s"
NC:prettyName="Example Possible Handler"
NC:uriTemplate="http://www.example.com/?url=%s" />
<RDF:Description RDF:about="urn:mimetype:example/type.savetodisk"
NC:value="example/type.savetodisk">
<NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.savetodisk"/>
<NC:fileExtensions>example_two</NC:fileExtensions>
<NC:fileExtensions>example_three</NC:fileExtensions>
</RDF:Description>
<RDF:Description RDF:about="urn:mimetype:handler:examplescheme.usehelperapp"
NC:alwaysAsk="true">
<NC:externalApplication RDF:resource="urn:mimetype:externalApplication:examplescheme.usehelperapp"/>
<NC:possibleApplication RDF:resource="urn:handler:web:https://www.example.com/?url=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=1&amp;url=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=2&amp;url=%s"/>
</RDF:Description>
<RDF:Description RDF:about="urn:mimetype:example/type.handleinternally"
NC:value="example/type.handleinternally"
NC:fileExtensions="example_one">
<NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.handleinternally"/>
</RDF:Description>
<RDF:Description RDF:about="urn:handler:web:http://www.example.com/?id=1&amp;url=%s"
NC:prettyName="Example Possible Handler One"
NC:uriTemplate="http://www.example.com/?id=1&amp;url=%s" />
<RDF:Description RDF:about="urn:handler:web:https://www.example.com/?url=%s"
NC:prettyName="Example Default Handler"
NC:uriTemplate="https://www.example.com/?url=%s" />
<RDF:Description RDF:about="urn:mimetype:handler:examplescheme.usesystemdefault"
NC:useSystemDefault="true"
NC:alwaysAsk="false">
<NC:possibleApplication RDF:resource="urn:handler:web:https://compose.mail.yahoo.com/?To=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:https://mail.google.com/mail/?extsrc=mailto&amp;url=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?url=%s"/>
</RDF:Description>
<RDF:Description RDF:about="urn:mimetype:handler:ircs"
NC:alwaysAsk="true">
<NC:possibleApplication RDF:resource="urn:handler:web:https://www.mibbit.com/?url=%s"/>
<RDF:Description RDF:about="urn:mimetype:handler:example/type.usesystemdefault"
NC:useSystemDefault="true"
NC:alwaysAsk="false">
<NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?url=%s"/>
</RDF:Description>
<RDF:Description RDF:about="urn:handler:web:https://mail.google.com/mail/?extsrc=mailto&amp;url=%s"
NC:prettyName="Gmail"
NC:uriTemplate="https://mail.google.com/mail/?extsrc=mailto&amp;url=%s" />
<RDF:Description RDF:about="urn:mimetype:webcal"
NC:value="webcal">
<NC:handlerProp RDF:resource="urn:mimetype:handler:webcal"/>
</RDF:Description>
<RDF:Description RDF:about="urn:handler:web:http://30boxes.com/external/widget?refer=ff&amp;url=%s"
NC:prettyName="30 Boxes"
NC:uriTemplate="http://30boxes.com/external/widget?refer=ff&amp;url=%s" />
<RDF:Description RDF:about="urn:root"
NC:en-US_defaultHandlersVersion="999" />
<RDF:Description RDF:about="urn:mimetype:handler:irc"
<RDF:Description RDF:about="urn:mimetype:handler:example/type.usehelperapp"
NC:alwaysAsk="true">
<NC:possibleApplication RDF:resource="urn:handler:web:https://www.mibbit.com/?url=%s"/>
</RDF:Description>
<RDF:Description RDF:about="urn:mimetype:irc"
NC:value="irc">
<NC:handlerProp RDF:resource="urn:mimetype:handler:irc"/>
<NC:externalApplication RDF:resource="urn:mimetype:externalApplication:example/type.usehelperapp"/>
<NC:possibleApplication RDF:resource="urn:handler:web:https://www.example.com/?url=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=1&amp;url=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=2&amp;url=%s"/>
</RDF:Description>
<RDF:Description RDF:about="urn:mimetype:externalApplication:examplescheme.usehelperapp"
NC:prettyName="Example Default Handler"
NC:uriTemplate="https://www.example.com/?url=%s" />
<RDF:Description RDF:about="urn:mimetypes">
<NC:MIME-types RDF:resource="urn:mimetypes:root"/>
</RDF:Description>
<RDF:Description RDF:about="urn:mimetype:handler:example/type.savetodisk"
NC:saveToDisk="true"
NC:alwaysAsk="true">
<NC:externalApplication RDF:resource="urn:mimetype:externalApplication:example/type.savetodisk"/>
<NC:possibleApplication RDF:resource="urn:handler:web:https://www.example.com/?url=%s"/>
</RDF:Description>
</RDF:RDF>

View File

@ -1,83 +1,105 @@
<?xml version="1.0"?>
<RDF:RDF xmlns:NC="http://home.netscape.com/NC-rdf#"
xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<RDF:Description RDF:about="urn:schemes">
<NC:Protocol-Schemes RDF:resource="urn:schemes:root"/>
<RDF:Description RDF:about="urn:mimetype:externalApplication:example/type.usehelperapp"
NC:prettyName="Example Default Handler"
NC:uriTemplate="https://www.example.com/?url=%s" />
<RDF:Description RDF:about="urn:scheme:examplescheme.usesystemdefault"
NC:value="examplescheme.usesystemdefault">
<NC:handlerProp RDF:resource="urn:scheme:handler:examplescheme.usesystemdefault"/>
</RDF:Description>
<RDF:Seq RDF:about="urn:mimetypes:root">
<RDF:li RDF:resource="urn:mimetype:nonexistent/type"/>
</RDF:Seq>
<RDF:Description RDF:about="urn:mimetype:handler:nonexistent/type"
NC:handleInternal="true"
NC:alwaysAsk="false" />
<RDF:Description RDF:about="urn:scheme:irc"
NC:value="irc">
<NC:handlerProp RDF:resource="urn:scheme:handler:irc"/>
</RDF:Description>
<RDF:Description RDF:about="urn:scheme:externalApplication:webcal"
NC:prettyName="30 Boxes"
NC:uriTemplate="http://30boxes.com/external/widget?refer=ff&amp;url=%s" />
<RDF:Description RDF:about="urn:scheme:handler:ircs"
<RDF:Description RDF:about="urn:scheme:handler:examplescheme.usehelperapp"
NC:alwaysAsk="true">
<NC:possibleApplication RDF:resource="urn:handler:web:https://www.mibbit.com/?url=%s"/>
<NC:externalApplication RDF:resource="urn:scheme:externalApplication:examplescheme.usehelperapp"/>
<NC:possibleApplication RDF:resource="urn:handler:web:https://www.example.com/?url=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=1&amp;url=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=2&amp;url=%s"/>
</RDF:Description>
<RDF:Description RDF:about="urn:scheme:handler:mailto"
<RDF:Description RDF:about="urn:handler:web:http://www.example.com/?url=%s"
NC:prettyName="Example Possible Handler"
NC:uriTemplate="http://www.example.com/?url=%s" />
<RDF:Description RDF:about="urn:scheme:handler:examplescheme.usesystemdefault"
NC:useSystemDefault="true"
NC:alwaysAsk="false">
<NC:possibleApplication RDF:resource="urn:handler:web:https://compose.mail.yahoo.com/?To=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:https://mail.google.com/mail/?extsrc=mailto&amp;url=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?url=%s"/>
</RDF:Description>
<RDF:Description RDF:about="urn:scheme:ircs"
NC:value="ircs">
<NC:handlerProp RDF:resource="urn:scheme:handler:ircs"/>
<RDF:Description RDF:about="urn:mimetype:example/type.savetodisk"
NC:value="example/type.savetodisk">
<NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.savetodisk"/>
<NC:fileExtensions>example_two</NC:fileExtensions>
<NC:fileExtensions>example_three</NC:fileExtensions>
</RDF:Description>
<RDF:Description RDF:about="urn:handler:web:https://www.mibbit.com/?url=%s"
NC:prettyName="Mibbit"
NC:uriTemplate="https://www.mibbit.com/?url=%s" />
<RDF:Description RDF:about="urn:mimetype:nonexistent/type"
NC:value="nonexistent/type"
NC:fileExtensions="pdf">
<NC:handlerProp RDF:resource="urn:mimetype:handler:nonexistent/type"/>
<RDF:Description RDF:about="urn:mimetype:example/type.usesystemdefault"
NC:value="example/type.usesystemdefault">
<NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.usesystemdefault"/>
</RDF:Description>
<RDF:Description RDF:about="urn:handler:web:https://compose.mail.yahoo.com/?To=%s"
NC:prettyName="Yahoo! Mail"
NC:uriTemplate="https://compose.mail.yahoo.com/?To=%s" />
<RDF:Description RDF:about="urn:handler:web:https://mail.google.com/mail/?extsrc=mailto&amp;url=%s"
NC:prettyName="Gmail"
NC:uriTemplate="https://mail.google.com/mail/?extsrc=mailto&amp;url=%s" />
<RDF:Description RDF:about="urn:scheme:webcal"
NC:value="webcal">
<NC:handlerProp RDF:resource="urn:scheme:handler:webcal"/>
<RDF:Description RDF:about="urn:mimetype:handler:example/type.savetodisk"
NC:saveToDisk="true"
NC:alwaysAsk="true">
<NC:externalApplication RDF:resource="urn:mimetype:externalApplication:example/type.savetodisk"/>
<NC:possibleApplication RDF:resource="urn:handler:web:https://www.example.com/?url=%s"/>
</RDF:Description>
<RDF:Description RDF:about="urn:handler:web:http://www.example.com/?id=1&amp;url=%s"
NC:prettyName="Example Possible Handler One"
NC:uriTemplate="http://www.example.com/?id=1&amp;url=%s" />
<RDF:Description RDF:about="urn:mimetype:example/type.handleinternally"
NC:value="example/type.handleinternally"
NC:fileExtensions="example_one">
<NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.handleinternally"/>
</RDF:Description>
<RDF:Description RDF:about="urn:root"
NC:en-US_defaultHandlersVersion="999" />
<RDF:Description RDF:about="urn:scheme:mailto"
NC:value="mailto">
<NC:handlerProp RDF:resource="urn:scheme:handler:mailto"/>
<RDF:Description RDF:about="urn:mimetype:handler:example/type.usesystemdefault"
NC:useSystemDefault="true"
NC:alwaysAsk="false">
<NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?url=%s"/>
</RDF:Description>
<RDF:Description RDF:about="urn:scheme:handler:irc"
<RDF:Description RDF:about="urn:scheme:externalApplication:examplescheme.usehelperapp"
NC:prettyName="Example Default Handler"
NC:uriTemplate="https://www.example.com/?url=%s" />
<RDF:Seq RDF:about="urn:mimetypes:root">
<RDF:li RDF:resource="urn:mimetype:example/type.handleinternally"/>
<RDF:li RDF:resource="urn:mimetype:example/type.savetodisk"/>
<RDF:li RDF:resource="urn:mimetype:example/type.usehelperapp"/>
<RDF:li RDF:resource="urn:mimetype:example/type.usesystemdefault"/>
</RDF:Seq>
<RDF:Description RDF:about="urn:mimetype:handler:example/type.usehelperapp"
NC:alwaysAsk="true">
<NC:possibleApplication RDF:resource="urn:handler:web:https://www.mibbit.com/?url=%s"/>
<NC:externalApplication RDF:resource="urn:mimetype:externalApplication:example/type.usehelperapp"/>
<NC:possibleApplication RDF:resource="urn:handler:web:https://www.example.com/?url=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=1&amp;url=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:http://www.example.com/?id=2&amp;url=%s"/>
</RDF:Description>
<RDF:Description RDF:about="urn:scheme:handler:webcal"
NC:alwaysAsk="true">
<NC:externalApplication RDF:resource="urn:scheme:externalApplication:webcal"/>
<NC:possibleApplication RDF:resource="urn:handler:web:http://30boxes.com/external/widget?refer=ff&amp;url=%s"/>
<NC:possibleApplication RDF:resource="urn:handler:web:https://30boxes.com/external/widget?refer=ff&amp;url=%s"/>
<RDF:Description RDF:about="urn:scheme:examplescheme.usehelperapp"
NC:value="examplescheme.usehelperapp">
<NC:handlerProp RDF:resource="urn:scheme:handler:examplescheme.usehelperapp"/>
</RDF:Description>
<RDF:Seq RDF:about="urn:schemes:root">
<RDF:li RDF:resource="urn:scheme:irc"/>
<RDF:li RDF:resource="urn:scheme:ircs"/>
<RDF:li RDF:resource="urn:scheme:mailto"/>
<RDF:li RDF:resource="urn:scheme:webcal"/>
<RDF:li RDF:resource="urn:scheme:examplescheme.usehelperapp"/>
<RDF:li RDF:resource="urn:scheme:examplescheme.usesystemdefault"/>
</RDF:Seq>
<RDF:Description RDF:about="urn:handler:web:https://www.example.com/?url=%s"
NC:prettyName="Example Default Handler"
NC:uriTemplate="https://www.example.com/?url=%s" />
<RDF:Description RDF:about="urn:mimetype:externalApplication:example/type.savetodisk"
NC:prettyName="Example Default Handler"
NC:uriTemplate="https://www.example.com/?url=%s" />
<RDF:Description RDF:about="urn:mimetypes">
<NC:MIME-types RDF:resource="urn:mimetypes:root"/>
</RDF:Description>
<RDF:Description RDF:about="urn:handler:web:http://30boxes.com/external/widget?refer=ff&amp;url=%s"
NC:prettyName="30 Boxes"
NC:uriTemplate="http://30boxes.com/external/widget?refer=ff&amp;url=%s" />
<RDF:Description RDF:about="urn:handler:web:https://30boxes.com/external/widget?refer=ff&amp;url=%s"
NC:prettyName="30 Boxes"
NC:uriTemplate="https://30boxes.com/external/widget?refer=ff&amp;url=%s" />
<RDF:Description RDF:about="urn:handler:web:http://www.example.com/?id=2&amp;url=%s"
NC:prettyName="Example Possible Handler Two"
NC:uriTemplate="http://www.example.com/?id=2&amp;url=%s" />
<RDF:Description RDF:about="urn:schemes">
<NC:Protocol-Schemes RDF:resource="urn:schemes:root"/>
</RDF:Description>
<RDF:Description RDF:about="urn:mimetype:handler:example/type.handleinternally"
NC:handleInternal="true"
NC:alwaysAsk="false" />
<RDF:Description RDF:about="urn:mimetype:example/type.usehelperapp"
NC:value="example/type.usehelperapp">
<NC:handlerProp RDF:resource="urn:mimetype:handler:example/type.usehelperapp"/>
<NC:fileExtensions>example_two</NC:fileExtensions>
<NC:fileExtensions>example_three</NC:fileExtensions>
</RDF:Description>
</RDF:RDF>

View File

@ -1,35 +1,65 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the handlerService interfaces using JSON backend.
/*
* Tests the nsIHandlerService interface using the JSON backend.
*/
XPCOMUtils.defineLazyServiceGetter(this, "gHandlerService",
"@mozilla.org/uriloader/handler-service-json;1",
"nsIHandlerService");
var scriptFile = do_get_file("common_test_handlerService.js");
Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec);
/**
* 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;
var prepareImportDB = Task.async(function* () {
yield reloadData();
yield OS.File.copy(do_get_file("handlers.json").path, jsonPath);
});
var removeImportDB = Task.async(function* () {
yield reloadData();
yield OS.File.remove(jsonPath, { ignoreAbsent: true });
});
var reloadData = Task.async(function* () {
// Force the initialization of handlerService to prevent observer is not initialized yet.
let svc = 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);
});
var scriptFile = do_get_file("common_test_handlerService.js");
Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec);
/**
* Ensures forward compatibility by checking that the "store" method preserves
* unknown properties in the test data. This is specific to the JSON back-end.
*/
add_task(function* test_store_keeps_unknown_properties() {
// Create a new nsIHandlerInfo instance before loading the test data.
yield deleteHandlerStore();
let handlerInfo =
HandlerServiceTestUtils.getHandlerInfo("example/type.handleinternally");
yield copyTestDataToHandlerStore();
gHandlerService.store(handlerInfo);
yield unloadHandlerStore();
let data = JSON.parse(new TextDecoder().decode(yield OS.File.read(jsonPath)));
do_check_eq(data.mimeTypes["example/type.handleinternally"].unknownProperty,
"preserved");
});

View File

@ -1,37 +1,48 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests the handlerService interfaces using RDF backend.
/*
* Tests the nsIHandlerService interface using the JSON backend.
*/
XPCOMUtils.defineLazyServiceGetter(this, "gHandlerService",
"@mozilla.org/uriloader/handler-service;1",
"nsIHandlerService");
var scriptFile = do_get_file("common_test_handlerService.js");
Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec);
/**
* 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;
var prepareImportDB = Task.async(function* () {
yield reloadData();
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);
});
var removeImportDB = Task.async(function* () {
yield reloadData();
yield OS.File.remove(rdfFile.path, { ignoreAbsent: true });
});
var reloadData = Task.async(function* () {
// Force the initialization of handlerService to prevent observer is not initialized yet.
let svc = gHandlerService;
let promise = TestUtils.topicObserved("handlersvc-rdf-replace-complete");
Services.obs.notifyObservers(null, "handlersvc-rdf-replace");
yield promise;
});
var scriptFile = do_get_file("common_test_handlerService.js");
Services.scriptloader.loadSubScript(NetUtil.newURI(scriptFile).spec);