Frame ancestry handler refactor (#1352)

* Validate source window before handling messages

* Add unregisterHandler to CrossFrameAPI

* Refactor the process FrameAncestryHandler uses to get ancestor frame IDs

* Store a mapping of child frame information

* Update getFrameAncestryInfo to only run once
This commit is contained in:
toasted-nutbread 2021-02-06 16:19:55 -05:00 committed by GitHub
parent 356e7f5274
commit 9f5cbaac5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 69 additions and 64 deletions

View File

@ -33,7 +33,9 @@ class FrameAncestryHandler {
this._frameId = frameId; this._frameId = frameId;
this._isPrepared = false; this._isPrepared = false;
this._requestMessageId = 'FrameAncestryHandler.requestFrameInfo'; this._requestMessageId = 'FrameAncestryHandler.requestFrameInfo';
this._responseMessageId = `${this._requestMessageId}.response`; this._responseMessageIdBase = `${this._requestMessageId}.response.`;
this._getFrameAncestryInfoPromise = null;
this._childFrameMap = new Map();
} }
/** /**
@ -59,7 +61,16 @@ class FrameAncestryHandler {
* @param timeout The maximum time to wait to receive a response to frame information requests. * @param timeout The maximum time to wait to receive a response to frame information requests.
* @returns An array of frame IDs corresponding to the ancestors of the current frame. * @returns An array of frame IDs corresponding to the ancestors of the current frame.
*/ */
getFrameAncestryInfo(timeout=5000) { async getFrameAncestryInfo() {
if (this._getFrameAncestryInfoPromise === null) {
this._getFrameAncestryInfoPromise = this._getFrameAncestryInfo(5000);
}
return await this._getFrameAncestryInfoPromise;
}
// Private
_getFrameAncestryInfo(timeout=5000) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const targetWindow = window.parent; const targetWindow = window.parent;
if (window === targetWindow) { if (window === targetWindow) {
@ -68,10 +79,9 @@ class FrameAncestryHandler {
} }
const uniqueId = generateId(16); const uniqueId = generateId(16);
const responseMessageId = this._responseMessageId; let nonce = generateId(16);
const responseMessageId = `${this._responseMessageIdBase}${uniqueId}`;
const results = []; const results = [];
let resultsExpectedCount = null;
let resultsCount = 0;
let timer = null; let timer = null;
const cleanup = () => { const cleanup = () => {
@ -79,42 +89,24 @@ class FrameAncestryHandler {
clearTimeout(timer); clearTimeout(timer);
timer = null; timer = null;
} }
chrome.runtime.onMessage.removeListener(onMessage); api.crossFrame.unregisterHandler(responseMessageId);
}; };
const onMessage = (message, sender, sendResponse) => { const onMessage = (params) => {
// Validate message if (params.nonce !== nonce) { return null; }
if (
typeof message !== 'object' ||
message === null ||
message.action !== responseMessageId
) {
return;
}
const {params} = message;
if (params.uniqueId !== uniqueId) { return; } // Wrong ID
const {frameId, index, more} = params;
console.log({frameId, index, more});
if (typeof results[index] !== 'undefined') { return; } // Invalid repeat
// Add result // Add result
results[index] = frameId; const {frameId, more} = params;
++resultsCount; results.push(frameId);
if (!more) { nonce = generateId(16);
resultsExpectedCount = index + 1;
}
if (resultsExpectedCount !== null && resultsCount >= resultsExpectedCount) { if (!more) {
// Cleanup // Cleanup
cleanup(); cleanup();
sendResponse();
// Finish // Finish
resolve(results); resolve(results);
} else {
resetTimeout();
} }
return {nonce};
}; };
const onTimeout = () => { const onTimeout = () => {
timer = null; timer = null;
@ -127,61 +119,68 @@ class FrameAncestryHandler {
}; };
// Start // Start
chrome.runtime.onMessage.addListener(onMessage); api.crossFrame.registerHandlers([[responseMessageId, {async: false, handler: onMessage}]]);
resetTimeout(); resetTimeout();
this._requestFrameInfo(targetWindow, uniqueId, this._frameId, 0); const frameId = this._frameId;
this._requestFrameInfo(targetWindow, frameId, frameId, uniqueId, nonce);
}); });
} }
// Private
_onWindowMessage(event) { _onWindowMessage(event) {
const {source} = event;
if (source === window || source.parent !== window) { return; }
const {data} = event; const {data} = event;
if ( if (
typeof data === 'object' && typeof data === 'object' &&
data !== null && data !== null &&
data.action === this._requestMessageId data.action === this._requestMessageId
) { ) {
try { this._onRequestFrameInfo(data.params, source);
this._onRequestFrameInfo(data.params);
} catch (e) {
// NOP
}
} }
} }
_onRequestFrameInfo({uniqueId, originFrameId, index}) { async _onRequestFrameInfo(params, source) {
if (
typeof uniqueId !== 'string' ||
typeof originFrameId !== 'number' ||
!this._isNonNegativeInteger(index)
) {
return;
}
const {parent} = window;
const more = (window !== parent);
const responseParams = {uniqueId, frameId: this._frameId, index, more};
this._safeSendMessageToFrame(originFrameId, this._responseMessageId, responseParams);
if (more) {
this._requestFrameInfo(parent, uniqueId, originFrameId, index + 1);
}
}
async _safeSendMessageToFrame(frameId, action, params) {
try { try {
await api.sendMessageToFrame(frameId, action, params); let {originFrameId, childFrameId, uniqueId, nonce} = params;
if (
!this._isNonNegativeInteger(originFrameId) ||
typeof uniqueId !== 'string' ||
typeof nonce !== 'string'
) {
return;
}
const frameId = this._frameId;
const {parent} = window;
const more = (window !== parent);
const responseParams = {frameId, nonce, more};
const responseMessageId = `${this._responseMessageIdBase}${uniqueId}`;
try {
const response = await api.crossFrame.invoke(originFrameId, responseMessageId, responseParams);
if (response === null) { return; }
nonce = response.nonce;
} catch (e) {
return;
}
if (!this._childFrameMap.has(childFrameId)) {
this._childFrameMap.set(childFrameId, {window: source});
}
if (more) {
this._requestFrameInfo(parent, originFrameId, frameId, uniqueId, nonce);
}
} catch (e) { } catch (e) {
// NOP // NOP
} }
} }
_requestFrameInfo(targetWindow, uniqueId, originFrameId, index) { _requestFrameInfo(targetWindow, originFrameId, childFrameId, uniqueId, nonce) {
targetWindow.postMessage({ targetWindow.postMessage({
action: this._requestMessageId, action: this._requestMessageId,
params: {uniqueId, originFrameId, index} params: {originFrameId, childFrameId, uniqueId, nonce}
}, '*'); }, '*');
} }

View File

@ -248,6 +248,12 @@ class CrossFrameAPI {
} }
} }
unregisterHandler(key) {
return this._messageHandlers.delete(key);
}
// Private
_onConnect(port) { _onConnect(port) {
try { try {
let details; let details;