| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259 | /**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * Portions copyright for react-native-windows:
 *
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */
import React from 'react';
import {
  UIManager as NotTypedUIManager,
  View,
  requireNativeComponent,
  StyleSheet,
  Image,
  ImageSourcePropType,
  findNodeHandle,
} from 'react-native';
import {
  createOnShouldStartLoadWithRequest,
} from './WebViewShared';
import {
  NativeWebViewWindows,
  WebViewSharedProps,
  WebViewProgressEvent,
  WebViewNavigationEvent,
  WebViewErrorEvent,
  WebViewHttpErrorEvent,
  WebViewMessageEvent,
  RNCWebViewUIManagerWindows,
  State,
} from './WebViewTypes';
const UIManager = NotTypedUIManager as RNCWebViewUIManagerWindows;
const { resolveAssetSource } = Image;
const RCTWebView: typeof NativeWebViewWindows = requireNativeComponent(
  'RCTWebView',
);
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  hidden: {
    height: 0,
    flex: 0, // disable 'flex:1' when hiding a View
  },
  loadingView: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  loadingProgressBar: {
    height: 20,
  },
});
export default class WebView extends React.Component<WebViewSharedProps, State> {
  static defaultProps = {
    javaScriptEnabled: true,
  };
  state: State = {
    viewState: this.props.startInLoadingState ? 'LOADING' : 'IDLE',
    lastErrorEvent: null,
  }
  webViewRef = React.createRef<NativeWebViewWindows>();
  goForward = () => {
    UIManager.dispatchViewManagerCommand(
      this.getWebViewHandle(),
      UIManager.getViewManagerConfig('RCTWebView').Commands.goForward,
      undefined,
    );
  }
  goBack = () => {
    UIManager.dispatchViewManagerCommand(
      this.getWebViewHandle(),
      UIManager.getViewManagerConfig('RCTWebView').Commands.goBack,
      undefined,
    );
  }
  reload = () => {
    UIManager.dispatchViewManagerCommand(
      this.getWebViewHandle(),
      UIManager.getViewManagerConfig('RCTWebView').Commands.reload,
      undefined,
    );
  }
  injectJavaScript = (data: string) => {
    UIManager.dispatchViewManagerCommand(
      this.getWebViewHandle(),
      UIManager.getViewManagerConfig('RCTWebView').Commands.injectJavaScript,
      [data],
    );
  }
  /**
   * We return an event with a bunch of fields including:
   *  url, title, loading, canGoBack, canGoForward
   */
  updateNavigationState = (event: WebViewNavigationEvent) => {
    if (this.props.onNavigationStateChange) {
      this.props.onNavigationStateChange(event.nativeEvent);
    }
  }
  getWebViewHandle = () => {
    // eslint-disable-next-line react/no-string-refs
    return findNodeHandle(this.webViewRef.current);
  }
  onLoadingStart = (event: WebViewNavigationEvent) => {
    const { onLoadStart } = this.props;
    if(onLoadStart) {
      onLoadStart(event);
    }
    this.updateNavigationState(event);
  }
  onLoadingProgress = (event: WebViewProgressEvent) => {
    const { onLoadProgress } = this.props;
    if (onLoadProgress) {
      onLoadProgress(event);
    }
  };
  onLoadingError = (event: WebViewErrorEvent) => {
    event.persist(); // persist this event because we need to store it
    const {onError, onLoadEnd} = this.props;
    if(onError) {
      onError(event);
    }
    if(onLoadEnd) {
      onLoadEnd(event);
    }
    console.error('Encountered an error loading page', event.nativeEvent);
    this.setState({
      lastErrorEvent: event.nativeEvent,
      viewState: 'ERROR',
    });
  }
  onLoadingFinish =(event: WebViewNavigationEvent) => {
    const {onLoad, onLoadEnd} = this.props;
    if(onLoad) {
      onLoad(event);
    }
    if(onLoadEnd) {
      onLoadEnd(event);
    }
    this.setState({
      viewState: 'IDLE',
    });
    this.updateNavigationState(event);
  }
  onMessage = (event: WebViewMessageEvent) => {
    const { onMessage } = this.props;
    if (onMessage) {
      onMessage(event);
    }
  }
  onHttpError = (event: WebViewHttpErrorEvent) => {
    const { onHttpError } = this.props;
    if (onHttpError) {
      onHttpError(event);
    }
  }
  render () {
    const {
      nativeConfig = {},
      onMessage,
      onShouldStartLoadWithRequest: onShouldStartLoadWithRequestProp,
      originWhitelist,
      renderError,
      renderLoading,
      style,
      containerStyle,
      ...otherProps
    } = this.props;
    let otherView = null;
    if (this.state.viewState === 'LOADING') {
      otherView = this.props.renderLoading && this.props.renderLoading();
    } else if (this.state.viewState === 'ERROR') {
      const errorEvent = this.state.lastErrorEvent;
      otherView = this.props.renderError
        && this.props.renderError(
          errorEvent.domain,
          errorEvent.code,
          errorEvent.description,
        );
    } else if (this.state.viewState !== 'IDLE') {
      console.error('RCTWebView invalid state encountered: ', this.state.viewState);
    }
    const webViewStyles = [styles.container, this.props.style];
    if (
      this.state.viewState === 'LOADING'
      || this.state.viewState === 'ERROR'
    ) {
      // if we're in either LOADING or ERROR states, don't show the webView
      webViewStyles.push(styles.hidden);
    }
    const onShouldStartLoadWithRequest = createOnShouldStartLoadWithRequest(
      ()=>{},
      // casting cause it's in the default props
      originWhitelist as readonly string[],
      onShouldStartLoadWithRequestProp,
    );
    const NativeWebView
    = (nativeConfig.component as typeof NativeWebViewWindows | undefined)
    || RCTWebView;
    const webView = (
      <NativeWebView
        ref={this.webViewRef}
        key="webViewKey"
        {...otherProps}
        messagingEnabled={typeof onMessage === 'function'}
        onLoadingError={this.onLoadingError}
        onLoadingFinish={this.onLoadingFinish}
        onLoadingProgress={this.onLoadingProgress}
        onLoadingStart={this.onLoadingStart}
        onHttpError={this.onHttpError}
        onMessage={this.onMessage}
        onScroll={this.props.onScroll}
        onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
        source={resolveAssetSource(this.props.source as ImageSourcePropType)}
        style={webViewStyles}
        {...nativeConfig.props}
      />
    );
    return (
      <View style={styles.container}>
        {webView}
        {otherView}
      </View>
    );
  }
}
 |