Bug 1169633 - [Browser API] getWebManifest(). r=kanru,bholley
☠☠ backed out by 9c45ef119f96 ☠ ☠
authorMarcos Caceres <marcos@marcosc.com>
Wed, 16 Sep 2015 06:55:00 +0200
changeset 295675 dca7021e514aa4bf7905439a3552f0265049c8f2
parent 295674 d2914165999980ea333b3a13732b0d53514b2ab7
child 295676 310477a8720f081626b19e895ad01c2239bdb336
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskanru, bholley
bugs1169633
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1169633 - [Browser API] getWebManifest(). r=kanru,bholley
dom/browser-element/BrowserElementChildPreload.js
dom/browser-element/BrowserElementParent.js
dom/browser-element/mochitest/async.js
dom/browser-element/mochitest/browserElement_getWebManifest.js
dom/browser-element/mochitest/file_illegal_web_manifest.html
dom/browser-element/mochitest/file_web_manifest.html
dom/browser-element/mochitest/file_web_manifest.json
dom/browser-element/mochitest/mochitest-oop.ini
dom/browser-element/mochitest/mochitest.ini
dom/browser-element/mochitest/test_browserElement_inproc_getWebManifest.html
dom/browser-element/mochitest/test_browserElement_oop_getWebManifest.html
dom/browser-element/nsIBrowserElementAPI.idl
dom/html/nsBrowserElement.cpp
dom/html/nsBrowserElement.h
dom/webidl/BrowserElement.webidl
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -7,20 +7,25 @@
 dump("######################## BrowserElementChildPreload.js loaded\n");
 
 var BrowserElementIsReady = false;
 
 var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu }  = Components;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "acs",
                                    "@mozilla.org/audiochannel/service;1",
                                    "nsIAudioChannelService");
+XPCOMUtils.defineLazyModuleGetter(this, "ManifestFinder",
+                                  "resource://gre/modules/ManifestFinder.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ManifestObtainer",
+                                  "resource://gre/modules/ManifestObtainer.jsm");
 
 var kLongestReturnedString = 128;
 
 function debug(msg) {
   //dump("BrowserElementChildPreload - " + msg + "\n");
 }
 
 function sendAsyncMsg(msg, data) {
@@ -246,17 +251,18 @@ BrowserElementChild.prototype = {
       "find-all": this._recvFindAll.bind(this),
       "find-next": this._recvFindNext.bind(this),
       "clear-match": this._recvClearMatch.bind(this),
       "execute-script": this._recvExecuteScript,
       "get-audio-channel-volume": this._recvGetAudioChannelVolume,
       "set-audio-channel-volume": this._recvSetAudioChannelVolume,
       "get-audio-channel-muted": this._recvGetAudioChannelMuted,
       "set-audio-channel-muted": this._recvSetAudioChannelMuted,
-      "get-is-audio-channel-active": this._recvIsAudioChannelActive
+      "get-is-audio-channel-active": this._recvIsAudioChannelActive,
+      "get-web-manifest": this._recvGetWebManifest,
     }
 
     addMessageListener("browser-element-api:call", function(aMessage) {
       if (aMessage.data.msg_name in mmCalls) {
         return mmCalls[aMessage.data.msg_name].apply(self, arguments);
       }
     });
 
@@ -1473,16 +1479,37 @@ BrowserElementChild.prototype = {
     debug("Received isAudioChannelActive message: (" + data.json.id + ")");
 
     let active = acs.isAudioChannelActive(content, data.json.args.audioChannel);
     sendAsyncMsg('got-is-audio-channel-active', {
       id: data.json.id, successRv: active
     });
   },
 
+  _recvGetWebManifest: Task.async(function* (data) {
+    debug(`Received GetWebManifest message: (${data.json.id})`);
+    let manifest = null;
+    let hasManifest = ManifestFinder.contentHasManifestLink(content);
+    if (hasManifest) {
+      try {
+        manifest = yield ManifestObtainer.contentObtainManifest(content);
+      } catch (e) {
+        sendAsyncMsg('got-web-manifest', {
+          id: data.json.id,
+          errorMsg: `Error fetching web manifest: ${e}.`,
+        });
+        return;
+      }
+    }
+    sendAsyncMsg('got-web-manifest', {
+      id: data.json.id,
+      successRv: manifest
+    });
+  }),
+
   _initFinder: function() {
     if (!this._finder) {
       try {
         this._findLimit = Services.prefs.getIntPref("accessibility.typeaheadfind.matchesCountLimit");
       } catch (e) {
         // Pref not available, assume 0, no match counting.
         this._findLimit = 0;
       }
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -205,17 +205,18 @@ BrowserElementParent.prototype = {
       "scrollviewchange": this._handleScrollViewChange,
       "caretstatechanged": this._handleCaretStateChanged,
       "findchange": this._handleFindChange,
       "execute-script-done": this._gotDOMRequestResult,
       "got-audio-channel-volume": this._gotDOMRequestResult,
       "got-set-audio-channel-volume": this._gotDOMRequestResult,
       "got-audio-channel-muted": this._gotDOMRequestResult,
       "got-set-audio-channel-muted": this._gotDOMRequestResult,
-      "got-is-audio-channel-active": this._gotDOMRequestResult
+      "got-is-audio-channel-active": this._gotDOMRequestResult,
+      "got-web-manifest": this._gotDOMRequestResult,
     };
 
     let mmSecuritySensitiveCalls = {
       "audioplaybackchange": this._fireEventFromMsg,
       "showmodalprompt": this._handleShowModalPrompt,
       "contextmenu": this._fireCtxMenuEvent,
       "securitychange": this._fireEventFromMsg,
       "locationchange": this._fireEventFromMsg,
@@ -1028,16 +1029,18 @@ BrowserElementParent.prototype = {
                                  muted: aMuted});
   },
 
   isAudioChannelActive: function(aAudioChannel) {
     return this._sendDOMRequest('get-is-audio-channel-active',
                                 {audioChannel: aAudioChannel});
   },
 
+  getWebManifest: defineDOMRequestMethod('get-web-manifest'),
+
   /**
    * Called when the visibility of the window which owns this iframe changes.
    */
   _ownerVisibilityChange: function() {
     this._sendAsyncMsg('owner-visibility-change',
                        {visible: !this._window.document.hidden});
   },
 
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/async.js
@@ -0,0 +1,78 @@
+/* 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/. */
+
+/*
+ * This is an approximate implementation of ES7's async-await pattern.
+ * see: https://github.com/tc39/ecmascript-asyncawait
+ *
+ * It allows for simple creation of async function and "tasks".
+ *
+ * For example:
+ *
+ *  var myThinger = {
+ *    doAsynThing: async(function*(url){
+ *      var result = yield fetch(url);
+ *      return process(result);
+ *    });
+ * }
+ *
+ * And Task-like things can be created as follows:
+ *
+ * var myTask = async(function*{
+ *   var result = yield fetch(url);
+ *   return result;
+ * });
+ * //returns a promise
+ *
+ * myTask().then(doSomethingElse);
+ *
+ */
+
+(function(exports) {
+  "use strict";
+  function async(func, self) {
+    return function asyncFunction() {
+      const functionArgs = Array.from(arguments);
+      return new Promise(function(resolve, reject) {
+        var gen;
+        if (typeof func !== "function") {
+          reject(new TypeError("Expected a Function."));
+        }
+        //not a generator, wrap it.
+        if (func.constructor.name !== "GeneratorFunction") {
+          gen = (function*() {
+            return func.apply(self, functionArgs);
+          }());
+        } else {
+          gen = func.apply(self, functionArgs);
+        }
+        try {
+          step(gen.next(undefined));
+        } catch (err) {
+          reject(err);
+        }
+
+        function step({value, done}) {
+          if (done) {
+            return resolve(value);
+          }
+          if (value instanceof Promise) {
+            return value.then(
+              result => step(gen.next(result)),
+              error => {
+                try {
+                  step(gen.throw(error));
+                } catch (err) {
+                  throw err;
+                }
+              }
+            ).catch(err => reject(err));
+          }
+          step(gen.next(value));
+        }
+      });
+    };
+  }
+  exports.async = async;
+}(this || self));
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/browserElement_getWebManifest.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the public domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/*globals async, ok, is, SimpleTest, browserElementTestHelpers*/
+
+// Bug 1169633 - getWebManifest tests
+'use strict';
+SimpleTest.waitForExplicitFinish();
+browserElementTestHelpers.setEnabledPref(true);
+browserElementTestHelpers.addPermission();
+
+// request to load a manifest from a page that doesn't have a manifest.
+// The expected result to be null.
+var test1 = async(function* () {
+  var manifest = yield requestManifest('file_empty.html');
+  is(manifest, null, 'it should be null.');
+});
+
+// request to load a manifest from a page that has a manifest.
+// The expected manifest to have a property name whose value is 'pass'.
+var test2 = async(function* () {
+  var manifest = yield requestManifest('file_web_manifest.html');
+  is(manifest && manifest.name, 'pass', 'it should return a manifest with name pass.');
+});
+
+// Cause an exception by attempting to fetch a file URL,
+// expect onerror to be called.
+var test3 = async(function* () {
+  var gotError = false;
+  try {
+    yield requestManifest('file_illegal_web_manifest.html');
+  } catch (err) {
+    gotError = true;
+  }
+  ok(gotError, 'onerror was called on the DOMRequest.');
+});
+
+// Run the tests
+Promise
+  .all([test1(), test2(), test3()])
+  .then(SimpleTest.finish);
+
+function requestManifest(url) {
+  var iframe = document.createElement('iframe');
+  iframe.setAttribute('mozbrowser', 'true');
+  iframe.src = url;
+  document.body.appendChild(iframe);
+  return new Promise((resolve, reject) => {
+    iframe.addEventListener('mozbrowserloadend', function loadend() {
+      iframe.removeEventListener('mozbrowserloadend', loadend);
+      SimpleTest.executeSoon(() => {
+        var req = iframe.getWebManifest();
+        req.onsuccess = () => {
+          document.body.removeChild(iframe);
+          resolve(req.result);
+        };
+        req.onerror = () => {
+          document.body.removeChild(iframe);
+          reject(new Error(req.error));
+        };
+      });
+    });
+  });
+}
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/file_illegal_web_manifest.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset=utf-8>
+<head>
+<link rel="manifest" href="file://this_is_not_allowed!">
+</head>
+<h1>Support Page for Web Manifest Tests</h1>
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/file_web_manifest.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset=utf-8>
+<head>
+<link rel="manifest" href="file_web_manifest.json">
+</head>
+<h1>Support Page for Web Manifest Tests</h1>
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/file_web_manifest.json
@@ -0,0 +1,1 @@
+{"name": "pass"}
--- a/dom/browser-element/mochitest/mochitest-oop.ini
+++ b/dom/browser-element/mochitest/mochitest-oop.ini
@@ -109,8 +109,9 @@ disabled = bug 930449
 disabled = bug 924771
 [test_browserElement_oop_CloseApp.html]
 disabled = bug 924771
 [test_browserElement_oop_ExposableURI.html]
 disabled = bug 924771
 [test_browserElement_oop_GetContentDimensions.html]
 [test_browserElement_oop_AudioChannel.html]
 [test_browserElement_oop_SetNFCFocus.html]
+[test_browserElement_oop_getWebManifest.html]
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -1,13 +1,14 @@
 [DEFAULT]
 skip-if = buildapp == 'mulet' || (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) || e10s
 support-files =
   ../../../browser/base/content/test/general/audio.ogg
   ../../../dom/media/test/short-video.ogv
+  async.js
   browserElementTestHelpers.js
   browserElement_Alert.js
   browserElement_AlertInFrame.js
   browserElement_AllowEmbedAppsInNestedOOIframe.js
   browserElement_AppFramePermission.js
   browserElement_AppWindowNamespace.js
   browserElement_AudioPlayback.js
   browserElement_Auth.js
@@ -32,16 +33,17 @@ support-files =
   browserElement_ExecuteScript.js
   browserElement_ExposableURI.js
   browserElement_Find.js
   browserElement_FirstPaint.js
   browserElement_ForwardName.js
   browserElement_FrameWrongURI.js
   browserElement_GetScreenshot.js
   browserElement_GetScreenshotDppx.js
+  browserElement_getWebManifest.js
   browserElement_Iconchange.js
   browserElement_LoadEvents.js
   browserElement_Manifestchange.js
   browserElement_Metachange.js
   browserElement_NextPaint.js
   browserElement_OpenNamed.js
   browserElement_OpenTab.js
   browserElement_OpenWindow.js
@@ -123,20 +125,24 @@ support-files =
   file_focus.html
   file_http_401_response.sjs
   file_http_407_response.sjs
   file_inputmethod.html
   file_post_request.html
   file_wyciwyg.html
   file_audio.html
   iframe_file_audio.html
+  file_web_manifest.html
+  file_web_manifest.json
+  file_illegal_web_manifest.html
 
 # Note: browserElementTestHelpers.js looks at the test's filename to determine
 # whether the test should be OOP.  "_oop_" signals OOP, "_inproc_" signals in
 # process.  Default is OOP.
+[test_browserElement_inproc_getWebManifest.html]
 [test_browserElement_NoAttr.html]
 [test_browserElement_NoPref.html]
 [test_browserElement_NoPermission.html]
 [test_browserElement_inproc_Alert.html]
 [test_browserElement_inproc_Viewmode.html]
 [test_browserElement_inproc_ThemeColor.html]
 skip-if = buildapp == 'b2g'
 [test_browserElement_inproc_AlertInFrame.html]
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/test_browserElement_inproc_getWebManifest.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Bug 1169633</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript;version=1.8"
+  src="async.js">
+</script>
+<script type="application/javascript;version=1.8"
+  src="browserElement_getWebManifest.js">
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/test_browserElement_oop_getWebManifest.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Bug 1169633</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="browserElementTestHelpers.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script type="application/javascript;version=1.8"
+  src="async.js"></script>
+<script type="application/javascript;version=1.8"
+  src="browserElement_getWebManifest.js">
+</script>
+</body>
+</html>
--- a/dom/browser-element/nsIBrowserElementAPI.idl
+++ b/dom/browser-element/nsIBrowserElementAPI.idl
@@ -4,34 +4,34 @@
  * 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/. */
 
 #include "nsISupports.idl"
 
 interface nsIDOMDOMRequest;
 interface nsIFrameLoader;
 
-[scriptable, function, uuid(c0c2dd9b-41ef-42dd-a4c1-e456619c1941)]
+[scriptable, function, uuid(00d0e19d-bd67-491f-8e85-b9905224d3bb)]
 interface nsIBrowserElementNextPaintListener : nsISupports
 {
   void recvNextPaint();
 };
 
 %{C++
 #define BROWSER_ELEMENT_API_CONTRACTID "@mozilla.org/dom/browser-element-api;1"
 #define BROWSER_ELEMENT_API_CID                                 \
     { 0x651db7e3, 0x1734, 0x4536,                               \
       { 0xb1, 0x5a, 0x5b, 0x3a, 0xe6, 0x44, 0x13, 0x4c } }
 %}
 
 /**
  * Interface to the BrowserElementParent implementation. All methods
  * but setFrameLoader throw when the remote process is dead.
  */
-[scriptable, uuid(56bd3e12-4a8b-422a-89fc-6dc25aa30aa2)]
+[scriptable, uuid(1e098c3a-7d65-452d-a2b2-9ffd1b6e04bb)]
 interface nsIBrowserElementAPI : nsISupports
 {
   const long FIND_CASE_SENSITIVE = 0;
   const long FIND_CASE_INSENSITIVE = 1;
 
   const long FIND_FORWARD = 0;
   const long FIND_BACKWARD = 1;
 
@@ -95,9 +95,11 @@ interface nsIBrowserElementAPI : nsISupp
   nsIDOMDOMRequest getAudioChannelMuted(in uint32_t audioChannel);
   nsIDOMDOMRequest setAudioChannelMuted(in uint32_t audioChannel, in bool muted);
 
   nsIDOMDOMRequest isAudioChannelActive(in uint32_t audioChannel);
 
   void setNFCFocus(in boolean isFocus);
 
   nsIDOMDOMRequest executeScript(in DOMString script, in jsval options);
+
+  nsIDOMDOMRequest getWebManifest();
 };
--- a/dom/html/nsBrowserElement.cpp
+++ b/dom/html/nsBrowserElement.cpp
@@ -742,9 +742,26 @@ nsBrowserElement::ExecuteScript(const ns
       aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     }
     return nullptr;
   }
 
   return req.forget().downcast<DOMRequest>();
 }
 
+already_AddRefed<DOMRequest>
+nsBrowserElement::GetWebManifest(ErrorResult& aRv)
+{
+  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  nsresult rv = mBrowserElementAPI->GetWebManifest(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>();
+}
+
+
 } // namespace mozilla
--- a/dom/html/nsBrowserElement.h
+++ b/dom/html/nsBrowserElement.h
@@ -107,16 +107,18 @@ public:
 
   already_AddRefed<dom::DOMRequest> SetInputMethodActive(bool isActive,
                                                          ErrorResult& aRv);
 
   already_AddRefed<dom::DOMRequest> ExecuteScript(const nsAString& aScript,
                                                   const dom::BrowserElementExecuteScriptOptions& aOptions,
                                                   ErrorResult& aRv);
 
+  already_AddRefed<dom::DOMRequest> GetWebManifest(ErrorResult& aRv);
+
   void SetNFCFocus(bool isFocus,
                    ErrorResult& aRv);
 
 protected:
   NS_IMETHOD_(already_AddRefed<nsFrameLoader>) GetFrameLoader() = 0;
   void InitBrowserElementAPI();
   nsCOMPtr<nsIBrowserElementAPI> mBrowserElementAPI;
   nsTArray<nsRefPtr<dom::BrowserElementAudioChannel>> mBrowserElementAudioChannels;
--- a/dom/webidl/BrowserElement.webidl
+++ b/dom/webidl/BrowserElement.webidl
@@ -169,10 +169,14 @@ interface BrowserElementPrivileged {
    CheckAnyPermissions="browser"]
   void clearMatch();
 
   [Throws,
    Pref="dom.mozBrowserFramesEnabled",
    CheckAllPermissions="browser browser:universalxss"]
   DOMRequest executeScript(DOMString script,
                            optional BrowserElementExecuteScriptOptions options);
+  [Throws,
+   Pref="dom.mozBrowserFramesEnabled",
+   CheckAllPermissions="browser"]
+  DOMRequest getWebManifest();
 
 };