-
Notifications
You must be signed in to change notification settings - Fork 9.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
core(oopif): attach to all descendants #7608
Changes from all commits
39a03f7
3edfccf
9bfe385
1e4a35c
c8ee8aa
9ac2e10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,8 +47,8 @@ class Driver { | |
*/ | ||
this._eventEmitter = /** @type {CrdpEventEmitter} */ (new EventEmitter()); | ||
this._connection = connection; | ||
// Used to save network and lifecycle protocol traffic. Just Page, Network, and Target are needed. | ||
this._devtoolsLog = new DevtoolsLog(/^(Page|Network|Target)\./); | ||
// Used to save network and lifecycle protocol traffic. Just Page and Network are needed. | ||
this._devtoolsLog = new DevtoolsLog(/^(Page|Network)\./); | ||
this.online = true; | ||
/** @type {Map<string, number>} */ | ||
this._domainEnabledCounts = new Map(); | ||
|
@@ -69,32 +69,25 @@ class Driver { | |
*/ | ||
this._monitoredUrl = null; | ||
|
||
let targetProxyMessageId = 0; | ||
/** | ||
* Used for sending messages to subtargets. Each message needs a unique ID even if we don't bother | ||
* reading back the result of each command. | ||
* | ||
* @type {number} | ||
* @private | ||
*/ | ||
this._targetProxyMessageId = 0; | ||
|
||
this.on('Target.attachedToTarget', event => { | ||
targetProxyMessageId++; | ||
// We're only interested in network requests from iframes for now as those are "part of the page". | ||
if (event.targetInfo.type !== 'iframe') return; | ||
|
||
// We want to receive information about network requests from iframes, so enable the Network domain. | ||
// Network events from subtargets will be stringified and sent back on `Target.receivedMessageFromTarget`. | ||
this.sendCommand('Target.sendMessageToTarget', { | ||
message: JSON.stringify({id: targetProxyMessageId, method: 'Network.enable'}), | ||
sessionId: event.sessionId, | ||
}); | ||
this._handleTargetAttached(event, []).catch(this._handleEventError); | ||
}); | ||
|
||
connection.on('protocolevent', event => { | ||
this._devtoolsLog.record(event); | ||
if (this._networkStatusMonitor) { | ||
this._networkStatusMonitor.dispatch(event); | ||
} | ||
|
||
// @ts-ignore TODO(bckenny): tsc can't type event.params correctly yet, | ||
// typing as property of union instead of narrowing from union of | ||
// properties. See https://github.com/Microsoft/TypeScript/pull/22348. | ||
this._eventEmitter.emit(event.method, event.params); | ||
this.on('Target.receivedMessageFromTarget', event => { | ||
this._handleReceivedMessageFromTarget(event, []).catch(this._handleEventError); | ||
}); | ||
|
||
connection.on('protocolevent', this._handleProtocolEvent.bind(this)); | ||
|
||
/** | ||
* @type {number} | ||
* @private | ||
|
@@ -275,6 +268,121 @@ class Driver { | |
this._nextProtocolTimeout = timeout; | ||
} | ||
|
||
/** | ||
* @param {LH.Protocol.RawEventMessage} event | ||
*/ | ||
_handleProtocolEvent(event) { | ||
this._devtoolsLog.record(event); | ||
if (this._networkStatusMonitor) { | ||
this._networkStatusMonitor.dispatch(event); | ||
} | ||
|
||
// @ts-ignore TODO(bckenny): tsc can't type event.params correctly yet, | ||
// typing as property of union instead of narrowing from union of | ||
// properties. See https://github.com/Microsoft/TypeScript/pull/22348. | ||
this._eventEmitter.emit(event.method, event.params); | ||
} | ||
|
||
/** | ||
* @param {Error} error | ||
*/ | ||
_handleEventError(error) { | ||
log.error('Driver', 'Unhandled event error', error.message); | ||
} | ||
|
||
/** | ||
* @param {LH.Crdp.Target.ReceivedMessageFromTargetEvent} event | ||
* @param {string[]} parentSessionIds The list of session ids of the parents from youngest to oldest. | ||
*/ | ||
async _handleReceivedMessageFromTarget(event, parentSessionIds) { | ||
const {sessionId, message} = event; | ||
patrickhulce marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/** @type {LH.Protocol.RawMessage} */ | ||
const protocolMessage = JSON.parse(message); | ||
|
||
// Message was a response to some command, not an event, so we'll ignore it. | ||
if ('id' in protocolMessage) return; | ||
|
||
// We receive messages from the outermost subtarget which wraps the messages from the inner subtargets. | ||
// We are recursively processing them from outside in, so build the list of parentSessionIds accordingly. | ||
const sessionIdPath = [sessionId, ...parentSessionIds]; | ||
|
||
if (protocolMessage.method === 'Target.receivedMessageFromTarget') { | ||
// Unravel any messages from subtargets by recursively processing | ||
await this._handleReceivedMessageFromTarget(protocolMessage.params, sessionIdPath); | ||
} | ||
|
||
if (protocolMessage.method === 'Target.attachedToTarget') { | ||
// Process any attachedToTarget messages from subtargets | ||
await this._handleTargetAttached(protocolMessage.params, sessionIdPath); | ||
} | ||
|
||
if (protocolMessage.method.startsWith('Network')) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. emit this one so it's handled as usual instead of handling it here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well our current handling isn't actually on driver events it's off the connection event, and I thought it would be weird to forcibly emit an event from within driver on the connection. but I guess you disagree? 🤞 hopes the answer isn't let's refactor this whole thing now ;) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. compromise: moved this bit into a function that gets called by both? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ah, I had forgotten about this. I also remembered we did it the way it is because you can't listen to
sounds perfect |
||
this._handleProtocolEvent(protocolMessage); | ||
} | ||
} | ||
|
||
/** | ||
* @param {LH.Crdp.Target.AttachedToTargetEvent} event | ||
* @param {string[]} parentSessionIds The list of session ids of the parents from youngest to oldest. | ||
*/ | ||
async _handleTargetAttached(event, parentSessionIds) { | ||
const sessionIdPath = [event.sessionId, ...parentSessionIds]; | ||
|
||
// We're only interested in network requests from iframes for now as those are "part of the page". | ||
patrickhulce marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// If it's not an iframe, just resume it and move on. | ||
if (event.targetInfo.type !== 'iframe') { | ||
// We suspended the target when we auto-attached, so make sure it goes back to being normal. | ||
await this.sendMessageToTarget(sessionIdPath, 'Runtime.runIfWaitingForDebugger'); | ||
return; | ||
} | ||
|
||
// Events from subtargets will be stringified and sent back on `Target.receivedMessageFromTarget`. | ||
// We want to receive information about network requests from iframes, so enable the Network domain. | ||
await this.sendMessageToTarget(sessionIdPath, 'Network.enable'); | ||
// We also want to receive information about subtargets of subtargets, so make sure we autoattach recursively. | ||
await this.sendMessageToTarget(sessionIdPath, 'Target.setAutoAttach', { | ||
autoAttach: true, | ||
// Pause targets on startup so we don't miss anything | ||
waitForDebuggerOnStart: true, | ||
}); | ||
|
||
// We suspended the target when we auto-attached, so make sure it goes back to being normal. | ||
await this.sendMessageToTarget(sessionIdPath, 'Runtime.runIfWaitingForDebugger'); | ||
} | ||
|
||
/** | ||
* Send protocol commands to a subtarget, wraps the message in as many `Target.sendMessageToTarget` | ||
* commands as necessary. | ||
* | ||
* @template {keyof LH.CrdpCommands} C | ||
* @param {string[]} sessionIdPath List of session ids to send to, from youngest-oldest/inside-out. | ||
* @param {C} method | ||
* @param {LH.CrdpCommands[C]['paramsType']} params | ||
* @return {Promise<LH.CrdpCommands[C]['returnType']>} | ||
*/ | ||
sendMessageToTarget(sessionIdPath, method, ...params) { | ||
this._targetProxyMessageId++; | ||
/** @type {LH.Crdp.Target.SendMessageToTargetRequest} */ | ||
let payload = { | ||
sessionId: sessionIdPath[0], | ||
message: JSON.stringify({id: this._targetProxyMessageId, method, params: params[0]}), | ||
}; | ||
|
||
for (const sessionId of sessionIdPath.slice(1)) { | ||
this._targetProxyMessageId++; | ||
payload = { | ||
sessionId, | ||
message: JSON.stringify({ | ||
id: this._targetProxyMessageId, | ||
method: 'Target.sendMessageToTarget', | ||
params: payload, | ||
}), | ||
}; | ||
} | ||
|
||
return this.sendCommand('Target.sendMessageToTarget', payload); | ||
} | ||
|
||
/** | ||
* Call protocol methods, with a timeout. | ||
* To configure the timeout for the next call, use 'setNextProtocolTimeout'. | ||
|
@@ -1044,7 +1152,8 @@ class Driver { | |
// Enable auto-attaching to subtargets so we receive iframe information | ||
await this.sendCommand('Target.setAutoAttach', { | ||
autoAttach: true, | ||
waitForDebuggerOnStart: false, | ||
// Pause targets on startup so we don't miss anything | ||
waitForDebuggerOnStart: true, | ||
patrickhulce marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
|
||
await this.sendCommand('Page.enable'); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, should we just be making our own OOPIF here rather than relying on this page having another nested one? We could do a page on the other port to go cross origin (assuming that triggers OOP) and have that embed the youtube video, for instance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish, I tried like 15 different combinations but localhost never seems to be oopif'd on its own, so we need at least one publicly hosted page that then also iframes a google-y origin that will guarantee 2 separate processes beyond our localhost one