Skip to content

Commit 243054f

Browse files
committed
Android WebView shouldOverrideUrlLoading callback
1 parent a97127b commit 243054f

File tree

12 files changed

+180
-9
lines changed

12 files changed

+180
-9
lines changed

Libraries/Components/WebView/WebView.android.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ var WebViewState = keyMirror({
3333
ERROR: null,
3434
});
3535

36+
type Event = Object;
37+
3638
/**
3739
* Renders a native WebView.
3840
*/
@@ -51,6 +53,7 @@ var WebView = React.createClass({
5153
onNavigationStateChange: PropTypes.func,
5254
startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
5355
style: View.propTypes.style,
56+
shouldOverrideUrlLoading: PropTypes.func,
5457

5558
html: deprecatedPropType(
5659
PropTypes.string,
@@ -195,6 +198,16 @@ var WebView = React.createClass({
195198
console.warn('WebView: `source.body` is not supported when using GET.');
196199
}
197200

201+
var onShouldOverrideUrlLoading = this.props.shouldOverrideUrlLoading && ((event: Event) => {
202+
var shouldOverride = this.props.shouldOverrideUrlLoading &&
203+
this.props.shouldOverrideUrlLoading(event.nativeEvent);
204+
UIManager.dispatchViewManagerCommandSync(
205+
this.getWebViewHandle(),
206+
UIManager.RCTWebView.Commands.shouldOverrideWithResult,
207+
[shouldOverride]
208+
);
209+
}).bind(this);
210+
198211
var webView =
199212
<RCTWebView
200213
ref={RCT_WEBVIEW_REF}
@@ -212,6 +225,7 @@ var WebView = React.createClass({
212225
onLoadingFinish={this.onLoadingFinish}
213226
onLoadingError={this.onLoadingError}
214227
testID={this.props.testID}
228+
onShouldOverrideUrlLoading={onShouldOverrideUrlLoading}
215229
/>;
216230

217231
return (

ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -585,8 +585,7 @@ public void onCancel() {
585585
}
586586
}
587587

588-
public void dispatchCommand(int reactTag, int commandId, @Nullable ReadableArray args) {
589-
UiThreadUtil.assertOnUiThread();
588+
private void dispatchCommandCommon(int reactTag, int commandId, @Nullable ReadableArray args) {
590589
View view = mTagsToViews.get(reactTag);
591590
if (view == null) {
592591
throw new IllegalViewOperationException("Trying to send command to a non-existing view " +
@@ -597,6 +596,16 @@ public void dispatchCommand(int reactTag, int commandId, @Nullable ReadableArray
597596
viewManager.receiveCommand(view, commandId, args);
598597
}
599598

599+
public void dispatchCommandSync(int reactTag, int commandId, @Nullable ReadableArray args) {
600+
dispatchCommandCommon(reactTag, commandId, args);
601+
}
602+
603+
public void dispatchCommand(int reactTag, int commandId, @Nullable ReadableArray args) {
604+
UiThreadUtil.assertOnUiThread();
605+
dispatchCommandCommon(reactTag, commandId, args);
606+
}
607+
608+
600609
/**
601610
* Show a {@link PopupMenu}.
602611
*

ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,11 @@ public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArra
538538
mOperationsQueue.enqueueDispatchCommand(reactTag, commandId, commandArgs);
539539
}
540540

541+
public void dispatchViewManagerCommandSync(int reactTag, int commandId, ReadableArray commandArgs) {
542+
assertViewExists(reactTag, "dispatchViewManagerCommandSync");
543+
mOperationsQueue.executeDispatchCommand(reactTag, commandId, commandArgs);
544+
}
545+
541546
/**
542547
* Show a PopupMenu.
543548
*

ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,11 @@ public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArra
356356
mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs);
357357
}
358358

359+
@ReactMethod
360+
public void dispatchViewManagerCommandSync(int reactTag, int commandId, ReadableArray commandArgs) {
361+
mUIImplementation.dispatchViewManagerCommandSync(reactTag, commandId, commandArgs);
362+
}
363+
359364
/**
360365
* Show a PopupMenu.
361366
*

ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ public DispatchCommandOperation(int tag, int command, @Nullable ReadableArray ar
236236
public void execute() {
237237
mNativeViewHierarchyManager.dispatchCommand(mTag, mCommand, mArgs);
238238
}
239+
240+
public void executeSync() {
241+
mNativeViewHierarchyManager.dispatchCommandSync(mTag, mCommand, mArgs);
242+
}
239243
}
240244

241245
private final class ShowPopupMenuOperation extends ViewOperation {
@@ -583,6 +587,14 @@ public void enqueueDispatchCommand(
583587
mOperations.add(new DispatchCommandOperation(reactTag, commandId, commandArgs));
584588
}
585589

590+
public void executeDispatchCommand(
591+
int reactTag,
592+
int commandId,
593+
ReadableArray commandArgs) {
594+
DispatchCommandOperation operation = new DispatchCommandOperation(reactTag, commandId, commandArgs);
595+
operation.executeSync();
596+
}
597+
586598
public void enqueueUpdateExtraData(int reactTag, Object extraData) {
587599
mOperations.add(new UpdateViewExtraData(reactTag, extraData));
588600
}

ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ public boolean canCoalesce() {
6363
return true;
6464
}
6565

66+
public boolean isSync() {
67+
return false;
68+
}
69+
6670
/**
6771
* Given two events, coalesce them into a single event that will be sent to JS instead of two
6872
* separate events. By default, just chooses the one the is more recent.

ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,32 @@ public EventDispatcher(ReactApplicationContext reactContext) {
112112
public void dispatchEvent(Event event) {
113113
Assertions.assertCondition(event.isInitialized(), "Dispatched event hasn't been initialized");
114114
synchronized (mEventsStagingLock) {
115+
116+
if (event.isSync()) {
117+
synchronized (mEventsToDispatchLock) {
118+
addEventToEventsToDispatch(event);
119+
mReactContext.runOnJSQueueThread(new Runnable() {
120+
@Override
121+
public void run() {
122+
synchronized (mEventsToDispatchLock) {
123+
for (int eventIdx = 0; eventIdx < mEventsToDispatchSize; eventIdx++) {
124+
Event event = mEventsToDispatch[eventIdx];
125+
if (event == null) {
126+
continue;
127+
}
128+
129+
event.dispatch(mRCTEventEmitter);
130+
event.dispose();
131+
132+
}
133+
clearEventsToDispatch();
134+
}
135+
}
136+
});
137+
}
138+
return;
139+
}
140+
115141
mEventStaging.add(event);
116142
Systrace.startAsyncFlow(
117143
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,

ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717

1818
import android.graphics.Bitmap;
1919
import android.os.Build;
20+
import android.os.ConditionVariable;
2021
import android.text.TextUtils;
2122
import android.webkit.WebView;
2223
import android.webkit.WebViewClient;
2324

24-
import com.facebook.catalyst.views.webview.events.TopLoadingErrorEvent;
25-
import com.facebook.catalyst.views.webview.events.TopLoadingFinishEvent;
26-
import com.facebook.catalyst.views.webview.events.TopLoadingStartEvent;
25+
import com.facebook.react.views.webview.events.TopLoadingErrorEvent;
26+
import com.facebook.react.views.webview.events.TopLoadingFinishEvent;
27+
import com.facebook.react.views.webview.events.TopLoadingStartEvent;
28+
import com.facebook.react.views.webview.events.ShouldOverrideUrlLoadingEvent;
2729
import com.facebook.react.bridge.Arguments;
2830
import com.facebook.react.bridge.LifecycleEventListener;
2931
import com.facebook.react.bridge.ReactContext;
@@ -40,6 +42,7 @@
4042
import com.facebook.react.uimanager.annotations.ReactProp;
4143
import com.facebook.react.uimanager.events.Event;
4244
import com.facebook.react.uimanager.events.EventDispatcher;
45+
import com.facebook.react.common.MapBuilder;
4346

4447
/**
4548
* Manages instances of {@link WebView}
@@ -53,6 +56,7 @@
5356
* - topLoadingFinish
5457
* - topLoadingStart
5558
* - topLoadingError
59+
* - topShouldOverrideUrlLoading
5660
*
5761
* Each event will carry the following properties:
5862
* - target - view's react tag
@@ -74,6 +78,7 @@ public class ReactWebViewManager extends SimpleViewManager<WebView> {
7478
public static final int COMMAND_GO_BACK = 1;
7579
public static final int COMMAND_GO_FORWARD = 2;
7680
public static final int COMMAND_RELOAD = 3;
81+
public static final int COMMAND_SHOULD_OVERRIDE_WITH_RESULT = 4;
7782

7883
// Use `webView.loadUrl("about:blank")` to reliably reset the view
7984
// state and release page resources (including any running JavaScript).
@@ -143,6 +148,23 @@ public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload
143148
createWebViewEvent(webView, url)));
144149
}
145150

151+
@Override
152+
public boolean shouldOverrideUrlLoading(WebView webView, String url) {
153+
ReactWebView view = ((ReactWebView)webView);
154+
view.mShouldOverrideUrlLoadingResult = false;
155+
dispatchEvent(
156+
webView,
157+
new ShouldOverrideUrlLoadingEvent(
158+
webView.getId(),
159+
SystemClock.nanoTime(),
160+
createWebViewEvent(webView, url)));
161+
162+
view.mShouldOverrideUrlLoadingConditionVariable.close();
163+
view.mShouldOverrideUrlLoadingConditionVariable.block(10000);
164+
165+
return view.mShouldOverrideUrlLoadingResult;
166+
}
167+
146168
private void emitFinishEvent(WebView webView, String url) {
147169
dispatchEvent(
148170
webView,
@@ -180,6 +202,9 @@ private WritableMap createWebViewEvent(WebView webView, String url) {
180202
private static class ReactWebView extends WebView implements LifecycleEventListener {
181203
private @Nullable String injectedJS;
182204

205+
protected ConditionVariable mShouldOverrideUrlLoadingConditionVariable;
206+
protected boolean mShouldOverrideUrlLoadingResult;
207+
183208
/**
184209
* WebView must be created with an context of the current activity
185210
*
@@ -189,6 +214,7 @@ private static class ReactWebView extends WebView implements LifecycleEventListe
189214
*/
190215
public ReactWebView(ThemedReactContext reactContext) {
191216
super(reactContext);
217+
mShouldOverrideUrlLoadingConditionVariable = new ConditionVariable();
192218
}
193219

194220
@Override
@@ -222,6 +248,11 @@ private void cleanupCallbacksAndDestroy() {
222248
setWebViewClient(null);
223249
destroy();
224250
}
251+
252+
private void shouldOverrideWithResult(ReadableArray args) {
253+
this.mShouldOverrideUrlLoadingResult = args.getBoolean(0);
254+
this.mShouldOverrideUrlLoadingConditionVariable.open();
255+
}
225256
}
226257

227258
public ReactWebViewManager() {
@@ -342,7 +373,8 @@ protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
342373
return MapBuilder.of(
343374
"goBack", COMMAND_GO_BACK,
344375
"goForward", COMMAND_GO_FORWARD,
345-
"reload", COMMAND_RELOAD);
376+
"reload", COMMAND_RELOAD,
377+
"shouldOverrideWithResult", COMMAND_SHOULD_OVERRIDE_WITH_RESULT);
346378
}
347379

348380
@Override
@@ -357,6 +389,9 @@ public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray
357389
case COMMAND_RELOAD:
358390
root.reload();
359391
break;
392+
case COMMAND_SHOULD_OVERRIDE_WITH_RESULT:
393+
((ReactWebView) root).shouldOverrideWithResult(args);
394+
break;
360395
}
361396
}
362397

@@ -366,4 +401,11 @@ public void onDropViewInstance(WebView webView) {
366401
((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((ReactWebView) webView);
367402
((ReactWebView) webView).cleanupCallbacksAndDestroy();
368403
}
404+
405+
@Override
406+
public @Nullable Map getExportedCustomDirectEventTypeConstants() {
407+
return MapBuilder.builder()
408+
.put("topShouldOverrideUrlLoading", MapBuilder.of("registrationName", "onShouldOverrideUrlLoading"))
409+
.build();
410+
}
369411
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
package com.facebook.react.views.webview.events;
11+
12+
import com.facebook.react.bridge.WritableMap;
13+
import com.facebook.react.uimanager.events.Event;
14+
import com.facebook.react.uimanager.events.RCTEventEmitter;
15+
16+
/**
17+
* Event emitted when loading has started
18+
*/
19+
public class ShouldOverrideUrlLoadingEvent extends Event<ShouldOverrideUrlLoadingEvent> {
20+
21+
public static final String EVENT_NAME = "topShouldOverrideUrlLoading";
22+
private WritableMap mEventData;
23+
24+
public ShouldOverrideUrlLoadingEvent(int viewId, long timestampMs, WritableMap eventData) {
25+
super(viewId, timestampMs);
26+
mEventData = eventData;
27+
}
28+
29+
@Override
30+
public String getEventName() {
31+
return EVENT_NAME;
32+
}
33+
34+
@Override
35+
public boolean canCoalesce() {
36+
return false;
37+
}
38+
39+
@Override
40+
public boolean isSync() {
41+
return true;
42+
}
43+
44+
@Override
45+
public short getCoalescingKey() {
46+
// All events for a given view can be coalesced.
47+
return 0;
48+
}
49+
50+
@Override
51+
public void dispatch(RCTEventEmitter rctEventEmitter) {
52+
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
53+
}
54+
}

ReactAndroid/src/main/java/com/facebook/react/views/webview/events/TopLoadingErrorEvent.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*/
99

10-
package com.facebook.catalyst.views.webview.events;
10+
package com.facebook.react.views.webview.events;
1111

1212
import com.facebook.react.bridge.WritableMap;
1313
import com.facebook.react.uimanager.events.Event;

0 commit comments

Comments
 (0)