diff --git a/content/base/public/nsIFrameLoader.idl b/content/base/public/nsIFrameLoader.idl index 9f3c21c27784..4e91d82e3511 100644 --- a/content/base/public/nsIFrameLoader.idl +++ b/content/base/public/nsIFrameLoader.idl @@ -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; }; diff --git a/content/base/src/nsGkAtomList.h b/content/base/src/nsGkAtomList.h index 561a628e36f1..c3c9d9a0ab8b 100644 --- a/content/base/src/nsGkAtomList.h +++ b/content/base/src/nsGkAtomList.h @@ -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") diff --git a/content/html/content/src/nsGenericHTMLFrameElement.cpp b/content/html/content/src/nsGenericHTMLFrameElement.cpp index e700f01c0e0b..a015675c7264 100644 --- a/content/html/content/src/nsGenericHTMLFrameElement.cpp +++ b/content/html/content/src/nsGenericHTMLFrameElement.cpp @@ -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 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 appsService = do_GetService(APPS_SERVICE_CONTRACTID); + NS_ENSURE_TRUE_VOID(appsService); + + nsCOMPtr 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 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 appsService = do_GetService(APPS_SERVICE_CONTRACTID); - NS_ENSURE_TRUE(appsService, NS_OK); - - nsCOMPtr app; - appsService->GetAppByManifestURL(manifestURL, getter_AddRefs(app)); - if (app) { - aOut.Assign(manifestURL); - } + aOut.Assign(manifestURL); return NS_OK; } diff --git a/content/html/content/src/nsGenericHTMLFrameElement.h b/content/html/content/src/nsGenericHTMLFrameElement.h index 8f7386e916df..de3fe18a3936 100644 --- a/content/html/content/src/nsGenericHTMLFrameElement.h +++ b/content/html/content/src/nsGenericHTMLFrameElement.h @@ -88,6 +88,9 @@ protected: { return this; } + +private: + void GetManifestURLByType(nsIAtom *aAppType, nsAString& aOut); }; #endif // nsGenericHTMLFrameElement_h diff --git a/dom/apps/src/AppsUtils.jsm b/dom/apps/src/AppsUtils.jsm index 097a00b1b501..c903387b7340 100644 --- a/dom/apps/src/AppsUtils.jsm +++ b/dom/apps/src/AppsUtils.jsm @@ -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; }, diff --git a/dom/apps/src/PermissionsTable.jsm b/dom/apps/src/PermissionsTable.jsm index dfed59b8efd8..2b09c8277f63 100644 --- a/dom/apps/src/PermissionsTable.jsm +++ b/dom/apps/src/PermissionsTable.jsm @@ -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, diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index d92f14fbc0d5..f9a35e6c4888 100755 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -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; diff --git a/dom/apps/tests/file_app.sjs b/dom/apps/tests/file_app.sjs index fe6164724d09..cef10bd3ae3e 100644 --- a/dom/apps/tests/file_app.sjs +++ b/dom/apps/tests/file_app.sjs @@ -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('', ''); } - 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)); diff --git a/dom/apps/tests/file_widget_app.template.html b/dom/apps/tests/file_widget_app.template.html new file mode 100644 index 000000000000..661e2c5d8a0c --- /dev/null +++ b/dom/apps/tests/file_widget_app.template.html @@ -0,0 +1,68 @@ + + + + + +App Body. Version: VERSIONTOKEN + diff --git a/dom/apps/tests/file_widget_app.template.webapp b/dom/apps/tests/file_widget_app.template.webapp new file mode 100644 index 000000000000..7990043392fa --- /dev/null +++ b/dom/apps/tests/file_widget_app.template.webapp @@ -0,0 +1,11 @@ +{ + "name": "Really Rapid Release (widget)", + "description": "Updated even faster than Firefox, 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" + ] +} diff --git a/dom/apps/tests/mochitest.ini b/dom/apps/tests/mochitest.ini index 8b67d343d118..e5f55fd78c2d 100644 --- a/dom/apps/tests/mochitest.ini +++ b/dom/apps/tests/mochitest.ini @@ -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] diff --git a/dom/apps/tests/test_widget.html b/dom/apps/tests/test_widget.html new file mode 100644 index 000000000000..a4292c0a58df --- /dev/null +++ b/dom/apps/tests/test_widget.html @@ -0,0 +1,127 @@ + + + + + Test for DataStore - basic operation on a readonly db + + + + +
+ + + diff --git a/dom/interfaces/apps/mozIApplication.idl b/dom/interfaces/apps/mozIApplication.idl index ddc7c99797a1..4dfc6e68e07d 100644 --- a/dom/interfaces/apps/mozIApplication.idl +++ b/dom/interfaces/apps/mozIApplication.idl @@ -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; diff --git a/dom/interfaces/html/nsIMozBrowserFrame.idl b/dom/interfaces/html/nsIMozBrowserFrame.idl index fedbec09333c..19783e1fa52b 100644 --- a/dom/interfaces/html/nsIMozBrowserFrame.idl +++ b/dom/interfaces/html/nsIMozBrowserFrame.idl @@ -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.