瀏覽代碼

feat(ios): Generate history API events on iOS (#1082)

BREAKING CHANGE: if you use onNavigationStateChange on iOS it will now trigger on # changes to the url.

* Hook the `window.history` API on iOS to generate events

The underlying WKWebView doesn't seem to generate any events in response to the `window.history` API - none of the `WKNavigationDelegate` methods fire.

Given this limitation, the only way to know when the location changes via this API is to inject Javascript into the page and have it notify the native code directly when any of these functions are called.

The `setTimeout` call gives up the current tick, allowing the location to change before firing the event.

* Remove the outdated section about hash changes

Now that this bug is fixed, the workaround is no longer required.
Jason Safaiyeh 5 年之前
父節點
當前提交
361529630f
共有 2 個文件被更改,包括 38 次插入42 次删除
  1. 0
    38
      docs/Guide.md
  2. 38
    4
      ios/RNCWebView.m

+ 0
- 38
docs/Guide.md 查看文件

@@ -149,44 +149,6 @@ class MyWeb extends Component {
149 149
 }
150 150
 ```
151 151
 
152
-#### Intercepting hash URL changes
153
-
154
-While `onNavigationStateChange` will trigger on URL changes, it does not trigger when only the hash URL ("anchor") changes, e.g. from `https://example.com/users#list` to `https://example.com/users#help`.
155
-
156
-You can inject some JavaScript to wrap the history functions in order to intercept these hash URL changes.
157
-
158
-```jsx
159
-<WebView
160
-  source={{ uri: someURI }}
161
-  injectedJavaScript={`
162
-    (function() {
163
-      function wrap(fn) {
164
-        return function wrapper() {
165
-          var res = fn.apply(this, arguments);
166
-          window.ReactNativeWebView.postMessage('navigationStateChange');
167
-          return res;
168
-        }
169
-      }
170
-
171
-      history.pushState = wrap(history.pushState);
172
-      history.replaceState = wrap(history.replaceState);
173
-      window.addEventListener('popstate', function() {
174
-        window.ReactNativeWebView.postMessage('navigationStateChange');
175
-      });
176
-    })();
177
-
178
-    true;
179
-  `}
180
-  onMessage={({ nativeEvent: state }) => {
181
-    if (state.data === 'navigationStateChange') {
182
-      // Navigation state updated, can check state.canGoBack, etc.
183
-    }
184
-  }}
185
-/>
186
-```
187
-
188
-Thanks to [Janic Duplessis](https://github.com/react-native-community/react-native-webview/issues/24#issuecomment-483956651) for this workaround.
189
-
190 152
 ### Add support for File Upload
191 153
 
192 154
 ##### iOS

+ 38
- 4
ios/RNCWebView.m 查看文件

@@ -14,6 +14,7 @@
14 14
 #import "objc/runtime.h"
15 15
 
16 16
 static NSTimer *keyboardTimer;
17
+static NSString *const HistoryShimName = @"ReactNativeHistoryShim";
17 18
 static NSString *const MessageHandlerName = @"ReactNativeWebView";
18 19
 static NSURLCredential* clientAuthenticationCredential;
19 20
 static NSDictionary* customCertificatesForHost;
@@ -160,6 +161,31 @@ static NSDictionary* customCertificatesForHost;
160 161
     }
161 162
     wkWebViewConfig.userContentController = [WKUserContentController new];
162 163
 
164
+    // Shim the HTML5 history API:
165
+    [wkWebViewConfig.userContentController addScriptMessageHandler:self name:HistoryShimName];
166
+    NSString *source = [NSString stringWithFormat:
167
+      @"(function(history) {\n"
168
+      "  function notify(type) {\n"
169
+      "    setTimeout(function() {\n"
170
+      "      window.webkit.messageHandlers.%@.postMessage(type)\n"
171
+      "    }, 0)\n"
172
+      "  }\n"
173
+      "  function shim(f) {\n"
174
+      "    return function pushState() {\n"
175
+      "      notify('other')\n"
176
+      "      return f.apply(history, arguments)\n"
177
+      "    }\n"
178
+      "  }\n"
179
+      "  history.pushState = shim(history.pushState)\n"
180
+      "  history.replaceState = shim(history.replaceState)\n"
181
+      "  window.addEventListener('popstate', function() {\n"
182
+      "    notify('backforward')\n"
183
+      "  })\n"
184
+      "})(window.history)\n", HistoryShimName
185
+    ];
186
+    WKUserScript *script = [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
187
+    [wkWebViewConfig.userContentController addUserScript:script];
188
+
163 189
     if (_messagingEnabled) {
164 190
       [wkWebViewConfig.userContentController addScriptMessageHandler:self name:MessageHandlerName];
165 191
 
@@ -404,10 +430,18 @@ static NSDictionary* customCertificatesForHost;
404 430
 - (void)userContentController:(WKUserContentController *)userContentController
405 431
        didReceiveScriptMessage:(WKScriptMessage *)message
406 432
 {
407
-  if (_onMessage != nil) {
408
-    NSMutableDictionary<NSString *, id> *event = [self baseEvent];
409
-    [event addEntriesFromDictionary: @{@"data": message.body}];
410
-    _onMessage(event);
433
+  if ([message.name isEqualToString:HistoryShimName]) {
434
+    if (_onLoadingFinish) {
435
+      NSMutableDictionary<NSString *, id> *event = [self baseEvent];
436
+      [event addEntriesFromDictionary: @{@"navigationType": message.body}];
437
+      _onLoadingFinish(event);
438
+    }
439
+  } else if ([message.name isEqualToString:MessageHandlerName]) {
440
+    if (_onMessage) {
441
+      NSMutableDictionary<NSString *, id> *event = [self baseEvent];
442
+      [event addEntriesFromDictionary: @{@"data": message.body}];
443
+      _onMessage(event);
444
+    }
411 445
   }
412 446
 }
413 447