Bug 1005818 - Part 1: Load a widget as an app if the |src| is in the |widgetPages|. r=fabrice, sr=sicking

1. Add permission |embed-widgets| and Element attribute |mozwidget|
2. Add |hasWidgetPage| in /mozIApplication.idl
3. Check permission |embed-widgets| and the |src| is in the |widgetPages| when |GetAppManifest|
4. Add test case
5. Should enable preference |dom.enable_widgets|
This commit is contained in:
Junior Hsu 2014-08-19 15:14:08 +01:00
Родитель 2e784b9a4d
Коммит 7b304a0c41
14 изменённых файлов: 356 добавлений и 29 удалений

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

@ -288,6 +288,7 @@ interface nsIFrameLoader : nsISupports
/**
* Find out whether the owner content really is a browser or app frame
* Especially, a widget frame is regarded as an app frame.
*/
readonly attribute boolean ownerIsBrowserOrAppFrame;
};

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

@ -91,6 +91,7 @@ GK_ATOM(_and, "and")
GK_ATOM(anonid, "anonid")
GK_ATOM(any, "any")
GK_ATOM(mozapp, "mozapp")
GK_ATOM(mozwidget, "mozwidget")
GK_ATOM(applet, "applet")
GK_ATOM(applyImports, "apply-imports")
GK_ATOM(applyTemplates, "apply-templates")

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

@ -332,6 +332,63 @@ nsGenericHTMLFrameElement::GetIsExpectingSystemMessage(bool *aOut)
return NS_OK;
}
/** Get manifest url of app or widget
* @param AppType: nsGkAtoms::mozapp or nsGkAtoms::mozwidget
*/
void nsGenericHTMLFrameElement::GetManifestURLByType(nsIAtom *aAppType,
nsAString& aManifestURL)
{
aManifestURL.Truncate();
if (aAppType != nsGkAtoms::mozapp && aAppType != nsGkAtoms::mozwidget) {
return;
}
nsAutoString manifestURL;
GetAttr(kNameSpaceID_None, aAppType, manifestURL);
if (manifestURL.IsEmpty()) {
return;
}
// Check permission.
nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
NS_ENSURE_TRUE_VOID(permMgr);
nsIPrincipal *principal = NodePrincipal();
const char* aPermissionType = (aAppType == nsGkAtoms::mozapp) ? "embed-apps"
: "embed-widgets";
uint32_t permission = nsIPermissionManager::DENY_ACTION;
nsresult rv = permMgr->TestPermissionFromPrincipal(principal,
aPermissionType,
&permission);
NS_ENSURE_SUCCESS_VOID(rv);
if (permission != nsIPermissionManager::ALLOW_ACTION) {
return;
}
nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
NS_ENSURE_TRUE_VOID(appsService);
nsCOMPtr<mozIApplication> app;
appsService->GetAppByManifestURL(manifestURL, getter_AddRefs(app));
if (!app) {
return;
}
bool hasWidgetPage = false;
nsAutoString src;
if (aAppType == nsGkAtoms::mozwidget) {
GetAttr(kNameSpaceID_None, nsGkAtoms::src, src);
nsresult rv = app->HasWidgetPage(src, &hasWidgetPage);
if (!NS_SUCCEEDED(rv) || !hasWidgetPage) {
return;
}
}
aManifestURL.Assign(manifestURL);
}
NS_IMETHODIMP
nsGenericHTMLFrameElement::GetAppManifestURL(nsAString& aOut)
{
@ -342,35 +399,36 @@ nsGenericHTMLFrameElement::GetAppManifestURL(nsAString& aOut)
return NS_OK;
}
// Check permission.
nsIPrincipal *principal = NodePrincipal();
nsCOMPtr<nsIPermissionManager> permMgr =
services::GetPermissionManager();
NS_ENSURE_TRUE(permMgr, NS_OK);
nsAutoString appManifestURL;
nsAutoString widgetManifestURL;
uint32_t permission = nsIPermissionManager::DENY_ACTION;
nsresult rv = permMgr->TestPermissionFromPrincipal(principal,
"embed-apps",
&permission);
NS_ENSURE_SUCCESS(rv, NS_OK);
if (permission != nsIPermissionManager::ALLOW_ACTION) {
GetManifestURLByType(nsGkAtoms::mozapp, appManifestURL);
if (Preferences::GetBool("dom.enable_widgets")) {
GetManifestURLByType(nsGkAtoms::mozwidget, widgetManifestURL);
}
bool isApp = !appManifestURL.IsEmpty();
bool isWidget = !widgetManifestURL.IsEmpty();
if (!isApp && !isWidget) {
// No valid case to get manifest
return NS_OK;
}
if (isApp && isWidget) {
NS_WARNING("Can not simultaneously be mozapp and mozwidget");
return NS_OK;
}
nsAutoString manifestURL;
GetAttr(kNameSpaceID_None, nsGkAtoms::mozapp, manifestURL);
if (manifestURL.IsEmpty()) {
return NS_OK;
if (isApp) {
manifestURL.Assign(appManifestURL);
} else if (isWidget) {
manifestURL.Assign(widgetManifestURL);
}
nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
NS_ENSURE_TRUE(appsService, NS_OK);
nsCOMPtr<mozIApplication> app;
appsService->GetAppByManifestURL(manifestURL, getter_AddRefs(app));
if (app) {
aOut.Assign(manifestURL);
}
aOut.Assign(manifestURL);
return NS_OK;
}

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

@ -88,6 +88,9 @@ protected:
{
return this;
}
private:
void GetManifestURLByType(nsIAtom *aAppType, nsAString& aOut);
};
#endif // nsGenericHTMLFrameElement_h

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

@ -57,6 +57,10 @@ mozIApplication.prototype = {
return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
},
hasWidgetPage: function(aPageURL) {
return this.widgetPages.indexOf(aPageURL) != -1;
},
QueryInterface: function(aIID) {
if (aIID.equals(Ci.mozIApplication) ||
aIID.equals(Ci.nsISupports))
@ -100,6 +104,7 @@ function _setAppProperties(aObj, aApp) {
aObj.storeVersion = aApp.storeVersion || 0;
aObj.role = aApp.role || "";
aObj.redirects = aApp.redirects;
aObj.widgetPages = aApp.widgetPages || [];
aObj.kind = aApp.kind;
}
@ -678,6 +683,10 @@ ManifestHelper.prototype = {
return this._localeProp("package_path");
},
get widgetPages() {
return this._localeProp("widgetPages");
},
get size() {
return this._manifest["size"] || 0;
},

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

@ -232,6 +232,11 @@ this.PermissionsTable = { geolocation: {
privileged: DENY_ACTION,
certified: ALLOW_ACTION
},
"embed-widgets": {
app: DENY_ACTION,
privileged: ALLOW_ACTION,
certified: ALLOW_ACTION
},
"storage": {
app: ALLOW_ACTION,
privileged: ALLOW_ACTION,

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

@ -262,6 +262,10 @@ this.DOMApplicationRegistry = {
app.role = "";
}
if (app.widgetPages === undefined) {
app.widgetPages = [];
}
// At startup we can't be downloading, and the $TMP directory
// will be empty so we can't just apply a staged update.
app.downloading = false;
@ -321,6 +325,15 @@ this.DOMApplicationRegistry = {
return res.length > 0 ? res : null;
},
_saveWidgetsFullPath: function(aManifest, aDestApp) {
if (aManifest.widgetPages) {
aDestApp.widgetPages = aManifest.widgetPages.map(aManifest.resolveURL,
aManifest/* thisArg */);
} else {
aDestApp.widgetPages = [];
}
},
// Registers all the activities and system messages.
registerAppsHandlers: Task.async(function*(aRunUpdate) {
this.notifyAppsRegistryStart();
@ -345,6 +358,10 @@ this.DOMApplicationRegistry = {
let app = this.webapps[aResult.id];
app.csp = aResult.manifest.csp || "";
app.role = aResult.manifest.role || "";
let localeManifest = new ManifestHelper(aResult.manifest, app.origin, app.manifestURL);
this._saveWidgetsFullPath(localeManifest, app);
if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
app.redirects = this.sanitizeRedirects(aResult.redirects);
}
@ -984,6 +1001,8 @@ this.DOMApplicationRegistry = {
app.name = manifest.name;
app.csp = manifest.csp || "";
app.role = localeManifest.role;
this._saveWidgetsFullPath(localeManifest, app);
if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
app.redirects = this.sanitizeRedirects(manifest.redirects);
}
@ -2014,6 +2033,8 @@ this.DOMApplicationRegistry = {
aApp.downloadAvailable = true;
aApp.downloadSize = manifest.size;
aApp.updateManifest = aNewManifest;
this._saveWidgetsFullPath(manifest, aApp);
yield this._saveApps();
this.broadcastMessage("Webapps:UpdateState", {
@ -2072,6 +2093,7 @@ this.DOMApplicationRegistry = {
aApp.name = aNewManifest.name;
aApp.csp = manifest.csp || "";
aApp.role = manifest.role || "";
this._saveWidgetsFullPath(manifest, aApp);
aApp.updateTime = Date.now();
} else {
manifest =
@ -2471,6 +2493,7 @@ this.DOMApplicationRegistry = {
appObject.name = aManifest.name;
appObject.csp = aLocaleManifest.csp || "";
appObject.role = aLocaleManifest.role;
this._saveWidgetsFullPath(aLocaleManifest, appObject);
appObject.installerAppId = aData.appId;
appObject.installerIsBrowser = aData.isBrowser;
@ -2506,6 +2529,9 @@ this.DOMApplicationRegistry = {
app.csp = aManifest.csp || "";
let aLocaleManifest = new ManifestHelper(aManifest, app.origin, app.manifestURL);
this._saveWidgetsFullPath(aLocaleManifest, app);
app.appStatus = AppsUtils.getAppManifestStatus(aManifest);
app.removable = true;

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

@ -1,6 +1,7 @@
var gBasePath = "tests/dom/apps/tests/";
var gAppTemplatePath = "tests/dom/apps/tests/file_app.template.html";
var gAppcacheTemplatePath = "tests/dom/apps/tests/file_cached_app.template.appcache";
var gWidgetTemplatePath = "tests/dom/apps/tests/file_widget_app.template.html";
var gDefaultIcon = "default_icon";
function makeResource(templatePath, version, apptype) {
@ -14,7 +15,6 @@ function makeResource(templatePath, version, apptype) {
if (templatePath == gAppTemplatePath && apptype == 'cached') {
res = res.replace('<html>', '<html manifest="file_app.sjs?apptype=cached&getappcache=true">');
}
return res;
}
@ -46,7 +46,7 @@ function handleRequest(request, response) {
// Get the app type.
var apptype = query.apptype;
if (apptype != 'hosted' && apptype != 'cached')
if (apptype != 'hosted' && apptype != 'cached' && apptype != 'widget')
throw "Invalid app type: " + apptype;
// Get the version from server state and handle the etag.
@ -83,7 +83,12 @@ function handleRequest(request, response) {
response.write(makeResource(gAppcacheTemplatePath, version, apptype));
return;
}
else if (apptype == 'widget')
{
response.setHeader("Content-Type", "text/html", false);
response.write(makeResource(gWidgetTemplatePath, version, apptype));
return;
}
// Generate the app.
response.setHeader("Content-Type", "text/html", false);
response.write(makeResource(gAppTemplatePath, version, apptype));

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

@ -0,0 +1,68 @@
<html>
<head>
<script>
function sendMessage(msg) {
alert(msg);
}
function ok(p, msg) {
if (p)
sendMessage("OK: " + msg);
else
sendMessage("KO: " + msg);
}
function is(a, b, msg) {
if (a == b)
sendMessage("OK: " + a + " == " + b + " - " + msg);
else
sendMessage("KO: " + a + " != " + b + " - " + msg);
}
function installed(p) {
if (p)
sendMessage("IS_INSTALLED");
else
sendMessage("NOT_INSTALLED");
}
function finish() {
sendMessage("VERSION: MyWebApp vVERSIONTOKEN");
sendMessage("DONE");
}
function cbError() {
ok(false, "Error callback invoked");
finish();
}
function go() {
ok(true, "Launched app");
var request = window.navigator.mozApps.getSelf();
request.onsuccess = function() {
var widget = request.result;
ok(widget,"Should be a widget");
checkWidget(widget);
}
request.onerror = cbError;
}
function checkWidget(widget) {
// If the widget is installed, |widget| will be non-null. If it is, verify its state.
installed(!!widget);
if (widget) {
var widgetName = "Really Rapid Release (APPTYPETOKEN)";
var manifest = SpecialPowers.wrap(widget.manifest);
is(manifest.name, widgetName, "Manifest name should be correct");
is(widget.origin, "http://test", "Widget origin should be correct");
is(widget.installOrigin, "http://mochi.test:8888", "Install origin should be correct");
}
finish();
}
</script>
</head>
<body onload="go();">
App Body. Version: VERSIONTOKEN
</body></html>

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

@ -0,0 +1,11 @@
{
"name": "Really Rapid Release (widget)",
"description": "Updated even faster than <a href='http://mozilla.org'>Firefox</a>, just to annoy slashdotters.",
"launch_path": "/tests/dom/apps/tests/file_app.sjs?apptype=widget",
"icons": {
"128": "ICONTOKEN"
},
"widgetPages": [
"/tests/dom/apps/tests/file_app.sjs?apptype=widget"
]
}

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

@ -10,6 +10,8 @@ support-files =
file_packaged_app.sjs
file_packaged_app.template.html
file_packaged_app.template.webapp
file_widget_app.template.webapp
file_widget_app.template.html
signed_app.sjs
signed_app_template.webapp
signed/*
@ -28,3 +30,4 @@ skip-if = buildapp == "b2g" || toolkit == "android" # see bug 989806
[test_receipt_operations.html]
[test_signed_pkg_install.html]
[test_uninstall_errors.html]
[test_widget.html]

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

@ -0,0 +1,127 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test for DataStore - basic operation on a readonly db</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<div id="container"></div>
<script type="application/javascript;version=1.7">
var gWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=widget&getmanifest=true';
var gApp;
function cbError() {
ok(false, "Error callback invoked");
finish();
}
function installApp() {
var request = navigator.mozApps.install(gWidgetManifestURL);
request.onerror = cbError;
request.onsuccess = function() {
gApp = request.result;
runTest();
}
}
function uninstallApp() {
// Uninstall the app.
var request = navigator.mozApps.mgmt.uninstall(gApp);
request.onerror = cbError;
request.onsuccess = function() {
// All done.
info("All done");
runTest();
}
}
function testApp() {
var ifr = document.createElement('iframe');
ifr.setAttribute('mozbrowser', 'true');
ifr.setAttribute('mozwidget', gApp.manifestURL);
ifr.setAttribute('src', gApp.origin+gApp.manifest.launch_path);
var domParent = document.getElementById('container');
// Set us up to listen for messages from the app.
var listener = function(e) {
var message = e.detail.message;
if (/^OK/.exec(message)) {
ok(true, "Message from widget: " + message);
} else if (/KO/.exec(message)) {
ok(false, "Message from widget: " + message);
} else if (/DONE/.exec(message)) {
ok(true, "Message from widget complete");
ifr.removeEventListener('mozbrowsershowmodalprompt', listener);
domParent.removeChild(ifr);
runTest();
}
}
// This event is triggered when the app calls "alert".
ifr.addEventListener('mozbrowsershowmodalprompt', listener, false);
domParent.appendChild(ifr);
}
var tests = [
// Permissions
function() {
SpecialPowers.pushPermissions(
[{ "type": "browser", "allow": 1, "context": document },
{ "type": "embed-widgets", "allow": 1, "context": document },
{ "type": "webapps-manage", "allow": 1, "context": document }], runTest);
},
// Preferences
function() {
SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true],
["dom.enable_widgets", true]]}, runTest);
},
function() {
SpecialPowers.setAllAppsLaunchable(true);
runTest();
},
// No confirmation needed when an app is installed
function() {
SpecialPowers.autoConfirmAppInstall(runTest);
},
// Installing the app
installApp,
// Run tests in app
testApp,
// Uninstall the app
uninstallApp
];
function runTest() {
if (!tests.length) {
finish();
return;
}
var test = tests.shift();
test();
}
function finish() {
SimpleTest.finish();
}
if (SpecialPowers.isMainProcess()) {
SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
}
SimpleTest.waitForExplicitFinish();
runTest();
</script>
</body>
</html>

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

@ -11,12 +11,18 @@
* We expose Gecko-internal helpers related to "web apps" through this
* sub-interface.
*/
[scriptable, uuid(7bd62430-c374-49eb-be1b-ce821a180360)]
[scriptable, uuid(1d856b11-ac29-47d3-bd52-a86e3d45fcf4)]
interface mozIApplication: nsISupports
{
/* Return true if this app has |permission|. */
boolean hasPermission(in string permission);
/**
* Return true if this app can be a widget and
* its |widgetPages| contains |page|
*/
boolean hasWidgetPage(in DOMString pageURL);
/* Application status as defined in nsIPrincipal. */
readonly attribute unsigned short appStatus;

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

@ -25,8 +25,11 @@ interface nsIMozBrowserFrame : nsIDOMMozBrowserFrame
* Gets whether this frame really is an app frame.
*
* In order to really be an app frame, this frame must really be a browser
* frame (this requirement will go away eventually), and the frame's mozapp
* attribute must point to the manifest of a valid app.
* frame (this requirement will go away eventually), and must satisfy one
* and only one of the following conditions:
* 1. the frame's mozapp attribute must point to the manifest of a valid app
* 2. the frame's mozwidget attribute must point to the manifest of a valid
* app, and the src should be in the |widgetPages| specified by the manifest.
*/
[infallible] readonly attribute boolean reallyIsApp;
@ -41,7 +44,8 @@ interface nsIMozBrowserFrame : nsIDOMMozBrowserFrame
[infallible] readonly attribute boolean isExpectingSystemMessage;
/**
* Gets this frame's app manifest URL, if the frame really is an app frame.
* Gets this frame's app manifest URL or widget manifest URL, if the frame
* really is an app frame.
* Otherwise, returns the empty string.
*
* This method is guaranteed not to fail.