ManifestObtainer.sys.mjs (5414B)
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 */ 24 25 import { ManifestProcessor } from "resource://gre/modules/ManifestProcessor.sys.mjs"; 26 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 * 32 * @param {XULBrowser} The browser to check for the manifest. 33 * @param {object} aOptions 34 * @param {boolean} aOptions.checkConformance If spec conformance messages should be collected. 35 * Adds proprietary moz_* members to manifest. 36 * @return {Promise<object>} The processed manifest. 37 */ 38 async browserObtainManifest( 39 aBrowser, 40 aOptions = { checkConformance: false } 41 ) { 42 if (!isXULBrowser(aBrowser)) { 43 throw new TypeError("Invalid input. Expected XUL browser."); 44 } 45 46 const actor = 47 aBrowser.browsingContext.currentWindowGlobal.getActor("ManifestMessages"); 48 49 const reply = await actor.sendQuery( 50 "DOM:ManifestObtainer:Obtain", 51 aOptions 52 ); 53 if (!reply.success) { 54 const error = toError(reply.result); 55 throw error; 56 } 57 return reply.result; 58 }, 59 /** 60 * Public interface for obtaining a web manifest from a XUL browser. 61 * 62 * @param {Window} aContent A content Window from which to extract the manifest. 63 * @param {object} aOptions 64 * @param {boolean} aOptions.checkConformance If spec conformance messages should be collected. 65 * Adds proprietary moz_* members to manifest. 66 * @return {Promise<object>} The processed manifest. 67 */ 68 async contentObtainManifest( 69 aContent, 70 aOptions = { checkConformance: false } 71 ) { 72 if (!Services.prefs.getBoolPref("dom.manifest.enabled")) { 73 throw new Error( 74 "Obtaining manifest is disabled by pref: dom.manifest.enabled" 75 ); 76 } 77 if (!aContent || isXULBrowser(aContent)) { 78 const err = new TypeError("Invalid input. Expected a DOM Window."); 79 return Promise.reject(err); 80 } 81 const response = await fetchManifest(aContent); 82 const result = await processResponse(response, aContent, aOptions); 83 const clone = Cu.cloneInto(result, aContent); 84 return clone; 85 }, 86 }; 87 88 function toError(aErrorClone) { 89 let error; 90 switch (aErrorClone.name) { 91 case "TypeError": 92 error = new TypeError(); 93 break; 94 default: 95 error = new Error(); 96 } 97 Object.getOwnPropertyNames(aErrorClone).forEach( 98 name => (error[name] = aErrorClone[name]) 99 ); 100 return error; 101 } 102 103 function isXULBrowser(aBrowser) { 104 if (!aBrowser || !aBrowser.namespaceURI || !aBrowser.localName) { 105 return false; 106 } 107 const XUL_NS = 108 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 109 return aBrowser.namespaceURI === XUL_NS && aBrowser.localName === "browser"; 110 } 111 112 /** 113 * Asynchronously processes the result of response after having fetched 114 * a manifest. 115 * 116 * @param {Response} aResp Response from fetch(). 117 * @param {Window} aContentWindow The content window. 118 * @return {Promise<object>} The processed manifest. 119 */ 120 async function processResponse(aResp, aContentWindow, aOptions) { 121 const badStatus = aResp.status < 200 || aResp.status >= 300; 122 if (aResp.type === "error" || badStatus) { 123 const msg = `Fetch error: ${aResp.status} - ${aResp.statusText} at ${aResp.url}`; 124 throw new Error(msg); 125 } 126 const text = await aResp.text(); 127 const args = { 128 jsonText: text, 129 manifestURL: aResp.url, 130 docURL: aContentWindow.location.href, 131 }; 132 const processingOptions = Object.assign({}, args, aOptions); 133 const manifest = ManifestProcessor.process(processingOptions); 134 return manifest; 135 } 136 137 /** 138 * Asynchronously fetches a web manifest. 139 * 140 * @param {Window} a The content Window from where to extract the manifest. 141 * @return {Promise<object>} 142 */ 143 async function fetchManifest(aWindow) { 144 if (!aWindow || aWindow.top !== aWindow) { 145 const msg = "Window must be a top-level browsing context."; 146 throw new Error(msg); 147 } 148 const elem = aWindow.document.querySelector("link[rel~='manifest']"); 149 if (!elem || !elem.getAttribute("href")) { 150 // There is no actual manifest to fetch, we just return null. 151 return new aWindow.Response("null"); 152 } 153 // Throws on malformed URLs 154 const manifestURL = new aWindow.URL(elem.href, elem.baseURI); 155 const reqInit = { 156 credentials: "omit", 157 mode: "cors", 158 }; 159 if (elem.crossOrigin === "use-credentials") { 160 reqInit.credentials = "include"; 161 } 162 const request = new aWindow.Request(manifestURL, reqInit); 163 request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_MANIFEST); 164 // Can reject... 165 return aWindow.fetch(request); 166 }