Skip to content

Conversation

chrisbobbe
Copy link
Contributor

@chrisbobbe chrisbobbe commented Jul 1, 2021

We'd been on a version 10.x, a previous major version, so this wasn't done automatically with our recent yarn upgrade PRs.

The RN 64 upgrade issue is #4426.

@chrisbobbe chrisbobbe requested review from gnprice and WesleyAC July 1, 2021 16:16
@chrisbobbe
Copy link
Contributor Author

I tested with the office Android device and my iPhone, and didn't notice any differences in behavior. Messages appear, and reactions are processed, so inbound events still seem to be working as before.

@chrisbobbe
Copy link
Contributor Author

Just fixed a conflict.

@chrisbobbe chrisbobbe added the dependencies Pull requests that update a dependency file label Jul 2, 2021
@WesleyAC
Copy link
Contributor

WesleyAC commented Jul 2, 2021

Tested on Android and it seems fine, LGTM!

The indicated file doesn't exist, and there would be no point
updating it to say `flow-typed/react-native-webview_v10.x.x.js`
because there isn't an autogenerated libdef in flow-typed/npm, which
the comment requires as the reason to include something in the list.
…View.

React.createRef() is a new, simpler API introduced in React 16.3;
see the "note" box at
  https://reactjs.org/docs/refs-and-the-dom.html.
To get a version that has the React Native peer dep range bumped to
include React Native v0.64, which we hope to upgrade to soon
(zulip#4426).

There is one announced breaking change for Android; the
`setSupportMultipleWindows` prop is introduced, defaulting to
`true` [1]. This is to "mitigate the security advisory
CVE-2020-6506". The advisory says, "This vulnerability affects React
Native apps which use a react-native-webview that allows navigation
to arbitrary URLs, and when that app runs on systems with an Android
WebView version prior to 83.0.4103.106."

I'm skeptical that we were affected, because I don't think we allow
navigation to arbitrary URLs; see our comments on our use of the
`originWhitelist` and `onShouldStartLoadWithRequest` props. But good
that they're addressing reported vulnerabilities.

[1] https://github.com/react-native-webview/react-native-webview/releases/tag/v11.0.0
@chrisbobbe chrisbobbe force-pushed the pr-webview-11-6-4 branch from 26e359c to cf8f1a8 Compare July 3, 2021 13:34
@chrisbobbe chrisbobbe merged commit cf8f1a8 into zulip:master Jul 3, 2021
@chrisbobbe
Copy link
Contributor Author

Thanks for the review! Merged.

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

Thanks to both of you!

Comments below; my only question is about a type annotation we've found tricky.

context: ThemeData;

webview: ?WebView;
webviewRef = React.createRef<React$ElementRef<typeof WebView>>();
Copy link
Member

Choose a reason for hiding this comment

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

I remember there being some uncertainty about how these types for React.createRef should look. I grepped for examples, and we only have a couple of them but it looks like they're like this but without the typeof. So probably that's what we want?

Looking at history with git log --stat -p -G 'React.createRef<React', I find d346073 switching something to the non-typeof form, and that links to #4278 (comment) .

But then, the form we switched away from there wasn't this one -- it had the typeof and not the React$ElementRef. We switched to having the React$ElementRef and not the typeof. Maybe we do want both? Rereading that linked comment, I'm not sure.

How did you end up with this form?

Copy link
Contributor Author

@chrisbobbe chrisbobbe Jul 7, 2021

Choose a reason for hiding this comment

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

I think the React$ElementRef is right, especially looking back at your explanation at that comment. Are you having doubts about that?

If I remove the typeof, I get the following error:

Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/webview/MessageList.js:161:49

Cannot use WebView as a type. A name can be used as a type only if it refers to a type, interface, class, or enum
definition. To get the type of a non-class value, use typeof. [value-as-type]

     158│   static contextType = ThemeContext;
     159│   context: ThemeData;
     160│
     161│   webviewRef = React.createRef<React$ElementRef<WebView>>();
     162│   sendInboundEventsIsReady: boolean;
     163│   unsentInboundEvents: WebViewInboundEvent[] = [];
     164│

Looking at the libdef, we're saying the 'react-native-webview' module exports a value at the named export WebView with a particular type, and indeed, it doesn't use class, interface, etc.:

declare export var WebView: React$ComponentType<WebViewProps>;

If we had instead done declare export class WebView …, then I suspect we wouldn't have to do typeof. The trouble is that we'd want it to be declare export class WebView extends React.Component<WebViewProps, …>, and I don't think we can get React.Component into the libdef without just copying it. @types/react has a TypeScript definition here that we could translate; probably we could find a definition already in Flow to copy if we wanted.

A kind of important thing we drop by saying the WebView value has the type React$ComponentType<WebViewProps> is the postMessage method.

Copy link
Contributor Author

@chrisbobbe chrisbobbe Jul 7, 2021

Choose a reason for hiding this comment

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

In fact, looking again, I think this bit at the end of react-native-webview isn't quite correct:

  //
  // node_modules/react-native-webview/lib/WebView.d.ts
  //

  declare export type WebViewProps = $ReadOnly<{| ...IOSWebViewProps, ...AndroidWebViewProps |}>;
  declare export var WebView: React$ComponentType<WebViewProps>;
  declare export default WebView;

Instead of copying/translating from WebView.d.ts (see this comment), we should be using WebView.ios.d.ts and WebView.android.d.ts. Those use declare class WebView extends React.Component<…>. The question remains, though, of how best to get the React.Component definition. Also, I guess, how to tell Flow which type we'll get on Android, and which on iOS.

Copy link
Member

Choose a reason for hiding this comment

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

I think the React$ElementRef is right, especially looking back at your explanation at that comment. Are you having doubts about that?

Yeah, I'm convinced of that. The question I'm thinking of is whether we want the typeof.

Going and rereading the React libdef (in /tmp/flow/flowlib_*/react.js -- it's built into Flow) again, I think we do. So I think this code is right, and our handful of other uses of createRef should switch to looking like this too. I guess I'll go try that and see what happens.

First, though, to write down my reasoning:

  • What we're trying to express here is what the type of the current property is going to be, when it's not null.
  • The type of the ref pseudoprop says the answer needs to look like React$ElementRef<ElementType>, for some ElementType that's a subtype of React$ElementType. (That's from React$Ref, which I quoted in Decouple Navigation from Redux (Part 2) #4278 (comment) .)
  • OK, so what's an appropriate type to use for ElementType here? Does it look like WebView, or like typeof WebView?
  • Well, one thing is that WebView isn't even a type -- it's just a value. And in general, when a React component-type is a class, then it's a type as well as a value… but when it's a function, it's only a value.
    • Hmm, I guess ref can't be used on function components anyway. But this is a generic React API, so it must work the same for React DOM; and there, there's a third type of React element, namely where the element-type is just a string and it makes an HTML element with that string as the HTML element type. And those do support refs.
    • So in the HTML case, the element-type in the actual React element that render returns is a string, like 'div'. And that isn't a type -- you can't say React$ElementRef<'div'>. So it must be React$ElementRef<typeof 'div'>.
    • In our case, the element-type in the actual React element is WebView, the class itself. So the parallel thing would be React$ElementRef<typeof WebView>.
  • Separately: when the element-type is a class, the value of current will be an instance of that class. But when the element-type is a string, the value of current will be a DOM element. How is that getting expressed?
    • I think the answer must be that that's the job of React$ElementRef. It's not defined in that react.js libdef -- it must be defined effectively by logic in the Flow implementation itself.
    • That also provides an explanation for why there's that React$ElementRef indirection in the first place. It's there to solve the problem that "class -> instance of that class; string -> DOM element" is kind of irregular and can't be expressed in the type system itself.

Hmm, actually, now that I've reasoned through it more fully like that, I see that if my understanding of what's going on here is right, then React$ElementRef<typeof Foo> -- for any Foo we'll ever write -- is actually just a long way of spelling Foo.

So maybe the actual answer is that we should just write it that way. Where possible, that is -- which in a case like WebView means saying declare export class instead of declare export var.

In the case discussed at #4278 (comment) , I think the answer we got was right, with React$ElementRef and no typeof. But that's because the thing we pass to ElementRef is already abstract:

  declare export type NavigationContainerType = React$AbstractComponent<
    /* … */,
    BaseNavigationContainerInterface,
  >;

In particular, it's equivalent to typeof NavigationContainer.

Copy link
Member

Choose a reason for hiding this comment

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

A kind of important thing we drop by saying the WebView value has the type React$ComponentType<WebViewProps> is the postMessage method.

I think this is expressed by saying React$AbstractComponent<WebViewProps, { postMessage: /* … */, ... }>.

Here's the relationship between ComponentType and AbstractComponent:

declare type React$ComponentType<-Config> = React$AbstractComponent<Config, mixed>;

So ComponentType is just a simplification of AbstractComponent, with its second type parameter dropped.

And that second type parameter appears to be a type bound for the resulting instances, based on how it's used elsewhere in react.js and also how it seems to be successfully used for NavigationContainerType (in the bit I quoted above).

Copy link
Contributor Author

@chrisbobbe chrisbobbe Jul 7, 2021

Choose a reason for hiding this comment

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

I think this is expressed by saying React$AbstractComponent<WebViewProps, { postMessage: /* … */, ... }>.

Very interesting! Shall I give this a try? I wasn't looking forward to doing a declare export class WebView extends React.Component because of the importing-React thing, but this looks like it might work well instead?

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I guess most of the existing examples are all TextInput; and that also ends up needing the ElementRef for similar reasons. (And then doesn't actually work -- the .current ends up being any when used -- for reasons I don't understand.)

The one thing I see that we can update is the comment on mentionWarnings in ComposeBox.js. I think we want

   // TODO: Type-check this, once we've adjusted our `react-redux`
   // wrapper to do the right thing. It should be
   //
-  //   mentionWarnings = React.createRef<React$ElementRef<MentionWarnings>>()
+  //   mentionWarnings = React.createRef<MentionWarnings>()
   //
   // but we need our `react-redux` wrapper to be aware of
   // `{ forwardRef: true }`, since we use that.

Comment on lines +106 to +115
So with this upgrade, I did a reset by translating the relevant
`react-native-webview` files (and parts of a file from
`@types/react-native`, which those files depended on) with Flowgen into a
totally fresh libdef. It worked surprisingly well, preserving jsdocs and
ordering. (I think there may have been a glitch with copying jsdocs in the
translation, maybe in @types/react-native, but I was able to run the
`flowgen` command with `--no-jsdoc` and then copy them over by hand.)

Hopefully this will allow us to restart the diffing approach more easily for
future upgrades.
Copy link
Member

Choose a reason for hiding this comment

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

Great! Yeah, agreed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies Pull requests that update a dependency file
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants