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

Improve DevTools protocol version handling #24219

Open
bvaughn opened this issue Mar 30, 2022 · 0 comments
Open

Improve DevTools protocol version handling #24219

bvaughn opened this issue Mar 30, 2022 · 0 comments

Comments

@bvaughn
Copy link
Contributor

bvaughn commented Mar 30, 2022

How does DevTools work and what is the "protocol version"?

The React DevTools extension consists of multiple pieces:

  • The frontend portion is the extension you see (the Components tree, the Profiler, etc.).
  • The backend portion is invisible. It runs in the same context as React itself.

The frontend displays the current React tree, but it has no way to observe the tree directly because it runs in different memory space. It relies on the backend to describe the tree by sending "messages" through an API like postMessage.

The most optimized way for the backend to share the state of the React tree is to send a small packet of "op codes" whenever the tree changes (e.g. whenever React DOM modifies the page). These "op codes" are just an array of numbers which correspond to operations like "add something to the tree" or "remove something from the tree". For example, the operation to add a component to the tree might look like this:

[
  // ...
  1,   // number 1 signifies an "add" operation
  2,   // component id
  1,   // type: class component
  1,   // parent id
  0,   // owner id
  1,   // id of component display name (corresponds to the string table)
  0,   // id of key string in the string table (zero indicates no key)
  // ...
]

The format described above is highly optimized but also inflexible. (For example, if the backend needs to add an additional piece of information to the above operation, the frontend needs to also know to advance its index within the operations array by one additional place.)

DevTools version 4.13 introduced the concept of a protocol version number to allow the frontend to ask the backend which version of the protcol it used. In hindsight, this number should have included in the operations array itself (as the first digit).

The current problem

Generally the DevTools frontend and backend pieces are bundled together. For example, the most common DevTools surface– the browser extension– ships both pieces together and injects the backend into the page during initialization. However there are some less common targets are not so tightly coupled– such as React Native (which embeds the backend into the application itself) or Replay (which records the backend "operations" array as part of its session data). In these cases, the frontend (UI) launched by the user (or embedded in the Replay player) may depend on an incompatible protocol.

This results in errors like #24142 and #23307 (and even https://github.com/RecordReplay/devtools/issues/5344):
React DevTools Unsupported Bridge operation error

Potential solutions

As mentioned above, the protocol version should have included in the operations array itself (as the first digit). We should fix this when we eventually make a major breaking change (aka DevTools version 5) but in the meanwhile, maybe there's a way we can improve the current situation.

Some changes have already been made to support older protocol versions when possible (#24093) and to more clearly communicate the reason for the error when that is not possible (#24147) but perhaps there's more still that we could do?

Option 1: Add protocol version to the start of the "operations" array

We could retroactively update the "operations" array to always begin with the protocol version number.

We'd need a way to detect this though (to distinguish this newer message format from older ones). Currently, each "operations" message begins with two numbers– representing the renderer and the root (tree root). For example:

// Older message format
[
  3, // renderer id
  1, // (tree) root element id
  0, // string table size
  // operations ...
]

Since the current operations array starts with a positive integer (the renderer ID) it would be ambiguous to insert another positive integer (the bridge protocol version). For this reason, I propose inserting two new numbers to the start of the operations array: The first one being 0 (so we can reliably detect the newer format) and the second one being the protocol version. For example:

// Newer message format
[
  0, // signifies the newer operations array format
  2, // bridge protocol version
  3, // renderer id
  1, // (tree) root element id
  0, // string table size
  // operations ...
]

The frontend could then reliably distinguish between these two backend formats:

[3,1,0,...] // old format
[0,2,3,1,0,...] // new format
  • Pros:
    • Would enable DevTools to get rid of the separate protocol version request method and more easily differentiate between different versions of the operations array when parsing.
    • Would also enable newer DevTools frontends to parse recorded operations arrays from older backends (e.g. the Replay case).
  • Cons:
    • Would require us to retroactively publish patch updates to older backend releases.
    • Older frontends wouldn't support this change– and would error. (Although this is arguably no worse than the current situation.)

Option 2: Automatically fall back to support older protocols

We could leave the current architecture in place (at least until version 5) but in the case of a protocol error (UnsupportedBridgeOperationError) we could have DevTools try to automatically force-downgrade replay the "operations" array with the assumption of an older protocol version number.

This is essentially how things already work for the React Native case, (as of version 4.24.1), but formalizing it would expand to also cover usecases like Replay.

  • Pros:
    • Works with pre-existing DevTools backends.
  • Cons:
    • Adds code complexity.

Option 3: User configurable protocol version number

We could leave the current architecture in place (at least until version 5) but provide some sort of UI mechanism to allow users to override the default assumed protocol version number.

  • Pros:
    • Works with pre-existing DevTools backends.
  • Cons:
    • Poor/confusing UX.

Solutions not considered

One solution not mentined above would be to add a new message type (e.g. "new-operations") that begins with the bridge protocol version number. Newer frontends could listen for this message, but still fall back to listening for the old message ("operations"). Older frontends would continue to listen to the older message and not break (at least not in any new way).

The reason I think this solution is probably not worth pursuing is that it would double the amount of information the backend sends to the frontend via e.g. postMessage during performance-sensitive times. One of the main goals of the new DevTools was to reduce this kind of overhead, so I don't think that's worth compromising on.

A variation of this might be for backends to send the newer message type (e.g. "new-operations") only and no longer continue to send the "operations" message. This would avoid the perfromance problem but would leave older frontends in a broken state (perpetually waiting on the "operations" array that is never sent).

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

No branches or pull requests

1 participant