Bug 1044736 - Part 6. Widget should only require embed-widgets permission. r=fabrice
authorKan-Ru Chen (陳侃如) <kanru@kanru.info>
Wed, 12 Nov 2014 19:27:32 +0800
changeset 215152 b1a6ecfca67465ee773cd85ad99749a1a0727bd6
parent 215151 61fc517d0336ad0ce36db3beab213f7c3a7201b0
child 215153 e0b32a35fe6fa5cb95a85640e312adc678b06c34
push id12091
push userkchen@mozilla.com
push dateWed, 12 Nov 2014 11:27:55 +0000
treeherderb2g-inbound@b1a6ecfca674 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs1044736
milestone36.0a1
Bug 1044736 - Part 6. Widget should only require embed-widgets permission. r=fabrice As discussed on dev-webapi[1] the app that wants to use widgets only need the "embed-widgets" permission to use <iframe mozbrowser mozwidget> If the app also wants to implement a browser, it could request the "browser" separately. A <iframe mozbrowser mozwidget> will have restricted mozbrowser API defined on the prototype if the embedder has the "brower" permission; they will always throw when used. [1]: https://groups.google.com/d/msg/mozilla.dev.webapi/uQweGWtVKRA/Bj1jZq3LN-0J
dom/apps/tests/file_test_widget.js
dom/apps/tests/mochitest.ini
dom/apps/tests/test_widget.html
dom/apps/tests/test_widget_browser.html
dom/html/nsBrowserElement.cpp
dom/html/nsBrowserElement.h
dom/html/nsGenericHTMLFrameElement.cpp
--- a/dom/apps/tests/file_test_widget.js
+++ b/dom/apps/tests/file_test_widget.js
@@ -1,11 +1,12 @@
 var gWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=widget&getmanifest=true';
 var gInvalidWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=invalidWidget&getmanifest=true';
 var gApp;
+var gHasBrowserPermission;
 
 function onError() {
   ok(false, "Error callback invoked");
   finish();
 }
 
 function installApp(path) {
   var request = navigator.mozApps.install(path);
@@ -77,31 +78,44 @@ function testApp(isValidWidget) {
     ifr.addEventListener(topic, function() {
       ok(false, topic + " should be hidden");
     }, false);
   });
 }
 
 function testLimitedBrowserAPI(ifr) {
   var securitySensitiveCalls = [
-    'sendMouseEvent',
-    'sendTouchEvent',
-    'goBack',
-    'goForward',
-    'reload',
-    'stop',
-    'download',
-    'purgeHistory',
-    'getScreenshot',
-    'zoom',
-    'getCanGoBack',
-    'getCanGoForward'
+    { api: 'sendMouseEvent'      , args: ['mousedown', 0, 0, 0, 0, 0] },
+    { api: 'sendTouchEvent'      , args: ['touchstart', [0], [0], [0], [1], [1], [0], [1], 1, 0] },
+    { api: 'goBack'              , args: [] },
+    { api: 'goForward'           , args: [] },
+    { api: 'reload'              , args: [] },
+    { api: 'stop'                , args: [] },
+    { api: 'download'            , args: ['http://example.org'] },
+    { api: 'purgeHistory'        , args: [] },
+    { api: 'getScreenshot'       , args: [0, 0] },
+    { api: 'zoom'                , args: [0.1] },
+    { api: 'getCanGoBack'        , args: [] },
+    { api: 'getCanGoForward'     , args: [] },
+    { api: 'getContentDimensions', args: [] }
   ];
   securitySensitiveCalls.forEach( function(call) {
-    is(typeof ifr[call], "undefined", call + " should be hidden for widget");
+    if (gHasBrowserPermission) {
+      isnot(typeof ifr[call.api], "undefined", call.api + " should be defined");
+      var didThrow;
+      try {
+        ifr[call.api].apply(ifr, call.args);
+      } catch (e) {
+        ok(e instanceof DOMException, "throw right exception type");
+        didThrow = e.code;
+      }
+      is(didThrow, DOMException.INVALID_NODE_TYPE_ERR, "call " + call.api + " should throw exception");
+    } else {
+      is(typeof ifr[call.api], "undefined", call.api + " should be hidden for widget");
+    }
   });
 }
 
 function loadFrameScript(mm) {
   var script = 'data:,\
   function ok(p, msg) { \
   if (p) { \
   sendAsyncMessage("OK", msg); \
@@ -149,17 +163,17 @@ function loadFrameScript(mm) {
   ';
   mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
 }
 
 var tests = [
   // Permissions
   function() {
     SpecialPowers.pushPermissions(
-      [{ "type": "browser", "allow": 1, "context": document },
+      [{ "type": "browser", "allow": gHasBrowserPermission ? 1 : 0, "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],
--- a/dom/apps/tests/mochitest.ini
+++ b/dom/apps/tests/mochitest.ini
@@ -40,8 +40,10 @@ skip-if = (toolkit == 'android' && proce
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_receipt_operations.html]
 [test_signed_pkg_install.html]
 [test_uninstall_errors.html]
 [test_theme_role.html]
 [test_web_app_install.html]
 [test_widget.html]
 skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in mochitest app
+[test_widget_browser.html]
+skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in mochitest app
--- a/dom/apps/tests/test_widget.html
+++ b/dom/apps/tests/test_widget.html
@@ -6,12 +6,13 @@
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="file_test_widget.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">
   SimpleTest.waitForExplicitFinish();
+  gHasBrowserPermission = false;
   runTest();
   </script>
 </body>
 </html>
copy from dom/apps/tests/test_widget.html
copy to dom/apps/tests/test_widget_browser.html
--- a/dom/apps/tests/test_widget.html
+++ b/dom/apps/tests/test_widget_browser.html
@@ -6,12 +6,13 @@
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="file_test_widget.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">
   SimpleTest.waitForExplicitFinish();
+  gHasBrowserPermission = true;
   runTest();
   </script>
 </body>
 </html>
--- a/dom/html/nsBrowserElement.cpp
+++ b/dom/html/nsBrowserElement.cpp
@@ -109,36 +109,49 @@ nsBrowserElement::IsBrowserElementOrThro
 {
   if (mBrowserElementAPI) {
     return true;
   }
   aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
   return false;
 }
 
+bool
+nsBrowserElement::IsNotWidgetOrThrow(ErrorResult& aRv)
+{
+  if (!mOwnerIsWidget) {
+    return true;
+  }
+  aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
+  return false;
+}
+
 void
 nsBrowserElement::InitBrowserElementAPI()
 {
   bool isBrowserOrApp;
   nsCOMPtr<nsIFrameLoader> frameLoader = GetFrameLoader();
   NS_ENSURE_TRUE_VOID(frameLoader);
   nsresult rv = frameLoader->GetOwnerIsBrowserOrAppFrame(&isBrowserOrApp);
   NS_ENSURE_SUCCESS_VOID(rv);
+  rv = frameLoader->GetOwnerIsWidget(&mOwnerIsWidget);
+  NS_ENSURE_SUCCESS_VOID(rv);
 
   if (!isBrowserOrApp) {
     return;
   }
 
   mBrowserElementAPI = do_CreateInstance("@mozilla.org/dom/browser-element-api;1");
   if (mBrowserElementAPI) {
     mBrowserElementAPI->SetFrameLoader(frameLoader);
   }
 }
 
 nsBrowserElement::nsBrowserElement()
+  : mOwnerIsWidget(false)
 {
   mObserver = new BrowserShownObserver(this);
   mObserver->AddObserver();
 }
 
 nsBrowserElement::~nsBrowserElement()
 {
   mObserver->RemoveObserver();
@@ -205,16 +218,17 @@ nsBrowserElement::SendMouseEvent(const n
                                  uint32_t aX,
                                  uint32_t aY,
                                  uint32_t aButton,
                                  uint32_t aClickCount,
                                  uint32_t aModifiers,
                                  ErrorResult& aRv)
 {
   NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
 
   nsresult rv = mBrowserElementAPI->SendMouseEvent(aType,
                                                    aX,
                                                    aY,
                                                    aButton,
                                                    aClickCount,
                                                    aModifiers);
 
@@ -232,16 +246,17 @@ nsBrowserElement::SendTouchEvent(const n
                                  const Sequence<uint32_t>& aRys,
                                  const Sequence<float>& aRotationAngles,
                                  const Sequence<float>& aForces,
                                  uint32_t aCount,
                                  uint32_t aModifiers,
                                  ErrorResult& aRv)
 {
   NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
 
   if (aIdentifiers.Length() != aCount ||
       aXs.Length() != aCount ||
       aYs.Length() != aCount ||
       aRxs.Length() != aCount ||
       aRys.Length() != aCount ||
       aRotationAngles.Length() != aCount ||
       aForces.Length() != aCount) {
@@ -264,66 +279,71 @@ nsBrowserElement::SendTouchEvent(const n
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
 void
 nsBrowserElement::GoBack(ErrorResult& aRv)
 {
   NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
 
   nsresult rv = mBrowserElementAPI->GoBack();
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
 void
 nsBrowserElement::GoForward(ErrorResult& aRv)
 {
   NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
 
   nsresult rv = mBrowserElementAPI->GoForward();
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
 void
 nsBrowserElement::Reload(bool aHardReload, ErrorResult& aRv)
 {
   NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
 
   nsresult rv = mBrowserElementAPI->Reload(aHardReload);
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
 void
 nsBrowserElement::Stop(ErrorResult& aRv)
 {
   NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
 
   nsresult rv = mBrowserElementAPI->Stop();
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
 already_AddRefed<DOMRequest>
 nsBrowserElement::Download(const nsAString& aUrl,
                            const BrowserElementDownloadOptions& aOptions,
                            ErrorResult& aRv)
 {
   NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
 
   nsCOMPtr<nsIDOMDOMRequest> req;
   AutoJSAPI jsapi;
   jsapi.Init();
   JS::Rooted<JS::Value> options(jsapi.cx());
   if (!ToJSValue(jsapi.cx(), aOptions, &options)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
@@ -337,16 +357,17 @@ nsBrowserElement::Download(const nsAStri
 
   return req.forget().downcast<DOMRequest>();
 }
 
 already_AddRefed<DOMRequest>
 nsBrowserElement::PurgeHistory(ErrorResult& aRv)
 {
   NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
 
   nsCOMPtr<nsIDOMDOMRequest> req;
   nsresult rv = mBrowserElementAPI->PurgeHistory(getter_AddRefs(req));
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
@@ -356,16 +377,17 @@ nsBrowserElement::PurgeHistory(ErrorResu
 
 already_AddRefed<DOMRequest>
 nsBrowserElement::GetScreenshot(uint32_t aWidth,
                                 uint32_t aHeight,
                                 const nsAString& aMimeType,
                                 ErrorResult& aRv)
 {
   NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
 
   nsCOMPtr<nsIDOMDOMRequest> req;
   nsresult rv = mBrowserElementAPI->GetScreenshot(aWidth, aHeight, aMimeType,
                                                   getter_AddRefs(req));
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     if (rv == NS_ERROR_INVALID_ARG) {
       aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
@@ -377,59 +399,64 @@ nsBrowserElement::GetScreenshot(uint32_t
 
   return req.forget().downcast<DOMRequest>();
 }
 
 void
 nsBrowserElement::Zoom(float aZoom, ErrorResult& aRv)
 {
   NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
+  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
+
   nsresult rv = mBrowserElementAPI->Zoom(aZoom);
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
 already_AddRefed<DOMRequest>
 nsBrowserElement::GetCanGoBack(ErrorResult& aRv)
 {
   NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
 
   nsCOMPtr<nsIDOMDOMRequest> req;
   nsresult rv = mBrowserElementAPI->GetCanGoBack(getter_AddRefs(req));
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   return req.forget().downcast<DOMRequest>();
 }
 
 already_AddRefed<DOMRequest>
 nsBrowserElement::GetCanGoForward(ErrorResult& aRv)
 {
   NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
 
   nsCOMPtr<nsIDOMDOMRequest> req;
   nsresult rv = mBrowserElementAPI->GetCanGoForward(getter_AddRefs(req));
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   return req.forget().downcast<DOMRequest>();
 }
 
 already_AddRefed<DOMRequest>
 nsBrowserElement::GetContentDimensions(ErrorResult& aRv)
 {
   NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
 
   nsCOMPtr<nsIDOMDOMRequest> req;
   nsresult rv = mBrowserElementAPI->GetContentDimensions(getter_AddRefs(req));
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
--- a/dom/html/nsBrowserElement.h
+++ b/dom/html/nsBrowserElement.h
@@ -91,16 +91,18 @@ public:
 
 protected:
   NS_IMETHOD_(already_AddRefed<nsFrameLoader>) GetFrameLoader() = 0;
   nsCOMPtr<nsIBrowserElementAPI> mBrowserElementAPI;
 
 private:
   void InitBrowserElementAPI();
   bool IsBrowserElementOrThrow(ErrorResult& aRv);
+  bool IsNotWidgetOrThrow(ErrorResult& aRv);
+  bool mOwnerIsWidget;
 
   class BrowserShownObserver;
   friend class BrowserShownObserver;
   nsRefPtr<BrowserShownObserver> mObserver;
 };
 
 } // namespace mozilla
 
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -302,16 +302,20 @@ nsGenericHTMLFrameElement::GetReallyIsBr
   nsIPrincipal *principal = NodePrincipal();
   nsCOMPtr<nsIPermissionManager> permMgr =
     services::GetPermissionManager();
   NS_ENSURE_TRUE(permMgr, NS_OK);
 
   uint32_t permission = nsIPermissionManager::DENY_ACTION;
   nsresult rv = permMgr->TestPermissionFromPrincipal(principal, "browser", &permission);
   NS_ENSURE_SUCCESS(rv, NS_OK);
+  if (permission != nsIPermissionManager::ALLOW_ACTION) {
+    rv = permMgr->TestPermissionFromPrincipal(principal, "embed-widgets", &permission);
+    NS_ENSURE_SUCCESS(rv, NS_OK);
+  }
   *aOut = permission == nsIPermissionManager::ALLOW_ACTION;
   return NS_OK;
 }
 
 /* [infallible] */ NS_IMETHODIMP
 nsGenericHTMLFrameElement::GetReallyIsApp(bool *aOut)
 {
   nsAutoString manifestURL;