Skip to content
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(tsc): add type checking to remote protocol commands #4914

Merged
merged 3 commits into from
Apr 4, 2018

Conversation

brendankenny
Copy link
Member

This PR adds type checking/inference for Driver.sendCommand (to go along with typing events in #4886).

command-completion-gif

Like #4886, this PR has changes to scripts/extract-crdp-mapping.js to extract all the protocol commands which are then written to typings/crdp-mapping.d.ts. Again, hopefully most of this can be upstreamed into devtools-protocol. This time the types need both the options params object (if any) associated with the command but also the command's return type (if any).

The other set of changes are in Driver, Connection, and ExtensionConnection (everything else is just minor fixes for any type errors that popped up after all the above changes). This time sendCommand is pulled out of the class bodies for stricter typing. Getting the typing correct is a little harder than it was for the events because this time we aren't tying together an event name and payload type, but two function parameters and a return type.

Typescript types (in typescript or jsdoc) can express that one property of an object is defined only for certain values of a different property (or similarly, for an event payload in a callback and an event name), but typescript can't yet express that a function parameter is not to be provided depending on the value of another parameter (there's a proposal to enable this, which is now on the typescript roadmap, but under the "Future" heading with no planned implementation schedule).

As a result, the solution is a little more awkward, but not too bad. The function is defined with somewhat loose argument types, but when added to the class prototype it's cast to a much stricter type as the external interface. This allows casting with an overloaded type signature, which can create a one argument version (for commands like DOM.enable) or a two argument version so options can be passed (for e.g. DOM.querySelector), depending on the first argument (the command string).

One last wrinkle: for commands that have all optional options that we don't use, we have often just dropped the second argument when calling (e.g. for Network.enable). I've added void as one option for the params object for those commands, so they actually show up in both the one and two argument versions of sendCommand

@@ -656,11 +637,9 @@ class Driver {
return this._isolatedExecutionContextId;
}

/** @type {LH.Crdp.Page.GetResourceTreeResponse} */
Copy link
Member Author

Choose a reason for hiding this comment

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

typed responses lets us get rid of a lot of these explicit types :)

@@ -86,7 +86,7 @@ function enableNexus5X(driver) {
// Network.enable must be called for UA overriding to work
driver.sendCommand('Network.enable'),
driver.sendCommand('Network.setUserAgentOverride', NEXUS5X_USERAGENT),
driver.sendCommand('Emulation.setTouchEmulationEnabled', {
driver.sendCommand('Page.setTouchEmulationEnabled', {
Copy link
Member Author

Choose a reason for hiding this comment

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

first real bug caught with type checks? Haven't confirmed that it was a problem, but there is both Emulation.setTouchEmulationEnabled and Page.setTouchEmulationEnabled, but only the Page one takes these params

Copy link
Member

Choose a reason for hiding this comment

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

ha! yeah I think this might have been a small bug that normally didn't matter.
I think we want Emulation.setEmitTouchEventsForMouse instead.

that's what devtools uses when you toggle device type:
image

Copy link
Member

Choose a reason for hiding this comment

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

seems like you're fixing this in a separate PR?

can you write up the list of changes that are on their way?

Copy link
Member Author

Choose a reason for hiding this comment

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

sorry, I can fix this :)

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

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

coool beans 😎

* params object.
*/
export type SendCommand = {
<C extends VoidParamsKeys>(method: C, params?: void, cmdOpts?: {silent?: boolean}): Promise<CrdpCommands[C]['returnType']>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

a little unfortunate that types make us do an extends and we can't do in

@@ -809,11 +785,9 @@ class Driver {
* @return {Promise<Array<Element>>} The found elements, or [], resolved in a promise
*/
async querySelectorAll(selector) {
/** @type {LH.Crdp.DOM.GetDocumentResponse} */
const documentResponse = await this.sendCommand('DOM.getDocument');
const documentResponse = await this.sendCommand('DOM.getDocument', {});
Copy link
Collaborator

Choose a reason for hiding this comment

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

I thought the | void would let us omit the parameters if they're all optional?

Copy link
Member Author

Choose a reason for hiding this comment

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

whoops, yes, those were from an earlier version of the PR

@@ -111,7 +98,7 @@ class Connection {
// just log these occurrences.
const error = object.error && object.error.message;
log.formatProtocol(`disowned method <= browser ${error ? 'ERR' : 'OK'}`,
{method: object.method, params: error || object.result}, 'verbose');
{method: 'UNKNOWN', params: error || object.result}, 'verbose');
Copy link
Collaborator

Choose a reason for hiding this comment

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

do we not get back a method from disowned messages? seems like || 'UNKOWN' would work if we do :)

Copy link
Member Author

Choose a reason for hiding this comment

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

do we not get back a method from disowned messages?

we can check on the Chrome side if it's possible, but I did a bunch of runs watching the properties of all messages sent back over the protocol and never got a method on a non-event response

Copy link
Member

@paulirish paulirish left a comment

Choose a reason for hiding this comment

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

the .d.ts is now in the devtools-protocol package: https://cdn.jsdelivr.net/npm/devtools-protocol@0.0.547074/types/protocol.d.ts

want to switch to that and off of vscode-chrome-debug-core?

@@ -86,7 +86,7 @@ function enableNexus5X(driver) {
// Network.enable must be called for UA overriding to work
driver.sendCommand('Network.enable'),
driver.sendCommand('Network.setUserAgentOverride', NEXUS5X_USERAGENT),
driver.sendCommand('Emulation.setTouchEmulationEnabled', {
driver.sendCommand('Page.setTouchEmulationEnabled', {
Copy link
Member

Choose a reason for hiding this comment

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

ha! yeah I think this might have been a small bug that normally didn't matter.
I think we want Emulation.setEmitTouchEventsForMouse instead.

that's what devtools uses when you toggle device type:
image

* @param {string} domainName
* @return {Map<string, {paramsType: string, returnType: string}>}
*/
function getCommandMap(sourceRoot, domainName) {
Copy link
Member

Choose a reason for hiding this comment

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

do you want to upstream all this into devtools-protocol?

Copy link
Member Author

@brendankenny brendankenny Apr 3, 2018

Choose a reason for hiding this comment

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

do you want to upstream all this into devtools-protocol?

the event map (just 'Domain.eventName': Crdp.EventPayloadType) I think could easily live upstream, it's so simple. The command map seems more specialized to our use case, so I'm not sure. We could propose it to everyone involved in ChromeDevTools/devtools-protocol#90 and see what they think

Copy link
Member

Choose a reason for hiding this comment

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

cc @@JoelEinbinder whatchu think homeboy

Copy link
Member Author

Choose a reason for hiding this comment

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

he sounded 👍 for upstreaming (if not for using in pptr)

@brendankenny
Copy link
Member Author

the .d.ts is now in the devtools-protocol package. want to switch to that and off of vscode-chrome-debug-core?

yeah, it was just going to require changes to this and the previous event stuff because they changed the structure a bit. I was going to do that in a quick follow up PR.

@brendankenny
Copy link
Member Author

yah?

@@ -86,7 +86,7 @@ function enableNexus5X(driver) {
// Network.enable must be called for UA overriding to work
driver.sendCommand('Network.enable'),
driver.sendCommand('Network.setUserAgentOverride', NEXUS5X_USERAGENT),
driver.sendCommand('Emulation.setTouchEmulationEnabled', {
driver.sendCommand('Page.setTouchEmulationEnabled', {
Copy link
Member

Choose a reason for hiding this comment

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

seems like you're fixing this in a separate PR?

can you write up the list of changes that are on their way?

@brendankenny
Copy link
Member Author

can you write up the list of changes that are on their way?

I believe the only (direct) next steps are to

  • upstream CrdpEvents and CrdpCommands to devtools-protocol
  • delete our local scripts/extract-crdp-mappings.js and typings/crdp-mappings.d.ts and remove vscode-chrome-debug-core as a dep
  • switch over to devtools-protocol for typing the protocol

@brendankenny
Copy link
Member Author

@patrickhulce also gave the 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants