Bug 1108096 - Langpack support for b2g/gaia r=ferjm,sicking

This commit is contained in:
Fabrice Desré 2015-01-10 15:00:27 -08:00
Родитель 9e379bcc00
Коммит 2440b76622
21 изменённых файлов: 896 добавлений и 19 удалений

Просмотреть файл

@ -354,6 +354,15 @@ this.DOMApplicationRegistry = {
aCallback(res); aCallback(res);
}, },
getAdditionalLanguages: function(aManifestURL) {
for (let id in this.webapps) {
if (this.webapps[id].manifestURL == aManifestURL) {
return this.webapps[id].additionalLanguages || {};
}
}
return {};
},
/** /**
* nsIAppsService API * nsIAppsService API
*/ */

Просмотреть файл

@ -22,6 +22,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm"); "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
"@mozilla.org/AppsService;1",
"nsIAppsService");
// Shared code for AppsServiceChild.jsm, TrustedHostedAppsUtils.jsm, // Shared code for AppsServiceChild.jsm, TrustedHostedAppsUtils.jsm,
// Webapps.jsm and Webapps.js // Webapps.jsm and Webapps.js
@ -485,6 +489,7 @@ this.AppsUtils = {
* Checks if the app role is allowed: * Checks if the app role is allowed:
* Only certified apps can be themes. * Only certified apps can be themes.
* Only privileged or certified apps can be addons. * Only privileged or certified apps can be addons.
* Langpacks need to be privileged.
* @param aRole : the role assigned to this app. * @param aRole : the role assigned to this app.
* @param aStatus : the APP_STATUS_* for this app. * @param aStatus : the APP_STATUS_* for this app.
*/ */
@ -492,6 +497,13 @@ this.AppsUtils = {
if (aRole == "theme" && aStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED) { if (aRole == "theme" && aStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return false; return false;
} }
if (aRole == "langpack" && aStatus !== Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
let allow = false;
try {
allow = Services.prefs.getBoolPref("dom.apps.allow_unsigned_langpacks");
} catch(e) {}
return allow;
}
if (!this.allowUnsignedAddons && if (!this.allowUnsignedAddons &&
(aRole == "addon" && (aRole == "addon" &&
aStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED && aStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
@ -732,7 +744,16 @@ this.AppsUtils = {
// Returns the hash for a JS object. // Returns the hash for a JS object.
computeObjectHash: function(aObject) { computeObjectHash: function(aObject) {
return this.computeHash(JSON.stringify(aObject)); return this.computeHash(JSON.stringify(aObject));
} },
getAppManifestURLFromWindow: function(aWindow) {
let appId = aWindow.document.nodePrincipal.appId;
if (appId === Ci.nsIScriptSecurityManager.NO_APP_ID) {
return null;
}
return appsService.getManifestURLByLocalId(appId);
},
} }
/** /**

315
dom/apps/Langpacks.jsm Normal file
Просмотреть файл

@ -0,0 +1,315 @@
/* 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 file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
this.EXPORTED_SYMBOLS = ["Langpacks"];
let debug = Services.prefs.getBoolPref("dom.mozApps.debug")
? (aMsg) => {
dump("-*-*- Langpacks: " + aMsg + "\n");
}
: (aMsg) => {};
/**
* Langpack support
*
* Manifest format is:
*
* "languages-target" : { "app://*.gaiamobile.org/manifest.webapp": "2.2" },
* "languages-provided": {
* "de": {
* "version": 201411051234,
* "name": "Deutsch",
* "apps": {
* "app://calendar.gaiamobile.org/manifest.webapp": "/de/calendar",
* "app://email.gaiamobile.org/manifest.webapp": "/de/email"
* }
* },
* "role" : "langpack"
*/
this.Langpacks = {
_data: {},
_broadcaster: null,
_appIdFromManifestURL: null,
init: function() {
ppmm.addMessageListener("Webapps:GetLocalizationResource", this);
},
registerRegistryFunctions: function(aBroadcaster, aIdGetter) {
this._broadcaster = aBroadcaster;
this._appIdFromManifestURL = aIdGetter;
},
receiveMessage: function(aMessage) {
let data = aMessage.data;
let mm = aMessage.target;
switch (aMessage.name) {
case "Webapps:GetLocalizationResource":
this.getLocalizationResource(data, mm);
break;
default:
debug("Unexpected message: " + aMessage.name);
}
},
getAdditionalLanguages: function(aManifestURL) {
debug("getAdditionalLanguages " + aManifestURL);
let res = { langs: {} };
let langs = res.langs;
if (this._data[aManifestURL]) {
res.appId = this._data[aManifestURL].appId;
for (let lang in this._data[aManifestURL].langs) {
if (!langs[lang]) {
langs[lang] = [];
}
let current = this._data[aManifestURL].langs[lang];
langs[lang].push({
version: current.version,
name: current.name,
target: current.target
});
}
}
debug("Languages found: " + uneval(res));
return res;
},
sendAppUpdate: function(aManifestURL) {
debug("sendAppUpdate " + aManifestURL);
if (!this._broadcaster) {
debug("No broadcaster!");
return;
}
let res = this.getAdditionalLanguages(aManifestURL);
let message = {
id: res.appId,
app: {
additionalLanguages: res.langs
}
}
this._broadcaster("Webapps:UpdateState", message);
},
getLocalizationResource: function(aData, aMm) {
debug("getLocalizationResource " + uneval(aData));
function sendError(aMsg, aCode) {
debug(aMsg);
aMm.sendAsyncMessage("Webapps:GetLocalizationResource:Return",
{ requestID: aData.requestID, oid: aData.oid, error: aCode });
}
// No langpack available for this app.
if (!this._data[aData.manifestURL]) {
return sendError("No langpack for this app.", "NoLangpack");
}
// We have langpack(s) for this app, but not for this language.
if (!this._data[aData.manifestURL].langs[aData.lang]) {
return sendError("No language " + aData.lang + " for this app.",
"UnavailableLanguage");
}
// Check that we have the right version.
let item = this._data[aData.manifestURL].langs[aData.lang];
if (item.target != aData.version) {
return sendError("No version " + aData.version + " for this app.",
"UnavailableVersion");
}
// The path can't be an absolute uri.
if (isAbsoluteURI(aData.path)) {
return sendError("url can't be absolute.", "BadUrl");
}
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);
// 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";
}
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 });
} else {
sendError("Error loading " + href, "UnavailableResource");
}
});
xhr.addEventListener("error", function() {
sendError("Error loading " + href, "UnavailableResource");
});
xhr.send(null);
},
// Validates the langpack part of a manifest.
checkManifest: function(aManifest) {
if (!("languages-target" in aManifest)) {
debug("Error: no 'languages-target' property.")
return false;
}
if (!("languages-provided" in aManifest)) {
debug("Error: no 'languages-provided' property.")
return false;
}
for (let lang in aManifest["languages-provided"]) {
let item = aManifest["languages-provided"][lang];
if (!item.version) {
debug("Error: missing 'version' in languages-provided." + lang);
return false;
}
if (typeof item.version !== "number") {
debug("Error: languages-provided." + lang +
".version must be a number but is a " + (typeof item.version));
return false;
}
if (!item.apps) {
debug("Error: missing 'apps' in languages-provided." + lang);
return false;
}
for (let app in item.apps) {
// Keys should be manifest urls, ie. absolute urls.
if (!isAbsoluteURI(app)) {
debug("Error: languages-provided." + lang + "." + app +
" must be an absolute manifest url.");
return false;
}
if (typeof item.apps[app] !== "string") {
debug("Error: languages-provided." + lang + ".apps." + app +
" value must be a string but is " + (typeof item.apps[app]) +
" : " + item.apps[app]);
return false;
}
}
}
return true;
},
// Check if this app is a langpack and update registration if needed.
register: function(aApp, aManifest) {
debug("register app " + aApp.manifestURL + " role=" + aApp.role);
if (aApp.role !== "langpack") {
debug("Not a langpack.");
// Not a langpack, but that's fine.
return;
}
if (!this.checkManifest(aManifest)) {
debug("Invalid langpack manifest.");
return;
}
let platformVersion = aManifest["languages-target"]
["app://*.gaiamobile.org/manifest.webapp"];
let origin = Services.io.newURI(aApp.origin, null, null);
for (let lang in aManifest["languages-provided"]) {
let item = aManifest["languages-provided"][lang];
let version = item.version; // The langpack version, not the platform.
let name = item.name || lang; // If no name specified, default to lang.
for (let app in item.apps) {
let sendEvent = false;
if (!this._data[app] ||
!this._data[app].langs[lang] ||
this._data[app].langs[lang].version > version) {
if (!this._data[app]) {
this._data[app] = {
appId: this._appIdFromManifestURL(app),
langs: {}
};
}
this._data[app].langs[lang] = {
version: version,
target: platformVersion,
name: name,
url: origin.resolve(item.apps[app]),
from: aApp.manifestURL
}
sendEvent = true;
debug("Registered " + app + " -> " + uneval(this._data[app].langs[lang]));
}
// Fire additionallanguageschange event.
// This will only be dispatched to documents using the langpack api.
if (sendEvent) {
this.sendAppUpdate(app);
ppmm.broadcastAsyncMessage(
"Webapps:AdditionalLanguageChange",
{ manifestURL: app,
languages: this.getAdditionalLanguages(app).langs });
}
}
}
},
// Check if this app is a langpack and update registration by removing all
// the entries from this app.
unregister: function(aApp, aManifest) {
debug("unregister app " + aApp.manifestURL + " role=" + aApp.role);
if (aApp.role !== "langpack") {
debug("Not a langpack.");
// Not a langpack, but that's fine.
return;
}
for (let app in this._data) {
let sendEvent = false;
for (let lang in this._data[app].langs) {
if (this._data[app].langs[lang].from == aApp.manifestURL) {
sendEvent = true;
delete this._data[app].langs[lang];
}
}
// Fire additionallanguageschange event.
// This will only be dispatched to documents using the langpack api.
if (sendEvent) {
this.sendAppUpdate(app);
ppmm.broadcastAsyncMessage(
"Webapps:AdditionalLanguageChange",
{ manifestURL: app,
languages: this.getAdditionalLanguages(app).langs });
}
}
}
}
Langpacks.init();

Просмотреть файл

@ -44,11 +44,23 @@ WebappsRegistry.prototype = {
receiveMessage: function(aMessage) { receiveMessage: function(aMessage) {
let msg = aMessage.json; let msg = aMessage.json;
if (msg.oid != this._id) let req;
return if (aMessage.name != "Webapps:AdditionalLanguageChange") {
let req = this.getRequest(msg.requestID); if (msg.oid != this._id) {
if (!req) return
return; }
if (aMessage.name == "Webapps:GetAdditionalLanguages:Return" ||
aMessage.name == "Webapps:GetLocalizationResource:Return") {
req = this.takePromiseResolver(msg.requestID);
} else {
req = this.getRequest(msg.requestID);
}
if (!req) {
return;
}
}
let app = msg.app; let app = msg.app;
switch (aMessage.name) { switch (aMessage.name) {
case "Webapps:Install:Return:OK": case "Webapps:Install:Return:OK":
@ -78,6 +90,26 @@ WebappsRegistry.prototype = {
this.removeMessageListeners(aMessage.name); this.removeMessageListeners(aMessage.name);
Services.DOMRequest.fireSuccess(req, convertAppsArray(msg.apps, this._window)); Services.DOMRequest.fireSuccess(req, convertAppsArray(msg.apps, this._window));
break; break;
case "Webapps:AdditionalLanguageChange":
// Check if the current page is from the app receiving the event.
let manifestURL = AppsUtils.getAppManifestURLFromWindow(this._window);
if (manifestURL && manifestURL == msg.manifestURL) {
// Let's dispatch an "additionallanguageschange" event on the document.
let doc = this._window.document;
let event = doc.createEvent("CustomEvent");
event.initCustomEvent("additionallanguageschange", true, true,
Cu.cloneInto(msg.languages, this._window));
doc.dispatchEvent(event);
}
break;
case "Webapps:GetLocalizationResource:Return":
this.removeMessageListeners(["Webapps:GetLocalizationResource:Return"]);
if (msg.error) {
req.reject(new this._window.DOMError(msg.error));
} else {
req.resolve(Cu.cloneInto(msg.data, this._window));
}
break;
} }
this.removeRequest(msg.requestID); this.removeRequest(msg.requestID);
}, },
@ -231,7 +263,8 @@ WebappsRegistry.prototype = {
uninit: function() { uninit: function() {
this._mgmt = null; this._mgmt = null;
cpmm.sendAsyncMessage("Webapps:UnregisterForMessages", cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
["Webapps:Install:Return:OK"]); ["Webapps:Install:Return:OK",
"Webapps:AdditionalLanguageChange"]);
}, },
installPackage: function(aURL, aParams) { installPackage: function(aURL, aParams) {
@ -248,19 +281,69 @@ WebappsRegistry.prototype = {
return request; return request;
}, },
_getCurrentAppManifestURL: function() {
let appId = this._window.document.nodePrincipal.appId;
if (appId === Ci.nsIScriptSecurityManager.NO_APP_ID) {
return null;
}
return appsService.getManifestURLByLocalId(appId);
},
getAdditionalLanguages: function() {
let manifestURL = AppsUtils.getAppManifestURLFromWindow(this._window);
return new this._window.Promise((aResolve, aReject) => {
if (!manifestURL) {
aReject("NotInApp");
} else {
let langs = DOMApplicationRegistry.getAdditionalLanguages(manifestURL);
aResolve(Cu.cloneInto(langs, this._window));
}
});
},
getLocalizationResource: function(aLanguage, aVersion, aPath, aType) {
let manifestURL = AppsUtils.getAppManifestURLFromWindow(this._window);
if (!manifestURL) {
return new Promise((aResolve, aReject) => {
aReject("NotInApp");
});
}
this.addMessageListeners(["Webapps:GetLocalizationResource:Return"]);
return this.createPromise((aResolve, aReject) => {
cpmm.sendAsyncMessage("Webapps:GetLocalizationResource", {
manifestURL: manifestURL,
lang: aLanguage,
version: aVersion,
path: aPath,
dataType: aType,
oid: this._id,
requestID: this.getPromiseResolverId({
resolve: aResolve,
reject: aReject
})
});
});
},
// nsIDOMGlobalPropertyInitializer implementation // nsIDOMGlobalPropertyInitializer implementation
init: function(aWindow) { init: function(aWindow) {
const prefs = new Preferences(); const prefs = new Preferences();
this._window = aWindow; this._window = aWindow;
this.initDOMRequestHelper(aWindow, "Webapps:Install:Return:OK"); this.initDOMRequestHelper(aWindow, ["Webapps:Install:Return:OK",
"Webapps:AdditionalLanguageChange"]);
let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor) let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils); .getInterface(Ci.nsIDOMWindowUtils);
this._id = util.outerWindowID; this._id = util.outerWindowID;
cpmm.sendAsyncMessage("Webapps:RegisterForMessages", cpmm.sendAsyncMessage("Webapps:RegisterForMessages",
{ messages: ["Webapps:Install:Return:OK"]}); { messages: ["Webapps:Install:Return:OK",
"Webapps:AdditionalLanguageChange"]});
let principal = aWindow.document.nodePrincipal; let principal = aWindow.document.nodePrincipal;
let appId = principal.appId; let appId = principal.appId;

Просмотреть файл

@ -81,6 +81,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
XPCOMUtils.defineLazyModuleGetter(this, "ScriptPreloader", XPCOMUtils.defineLazyModuleGetter(this, "ScriptPreloader",
"resource://gre/modules/ScriptPreloader.jsm"); "resource://gre/modules/ScriptPreloader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Langpacks",
"resource://gre/modules/Langpacks.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TrustedHostedAppsUtils", XPCOMUtils.defineLazyModuleGetter(this, "TrustedHostedAppsUtils",
"resource://gre/modules/TrustedHostedAppsUtils.jsm"); "resource://gre/modules/TrustedHostedAppsUtils.jsm");
@ -243,6 +246,9 @@ this.DOMApplicationRegistry = {
["webapps", "webapps.json"], true).path; ["webapps", "webapps.json"], true).path;
this.loadAndUpdateApps(); this.loadAndUpdateApps();
Langpacks.registerRegistryFunctions(this.broadcastMessage.bind(this),
this._appIdForManifestURL.bind(this));
}, },
// loads the current registry, that could be empty on first run. // loads the current registry, that could be empty on first run.
@ -424,6 +430,7 @@ this.DOMApplicationRegistry = {
} }
app.kind = this.appKind(app, aResult.manifest); app.kind = this.appKind(app, aResult.manifest);
UserCustomizations.register(aResult.manifest, app); UserCustomizations.register(aResult.manifest, app);
Langpacks.register(app, aResult.manifest);
}); });
// Nothing else to do but notifying we're ready. // Nothing else to do but notifying we're ready.
@ -1149,6 +1156,7 @@ this.DOMApplicationRegistry = {
this._registerInterAppConnections(manifest, app); this._registerInterAppConnections(manifest, app);
appsToRegister.push({ manifest: manifest, app: app }); appsToRegister.push({ manifest: manifest, app: app });
UserCustomizations.register(manifest, app); UserCustomizations.register(manifest, app);
Langpacks.register(app, manifest);
}); });
this._safeToClone.resolve(); this._safeToClone.resolve();
this._registerActivitiesForApps(appsToRegister, aRunUpdate); this._registerActivitiesForApps(appsToRegister, aRunUpdate);
@ -1520,6 +1528,8 @@ this.DOMApplicationRegistry = {
this.safeToClone.then( () => { this.safeToClone.then( () => {
for (let id in this.webapps) { for (let id in this.webapps) {
tmp.push({ id: id }); tmp.push({ id: id });
this.webapps[id].additionalLanguages =
Langpacks.getAdditionalLanguages(this.webapps[id].manifestURL).langs;
} }
this._readManifests(tmp).then( this._readManifests(tmp).then(
function(manifests) { function(manifests) {
@ -1964,6 +1974,10 @@ this.DOMApplicationRegistry = {
// Update the asm.js scripts we need to compile. // Update the asm.js scripts we need to compile.
yield ScriptPreloader.preload(app, newManifest); yield ScriptPreloader.preload(app, newManifest);
// Update langpack information.
Langpacks.register(app, newManifest);
yield this._saveApps(); yield this._saveApps();
// Update the handlers and permissions for this app. // Update the handlers and permissions for this app.
this.updateAppHandlers(oldManifest, newManifest, app); this.updateAppHandlers(oldManifest, newManifest, app);
@ -2081,11 +2095,13 @@ this.DOMApplicationRegistry = {
this.notifyAppsRegistryReady(); this.notifyAppsRegistryReady();
} }
// Update user customizations. // Update user customizations and langpacks.
if (aOldManifest) { if (aOldManifest) {
UserCustomizations.unregister(aOldManifest, aApp); UserCustomizations.unregister(aOldManifest, aApp);
Langpacks.unregister(aApp, aOldManifest);
} }
UserCustomizations.register(aNewManifest, aApp); UserCustomizations.register(aNewManifest, aApp);
Langpacks.register(aApp, aNewManifest);
}, },
checkForUpdate: function(aData, aMm) { checkForUpdate: function(aData, aMm) {
@ -3185,6 +3201,9 @@ this.DOMApplicationRegistry = {
// Check if we have asm.js code to preload for this application. // Check if we have asm.js code to preload for this application.
yield ScriptPreloader.preload(aNewApp, aManifest); yield ScriptPreloader.preload(aNewApp, aManifest);
// Update langpack information.
yield Langpacks.register(aNewApp, aManifest);
this.broadcastMessage("Webapps:FireEvent", { this.broadcastMessage("Webapps:FireEvent", {
eventType: ["downloadsuccess", "downloadapplied"], eventType: ["downloadsuccess", "downloadapplied"],
manifestURL: aNewApp.manifestURL manifestURL: aNewApp.manifestURL
@ -4094,6 +4113,7 @@ this.DOMApplicationRegistry = {
this._unregisterActivities(aApp.manifest, aApp); this._unregisterActivities(aApp.manifest, aApp);
} }
UserCustomizations.unregister(aApp.manifest, aApp); UserCustomizations.unregister(aApp.manifest, aApp);
Langpacks.unregister(aApp, aApp.manifest);
let dir = this._getAppDir(id); let dir = this._getAppDir(id);
try { try {

Просмотреть файл

@ -34,6 +34,7 @@ EXTRA_JS_MODULES += [
'AppsServiceChild.jsm', 'AppsServiceChild.jsm',
'FreeSpaceWatcher.jsm', 'FreeSpaceWatcher.jsm',
'InterAppCommService.jsm', 'InterAppCommService.jsm',
'Langpacks.jsm',
'OfflineCacheInstaller.jsm', 'OfflineCacheInstaller.jsm',
'PermissionsInstaller.jsm', 'PermissionsInstaller.jsm',
'PermissionsTable.jsm', 'PermissionsTable.jsm',

Просмотреть файл

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>Langpack Test : event</title>
<script>
var baseURL = "http://mochi.test:8888/tests/dom/apps/tests/langpack/";
var eventCount = 0;
function languageChanged(evt) {
eventCount++;
alert(JSON.stringify(evt.detail));
if (eventCount == 1) {
var req = navigator.mozApps.install(baseURL + "lang2.webapp");
}
}
// Set up the event handler, and install an app.
function run() {
document.addEventListener("additionallanguageschange", languageChanged);
navigator.mozApps.install(baseURL + "lang1.webapp");
}
</script>
</head>
<body onload="run()">
<h1>Langpack Test : event</h1>
</body>
</html>

Просмотреть файл

@ -0,0 +1 @@
{ "hello" : "Bonjour" }

Просмотреть файл

@ -0,0 +1 @@
hello=Bonjour

Просмотреть файл

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>Langpack Test : getAdditionalLanguages()</title>
<script>
function run() {
navigator.mozApps.getAdditionalLanguages().then(languages => {
alert(JSON.stringify(languages));
});
}
</script>
</head>
<body onload="run()">
<h1>Langpack Test : getAdditionalLanguages()</h1>
</body>
</html>

Просмотреть файл

@ -0,0 +1,14 @@
{
"name": "French locale",
"languages-target" : { "app://*.gaiamobile.org/manifest.webapp": "2.2" },
"languages-provided": {
"fr": {
"version": 201411051234,
"name": "Français",
"apps": {
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/fr/"
}
}
},
"role" : "langpack"
}

Просмотреть файл

@ -0,0 +1 @@
Content-Type: application/manifest+json

Просмотреть файл

@ -0,0 +1,21 @@
{
"name": "German an Polish locales",
"languages-target" : { "app://*.gaiamobile.org/manifest.webapp": "2.2" },
"languages-provided": {
"de": {
"version": 201411051234,
"name": "Deutsch",
"apps": {
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/de/"
}
},
"pl": {
"version": 201411051234,
"name": "Polski",
"apps": {
"http://mochi.test:8888/tests/dom/apps/tests/langpack/manifest.webapp": "tests/dom/apps/tests/langpack/pl/"
}
}
},
"role" : "langpack"
}

Просмотреть файл

@ -0,0 +1 @@
Content-Type: application/manifest+json

Просмотреть файл

@ -0,0 +1,3 @@
{
"name": "Localization test app"
}

Просмотреть файл

@ -0,0 +1 @@
Content-Type: application/manifest+json

Просмотреть файл

@ -0,0 +1,92 @@
<!DOCTYPE html>
<html>
<head>
<title>Langpack Test : resources</title>
<script>
function success(data) {
return new Promise(function(resolve, reject) {
if (typeof data === "object") {
// Read what's inside the blob.
var reader = new FileReader();
reader.onload = function(e) {
alert(e.target.result);
resolve();
};
reader.readAsText(data);
} else {
alert(data);
resolve();
}
});
}
function successJSON(data) {
return new Promise(function(resolve, reject) {
alert(JSON.stringify(data));
resolve();
});
}
function error(domError) {
return new Promise(function(resolve, reject) {
alert(domError.name);
resolve();
});
}
// Error: Bad resource.
function test1() {
return navigator.mozApps.getLocalizationResource("fr", "2.2", "./foo.html", "binary")
.then(success, error);
}
// Error: Unknown locale.
function test2() {
return navigator.mozApps.getLocalizationResource("es", "2.2", "./foo.html", "binary")
.then(success, error);
}
// Error: Bad version.
function test3() {
return navigator.mozApps.getLocalizationResource("fr", "2.0", "./foo.html", "binary")
.then(success, error);
}
// Error: Absolute url.
function test4() {
return navigator.mozApps.getLocalizationResource("fr", "2.2", "http://example.com/foo.html", "binary")
.then(success, error);
}
// Ok, binary data.
function test5() {
return navigator.mozApps.getLocalizationResource("fr", "2.2", "./app.properties", "binary")
.then(success, error);
}
// Ok, text data.
function test6() {
return navigator.mozApps.getLocalizationResource("fr", "2.2", "./app.properties", "text")
.then(success, error);
}
// Ok, json data.
function test7() {
return navigator.mozApps.getLocalizationResource("fr", "2.2", "./app.json", "json")
.then(successJSON, error);
}
function run() {
test1().then(test2)
.then(test3)
.then(test4)
.then(test5)
.then(test6)
.then(test7);
}
</script>
</head>
<body onload="run()">
<h1>Langpack Test : resources</h1>
</body>
</html>

Просмотреть файл

@ -22,6 +22,7 @@ support-files =
file_widget_app.template.webapp file_widget_app.template.webapp
file_widget_app.template.html file_widget_app.template.html
file_test_widget.js file_test_widget.js
langpack/*
signed_app.sjs signed_app.sjs
signed_app_template.webapp signed_app_template.webapp
signed/* signed/*
@ -38,6 +39,8 @@ skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in moch
[test_import_export.html] [test_import_export.html]
[test_install_multiple_apps_origin.html] [test_install_multiple_apps_origin.html]
[test_install_receipts.html] [test_install_receipts.html]
[test_langpacks.html]
skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in mochitest app
[test_marketplace_pkg_install.html] [test_marketplace_pkg_install.html]
skip-if = buildapp == "b2g" || toolkit == "android" # see bug 989806 skip-if = buildapp == "b2g" || toolkit == "android" # see bug 989806
[test_packaged_app_install.html] [test_packaged_app_install.html]

Просмотреть файл

@ -0,0 +1,221 @@
<!DOCTYPE HTML><!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1108096
-->
<html>
<head>
<meta charset="utf-8">
<title>Test for Bug 1108096 - Langpack support</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<script type="application/javascript;version=1.7">
/**
* Test for Bug 1108096
* This file covers testing langpacks.
*
* The setup is as follows:
* - app is the localizable application.
* - langpack1 provides the French locale.
* - langpack2 provides the German and Polish locales.
*/
SimpleTest.waitForExplicitFinish();
const uriPrefix = "http://mochi.test:8888/tests/dom/apps/tests/langpack/";
let appManifestURL = uriPrefix + "manifest.webapp";
let lang1ManifestURL = uriPrefix + "lang1.webapp";
let lang2ManifestURL = uriPrefix + "lang2.webapp";
let gGenerator = runTest();
function go() {
gGenerator.next();
}
function continueTest() {
try {
gGenerator.next();
} catch (e if e instanceof StopIteration) {
SimpleTest.finish();
}
}
function mozAppsError() {
ok(false, "mozApps error: " + this.error.name);
SimpleTest.finish();
}
// Triggers one navigation test to the given page.
// Waits for alert() messages before tearing down the iframe.
function openPage(pageURL, messages) {
info("Navigating to " + pageURL);
let ifr = document.createElement("iframe");
let listener = function(event) {
let message = messages.shift();
is(event.detail.message, message, "Checking alert message for " + pageURL);
if (messages.length == 0) {
ifr.removeEventListener("mozbrowsershowmodalprompt", listener);
ifr.parentNode.removeChild(ifr);
continueTest();
}
}
ifr.addEventListener("mozbrowsershowmodalprompt", listener, false);
// Open the app url in an iframe.
ifr.setAttribute("mozapp", appManifestURL);
ifr.setAttribute("mozbrowser", "true");
ifr.setAttribute("src", uriPrefix + pageURL);
document.getElementById("container").appendChild(ifr);
}
let apps = [];
function installApp(manifestURL) {
info("About to install app at " + manifestURL);
let req = navigator.mozApps.install(manifestURL);
req.onsuccess = function() {
is(req.result.manifestURL, manifestURL, "app installed");
if (req.result.installState == "installed") {
is(req.result.installState, "installed", "app downloaded");
continueTest();
} else {
req.result.ondownloadapplied = function() {
is(req.result.installState, "installed", "app downloaded");
continueTest();
}
}
}
req.onerror = mozAppsError;
}
function runTest() {
// Set up.
SpecialPowers.setAllAppsLaunchable(true);
SpecialPowers.pushPrefEnv({'set': [
["dom.mozBrowserFramesEnabled", true],
["dom.apps.allow_unsigned_langpacks", true] ]},continueTest);
yield undefined;
SpecialPowers.pushPermissions(
[{ "type": "webapps-manage", "allow": 1, "context": document },
{ "type": "embed-apps", "allow": 1, "context": document },
{ "type": "browser", "allow": 1, "context": document } ],
continueTest);
yield undefined;
navigator.mozApps.mgmt.oninstall = function(evt) {
apps.push(evt.application);
};
let _ = JSON.stringify;
SpecialPowers.autoConfirmAppInstall(continueTest);
yield undefined;
SpecialPowers.autoConfirmAppUninstall(continueTest);
yield undefined;
// Install test app.
installApp(appManifestURL);
yield undefined;
// Opens the iframe to the test page, initial state.
// No locale is available.
openPage("index.html", [_({})]);
yield undefined;
// Install the fr langpack.
installApp(lang1ManifestURL);
yield undefined;
// Opens the iframe to the test page.
// Only the French locale is available.
openPage("index.html",
[_({"fr":[{"version":201411051234,"name":"Français","target":"2.2"}]})]);
yield undefined;
// Install the de and pl langpack.
installApp(lang2ManifestURL);
yield undefined;
// Opens the iframe to the test page.
// French, German and Polish locales are available.
openPage("index.html",
[_({"fr":[{"version":201411051234,"name":"Français","target":"2.2"}],"de":[{"version":201411051234,"name":"Deutsch","target":"2.2"}],"pl":[{"version":201411051234,"name":"Polski","target":"2.2"}]})]);
yield undefined;
// Uninstall the second langpack.
{
let app = apps.pop();
info("Uninstalling " + app.manifestURL);
req = navigator.mozApps.mgmt.uninstall(app);
req.onsuccess = continueTest;
req.onerror = mozAppsError;
yield undefined;
}
// Opens the iframe to the test page.
// Only the French locale is available.
openPage("index.html",
[_({"fr":[{"version":201411051234,"name":"Français","target":"2.2"}]})]);
yield undefined;
// Uninstall the first langpack.
{
let app = apps.pop();
info("Uninstalling " + app.manifestURL);
req = navigator.mozApps.mgmt.uninstall(app);
req.onsuccess = continueTest;
req.onerror = mozAppsError;
yield undefined;
}
// Opens the iframe to the test page, initial state.
// No locale is available.
openPage("index.html",
["{}"]);
yield undefined;
// Opens the iframe to the event test page.
// Will get additionallanguageschange events.
openPage("event.html",
[_({"fr":[{"version":201411051234,"name":"Français","target":"2.2"}]}),
_({"fr":[{"version":201411051234,"name":"Français","target":"2.2"}],"de":[{"version":201411051234,"name":"Deutsch","target":"2.2"}]}),
_({"fr":[{"version":201411051234,"name":"Français","target":"2.2"}],"de":[{"version":201411051234,"name":"Deutsch","target":"2.2"}],"pl":[{"version":201411051234,"name":"Polski","target":"2.2"}]})]);
yield undefined;
// Opens the iframe to the resource test page.
openPage("resources.html",
["UnavailableResource",
"UnavailableLanguage",
"UnavailableVersion",
"BadUrl",
"hello=Bonjour",
"hello=Bonjour",
_({"hello":"Bonjour"})]);
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>
</head>
<body onload="go()">
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<div id="container"></div>
</body>
</html>

Просмотреть файл

@ -20,7 +20,9 @@
var props = { var props = {
checkInstalled: "function", checkInstalled: "function",
getAdditionalLanguages: "function",
getInstalled: "function", getInstalled: "function",
getLocalizationResource: "function",
getSelf: "function", getSelf: "function",
install: "function", install: "function",
installPackage: "function", installPackage: "function",

Просмотреть файл

@ -9,6 +9,18 @@ dictionary InstallParameters {
sequence<DOMString> categories = []; sequence<DOMString> categories = [];
}; };
dictionary LanguageDesc {
DOMString target;
DOMString version;
DOMString name;
};
enum LocaleResourceType {
"binary",
"json",
"text"
};
[NoInterfaceObject, NavigatorProperty="mozApps", [NoInterfaceObject, NavigatorProperty="mozApps",
JSImplementation="@mozilla.org/webapps;1"] JSImplementation="@mozilla.org/webapps;1"]
interface DOMApplicationsRegistry { interface DOMApplicationsRegistry {
@ -19,6 +31,17 @@ interface DOMApplicationsRegistry {
DOMRequest getSelf(); DOMRequest getSelf();
DOMRequest getInstalled(); DOMRequest getInstalled();
DOMRequest checkInstalled(DOMString manifestUrl); DOMRequest checkInstalled(DOMString manifestUrl);
// Language pack API.
// These promises will be rejected if the page is not in an app context,
// i.e. they are implicitely acting on getSelf().
Promise<MozMap<sequence<LanguageDesc>>> getAdditionalLanguages();
// Resolves to a different object depending on the dataType value.
Promise<any>
getLocalizationResource(DOMString language,
DOMString version,
DOMString path,
LocaleResourceType dataType);
}; };
[JSImplementation="@mozilla.org/webapps/application;1", ChromeOnly] [JSImplementation="@mozilla.org/webapps/application;1", ChromeOnly]
@ -71,18 +94,18 @@ interface DOMApplication : EventTarget {
* https://wiki.mozilla.org/WebAPI/Inter_App_Communication_Alt_proposal * https://wiki.mozilla.org/WebAPI/Inter_App_Communication_Alt_proposal
* *
*/ */
Promise<MozInterAppConnection> connect(DOMString keyword, optional any rules); Promise<MozInterAppConnection> connect(DOMString keyword, optional any rules);
Promise<sequence<MozInterAppMessagePort>> getConnections(); Promise<sequence<MozInterAppMessagePort>> getConnections();
// Receipts handling functions. // Receipts handling functions.
DOMRequest addReceipt(optional DOMString receipt); DOMRequest addReceipt(optional DOMString receipt);
DOMRequest removeReceipt(optional DOMString receipt); DOMRequest removeReceipt(optional DOMString receipt);
DOMRequest replaceReceipt(optional DOMString oldReceipt, DOMRequest replaceReceipt(optional DOMString oldReceipt,
optional DOMString newReceipt); optional DOMString newReceipt);
// Export this app as a shareable Blob. // Export this app as a shareable Blob.
Promise<Blob> export(); Promise<Blob> export();
}; };
[JSImplementation="@mozilla.org/webapps/manager;1", [JSImplementation="@mozilla.org/webapps/manager;1",