/* 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 module provides a mapper between internal innerWindowId and documentId, // which is publicly exposed in extension APIs. // // The mapper relies on a secret key, initialized on first use and fixed for // the lifetime of the application, exposed to all processes via sharedData // with "extensions/documentIdKey". The main requirement is that its // initialization happens before its first use in child processes, such as // synchronous runtime.getDocumentId() calls in the child. // // In practice, the key is initialized from Extension.sys.mjs, by assigning to // "extensions/documentIdKey" (if needed), before sending the initialization // data for the first extension to the child (with sharedData.flush()). let gMapper = null; // Returns a mapper between innerWindowId numbers and documentId UUID. function ensureMapper() { if (gMapper) { return gMapper; } // The random key that is fixed for the duration of the application session. // This supports a stable mapping that cannot be predicted by extensions. let key; if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT) { // Look up the key generated by Extension.sys.mjs on the first run. key = Services.ppmm.sharedData.get("extensions/documentIdKey"); if (!key) { // key not found? Something invoked ExtensionDocumentId.sys.mjs before // the first extension started. Generate the key now. key = crypto.getRandomValues(new Uint8Array(16)); Services.ppmm.sharedData.set("extensions/documentIdKey", key); // Note: sharedData.flush() is not called because there is no imminent // need for the data in the child. Extension.sys.mjs flushes as needed // when it starts the first extension. } } else { key = Services.cpmm.sharedData.get("extensions/documentIdKey"); if (!key) { // This is unexpected - the parent should have assigned the common random // key before the first extension has started, in particular before any // extension context can call runtime.getDocumentId(). throw new Error("Failed to gather key to compute documentId"); } } gMapper = Cc["@mozilla.org/keyed-uuid-mapper;1"].createInstance( Ci.nsIKeyedUUIDMapper ); gMapper.init(key); return gMapper; } export const ExtensionDocumentId = { /** * @param {integer} [innerWindowId] * @returns {string|undefined} documentId (UUID) string, or undefined if the * input is omitted or zero (a valid innerWindowId is at least 1). */ getDocumentId(innerWindowId) { if (!innerWindowId) { // Note: 0 is also an invalid innerWindowId because it is never generated // by nsContentUtils::GenerateWindowId(). return undefined; } return ensureMapper().toUUID(innerWindowId); }, /** * @param {string} documentId - Supposedly the return value of getDocumentId. * @returns {integer|undefined} The positive non-zero innerWindowId, if any. * Returns undefined if the input was not from getDocumentId(). */ getInnerWindowIdForDocumentId(documentId) { try { // There are 2^128 possible UUIDs. But getDocumentId() took innerWindowId // as input, and nsContentUtils::GenerateWindowId() returns numbers that // are at least 1 and at most 2^53, which is far fewer than the range of // potential UUIDs. In other words, extensions cannot guess a valid UUID. // nsIKeyedUUIDMapper::FromUUID returning implies that the UUID is valid. return ensureMapper().fromUUID(documentId); } catch { return undefined; } }, /** * Returns BrowsingContext for a documentId, if still the current document. * * @param {string} documentId * @returns {BrowsingContext|null} */ getBrowsingContextForDocumentId(documentId) { const innerWindowId = this.getInnerWindowIdForDocumentId(documentId); if (!innerWindowId) { return null; } const wgp = WindowGlobalParent.getByInnerWindowId(innerWindowId); if (!wgp) { return null; } if (!wgp.isCurrentGlobal) { return null; } return wgp.browsingContext; }, };