diff --git a/.gitignore b/.gitignore
index 9195503e..6af04a02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,6 @@
ios/RCTWKWebView.xcodeproj/xcuserdata/*
ios/RCTWKWebView.xcodeproj/project.xcworkspace/xcuserdata/*
+
+macos/RCTWKWebView.xcodeproj/xcuserdata/*
+macos/RCTWKWebView.xcodeproj/project.xcworkspace/xcuserdata/*
diff --git a/README.md b/README.md
index ee5e2833..66b4320b 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,10 @@
[](https://badge.fury.io/js/react-native-wkwebview-reborn)
-React Native comes with [WebView](http://facebook.github.io/react-native/docs/webview.html) component, which uses UIWebView on iOS. This component uses [WKWebView](http://nshipster.com/wkwebkit/) introduced in iOS 8 with all the performance boost.
+React Native comes with [WebView](http://facebook.github.io/react-native/docs/webview.html) component, which uses UIWebView on iOS. This component uses [WKWebView](http://nshipster.com/wkwebkit/) introduced in iOS 8.0 and macOS 10.10 with all the performance boosts.
-**Deployment Target >= iOS 8.0 is required** *(which is React Native's current minimum deployment target anyway).*
+* **Deployment Target >= iOS 8.0 is required** *(which is React Native's current minimum deployment target anyway)* when deploying to iOS.
+* **Deployment Target >= macOS 10.10 is required** when deploying to macOS.
### Install
@@ -15,10 +16,10 @@ React Native comes with [WebView](http://facebook.github.io/react-native/docs/we
1. Install from npm (note the postfix in the package name): `npm install react-native-wkwebview-reborn`
2. In the XCode's "Project navigator", right click on your project's Libraries folder ➜ Add Files to <...>
-3. Go to node_modules ➜ react-native-wkwebview-reborn ➜ ios ➜ select `RCTWKWebView.xcodeproj`
-4. Go your build target ➜ Build Phases ➜ Link Binary With Libraries, click "+" and select `libRCTWkWebView.a` (see the following screenshot for reference)
+3. Go to node_modules ➜ react-native-wkwebview-reborn ➜ ios (or macos) ➜ select `RCTWKWebView.xcodeproj`
+4. Go your build target ➜ Build Phases ➜ Link Binary With Libraries, click "+" and select `libRCTWkWebView.a` (or `libRCTWkWebView-macos.a`, when building for macOS) (see the following screenshot for reference)

-5. Compile and profit (Remember to set Minimum Deployment Target = 8.0)
+5. Compile and profit (Remember to set `Minimum Deployment Target` to 8.0 for iOS, or 10.10 for macOS)
### Usage
@@ -127,6 +128,18 @@ Add JavaScript at document end. Since 1.20.0, the implementation has been change
- allowsInlineMediaPlayback
- decelerationRate
+#### Incomplete APIs for macOS
+
+Some iOS methods have not been fully implemented for macOS yet. This is because the macOS implementation of `WKWebView` has some differences, such as lacking a `scrollView`, and using `AppKit` instead of `UIKit`; and so I (@shirakaba) don't quite know what should be done in the macOS case. I therefore can't guarantee the success of the following methods for macOS:
+
+- `setHideKeyboardAccessoryView`
+- `setContentInset`
+- `setBackgroundColor`
+- `refreshContentInset`
+- `scrollViewDidScroll`
+
+`mailto` and `tel` schemes are also not supported yet because I don't know the macOS equivalent.
+
### Advanced Communication between React Native and WkWebView
diff --git a/WKWebView.macos.js b/WKWebView.macos.js
new file mode 100644
index 00000000..f42b839c
--- /dev/null
+++ b/WKWebView.macos.js
@@ -0,0 +1,558 @@
+'use strict';
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import ReactNative, {
+ requireNativeComponent,
+ EdgeInsetsPropType,
+ StyleSheet,
+ UIManager,
+ View,
+ ViewPropTypes,
+ NativeModules,
+ Text,
+ ActivityIndicator
+} from 'react-native';
+
+import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
+import deprecatedPropType from 'react-native/Libraries/Utilities/deprecatedPropType';
+import invariant from 'fbjs/lib/invariant';
+import keyMirror from 'fbjs/lib/keyMirror';
+const WKWebViewManager = NativeModules.WKWebViewManager;
+
+var BGWASH = 'rgba(255,255,255,0.8)';
+
+const WebViewState = keyMirror({
+ IDLE: null,
+ LOADING: null,
+ ERROR: null,
+});
+
+const NavigationType = keyMirror({
+ click: true,
+ formsubmit: true,
+ backforward: true,
+ reload: true,
+ formresubmit: true,
+ other: true,
+});
+
+const JSNavigationScheme = 'react-js-navigation';
+
+type ErrorEvent = {
+ domain: any;
+ code: any;
+ description: any;
+}
+
+type Event = Object;
+
+const defaultRenderLoading = () => (
+
+
+
+);
+const defaultRenderError = (errorDomain, errorCode, errorDesc) => (
+
+
+ Error loading page
+
+
+ {'Domain: ' + errorDomain}
+
+
+ {'Error Code: ' + errorCode}
+
+
+ {'Description: ' + errorDesc}
+
+
+);
+
+/**
+ * Renders a native WebView.
+ */
+
+class WKWebView extends React.Component {
+ static JSNavigationScheme = JSNavigationScheme;
+ static NavigationType = NavigationType;
+
+ static propTypes = {
+ ...ViewPropTypes,
+
+ html: deprecatedPropType(
+ PropTypes.string,
+ 'Use the `source` prop instead.'
+ ),
+
+ url: deprecatedPropType(
+ PropTypes.string,
+ 'Use the `source` prop instead.'
+ ),
+
+ /**
+ * Loads static html or a uri (with optional headers) in the WebView.
+ */
+ source: PropTypes.oneOfType([
+ PropTypes.shape({
+ /*
+ * The URI to load in the WebView. Can be a local or remote file.
+ */
+ uri: PropTypes.string,
+ /*
+ * The HTTP Method to use. Defaults to GET if not specified.
+ * NOTE: On Android, only GET and POST are supported.
+ */
+ method: PropTypes.string,
+ /*
+ * Additional HTTP headers to send with the request.
+ * NOTE: On Android, this can only be used with GET requests.
+ */
+ headers: PropTypes.object,
+ /*
+ * The HTTP body to send with the request. This must be a valid
+ * UTF-8 string, and will be sent exactly as specified, with no
+ * additional encoding (e.g. URL-escaping or base64) applied.
+ * NOTE: On Android, this can only be used with POST requests.
+ */
+ body: PropTypes.string,
+ }),
+ PropTypes.shape({
+ /*
+ * A static HTML page to display in the WebView.
+ */
+ html: PropTypes.string,
+ /*
+ * The base URL to be used for any relative links in the HTML.
+ */
+ baseUrl: PropTypes.string,
+ }),
+ /*
+ * Used internally by packager.
+ */
+ PropTypes.number,
+ ]),
+
+ /**
+ * This property specifies how the safe area insets are used to modify the
+ * content area of the scroll view. The default value of this property is
+ * "never". Available on iOS 11 and later.
+ */
+ contentInsetAdjustmentBehavior: PropTypes.oneOf([
+ 'automatic',
+ 'scrollableAxes',
+ 'never', // default
+ 'always',
+ ]),
+
+ /**
+ * Function that returns a view to show if there's an error.
+ */
+ renderError: PropTypes.func, // view to show if there's an error
+ /**
+ * Function that returns a loading indicator.
+ */
+ renderLoading: PropTypes.func,
+ /**
+ * Invoked when load finish
+ */
+ onLoad: PropTypes.func,
+ /**
+ * Invoked when load either succeeds or fails
+ */
+ onLoadEnd: PropTypes.func,
+ /**
+ * Invoked on load start
+ */
+ onLoadStart: PropTypes.func,
+ /**
+ * Invoked when load fails
+ */
+ onError: PropTypes.func,
+ /**
+ * Report the progress
+ */
+ onProgress: PropTypes.func,
+ /**
+ * A function that is invoked when the webview calls `window.postMessage`.
+ * Setting this property will inject a `postMessage` global into your
+ * webview, but will still call pre-existing values of `postMessage`.
+ *
+ * `window.postMessage` accepts one argument, `data`, which will be
+ * available on the event object, `event.nativeEvent.data`. `data`
+ * must be a string.
+ */
+ onMessage: PropTypes.func,
+ /**
+ * Receive scroll events from view
+ */
+ onScroll: PropTypes.func,
+ /**
+ * @platform macos
+ */
+ bounces: PropTypes.bool,
+ scrollEnabled: PropTypes.bool,
+ allowsBackForwardNavigationGestures: PropTypes.bool,
+ automaticallyAdjustContentInsets: PropTypes.bool,
+ contentInset: EdgeInsetsPropType,
+ onNavigationStateChange: PropTypes.func,
+ scalesPageToFit: PropTypes.bool,
+ startInLoadingState: PropTypes.bool,
+ style: ViewPropTypes.style,
+ /**
+ * If false injectJavaScript will run both main frame and iframe
+ * @platform macos
+ */
+ injectJavaScriptForMainFrameOnly: PropTypes.bool,
+ /**
+ * If false injectedJavaScript will run both main frame and iframe
+ * @platform macos
+ */
+ injectedJavaScriptForMainFrameOnly: PropTypes.bool,
+ /**
+ * Function that accepts a string that will be passed to the WebView and executed immediately as JavaScript.
+ */
+ injectJavaScript: PropTypes.string,
+ /**
+ * Sets the JS to be injected when the webpage loads.
+ */
+ injectedJavaScript: PropTypes.string,
+ /**
+ * Allows custom handling of any webview requests by a JS handler. Return true
+ * or false from this method to continue loading the request.
+ * @platform macos
+ */
+ onShouldStartLoadWithRequest: PropTypes.func,
+ /**
+ * Copies cookies from sharedHTTPCookieStorage when calling loadRequest.
+ * Set this to true to emulate behavior of WebView component.
+ */
+ sendCookies: PropTypes.bool,
+ /**
+ * If set to true, target="_blank" or window.open will be opened in WebView, instead
+ * of new window. Default is false to be backward compatible.
+ */
+ openNewWindowInWebView: PropTypes.bool,
+ /**
+ * Hide the accessory view when the keyboard is open. Default is false to be
+ * backward compatible.
+ */
+ hideKeyboardAccessoryView: PropTypes.bool,
+ /**
+ * Enable the keyboard to display when focusing an input in a webview programatically.
+ * [Removed because macOS WKWebView doesn't have an on-screen keyboard]
+ */
+ // keyboardDisplayRequiresUserAction: PropTypes.bool,
+ /**
+ * A Boolean value that determines whether pressing on a link displays a preview of the destination for the link. This props is available on devices that support 3D Touch. In iOS 10 and later, the default value is true; before that, the default value is false.
+ */
+ allowsLinkPreview: PropTypes.bool,
+ /**
+ * Sets the customized user agent by using of the WKWebView
+ */
+ customUserAgent: PropTypes.string,
+ userAgent: PropTypes.string,
+ /**
+ * A Boolean value that determines whether paging is enabled for the scroll view.
+ */
+ pagingEnabled: PropTypes.bool,
+ /**
+ * A Boolean value that sets whether diagonal scrolling is allowed.
+ */
+ directionalLockEnabled: PropTypes.bool,
+ };
+
+ state = {
+ viewState: WebViewState.IDLE,
+ lastErrorEvent: (null: ?ErrorEvent),
+ startInLoadingState: true,
+ };
+
+ componentWillMount() {
+ if (this.props.startInLoadingState) {
+ this.setState({ viewState: WebViewState.LOADING });
+ }
+ }
+
+ render() {
+ let otherView = null;
+
+ if (this.state.viewState === WebViewState.LOADING) {
+ otherView = (this.props.renderLoading || defaultRenderLoading)();
+ } else if (this.state.viewState === WebViewState.ERROR) {
+ const errorEvent = this.state.lastErrorEvent;
+ invariant(
+ errorEvent != null,
+ 'lastErrorEvent expected to be non-null'
+ );
+ otherView = (this.props.renderError || defaultRenderError)(
+ errorEvent.domain,
+ errorEvent.code,
+ errorEvent.description
+ );
+ } else if (this.state.viewState !== WebViewState.IDLE) {
+ console.error(
+ 'RCTWKWebView invalid state encountered: ' + this.state.loading
+ );
+ }
+
+ const webViewStyles = [styles.container, styles.webView, this.props.style];
+ if (this.state.viewState === WebViewState.LOADING ||
+ this.state.viewState === WebViewState.ERROR) {
+ // if we're in either LOADING or ERROR states, don't show the webView
+ webViewStyles.push(styles.hidden);
+ }
+
+ const onShouldStartLoadWithRequest = this.props.onShouldStartLoadWithRequest && ((event: Event) => {
+ const shouldStart = this.props.onShouldStartLoadWithRequest &&
+ this.props.onShouldStartLoadWithRequest(event.nativeEvent);
+ WKWebViewManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier);
+ });
+
+ let source = {};
+ if (this.props.source && typeof this.props.source == 'object') {
+ source = Object.assign({}, this.props.source, {
+ sendCookies: this.props.sendCookies,
+ customUserAgent: this.props.customUserAgent || this.props.userAgent
+ });
+ }
+
+ if (this.props.html) {
+ source.html = this.props.html;
+ } else if (this.props.url) {
+ source.uri = this.props.url;
+ }
+
+ const messagingEnabled = typeof this.props.onMessage === 'function';
+
+ const webView =
+ { this.webview = ref; }}
+ key="webViewKey"
+ style={webViewStyles}
+ contentInsetAdjustmentBehavior={this.props.contentInsetAdjustmentBehavior}
+ source={resolveAssetSource(source)}
+ injectJavaScriptForMainFrameOnly={this.props.injectJavaScriptForMainFrameOnly}
+ injectedJavaScriptForMainFrameOnly={this.props.injectedJavaScriptForMainFrameOnly}
+ injectJavaScript={this.props.injectJavaScript}
+ injectedJavaScript={this.props.injectedJavaScript}
+ bounces={this.props.bounces}
+ scrollEnabled={this.props.scrollEnabled}
+ contentInset={this.props.contentInset}
+ allowsBackForwardNavigationGestures={this.props.allowsBackForwardNavigationGestures}
+ automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
+ openNewWindowInWebView={this.props.openNewWindowInWebView}
+ hideKeyboardAccessoryView={this.props.hideKeyboardAccessoryView}
+ /* Removed because macOS doesn't have an on-screen keyboard. */
+ // keyboardDisplayRequiresUserAction={this.props.keyboardDisplayRequiresUserAction}
+ allowsLinkPreview={this.props.allowsLinkPreview}
+ onLoadingStart={this._onLoadingStart}
+ onLoadingFinish={this._onLoadingFinish}
+ onLoadingError={this._onLoadingError}
+ messagingEnabled={messagingEnabled}
+ onProgress={this._onProgress}
+ onMessage={this._onMessage}
+ onScroll={this._onScroll}
+ onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
+ pagingEnabled={this.props.pagingEnabled}
+ directionalLockEnabled={this.props.directionalLockEnabled}
+ />;
+
+ return (
+
+ {webView}
+ {otherView}
+
+ );
+ }
+
+ /**
+ * Go forward one page in the webview's history.
+ */
+ goForward = () => {
+ UIManager.dispatchViewManagerCommand(
+ this.getWebViewHandle(),
+ UIManager.RCTWKWebView.Commands.goForward,
+ null
+ );
+ };
+
+ /**
+ * Go back one page in the webview's history.
+ */
+ goBack = () => {
+ UIManager.dispatchViewManagerCommand(
+ this.getWebViewHandle(),
+ UIManager.RCTWKWebView.Commands.goBack,
+ null
+ );
+ };
+
+ /**
+ * Indicating whether there is a back item in the back-forward list that can be navigated to
+ */
+ canGoBack = () => {
+ return WKWebViewManager.canGoBack(this.getWebViewHandle());
+ };
+
+ /**
+ * Indicating whether there is a forward item in the back-forward list that can be navigated to
+ */
+ canGoForward = () => {
+ return WKWebViewManager.canGoForward(this.getWebViewHandle());
+ };
+
+ /**
+ * Reloads the current page.
+ */
+ reload = () => {
+ this.setState({ viewState: WebViewState.LOADING });
+ UIManager.dispatchViewManagerCommand(
+ this.getWebViewHandle(),
+ UIManager.RCTWKWebView.Commands.reload,
+ null
+ );
+ };
+
+ /**
+ * Stop loading the current page.
+ */
+ stopLoading = () => {
+ UIManager.dispatchViewManagerCommand(
+ this.getWebViewHandle(),
+ UIManager.RCTWKWebView.Commands.stopLoading,
+ null
+ )
+ };
+
+ /**
+ * Posts a message to the web view, which will emit a `message` event.
+ * Accepts one argument, `data`, which must be a string.
+ *
+ * In your webview, you'll need to something like the following.
+ *
+ * ```js
+ * document.addEventListener('message', e => { document.title = e.data; });
+ * ```
+ */
+ postMessage = (data) => {
+ UIManager.dispatchViewManagerCommand(
+ this.getWebViewHandle(),
+ UIManager.RCTWKWebView.Commands.postMessage,
+ [String(data)]
+ );
+ };
+
+ evaluateJavaScript = (js) => {
+ return WKWebViewManager.evaluateJavaScript(this.getWebViewHandle(), js);
+ };
+
+ /**
+ * We return an event with a bunch of fields including:
+ * url, title, loading, canGoBack, canGoForward
+ */
+ _updateNavigationState = (event: Event) => {
+ if (this.props.onNavigationStateChange) {
+ this.props.onNavigationStateChange(event.nativeEvent);
+ }
+ };
+
+ /**
+ * Returns the native webview node.
+ */
+ getWebViewHandle = (): any => {
+ return ReactNative.findNodeHandle(this.webview);
+ };
+
+ _onLoadingStart = (event: Event) => {
+ const onLoadStart = this.props.onLoadStart;
+ onLoadStart && onLoadStart(event);
+ this._updateNavigationState(event);
+ };
+
+ _onLoadingError = (event: Event) => {
+ event.persist(); // persist this event because we need to store it
+ const { onError, onLoadEnd } = this.props;
+ onError && onError(event);
+ onLoadEnd && onLoadEnd(event);
+ console.warn('Encountered an error loading page', event.nativeEvent);
+
+ this.setState({
+ lastErrorEvent: event.nativeEvent,
+ viewState: WebViewState.ERROR
+ });
+ };
+
+ _onLoadingFinish = (event: Event) => {
+ const { onLoad, onLoadEnd } = this.props;
+ onLoad && onLoad(event);
+ onLoadEnd && onLoadEnd(event);
+ this.setState({
+ viewState: WebViewState.IDLE,
+ });
+ this._updateNavigationState(event);
+ };
+
+ _onProgress = (event: Event) => {
+ const onProgress = this.props.onProgress;
+ onProgress && onProgress(event.nativeEvent.progress);
+ };
+
+ _onMessage = (event: Event) => {
+ var { onMessage } = this.props;
+ onMessage && onMessage(event);
+ };
+
+ _onScroll = (event: Event) => {
+ const onScroll = this.props.onScroll;
+ onScroll && onScroll(event.nativeEvent);
+ };
+}
+
+const RCTWKWebView = requireNativeComponent('RCTWKWebView', WKWebView, {
+ nativeOnly: {
+ onLoadingStart: true,
+ onLoadingError: true,
+ onLoadingFinish: true,
+ onMessage: true,
+ messagingEnabled: PropTypes.bool,
+ }
+});
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ errorContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: BGWASH,
+ },
+ errorText: {
+ fontSize: 14,
+ textAlign: 'center',
+ marginBottom: 2,
+ },
+ errorTextTitle: {
+ fontSize: 15,
+ fontWeight: '500',
+ marginBottom: 10,
+ },
+ hidden: {
+ height: 0,
+ flex: 0, // disable 'flex:1' when hiding a View
+ },
+ loadingView: {
+ backgroundColor: BGWASH,
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ height: 100,
+ },
+ webView: {
+ backgroundColor: '#ffffff',
+ }
+});
+
+export default WKWebView;
diff --git a/ios/RCTWKWebView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/RCTWKWebView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/ios/RCTWKWebView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ios/RCTWKWebView/RCTWKWebView.h b/ios/RCTWKWebView/RCTWKWebView.h
index 1327bdfd..0c208dc4 100644
--- a/ios/RCTWKWebView/RCTWKWebView.h
+++ b/ios/RCTWKWebView/RCTWKWebView.h
@@ -37,7 +37,7 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request
@property (nonatomic, copy) NSString *injectJavaScript;
@property (nonatomic, copy) NSString *injectedJavaScript;
@property (nonatomic, assign) BOOL hideKeyboardAccessoryView;
-@property (nonatomic, assign) BOOL keyboardDisplayRequiresUserAction;
+// @property (nonatomic, assign) BOOL keyboardDisplayRequiresUserAction; /* macOS doesn't have an on-screen keyboard */
- (void)goForward;
diff --git a/macos/RCTWKWebView.xcodeproj/project.pbxproj b/macos/RCTWKWebView.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..3f88db6c
--- /dev/null
+++ b/macos/RCTWKWebView.xcodeproj/project.pbxproj
@@ -0,0 +1,403 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 097457AB1D2A457C000D9368 /* RCTWKWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 097457A91D2A457C000D9368 /* RCTWKWebViewManager.m */; };
+ 097457AE1D2A4595000D9368 /* RCTWKWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 097457AD1D2A4595000D9368 /* RCTWKWebView.m */; };
+ 097457AF1D2AF4E0000D9368 /* RCTWKWebView.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 097457AC1D2A4595000D9368 /* RCTWKWebView.h */; };
+ 097457B01D2AF4E0000D9368 /* RCTWKWebViewManager.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 097457A81D2A457C000D9368 /* RCTWKWebViewManager.h */; };
+ 3E609CF61EAA815D00187C8C /* WeakScriptMessageDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E609CF51EAA815D00187C8C /* WeakScriptMessageDelegate.m */; };
+ 7D8047DA20A8C8F700B3157B /* RCTWKWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 097457A91D2A457C000D9368 /* RCTWKWebViewManager.m */; };
+ 7D8047DB20A8C8F700B3157B /* WeakScriptMessageDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3E609CF51EAA815D00187C8C /* WeakScriptMessageDelegate.m */; };
+ 7D8047DC20A8C8F700B3157B /* WKProcessPool+SharedProcessPool.m in Sources */ = {isa = PBXBuildFile; fileRef = E683F3D62080F3400005F1F5 /* WKProcessPool+SharedProcessPool.m */; };
+ 7D8047DD20A8C8F700B3157B /* RCTWKWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 097457AD1D2A4595000D9368 /* RCTWKWebView.m */; };
+ 7D8047E020A8C8F700B3157B /* RCTWKWebView.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 097457AC1D2A4595000D9368 /* RCTWKWebView.h */; };
+ 7D8047E120A8C8F700B3157B /* RCTWKWebViewManager.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 097457A81D2A457C000D9368 /* RCTWKWebViewManager.h */; };
+ E683F3D72080F3400005F1F5 /* WKProcessPool+SharedProcessPool.m in Sources */ = {isa = PBXBuildFile; fileRef = E683F3D62080F3400005F1F5 /* WKProcessPool+SharedProcessPool.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 097457981D2A440A000D9368 /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "include/$(PRODUCT_NAME)";
+ dstSubfolderSpec = 16;
+ files = (
+ 097457AF1D2AF4E0000D9368 /* RCTWKWebView.h in CopyFiles */,
+ 097457B01D2AF4E0000D9368 /* RCTWKWebViewManager.h in CopyFiles */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7D8047DF20A8C8F700B3157B /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "include/$(PRODUCT_NAME)";
+ dstSubfolderSpec = 16;
+ files = (
+ 7D8047E020A8C8F700B3157B /* RCTWKWebView.h in CopyFiles */,
+ 7D8047E120A8C8F700B3157B /* RCTWKWebViewManager.h in CopyFiles */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 0974579A1D2A440A000D9368 /* libRCTWKWebView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTWKWebView.a; sourceTree = BUILT_PRODUCTS_DIR; };
+ 097457A81D2A457C000D9368 /* RCTWKWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWKWebViewManager.h; sourceTree = ""; };
+ 097457A91D2A457C000D9368 /* RCTWKWebViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = RCTWKWebViewManager.m; sourceTree = ""; tabWidth = 2; };
+ 097457AC1D2A4595000D9368 /* RCTWKWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWKWebView.h; sourceTree = ""; };
+ 097457AD1D2A4595000D9368 /* RCTWKWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = RCTWKWebView.m; sourceTree = ""; tabWidth = 2; };
+ 3E609CF41EAA815D00187C8C /* WeakScriptMessageDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WeakScriptMessageDelegate.h; sourceTree = ""; };
+ 3E609CF51EAA815D00187C8C /* WeakScriptMessageDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WeakScriptMessageDelegate.m; sourceTree = ""; };
+ 7D8047E520A8C8F700B3157B /* libRCTWKWebView-macos.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRCTWKWebView-macos.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ E683F3D32080F2E10005F1F5 /* WKProcessPool+SharedProcessPool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WKProcessPool+SharedProcessPool.h"; sourceTree = ""; };
+ E683F3D62080F3400005F1F5 /* WKProcessPool+SharedProcessPool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "WKProcessPool+SharedProcessPool.m"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 097457971D2A440A000D9368 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7D8047DE20A8C8F700B3157B /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 097457911D2A440A000D9368 = {
+ isa = PBXGroup;
+ children = (
+ 0974579C1D2A440A000D9368 /* RCTWKWebView */,
+ 0974579B1D2A440A000D9368 /* Products */,
+ );
+ sourceTree = "";
+ };
+ 0974579B1D2A440A000D9368 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 0974579A1D2A440A000D9368 /* libRCTWKWebView.a */,
+ 7D8047E520A8C8F700B3157B /* libRCTWKWebView-macos.a */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 0974579C1D2A440A000D9368 /* RCTWKWebView */ = {
+ isa = PBXGroup;
+ children = (
+ 097457AC1D2A4595000D9368 /* RCTWKWebView.h */,
+ 097457AD1D2A4595000D9368 /* RCTWKWebView.m */,
+ 097457A81D2A457C000D9368 /* RCTWKWebViewManager.h */,
+ 097457A91D2A457C000D9368 /* RCTWKWebViewManager.m */,
+ 3E609CF41EAA815D00187C8C /* WeakScriptMessageDelegate.h */,
+ 3E609CF51EAA815D00187C8C /* WeakScriptMessageDelegate.m */,
+ E683F3D32080F2E10005F1F5 /* WKProcessPool+SharedProcessPool.h */,
+ E683F3D62080F3400005F1F5 /* WKProcessPool+SharedProcessPool.m */,
+ );
+ path = RCTWKWebView;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 097457991D2A440A000D9368 /* RCTWKWebView */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 097457A31D2A440A000D9368 /* Build configuration list for PBXNativeTarget "RCTWKWebView" */;
+ buildPhases = (
+ 097457961D2A440A000D9368 /* Sources */,
+ 097457971D2A440A000D9368 /* Frameworks */,
+ 097457981D2A440A000D9368 /* CopyFiles */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = RCTWKWebView;
+ productName = RCTWKWebView;
+ productReference = 0974579A1D2A440A000D9368 /* libRCTWKWebView.a */;
+ productType = "com.apple.product-type.library.static";
+ };
+ 7D8047D820A8C8F700B3157B /* RCTWKWebView-macos */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 7D8047E220A8C8F700B3157B /* Build configuration list for PBXNativeTarget "RCTWKWebView-macos" */;
+ buildPhases = (
+ 7D8047D920A8C8F700B3157B /* Sources */,
+ 7D8047DE20A8C8F700B3157B /* Frameworks */,
+ 7D8047DF20A8C8F700B3157B /* CopyFiles */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "RCTWKWebView-macos";
+ productName = RCTWKWebView;
+ productReference = 7D8047E520A8C8F700B3157B /* libRCTWKWebView-macos.a */;
+ productType = "com.apple.product-type.library.static";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 097457921D2A440A000D9368 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0920;
+ TargetAttributes = {
+ 097457991D2A440A000D9368 = {
+ CreatedOnToolsVersion = 7.3.1;
+ };
+ };
+ };
+ buildConfigurationList = 097457951D2A440A000D9368 /* Build configuration list for PBXProject "RCTWKWebView" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = 097457911D2A440A000D9368;
+ productRefGroup = 0974579B1D2A440A000D9368 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 097457991D2A440A000D9368 /* RCTWKWebView */,
+ 7D8047D820A8C8F700B3157B /* RCTWKWebView-macos */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 097457961D2A440A000D9368 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 097457AB1D2A457C000D9368 /* RCTWKWebViewManager.m in Sources */,
+ 3E609CF61EAA815D00187C8C /* WeakScriptMessageDelegate.m in Sources */,
+ E683F3D72080F3400005F1F5 /* WKProcessPool+SharedProcessPool.m in Sources */,
+ 097457AE1D2A4595000D9368 /* RCTWKWebView.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 7D8047D920A8C8F700B3157B /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7D8047DA20A8C8F700B3157B /* RCTWKWebViewManager.m in Sources */,
+ 7D8047DB20A8C8F700B3157B /* WeakScriptMessageDelegate.m in Sources */,
+ 7D8047DC20A8C8F700B3157B /* WKProcessPool+SharedProcessPool.m in Sources */,
+ 7D8047DD20A8C8F700B3157B /* RCTWKWebView.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ 097457A11D2A440A000D9368 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ };
+ name = Debug;
+ };
+ 097457A21D2A440A000D9368 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 097457A41D2A440A000D9368 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ HEADER_SEARCH_PATHS = (
+ "$(SRCROOT)/../../React/**",
+ "$(inherited)",
+ "$(SRCROOT)/node_modules/react-native/React/**",
+ "$(SRCROOT)/../react-native/React/**",
+ "$(SRCROOT)/../../../node_modules/react-native/React/**",
+ );
+ OTHER_LDFLAGS = "-ObjC";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ };
+ name = Debug;
+ };
+ 097457A51D2A440A000D9368 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ HEADER_SEARCH_PATHS = (
+ "$(SRCROOT)/../../React/**",
+ "$(inherited)",
+ "$(SRCROOT)/node_modules/react-native/React/**",
+ "$(SRCROOT)/../react-native/React/**",
+ "$(SRCROOT)/../../../node_modules/react-native/React/**",
+ );
+ OTHER_LDFLAGS = "-ObjC";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ };
+ name = Release;
+ };
+ 7D8047E320A8C8F700B3157B /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ HEADER_SEARCH_PATHS = (
+ "$(SRCROOT)/../../React/**",
+ "$(inherited)",
+ "$(SRCROOT)/node_modules/react-native/React/**",
+ "$(SRCROOT)/../react-native/React/**",
+ "$(SRCROOT)/../../../node_modules/react-native/React/**",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
+ OTHER_LDFLAGS = "-ObjC";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ SKIP_INSTALL = YES;
+ };
+ name = Debug;
+ };
+ 7D8047E420A8C8F700B3157B /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ HEADER_SEARCH_PATHS = (
+ "$(SRCROOT)/../../React/**",
+ "$(inherited)",
+ "$(SRCROOT)/node_modules/react-native/React/**",
+ "$(SRCROOT)/../react-native/React/**",
+ "$(SRCROOT)/../../../node_modules/react-native/React/**",
+ );
+ MACOSX_DEPLOYMENT_TARGET = 10.10;
+ OTHER_LDFLAGS = "-ObjC";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ SKIP_INSTALL = YES;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 097457951D2A440A000D9368 /* Build configuration list for PBXProject "RCTWKWebView" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 097457A11D2A440A000D9368 /* Debug */,
+ 097457A21D2A440A000D9368 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 097457A31D2A440A000D9368 /* Build configuration list for PBXNativeTarget "RCTWKWebView" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 097457A41D2A440A000D9368 /* Debug */,
+ 097457A51D2A440A000D9368 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 7D8047E220A8C8F700B3157B /* Build configuration list for PBXNativeTarget "RCTWKWebView-macos" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 7D8047E320A8C8F700B3157B /* Debug */,
+ 7D8047E420A8C8F700B3157B /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 097457921D2A440A000D9368 /* Project object */;
+}
diff --git a/macos/RCTWKWebView.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/macos/RCTWKWebView.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..b2ae6140
--- /dev/null
+++ b/macos/RCTWKWebView.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/macos/RCTWKWebView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/RCTWKWebView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..18d98100
--- /dev/null
+++ b/macos/RCTWKWebView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/macos/RCTWKWebView/RCTWKWebView.h b/macos/RCTWKWebView/RCTWKWebView.h
new file mode 100644
index 00000000..e98b831d
--- /dev/null
+++ b/macos/RCTWKWebView/RCTWKWebView.h
@@ -0,0 +1,52 @@
+#import
+#import
+#import
+
+@class RCTWKWebView;
+
+/**
+ * Special scheme used to pass messages to the injectedJavaScript
+ * code without triggering a page load. Usage:
+ *
+ * window.location.href = RCTJSNavigationScheme + '://hello'
+ */
+extern NSString *const RCTJSNavigationScheme;
+
+@protocol RCTWKWebViewDelegate
+
+- (BOOL)webView:(RCTWKWebView *)webView
+shouldStartLoadForRequest:(NSMutableDictionary *)request
+ withCallback:(RCTDirectEventBlock)callback;
+
+@end
+
+@interface RCTWKWebView : RCTView
+
+- (instancetype)initWithProcessPool:(WKProcessPool *)processPool;
+
+@property (nonatomic, weak) id delegate;
+
+@property (nonatomic, copy) NSDictionary *source;
+@property (nonatomic, assign) NSEdgeInsets contentInset;
+@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
+@property (nonatomic, assign) BOOL messagingEnabled;
+@property (nonatomic, assign) BOOL allowsLinkPreview;
+@property (nonatomic, assign) BOOL openNewWindowInWebView;
+@property (nonatomic, assign) BOOL injectJavaScriptForMainFrameOnly;
+@property (nonatomic, assign) BOOL injectedJavaScriptForMainFrameOnly;
+@property (nonatomic, copy) NSString *injectJavaScript;
+@property (nonatomic, copy) NSString *injectedJavaScript;
+@property (nonatomic, assign) BOOL hideKeyboardAccessoryView;
+@property (nonatomic, assign) BOOL keyboardDisplayRequiresUserAction;
+
+
+- (void)goForward;
+- (void)goBack;
+- (BOOL)canGoBack;
+- (BOOL)canGoForward;
+- (void)reload;
+- (void)stopLoading;
+- (void)postMessage:(NSString *)message;
+- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *error))completionHandler;
+
+@end
diff --git a/macos/RCTWKWebView/RCTWKWebView.m b/macos/RCTWKWebView/RCTWKWebView.m
new file mode 100644
index 00000000..c1d539eb
--- /dev/null
+++ b/macos/RCTWKWebView/RCTWKWebView.m
@@ -0,0 +1,633 @@
+#import "RCTWKWebView.h"
+
+#import "WeakScriptMessageDelegate.h"
+
+#import
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+#import
+
+// runtime trick to remove WKWebView keyboard default toolbar
+// see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279
+@interface _SwizzleHelperWK : NSObject @end
+@implementation _SwizzleHelperWK
+-(id)inputAccessoryView
+{
+ return nil;
+}
+@end
+
+@interface RCTWKWebView ()
+
+@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
+@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
+@property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
+@property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
+@property (nonatomic, copy) RCTDirectEventBlock onProgress;
+@property (nonatomic, copy) RCTDirectEventBlock onMessage;
+@property (nonatomic, copy) RCTDirectEventBlock onScroll;
+@property (assign) BOOL sendCookies;
+@property (nonatomic, strong) WKUserScript *atStartScript;
+@property (nonatomic, strong) WKUserScript *atEndScript;
+
+@end
+
+@implementation RCTWKWebView
+{
+ WKWebView *_webView;
+ BOOL _injectJavaScriptForMainFrameOnly;
+ BOOL _injectedJavaScriptForMainFrameOnly;
+ NSString *_injectJavaScript;
+ NSString *_injectedJavaScript;
+}
+
+- (void)reactSetFrame:(CGRect)frame
+{
+ [self setFrame:frame];
+}
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+ return self = [super initWithFrame:frame];
+}
+
+RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
+
+- (instancetype)initWithProcessPool:(WKProcessPool *)processPool
+{
+ if(self = [self initWithFrame:CGRectZero])
+ {
+ super.backgroundColor = [NSColor clearColor];
+
+ _automaticallyAdjustContentInsets = YES;
+ _contentInset = NSEdgeInsetsZero;
+
+ WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
+ config.processPool = processPool;
+ WKUserContentController* userController = [[WKUserContentController alloc]init];
+ [userController addScriptMessageHandler:[[WeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"reactNative"];
+ config.userContentController = userController;
+
+ _webView = [[WKWebView alloc] initWithFrame:self.bounds configuration:config];
+ _webView.allowsMagnification = YES; // macOS-only
+ _webView.UIDelegate = self;
+ _webView.navigationDelegate = self;
+
+/* Removed because macOS WKWebView doesn't have a scrollView */
+// _webView.scrollView.delegate = self;
+//
+//#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
+// // `contentInsetAdjustmentBehavior` is only available since iOS 11.
+// // We set the default behavior to "never" so that iOS
+// // doesn't do weird things to UIScrollView insets automatically
+// // and keeps it as an opt-in behavior.
+// if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
+// _webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
+// }
+//#endif
+ [self setupPostMessageScript];
+ [_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
+ [self addSubview:_webView];
+ }
+ return self;
+}
+
+- (void)setInjectJavaScript:(NSString *)injectJavaScript {
+ _injectJavaScript = injectJavaScript;
+ self.atStartScript = [[WKUserScript alloc] initWithSource:injectJavaScript
+ injectionTime:WKUserScriptInjectionTimeAtDocumentStart
+ forMainFrameOnly:_injectJavaScriptForMainFrameOnly];
+ [self resetupScripts];
+}
+
+- (void)setInjectedJavaScript:(NSString *)script {
+ _injectedJavaScript = script;
+ self.atEndScript = [[WKUserScript alloc] initWithSource:script
+ injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
+ forMainFrameOnly:_injectedJavaScriptForMainFrameOnly];
+ [self resetupScripts];
+}
+
+- (void)setInjectedJavaScriptForMainFrameOnly:(BOOL)injectedJavaScriptForMainFrameOnly {
+ _injectedJavaScriptForMainFrameOnly = injectedJavaScriptForMainFrameOnly;
+ if (_injectedJavaScript != nil) {
+ [self setInjectedJavaScript:_injectedJavaScript];
+ }
+}
+
+- (void)setInjectJavaScriptForMainFrameOnly:(BOOL)injectJavaScriptForMainFrameOnly {
+ _injectJavaScriptForMainFrameOnly = injectJavaScriptForMainFrameOnly;
+ if (_injectJavaScript != nil) {
+ [self setInjectJavaScript:_injectJavaScript];
+ }
+}
+
+- (void)setMessagingEnabled:(BOOL)messagingEnabled {
+ _messagingEnabled = messagingEnabled;
+ [self setupPostMessageScript];
+}
+
+- (void)resetupScripts {
+ [_webView.configuration.userContentController removeAllUserScripts];
+ [self setupPostMessageScript];
+ if (self.atStartScript) {
+ [_webView.configuration.userContentController addUserScript:self.atStartScript];
+ }
+ if (self.atEndScript) {
+ [_webView.configuration.userContentController addUserScript:self.atEndScript];
+ }
+}
+
+- (void)setupPostMessageScript {
+ if (_messagingEnabled) {
+ NSString *source=@"window.originalPostMessage = window.postMessage; window.postMessage = function (data) { window.webkit.messageHandlers.reactNative.postMessage(data); }";
+ WKUserScript *script = [[WKUserScript alloc] initWithSource:source
+ injectionTime:WKUserScriptInjectionTimeAtDocumentEnd
+ forMainFrameOnly:_injectedJavaScriptForMainFrameOnly];
+ [_webView.configuration.userContentController addUserScript:script];
+ }
+}
+
+- (void)loadRequest:(NSURLRequest *)request
+{
+ if (request.URL && _sendCookies) {
+ NSDictionary *cookies = [NSHTTPCookie requestHeaderFieldsWithCookies:[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL]];
+ if ([cookies objectForKey:@"Cookie"]) {
+ NSMutableURLRequest *mutableRequest = request.mutableCopy;
+ [mutableRequest addValue:cookies[@"Cookie"] forHTTPHeaderField:@"Cookie"];
+ request = mutableRequest;
+ }
+ }
+
+ [_webView loadRequest:request];
+}
+
+-(void)setAllowsLinkPreview:(BOOL)allowsLinkPreview
+{
+ if ([_webView respondsToSelector:@selector(allowsLinkPreview)]) {
+ _webView.allowsLinkPreview = allowsLinkPreview;
+ }
+}
+
+-(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView
+{
+ if (!hideKeyboardAccessoryView) {
+ return;
+ }
+
+ /* Removed because macOS WKWebView doesn't have a scrollView */
+ // UIView* subview;
+ // for (UIView* view in _webView.scrollView.subviews) {
+ // if([[view.class description] hasPrefix:@"WKContent"])
+ // subview = view;
+ // }
+
+ // if(subview == nil) return;
+
+ // NSString* name = [NSString stringWithFormat:@"%@_SwizzleHelperWK", subview.class.superclass];
+ // Class newClass = NSClassFromString(name);
+
+ // if(newClass == nil)
+ // {
+ // newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0);
+ // if(!newClass) return;
+
+ // Method method = class_getInstanceMethod([_SwizzleHelperWK class], @selector(inputAccessoryView));
+ // class_addMethod(newClass, @selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method));
+
+ // objc_registerClassPair(newClass);
+ // }
+
+ // object_setClass(subview, newClass);
+}
+
+/* Removed because macOS WKWebView doesn't have an on-screen keyboard */
+// // https://github.com/Telerik-Verified-Plugins/WKWebView/commit/04e8296adeb61f289f9c698045c19b62d080c7e3
+// // https://stackoverflow.com/a/48623286/3297914
+// -(void)setKeyboardDisplayRequiresUserAction:(BOOL)keyboardDisplayRequiresUserAction
+// {
+// if (!keyboardDisplayRequiresUserAction) {
+// Class class = NSClassFromString(@"WKContentView");
+// NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0};
+
+// if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) {
+// SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
+// Method method = class_getInstanceMethod(class, selector);
+// IMP original = method_getImplementation(method);
+// IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
+// ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
+// });
+// method_setImplementation(method, override);
+// } else {
+// SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
+// Method method = class_getInstanceMethod(class, selector);
+// IMP original = method_getImplementation(method);
+// IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
+// ((void (*)(id, SEL, void*, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3);
+// });
+// method_setImplementation(method, override);
+// }
+// }
+// }
+
+#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
+- (void)setContentInsetAdjustmentBehavior:(UIScrollViewContentInsetAdjustmentBehavior)behavior
+{
+ // `contentInsetAdjustmentBehavior` is available since iOS 11.
+ if ([_webView.scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) {
+ CGPoint contentOffset = _webView.scrollView.contentOffset;
+ _webView.scrollView.contentInsetAdjustmentBehavior = behavior;
+ _webView.scrollView.contentOffset = contentOffset;
+ }
+}
+#endif
+
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
+{
+ if (_onMessage) {
+ NSMutableDictionary *event = [self baseEvent];
+ [event addEntriesFromDictionary: @{
+ @"data": message.body,
+ @"name": message.name
+ }];
+ _onMessage(event);
+ }
+}
+
+- (void)goForward
+{
+ [_webView goForward];
+}
+
+- (void)evaluateJavaScript:(NSString *)javaScriptString
+ completionHandler:(void (^)(id, NSError *error))completionHandler
+{
+ [_webView evaluateJavaScript:javaScriptString completionHandler:completionHandler];
+}
+
+- (void)postMessage:(NSString *)message
+{
+ NSDictionary *eventInitDict = @{
+ @"data": message,
+ };
+ NSString *source = [NSString
+ stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));",
+ RCTJSONStringify(eventInitDict, NULL)
+ ];
+ [_webView evaluateJavaScript:source completionHandler:nil];
+}
+
+
+- (void)goBack
+{
+ [_webView goBack];
+}
+
+- (BOOL)canGoBack
+{
+ return [_webView canGoBack];
+}
+
+- (BOOL)canGoForward
+{
+ return [_webView canGoForward];
+}
+
+- (void)reload
+{
+ [_webView reload];
+}
+
+- (void)stopLoading
+{
+ [_webView stopLoading];
+}
+
+- (void)setSource:(NSDictionary *)source
+{
+ if (![_source isEqualToDictionary:source]) {
+ _source = [source copy];
+ _sendCookies = [source[@"sendCookies"] boolValue];
+ if ([source[@"customUserAgent"] length] != 0 && [_webView respondsToSelector:@selector(setCustomUserAgent:)]) {
+ [_webView setCustomUserAgent:source[@"customUserAgent"]];
+ }
+
+ // Allow loading local files:
+ //
+ // Only works for iOS 9+. So iOS 8 will simply ignore those two values
+ NSString *file = [RCTConvert NSString:source[@"file"]];
+ NSString *allowingReadAccessToURL = [RCTConvert NSString:source[@"allowingReadAccessToURL"]];
+
+ if (file && [_webView respondsToSelector:@selector(loadFileURL:allowingReadAccessToURL:)]) {
+ NSURL *fileURL = [RCTConvert NSURL:file];
+ NSURL *baseURL = [RCTConvert NSURL:allowingReadAccessToURL];
+ [_webView loadFileURL:fileURL allowingReadAccessToURL:baseURL];
+ return;
+ }
+
+ // Check for a static html source first
+ NSString *html = [RCTConvert NSString:source[@"html"]];
+ if (html) {
+ NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]];
+ if (!baseURL) {
+ baseURL = [NSURL URLWithString:@"about:blank"];
+ }
+ [_webView loadHTMLString:html baseURL:baseURL];
+ return;
+ }
+
+ NSURLRequest *request = [RCTConvert NSURLRequest:source];
+ // Because of the way React works, as pages redirect, we actually end up
+ // passing the redirect urls back here, so we ignore them if trying to load
+ // the same url. We'll expose a call to 'reload' to allow a user to load
+ // the existing page.
+ if ([request.URL isEqual:_webView.URL]) {
+ return;
+ }
+ if (!request.URL) {
+ // Clear the webview
+ [_webView loadHTMLString:@"" baseURL:nil];
+ return;
+ }
+ [self loadRequest:request];
+ }
+}
+
+/* My understanding of AppKit's equivalent to UIKit's layoutSubviews() */
+- (void)layout
+{
+ [super layout];
+ _webView.frame = self.bounds;
+}
+
+- (void)setContentInset:(NSEdgeInsets)contentInset
+{
+ _contentInset = contentInset;
+/* Removed because macOS WKWebView doesn't have a scrollView */
+// [RCTView autoAdjustInsetsForView:self
+// withScrollView:_webView.scrollView
+// updateOffset:NO];
+}
+
+- (void)setBackgroundColor:(NSColor *)backgroundColor
+{
+ CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
+/* Removed because macOS WKWebView doesn't have a scrollView */
+// self.opaque = _webView.opaque = _webView.scrollView.opaque = (alpha == 1.0);
+// _webView.backgroundColor = _webView.scrollView.backgroundColor = backgroundColor;
+}
+
+/* Removed because macOS WKWebView doesn't have a backgroundColor */
+//- (NSColor *)backgroundColor
+//{
+// return _webView.backgroundColor;
+//}
+
+- (NSMutableDictionary *)baseEvent
+{
+ NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary:@{
+ @"url": _webView.URL.absoluteString ?: @"",
+ @"loading" : @(_webView.loading),
+ @"title": _webView.title,
+ @"canGoBack": @(_webView.canGoBack),
+ @"canGoForward" : @(_webView.canGoForward),
+ }];
+
+ return event;
+}
+
+- (void)refreshContentInset
+{
+/* Removed because macOS WKWebView doesn't have a scrollView */
+// [RCTView autoAdjustInsetsForView:self
+// withScrollView:_webView.scrollView
+// updateOffset:YES];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary *)change
+ context:(void *)context
+{
+ if ([keyPath isEqualToString:@"estimatedProgress"]) {
+ if (!_onProgress) {
+ return;
+ }
+ _onProgress(@{@"progress": [change objectForKey:NSKeyValueChangeNewKey]});
+ }
+}
+
+- (void)dealloc
+{
+ [_webView removeObserver:self forKeyPath:@"estimatedProgress"];
+ _webView.navigationDelegate = nil;
+ _webView.UIDelegate = nil;
+ /* Removed because macOS WKWebView doesn't have a scrollView */
+// _webView.scrollView.delegate = nil;
+}
+
+/* Removed because macOS WKWebView doesn't have a scrollView */
+//- (void)scrollViewDidScroll:(UIScrollView *)scrollView
+//{
+// if (!scrollView.scrollEnabled) {
+// scrollView.bounds = _webView.bounds;
+// return;
+// }
+// NSDictionary *event = @{
+// @"contentOffset": @{
+// @"x": @(scrollView.contentOffset.x),
+// @"y": @(scrollView.contentOffset.y)
+// },
+// @"contentInset": @{
+// @"top": @(scrollView.contentInset.top),
+// @"left": @(scrollView.contentInset.left),
+// @"bottom": @(scrollView.contentInset.bottom),
+// @"right": @(scrollView.contentInset.right)
+// },
+// @"contentSize": @{
+// @"width": @(scrollView.contentSize.width),
+// @"height": @(scrollView.contentSize.height)
+// },
+// @"layoutMeasurement": @{
+// @"width": @(scrollView.frame.size.width),
+// @"height": @(scrollView.frame.size.height)
+// },
+// @"zoomScale": @(scrollView.zoomScale ?: 1),
+// };
+// _onScroll(event);
+//}
+
+#pragma mark - WKNavigationDelegate methods
+
+#if DEBUG
+- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
+ NSURLCredential * credential = [[NSURLCredential alloc] initWithTrust:[challenge protectionSpace].serverTrust];
+ completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
+}
+#endif
+
+- (void)webView:(__unused WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+ NSApplication *app = [NSApplication sharedApplication];
+ NSURLRequest *request = navigationAction.request;
+ NSURL* url = request.URL;
+ NSString* scheme = url.scheme;
+
+ BOOL isJSNavigation = [scheme isEqualToString:RCTJSNavigationScheme];
+
+// TODO: handle mailto and tel schemes
+// if ([scheme isEqualToString:@"mailto"] || [scheme isEqualToString:@"tel"]) {
+// if ([app canOpenURL:url]) {
+// [app openURL:url];
+// decisionHandler(WKNavigationActionPolicyCancel);
+// return;
+// }
+// }
+
+ // skip this for the JS Navigation handler
+ if (!isJSNavigation && _onShouldStartLoadWithRequest) {
+ NSMutableDictionary *event = [self baseEvent];
+ [event addEntriesFromDictionary: @{
+ @"url": (request.URL).absoluteString,
+ @"navigationType": @(navigationAction.navigationType)
+ }];
+ if (![self.delegate webView:self
+ shouldStartLoadForRequest:event
+ withCallback:_onShouldStartLoadWithRequest]) {
+ return decisionHandler(WKNavigationActionPolicyCancel);
+ }
+ }
+
+ if (_onLoadingStart) {
+ // We have this check to filter out iframe requests and whatnot
+ BOOL isTopFrame = [url isEqual:request.mainDocumentURL];
+ if (isTopFrame) {
+ NSMutableDictionary *event = [self baseEvent];
+ [event addEntriesFromDictionary: @{
+ @"url": url.absoluteString,
+ @"navigationType": @(navigationAction.navigationType)
+ }];
+ _onLoadingStart(event);
+ }
+ }
+
+ if (isJSNavigation) {
+ decisionHandler(WKNavigationActionPolicyCancel);
+ }
+ else {
+ decisionHandler(WKNavigationActionPolicyAllow);
+ }
+}
+
+- (void)webView:(__unused WKWebView *)webView didFailProvisionalNavigation:(__unused WKNavigation *)navigation withError:(NSError *)error
+{
+ if (_onLoadingError) {
+ if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
+ // NSURLErrorCancelled is reported when a page has a redirect OR if you load
+ // a new URL in the WebView before the previous one came back. We can just
+ // ignore these since they aren't real errors.
+ // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os
+ return;
+ }
+
+ NSMutableDictionary *event = [self baseEvent];
+ [event addEntriesFromDictionary:@{
+ @"domain": error.domain,
+ @"code": @(error.code),
+ @"description": error.localizedDescription,
+ }];
+ _onLoadingError(event);
+ }
+}
+
+- (void)webView:(WKWebView *)webView didFinishNavigation:(__unused WKNavigation *)navigation
+{
+ // we only need the final 'finishLoad' call so only fire the event when we're actually done loading.
+ if (_onLoadingFinish && !webView.loading && ![webView.URL.absoluteString isEqualToString:@"about:blank"]) {
+ _onLoadingFinish([self baseEvent]);
+ }
+}
+
+#pragma mark - WKUIDelegate
+
+- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setAlertStyle:NSAlertStyleWarning];
+ [alert addButtonWithTitle:@"Close"];
+ [alert setMessageText:@"JavaScript error"];
+ [alert setInformativeText:message];
+ [alert setAlertStyle:NSAlertStyleWarning];
+ [alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
+ if(returnCode == NSAlertFirstButtonReturn) completionHandler();
+ }];
+}
+
+- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler {
+ // TODO We have to think message to confirm "YES"
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setAlertStyle:NSAlertStyleWarning];
+ [alert addButtonWithTitle:@"OK"]; // first
+ [alert addButtonWithTitle:@"Cancel"]; // second
+ [alert setMessageText:@"JavaScript confirmation"];
+ [alert setInformativeText:message];
+ [alert setAlertStyle:NSAlertStyleWarning];
+ [alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
+ if(returnCode == NSAlertFirstButtonReturn){
+ completionHandler(YES);
+ } else if(returnCode == NSAlertSecondButtonReturn){
+ completionHandler(NO);
+ }
+ }];
+}
+
+- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler {
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setAlertStyle:NSAlertStyleWarning];
+ [alert addButtonWithTitle:@"OK"];
+ [alert addButtonWithTitle:@"Cancel"];
+ [alert setMessageText:prompt];
+ [alert setAlertStyle:NSAlertStyleWarning];
+
+ NSTextField *input = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)];
+ [input setStringValue:defaultText];
+ [alert setAccessoryView:input];
+
+ [alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
+ if(returnCode == NSAlertFirstButtonReturn){
+ NSString *userInput = [input stringValue];
+ completionHandler(userInput);
+ } else if(returnCode == NSAlertSecondButtonReturn){
+ completionHandler(nil);
+ }
+ }];
+}
+
+- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
+{
+ NSString *scheme = navigationAction.request.URL.scheme;
+ if ((navigationAction.targetFrame.isMainFrame || _openNewWindowInWebView) && ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"])) {
+ [webView loadRequest:navigationAction.request];
+ } else {
+ NSApplication *app = [NSApplication sharedApplication];
+ NSURL *url = navigationAction.request.URL;
+ // TODO:
+// if ([app canOpenURL:url]) {
+// [app openURL:url];
+// }
+ }
+ return nil;
+}
+
+- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView
+{
+ RCTLogWarn(@"Webview Process Terminated");
+}
+
+@end
diff --git a/macos/RCTWKWebView/RCTWKWebViewManager.h b/macos/RCTWKWebView/RCTWKWebViewManager.h
new file mode 100644
index 00000000..277f82a1
--- /dev/null
+++ b/macos/RCTWKWebView/RCTWKWebViewManager.h
@@ -0,0 +1,10 @@
+#import
+#import
+
+@interface RCTConvert (UIScrollView)
+
+@end
+
+@interface RCTWKWebViewManager : RCTViewManager
+
+@end
diff --git a/macos/RCTWKWebView/RCTWKWebViewManager.m b/macos/RCTWKWebView/RCTWKWebViewManager.m
new file mode 100644
index 00000000..179f0019
--- /dev/null
+++ b/macos/RCTWKWebView/RCTWKWebViewManager.m
@@ -0,0 +1,213 @@
+#import "RCTWKWebViewManager.h"
+
+#import "RCTWKWebView.h"
+#import "WKProcessPool+SharedProcessPool.h"
+#import
+#import
+#import
+#import
+
+#import
+
+@implementation RCTConvert (UIScrollView)
+
+/* Removed because macOS WKWebView doesn't have a scrollView */
+//#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
+//RCT_ENUM_CONVERTER(UIScrollViewContentInsetAdjustmentBehavior, (@{
+// @"automatic": @(UIScrollViewContentInsetAdjustmentAutomatic),
+// @"scrollableAxes": @(UIScrollViewContentInsetAdjustmentScrollableAxes),
+// @"never": @(UIScrollViewContentInsetAdjustmentNever),
+// @"always": @(UIScrollViewContentInsetAdjustmentAlways),
+// }), UIScrollViewContentInsetAdjustmentNever, integerValue)
+//#endif
+
+@end
+
+@interface RCTWKWebViewManager ()
+
+@end
+
+@implementation RCTWKWebViewManager
+{
+ NSConditionLock *_shouldStartLoadLock;
+ BOOL _shouldStartLoad;
+}
+
+RCT_EXPORT_MODULE()
+
+- (NSView *)view
+{
+ RCTWKWebView *webView = [[RCTWKWebView alloc] initWithProcessPool:[WKProcessPool sharedProcessPool]];
+ webView.delegate = self;
+ return webView;
+}
+
+RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
+/* Removed because macOS WKWebView doesn't have a scrollView */
+//RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL)
+//RCT_REMAP_VIEW_PROPERTY(pagingEnabled, _webView.scrollView.pagingEnabled, BOOL)
+//RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL)
+//RCT_REMAP_VIEW_PROPERTY(directionalLockEnabled, _webView.scrollView.directionalLockEnabled, BOOL)
+RCT_REMAP_VIEW_PROPERTY(allowsBackForwardNavigationGestures, _webView.allowsBackForwardNavigationGestures, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(injectJavaScriptForMainFrameOnly, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(injectedJavaScriptForMainFrameOnly, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(injectJavaScript, NSString)
+RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
+RCT_EXPORT_VIEW_PROPERTY(openNewWindowInWebView, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
+RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(hideKeyboardAccessoryView, BOOL)
+/* Removed because macOS doesn't have an on-screen keyboard */
+// RCT_EXPORT_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
+RCT_EXPORT_VIEW_PROPERTY(allowsLinkPreview, BOOL)
+/* Removed because macOS WKWebView doesn't have a scrollView */
+//#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
+//RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentBehavior)
+//#endif
+
+RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
+ RCTWKWebView *view = viewRegistry[reactTag];
+ if (![view isKindOfClass:[RCTWKWebView class]]) {
+ RCTLogError(@"Invalid view returned from registry, expecting RCTWKWebView, got: %@", view);
+ } else {
+ [view goBack];
+ }
+ }];
+}
+
+RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
+ RCTWKWebView *view = viewRegistry[reactTag];
+ if (![view isKindOfClass:[RCTWKWebView class]]) {
+ RCTLogError(@"Invalid view returned from registry, expecting RCTWKWebView, got: %@", view);
+ } else {
+ [view goForward];
+ }
+ }];
+}
+
+RCT_EXPORT_METHOD(canGoBack:(nonnull NSNumber *)reactTag
+ resolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
+ RCTWKWebView *view = viewRegistry[reactTag];
+
+ resolve([NSNumber numberWithBool:[view canGoBack]]);
+ }];
+}
+
+RCT_EXPORT_METHOD(canGoForward:(nonnull NSNumber *)reactTag
+ resolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
+ RCTWKWebView *view = viewRegistry[reactTag];
+
+ resolve([NSNumber numberWithBool:[view canGoForward]]);
+ }];
+}
+
+RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
+ RCTWKWebView *view = viewRegistry[reactTag];
+ if (![view isKindOfClass:[RCTWKWebView class]]) {
+ RCTLogError(@"Invalid view returned from registry, expecting RCTWKWebView, got: %@", view);
+ } else {
+ [view reload];
+ }
+ }];
+}
+
+RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
+ RCTWKWebView *view = viewRegistry[reactTag];
+ if (![view isKindOfClass:[RCTWKWebView class]]) {
+ RCTLogError(@"Invalid view returned from registry, expecting RCTWKWebView, got: %@", view);
+ } else {
+ [view stopLoading];
+ }
+ }];
+}
+
+RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
+ RCTWKWebView *view = viewRegistry[reactTag];
+ if (![view isKindOfClass:[RCTWKWebView class]]) {
+ RCTLogError(@"Invalid view returned from registry, expecting RCTWebView, got: %@", view);
+ } else {
+ [view postMessage:message];
+ }
+ }];
+}
+
+RCT_EXPORT_METHOD(evaluateJavaScript:(nonnull NSNumber *)reactTag
+ js:(NSString *)js
+ resolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject)
+{
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
+ RCTWKWebView *view = viewRegistry[reactTag];
+ if (![view isKindOfClass:[RCTWKWebView class]]) {
+ RCTLogError(@"Invalid view returned from registry, expecting RCTWKWebView, got: %@", view);
+ } else {
+ [view evaluateJavaScript:js completionHandler:^(id result, NSError *error) {
+ if (error) {
+ reject(@"js_error", @"Error occurred while evaluating Javascript", error);
+ } else {
+ resolve(result);
+ }
+ }];
+ }
+ }];
+}
+
+#pragma mark - Exported synchronous methods
+
+- (BOOL)webView:(__unused RCTWKWebView *)webView
+shouldStartLoadForRequest:(NSMutableDictionary *)request
+ withCallback:(RCTDirectEventBlock)callback
+{
+ _shouldStartLoadLock = [[NSConditionLock alloc] initWithCondition:arc4random()];
+ _shouldStartLoad = YES;
+ request[@"lockIdentifier"] = @(_shouldStartLoadLock.condition);
+ callback(request);
+
+ // Block the main thread for a maximum of 250ms until the JS thread returns
+ if ([_shouldStartLoadLock lockWhenCondition:0 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.25]]) {
+ BOOL returnValue = _shouldStartLoad;
+ [_shouldStartLoadLock unlock];
+ _shouldStartLoadLock = nil;
+ return returnValue;
+ } else {
+ RCTLogWarn(@"Did not receive response to shouldStartLoad in time, defaulting to YES");
+ return YES;
+ }
+}
+
+RCT_EXPORT_METHOD(startLoadWithResult:(BOOL)result lockIdentifier:(NSInteger)lockIdentifier)
+{
+ if ([_shouldStartLoadLock tryLockWhenCondition:lockIdentifier]) {
+ _shouldStartLoad = result;
+ [_shouldStartLoadLock unlockWithCondition:0];
+ } else {
+ RCTLogWarn(@"startLoadWithResult invoked with invalid lockIdentifier: "
+ "got %zd, expected %zd", lockIdentifier, _shouldStartLoadLock.condition);
+ }
+}
+
+@end
diff --git a/macos/RCTWKWebView/WKProcessPool+SharedProcessPool.h b/macos/RCTWKWebView/WKProcessPool+SharedProcessPool.h
new file mode 100644
index 00000000..e3a5e2f1
--- /dev/null
+++ b/macos/RCTWKWebView/WKProcessPool+SharedProcessPool.h
@@ -0,0 +1,3 @@
+@interface WKProcessPool (SharedProcessPool)
++ (WKProcessPool*)sharedProcessPool;
+@end
diff --git a/macos/RCTWKWebView/WKProcessPool+SharedProcessPool.m b/macos/RCTWKWebView/WKProcessPool+SharedProcessPool.m
new file mode 100644
index 00000000..eec006f7
--- /dev/null
+++ b/macos/RCTWKWebView/WKProcessPool+SharedProcessPool.m
@@ -0,0 +1,16 @@
+#import
+#import
+#import "WKProcessPool+SharedProcessPool.h"
+
+@implementation WKProcessPool (SharedProcessPool)
+
++ (WKProcessPool*)sharedProcessPool {
+ static WKProcessPool* _sharedProcessPool;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ _sharedProcessPool = [[WKProcessPool alloc] init];
+ });
+ return _sharedProcessPool;
+}
+
+@end
diff --git a/macos/RCTWKWebView/WeakScriptMessageDelegate.h b/macos/RCTWKWebView/WeakScriptMessageDelegate.h
new file mode 100644
index 00000000..dd676a0c
--- /dev/null
+++ b/macos/RCTWKWebView/WeakScriptMessageDelegate.h
@@ -0,0 +1,13 @@
+
+#import
+#import
+
+// Trampoline object to avoid retain cycle with the script message handler
+@interface WeakScriptMessageDelegate : NSObject
+
+@property (nonatomic, weak) id scriptDelegate;
+
+- (instancetype)initWithDelegate:(id)scriptDelegate;
+
+@end
+
diff --git a/macos/RCTWKWebView/WeakScriptMessageDelegate.m b/macos/RCTWKWebView/WeakScriptMessageDelegate.m
new file mode 100644
index 00000000..11a79174
--- /dev/null
+++ b/macos/RCTWKWebView/WeakScriptMessageDelegate.m
@@ -0,0 +1,20 @@
+
+#import "WeakScriptMessageDelegate.h"
+
+@implementation WeakScriptMessageDelegate
+
+- (instancetype)initWithDelegate:(id)scriptDelegate
+{
+ self = [super init];
+ if (self) {
+ _scriptDelegate = scriptDelegate;
+ }
+ return self;
+}
+
+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
+{
+ [self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
+}
+
+@end
diff --git a/package.json b/package.json
index d8bbaa39..a6f8bcd6 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,8 @@
"native",
"wkwebview",
"webview",
- "ios"
+ "ios",
+ "macos"
],
"license": "MIT",
"main": "index.js",
@@ -77,5 +78,5 @@
"sync-from-example": "cp ./example/node_modules/react-native-wkwebview-reborn/*.js ./;cp -r ./example/node_modules/react-native-wkwebview-reborn/ios ./",
"sync-to-example": "cp ./*.js ./example/node_modules/react-native-wkwebview-reborn/;cp -r ./ios ./example/node_modules/react-native-wkwebview-reborn/"
},
- "version": "1.21.0"
+ "version": "1.21.1"
}
diff --git a/react-native-wkwebview.podspec b/react-native-wkwebview.podspec
index acfd65d2..8d0a9570 100644
--- a/react-native-wkwebview.podspec
+++ b/react-native-wkwebview.podspec
@@ -5,17 +5,19 @@ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
Pod::Spec.new do |s|
s.name = "react-native-wkwebview"
s.version = package["version"]
- s.summary = "React Native WKWebView for iOS"
+ s.summary = "React Native WKWebView for iOS and macOS"
s.author = "Ruoyu Sun (https://github.com/insraq)"
s.homepage = "https://github.com/CRAlpha/react-native-wkwebview"
s.license = "MIT"
- s.platform = :ios, "8.0"
+ s.ios.deployment_target = "8.0"
+ s.osx.deployment_target = "10.10"
s.source = { :git => "https://github.com/CRAlpha/react-native-wkwebview.git", :tag => "v#{s.version}" }
- s.source_files = "ios/RCTWKWebView/*.{h,m}"
+ s.ios.source_files = "ios/RCTWKWebView/*.{h,m}"
+ s.osx.source_files = "macos/RCTWKWebView/*.{h,m}"
s.dependency "React"
end