Skip to content

Conversation

@patrickhulce
Copy link
Collaborator

Summary
Paves the way for devtools log artifact on the new FR driver/session system. This monkeypatches our puppeteer session so we can listen for any protocol event. It's on the agenda to discuss at next sync with their team if they'd be open a PR for an official API, but it's relatively lightweight (and enables backcompat) in the meantime.

Related Issues/PRs
ref #11313

@patrickhulce patrickhulce requested a review from a team as a code owner January 25, 2021 15:19
@patrickhulce patrickhulce requested review from connorjclark and removed request for a team January 25, 2021 15:19
@google-cla google-cla bot added the cla: yes label Jan 25, 2021
for (const method of delegateMethods) {
describe(`.${method}`, () => {
it('delegates to puppeteer', async () => {
const puppeteerFn = puppeteerSession[method] = jest.fn();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this change doing something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no just a prettier artifact

Copy link
Collaborator

@connorjclark connorjclark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

wondering if changing on/once to accept a regex would be better api

*/
'use strict';

const MONKEYPATCHED_EMIT = Symbol('monkeypatch');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SessionEmitMonkeypatch or w.e. casing what-have-you.

@patrickhulce
Copy link
Collaborator Author

wondering if changing on/once to accept a regex would be better api

I think some other method is desired due to the signature differences (possibly unavoidable for typescript explosion constraints? Crdp events already pushed the envelope before). You need to know what event happened for any function that can be invoked for more than 1 event type.

I'm open to other names other than onAny. The phrase looks ugly to me, but I don't have amazing suggestions, onEvery perhaps?

@connorjclark
Copy link
Collaborator

I think some other method is desired due to the signature differences (possibly unavoidable for typescript explosion constraints? Crdp events already pushed the envelope before). You need to know what event happened for any function that can be invoked for more than 1 event type.

hmm right, the callback does need methodName to disambiguate the events, since the event object themselves don't have that.

I did the easy part of the type change:

/**
   * Bind listeners for protocol events.
   * @template {keyof LH.CrdpEvents} E
   * @param {E|RegExp} eventName
   * @param {(...args: LH.CrdpEvents[E]) => void} callback
   */
  on2(eventName, callback) {
    if (eventName instanceof RegExp) {
      // ...
    } else {
      this._session.on(eventName, /** @type {*} */ (callback));
    }

    this.on2('Network.dataReceived', e => e.dataLength);
    this.on2(/./, e => e); // e is union of all. totes useless.
  }

somehow this work, idk why E=RegExp results in e being the union of all events

@patrickhulce
Copy link
Collaborator Author

I did the easy part of the type change:

That still doesn't seem like a usable type signature though, or is that what you meant by the easy part? :)

Taking a step back, I'm not really convinced that a regex-based method with a polymorphic signature is an easier API to understand and use than onEvery. for onEvery you get the benefit of narrowing the type as you filter in usage and worst case scenario it devolves to regex again and first line of that is if (!/the pattern/.test(event.method)) return with the benefit of human readable typedef

@connorjclark
Copy link
Collaborator

connorjclark commented Jan 25, 2021

Taking a step back, I'm not really convinced that a regex-based method with a polymorphic signature is an easier API to understand and use than onEvery. for onEvery you get the benefit of narrowing the type as you filter in usage

I was imagining usages of the "regexp" api would do the same.

and worst case scenario it devolves to regex again and first line of that is if (!/the pattern/.test(event.method)) return with the benefit of human readable typedef

ah, yea, ok so basically the code would end up being the same. nvm on the regexp.

onAnyProtocolMessage instead of onAny?

@patrickhulce
Copy link
Collaborator Author

onAnyProtocolMessage instead of onAny?

SGTM 👍

@connorjclark connorjclark changed the title core(fr): add session.onAny listener core(fr): add session.onAnyProtocolMessage listener Jan 25, 2021
@connorjclark connorjclark changed the title core(fr): add session.onAnyProtocolMessage listener core(fr): add session.onAny listener Jan 25, 2021
@patrickhulce patrickhulce changed the title core(fr): add session.onAny listener core(fr): add session.onAnyProtocolMessage listener Jan 25, 2021
@patrickhulce
Copy link
Collaborator Author

patrickhulce commented Jan 26, 2021

Since holding off for release I played around with a few more ideas, WDYT about this @connorjclark?

diff --git a/lighthouse-core/fraggle-rock/gather/session.js b/lighthouse-core/fragg
le-rock/gather/session.js
index dd15f9535..db3b27b6c 100644
--- a/lighthouse-core/fraggle-rock/gather/session.js
+++ b/lighthouse-core/fraggle-rock/gather/session.js
@@ -26,6 +26,11 @@ class ProtocolSession {
     };
     // @ts-expect-error - It's monkeypatching 🤷<U+200D>♂️.
     session.emit[SessionEmitMonkeypatch] = true;
+
+    /** @type {LH.Gatherer.FRProtocolSession['on']} @param {Array<*>} args */
+    this.on = (...args) => args.length === 1 ?
+      session.on('*', args[0]) :
+      session.on(args[0], args[1]);
   }

   /**
diff --git a/types/gatherer.d.ts b/types/gatherer.d.ts
index 9fc0dd197..4d81705a8 100644
--- a/types/gatherer.d.ts
+++ b/types/gatherer.d.ts
@@ -17,6 +17,7 @@ declare global {
       getNextProtocolTimeout(): number;
       setNextProtocolTimeout(ms: number): void;
       on<TEvent extends keyof LH.CrdpEvents>(event: TEvent, callback: (...args: LH.CrdpEvents[TEvent]) => void): void;
+      on(callback: (args: LH.Protocol.RawEventMessage) => void): void;
       once<TEvent extends keyof LH.CrdpEvents>(event: TEvent, callback: (...args: LH.CrdpEvents[TEvent]) => void): void;
       onAnyProtocolMessage(callback: (payload: LH.Protocol.RawEventMessage) => void): void
       off<TEvent extends keyof LH.CrdpEvents>(event: TEvent, callback: (...args: LH.CrdpEvents[TEvent]) => void): void;

Usage

// Listen to a specific event.
session.on('Page.frameNavigated', event => console.log(event));
// Listen to *all* events.
session.on(({method, params}) => console.log(method, params));

@brendankenny
Copy link
Contributor

I'll cast my vote for onAnyProtocolMessage (or whatever name) rather than overloading on:

  • least surprise (on still behaves like the typical EventEmitter.on)
  • .on(callback) kind of breaks the self-documentation of .on(eventName, callback) :)
  • overloading is (usually) broken for a lot of modern TypeScript functionality, e.g. param/tuple manipulation, call/apply type checking, etc and conditional types are the recommended solution if really needed (e.g adding types to an existing overloaded API that can't be changed)

Isn't this going to be called in like three places? Do we need a shorthand for it?

@patrickhulce
Copy link
Collaborator Author

Any other voters out there? The arguments are above, LMK by tomorrow at 10am CDT

https://fast-poll.com/poll/4c080449

@patrickhulce
Copy link
Collaborator Author

Alright the voters have spoken! Uncontested win for onAnyProtocolMessage, sorry @connorjclark

@connorjclark
Copy link
Collaborator

that's what I voted for doe

@patrickhulce
Copy link
Collaborator Author

that's what I voted for doe

😑...

bro, then why 🎉 my proposal for what I thought your request was!? time on life I'll never get back 😆

@connorjclark
Copy link
Collaborator

i'm an irrational voter sry

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants