Source code
Revision control
Copy as Markdown
Other Tools
/* 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
// 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;
},
};