Differential Revision: https://phabricator.services.mozilla.com/D308692
114 lines
4.3 KiB
JavaScript
114 lines
4.3 KiB
JavaScript
/* 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 = Cc["@mozilla.org/keyed-uuid-mapper;1"]
|
|
.createInstance(Ci.nsIKeyedUUIDMapper)
|
|
.generateKey();
|
|
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;
|
|
}
|
|
|
|
/** @typedef {number} integer */
|
|
|
|
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;
|
|
},
|
|
};
|