Bug 1118946 - API to provide localized properties r=ferjm,sicking

This commit is contained in:
Fabrice Desré 2015-03-05 22:43:57 -08:00
parent 1640f9ea16
commit df56e4d957
12 changed files with 398 additions and 29 deletions

View File

@ -47,14 +47,17 @@ this.Langpacks = {
_data: {},
_broadcaster: null,
_appIdFromManifestURL: null,
_appFromManifestURL: null,
init: function() {
ppmm.addMessageListener("Webapps:GetLocalizationResource", this);
ppmm.addMessageListener("Webapps:GetLocalizedValue", this);
},
registerRegistryFunctions: function(aBroadcaster, aIdGetter) {
registerRegistryFunctions: function(aBroadcaster, aIdGetter, aAppGetter) {
this._broadcaster = aBroadcaster;
this._appIdFromManifestURL = aIdGetter;
this._appFromManifestURL = aAppGetter;
},
receiveMessage: function(aMessage) {
@ -64,6 +67,9 @@ this.Langpacks = {
case "Webapps:GetLocalizationResource":
this.getLocalizationResource(data, mm);
break;
case "Webapps:GetLocalizedValue":
this.getLocalizedValue(data, mm);
break;
default:
debug("Unexpected message: " + aMessage.name);
}
@ -108,6 +114,35 @@ this.Langpacks = {
this._broadcaster("Webapps:UpdateState", message);
},
_getResource: function(aURL, aResponseType) {
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
xhr.mozBackgroundRequest = true;
xhr.open("GET", aURL);
// Default to text response type, but the webidl binding takes care of
// validating the dataType value.
xhr.responseType = "text";
if (aResponseType === "json") {
xhr.responseType = "json";
} else if (aResponseType === "binary") {
xhr.responseType = "blob";
}
return new Promise((aResolve, aReject) => {
xhr.addEventListener("load", function() {
debug("Success loading " + aURL);
if (xhr.status >= 200 && xhr.status < 400) {
aResolve(xhr.response);
} else {
aReject();
}
});
xhr.addEventListener("error", aReject);
xhr.send(null);
});
},
getLocalizationResource: function(aData, aMm) {
debug("getLocalizationResource " + uneval(aData));
@ -143,33 +178,103 @@ this.Langpacks = {
let href = item.url + aData.path;
debug("Will load " + href);
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
xhr.mozBackgroundRequest = true;
xhr.open("GET", href);
this._getResource(href, aData.dataType).then(
(aResponse) => {
aMm.sendAsyncMessage("Webapps:GetLocalizationResource:Return",
{ requestID: aData.requestID, oid: aData.oid, data: aResponse });
},
() => { sendError("Error loading " + href, "UnavailableResource"); }
);
},
// Default to text response type, but the webidl binding takes care of
// validating the dataType value.
xhr.responseType = "text";
if (aData.dataType === "json") {
xhr.responseType = "json";
} else if (aData.dataType === "binary") {
xhr.responseType = "blob";
getLocalizedValue: function(aData, aMm) {
debug("getLocalizedValue " + aData.property);
function sendError(aMsg, aCode) {
debug(aMsg);
aMm.sendAsyncMessage("Webapps:GetLocalizedValue:Return",
{ success: false,
requestID: aData.requestID,
oid: aData.oid,
error: aCode });
}
xhr.addEventListener("load", function() {
debug("Success loading " + href);
if (xhr.status >= 200 && xhr.status < 400) {
aMm.sendAsyncMessage("Webapps:GetLocalizationResource:Return",
{ requestID: aData.requestID, oid: aData.oid, data: xhr.response });
function getValueFromManifest(aManifest) {
debug("Getting " + aData.property + " from the manifest.");
let value = aManifest._localeProp(aData.property);
if (!value) {
sendError("No property " + aData.property + " in manifest", "UnknownProperty");
} else {
sendError("Error loading " + href, "UnavailableResource");
aMm.sendAsyncMessage("Webapps:GetLocalizedValue:Return",
{ success: true,
requestID: aData.requestID,
oid: aData.oid,
value: value });
}
});
xhr.addEventListener("error", function() {
sendError("Error loading " + href, "UnavailableResource");
});
xhr.send(null);
}
let self = this;
function getValueFromLangpack(aItem, aManifest) {
debug("Getting value from langpack at " + aItem.url + "/manifest.json")
let href = aItem.url + "/manifest.json";
function getProperty(aResponse, aProp) {
let root = aData.entryPoint && aResponse.entry_points &&
aResponse.entry_points[aData.entryPoint]
? aResponse.entry_points[aData.entryPoint]
: aResponse;
return root[aProp];
}
self._getResource(href, "json").then(
(aResponse) => {
let propValue = getProperty(aResponse, aData.property);
if (propValue) {
aMm.sendAsyncMessage("Webapps:GetLocalizedValue:Return",
{ success: true,
requestID: aData.requestID,
oid: aData.oid,
value: propValue });
} else {
getValueFromManifest(aManifest);
}
},
() => { getValueFromManifest(aManifest); }
);
}
// We need to get the app with the manifest since the version is only
// available in the manifest.
this._appFromManifestURL(aData.manifestURL, aData.entryPoint)
.then(aApp => {
let manifest = aApp.manifest;
// No langpack for this app or we have langpack(s) for this app, but
// not for this language.
// Fallback to the manifest values.
if (!this._data[aData.manifestURL] ||
!this._data[aData.manifestURL].langs[aData.lang]) {
getValueFromManifest(manifest);
return;
}
if (!manifest.version) {
getValueFromManifest(manifest);
return;
}
// Check that we have the langpack for the right app version.
let item = this._data[aData.manifestURL].langs[aData.lang];
// Only keep x.y in the manifest's version in case it's x.y.z
let manVersion = manifest.version.split('.').slice(0, 2).join('.');
if (item.target == manVersion) {
getValueFromLangpack(item, manifest);
return;
}
// Fallback on getting the value from the manifest.
getValueFromManifest(manifest);
})
.catch(aError => { sendError("No app!", "NoSuchApp") });
},
// Validates the langpack part of a manifest.

View File

@ -699,6 +699,24 @@ WebappsApplication.prototype = {
});
},
getLocalizedValue: function(aProperty, aLang, aEntryPoint) {
this.addMessageListeners(["Webapps:GetLocalizedValue:Return"]);
return this.createPromise((aResolve, aReject) => {
cpmm.sendAsyncMessage("Webapps:GetLocalizedValue",
{ manifestURL: this.manifestURL,
oid: this._id,
topId: this._topId,
property: aProperty,
lang: aLang,
entryPoint: aEntryPoint,
requestID: this.getPromiseResolverId({
resolve: aResolve,
reject: aReject
})
});
});
},
_prepareForContent: function() {
if (this.__DOM_IMPL__) {
return this.__DOM_IMPL__;
@ -736,7 +754,8 @@ WebappsApplication.prototype = {
if (aMessage.name == "Webapps:Connect:Return:OK" ||
aMessage.name == "Webapps:Connect:Return:KO" ||
aMessage.name == "Webapps:GetConnections:Return:OK" ||
aMessage.name == "Webapps:Export:Return") {
aMessage.name == "Webapps:Export:Return" ||
aMessage.name == "Webapps:GetLocalizedValue:Return") {
req = this.takePromiseResolver(msg.requestID);
} else {
req = this.takeRequest(msg.requestID);
@ -832,6 +851,14 @@ WebappsApplication.prototype = {
req.reject(new this._window.DOMError(msg.error || ""));
}
break;
case "Webapps:GetLocalizedValue:Return":
this.removeMessageListeners(["Webapps:GetLocalizedValue:Return"]);
if (msg.success) {
req.resolve(msg.value);
} else {
req.reject(new this._window.DOMError(msg.error || ""));
}
break;
}
},

View File

@ -251,7 +251,8 @@ this.DOMApplicationRegistry = {
this.loadAndUpdateApps();
Langpacks.registerRegistryFunctions(this.broadcastMessage.bind(this),
this._appIdForManifestURL.bind(this));
this._appIdForManifestURL.bind(this),
this.getFullAppByManifestURL.bind(this));
},
// loads the current registry, that could be empty on first run.
@ -4669,6 +4670,30 @@ this.DOMApplicationRegistry = {
return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
},
// Returns a promise that resolves to the app object with the manifest.
getFullAppByManifestURL: function(aManifestURL, aEntryPoint) {
let app = this.getAppByManifestURL(aManifestURL);
if (!app) {
return Promise.reject("NoSuchApp");
}
return this.getManifestFor(aManifestURL).then((aManifest) => {
let manifest = aEntryPoint && aManifest.entry_points &&
aManifest.entry_points[aEntryPoint]
? aManifest.entry_points[aEntryPoint]
: aManifest;
// `version` doesn't change based on entry points, and we need it
// to check langpack versions.
if (manifest !== aManifest) {
manifest.version = aManifest.version;
}
app.manifest = new ManifestHelper(manifest, app.origin, app.manifestURL);
return app;
});
},
_getAppWithManifest: Task.async(function*(aManifestURL) {
let app = this.getAppByManifestURL(aManifestURL);
if (!app) {

View File

@ -0,0 +1,8 @@
{
"name" : "Version française.",
"entry_points": {
"dialer": {
"name": "téléphone"
}
}
}

View File

@ -6,7 +6,9 @@
"revision": 201411051234,
"name": "Français",
"apps": {
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/fr/"
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/fr/",
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest_no_version.webapp": "tests/dom/apps/tests/langpack/fr/",
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest_version_xyz.webapp": "tests/dom/apps/tests/langpack/fr/"
}
}
},

View File

@ -6,14 +6,18 @@
"revision": 201411051234,
"name": "Deutsch",
"apps": {
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/de/"
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/de/",
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest_no_version.webapp": "tests/dom/apps/tests/langpack/de/",
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest_version_xyz.webapp": "tests/dom/apps/tests/langpack/de/"
}
},
"pl": {
"revision": 201411051234,
"name": "Polski",
"apps": {
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/pl/"
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/pl/",
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest_no_version.webapp": "tests/dom/apps/tests/langpack/pl/",
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest_version_xyz.webapp": "tests/dom/apps/tests/langpack/pl/"
}
}
},

View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html>
<head>
<title>Langpack Test : localized values</title>
<script>
function success(data) {
return new Promise(function(resolve, reject) {
alert(data);
resolve();
});
}
function error(domError) {
console.log(domError.name);
return new Promise(function(resolve, reject) {
alert(domError.name);
resolve();
});
}
var app;
function getApp() {
return new Promise(function(resolve, reject) {
var req = navigator.mozApps.getSelf();
req.onsuccess = () => { app = req.result; resolve(); }
req.onerror = () => { reject(req.error); }
});
}
// Success: unknown language, fallback to manifest.
function test1() {
return app.getLocalizedValue("name", "es")
.then(success, error);
}
// Error : unknown property.
function test2() {
return app.getLocalizedValue("foobar", "fr")
.then(success, error);
}
// Success, value from the langpack.
function test3() {
return app.getLocalizedValue("name", "fr")
.then(success, error);
}
// Success, value from the manifest's entry point.
function test4() {
return app.getLocalizedValue("name", "es", "dialer")
.then(success, error);
}
// Success, value from the langpack's entry point.
function test5() {
return app.getLocalizedValue("name", "fr", "dialer")
.then(success, error);
}
function run() {
getApp().then(test1)
.then(test2)
.then(test3)
.then(test4)
.then(test5)
.catch(error);
}
</script>
</head>
<body onload="run()">
<h1>Langpack Test : localized values</h1>
</body>
</html>

View File

@ -1,3 +1,9 @@
{
"name": "Localization test app"
"name": "Localization test app",
"version": "2.2",
"entry_points": {
"dialer": {
"name": "dialer"
}
}
}

View File

@ -0,0 +1,8 @@
{
"name": "Localization test app",
"entry_points": {
"dialer": {
"name": "dialer"
}
}
}

View File

@ -0,0 +1,9 @@
{
"name": "Localization test app",
"version": "2.2.1",
"entry_points": {
"dialer": {
"name": "dialer"
}
}
}

View File

@ -206,6 +206,101 @@ function runTest() {
req.onerror = mozAppsError;
yield undefined;
}
// Test localized values with a x.y manifest version.
installApp(appManifestURL);
yield undefined;
// Install the fr langpack.
installApp(lang1ManifestURL);
yield undefined;
// Install the de and pl langpack.
installApp(lang2ManifestURL);
yield undefined;
// Opens the iframe to the localized values test page.
openPage("localizedvalues.html",
["Localization test app",
"UnknownProperty",
"Version française.",
"dialer",
"téléphone"]);
yield undefined;
// Clean up after ourselves by uninstalling apps.
info(apps.length + " applications to uninstall.");
while (apps.length) {
let app = apps.pop();
req = navigator.mozApps.mgmt.uninstall(app);
req.onsuccess = continueTest;
req.onerror = mozAppsError;
yield undefined;
}
// Test localized values with versionless manifest.
appManifestURL = uriPrefix + "manifest_no_version.webapp";
installApp(appManifestURL);
yield undefined;
// Install the fr langpack.
installApp(lang1ManifestURL);
yield undefined;
// Install the de and pl langpack.
installApp(lang2ManifestURL);
yield undefined;
// Opens the iframe to the localized values test page.
openPage("localizedvalues.html",
["Localization test app",
"UnknownProperty",
"Localization test app",
"dialer",
"dialer"]);
yield undefined;
// Clean up after ourselves by uninstalling apps.
info(apps.length + " applications to uninstall.");
while (apps.length) {
let app = apps.pop();
req = navigator.mozApps.mgmt.uninstall(app);
req.onsuccess = continueTest;
req.onerror = mozAppsError;
yield undefined;
}
// Test localized values with a x.y.z manifest version.
appManifestURL = uriPrefix + "manifest_version_xyz.webapp"
installApp(appManifestURL);
yield undefined;
// Install the fr langpack.
installApp(lang1ManifestURL);
yield undefined;
// Install the de and pl langpack.
installApp(lang2ManifestURL);
yield undefined;
// Opens the iframe to the localized values test page.
openPage("localizedvalues.html",
["Localization test app",
"UnknownProperty",
"Version française.",
"dialer",
"téléphone"]);
yield undefined;
// Clean up after ourselves by uninstalling apps.
info(apps.length + " applications to uninstall.");
while (apps.length) {
let app = apps.pop();
req = navigator.mozApps.mgmt.uninstall(app);
req.onsuccess = continueTest;
req.onerror = mozAppsError;
yield undefined;
}
}
</script>

View File

@ -106,6 +106,12 @@ interface DOMApplication : EventTarget {
// Export this app as a shareable Blob.
Promise<Blob> export();
// Returns the localized value of a property, using either the manifest or
// a langpack if one is available.
Promise<DOMString> getLocalizedValue(DOMString property,
DOMString locale,
optional DOMString entryPoint);
};
[JSImplementation="@mozilla.org/webapps/manager;1",