Bug 1892041 - Part 1: Update test262 features. r=spidermonkey-reviewers,dminor
[gecko.git] / dom / manifest / ManifestObtainer.sys.mjs
blobde2863442a57e20d540c8a000d2b0738a5b17098
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
4  */
5 /*
6  * ManifestObtainer is an implementation of:
7  * http://w3c.github.io/manifest/#obtaining
8  *
9  * Exposes 2 public method:
10  *
11  *  .contentObtainManifest(aContent) - used in content process
12  *  .browserObtainManifest(aBrowser) - used in browser/parent process
13  *
14  * both return a promise. If successful, you get back a manifest object.
15  *
16  * Import it with URL:
17  *   'chrome://global/content/manifestMessages.js'
18  *
19  * e10s IPC message from this components are handled by:
20  *   dom/ipc/manifestMessages.js
21  *
22  * Which is injected into every browser instance via browser.js.
23  */
25 import { ManifestProcessor } from "resource://gre/modules/ManifestProcessor.sys.mjs";
27 export var ManifestObtainer = {
28   /**
29    * Public interface for obtaining a web manifest from a XUL browser, to use
30    * on the parent process.
31    * @param  {XULBrowser} The browser to check for the manifest.
32    * @param {Object} aOptions
33    * @param {Boolean} aOptions.checkConformance If spec conformance messages should be collected.
34    *                                            Adds proprietary moz_* members to manifest.
35    * @return {Promise<Object>} The processed manifest.
36    */
37   async browserObtainManifest(
38     aBrowser,
39     aOptions = { checkConformance: false }
40   ) {
41     if (!isXULBrowser(aBrowser)) {
42       throw new TypeError("Invalid input. Expected XUL browser.");
43     }
45     const actor =
46       aBrowser.browsingContext.currentWindowGlobal.getActor("ManifestMessages");
48     const reply = await actor.sendQuery(
49       "DOM:ManifestObtainer:Obtain",
50       aOptions
51     );
52     if (!reply.success) {
53       const error = toError(reply.result);
54       throw error;
55     }
56     return reply.result;
57   },
58   /**
59    * Public interface for obtaining a web manifest from a XUL browser.
60    * @param {Window} aContent A content Window from which to extract the manifest.
61    * @param {Object} aOptions
62    * @param {Boolean} aOptions.checkConformance If spec conformance messages should be collected.
63    *                                            Adds proprietary moz_* members to manifest.
64    * @return {Promise<Object>} The processed manifest.
65    */
66   async contentObtainManifest(
67     aContent,
68     aOptions = { checkConformance: false }
69   ) {
70     if (!Services.prefs.getBoolPref("dom.manifest.enabled")) {
71       throw new Error(
72         "Obtaining manifest is disabled by pref: dom.manifest.enabled"
73       );
74     }
75     if (!aContent || isXULBrowser(aContent)) {
76       const err = new TypeError("Invalid input. Expected a DOM Window.");
77       return Promise.reject(err);
78     }
79     const response = await fetchManifest(aContent);
80     const result = await processResponse(response, aContent, aOptions);
81     const clone = Cu.cloneInto(result, aContent);
82     return clone;
83   },
86 function toError(aErrorClone) {
87   let error;
88   switch (aErrorClone.name) {
89     case "TypeError":
90       error = new TypeError();
91       break;
92     default:
93       error = new Error();
94   }
95   Object.getOwnPropertyNames(aErrorClone).forEach(
96     name => (error[name] = aErrorClone[name])
97   );
98   return error;
101 function isXULBrowser(aBrowser) {
102   if (!aBrowser || !aBrowser.namespaceURI || !aBrowser.localName) {
103     return false;
104   }
105   const XUL_NS =
106     "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
107   return aBrowser.namespaceURI === XUL_NS && aBrowser.localName === "browser";
111  * Asynchronously processes the result of response after having fetched
112  * a manifest.
113  * @param {Response} aResp Response from fetch().
114  * @param {Window} aContentWindow The content window.
115  * @return {Promise<Object>} The processed manifest.
116  */
117 async function processResponse(aResp, aContentWindow, aOptions) {
118   const badStatus = aResp.status < 200 || aResp.status >= 300;
119   if (aResp.type === "error" || badStatus) {
120     const msg = `Fetch error: ${aResp.status} - ${aResp.statusText} at ${aResp.url}`;
121     throw new Error(msg);
122   }
123   const text = await aResp.text();
124   const args = {
125     jsonText: text,
126     manifestURL: aResp.url,
127     docURL: aContentWindow.location.href,
128   };
129   const processingOptions = Object.assign({}, args, aOptions);
130   const manifest = ManifestProcessor.process(processingOptions);
131   return manifest;
135  * Asynchronously fetches a web manifest.
136  * @param {Window} a The content Window from where to extract the manifest.
137  * @return {Promise<Object>}
138  */
139 async function fetchManifest(aWindow) {
140   if (!aWindow || aWindow.top !== aWindow) {
141     const msg = "Window must be a top-level browsing context.";
142     throw new Error(msg);
143   }
144   const elem = aWindow.document.querySelector("link[rel~='manifest']");
145   if (!elem || !elem.getAttribute("href")) {
146     // There is no actual manifest to fetch, we just return null.
147     return new aWindow.Response("null");
148   }
149   // Throws on malformed URLs
150   const manifestURL = new aWindow.URL(elem.href, elem.baseURI);
151   const reqInit = {
152     credentials: "omit",
153     mode: "cors",
154   };
155   if (elem.crossOrigin === "use-credentials") {
156     reqInit.credentials = "include";
157   }
158   const request = new aWindow.Request(manifestURL, reqInit);
159   request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_MANIFEST);
160   // Can reject...
161   return aWindow.fetch(request);