diff --git a/Libraries/Components/WebView/WebView.android.js b/Libraries/Components/WebView/WebView.android.js
index ebf06456671dd7..8b939e1fdb5cf5 100644
--- a/Libraries/Components/WebView/WebView.android.js
+++ b/Libraries/Components/WebView/WebView.android.js
@@ -33,6 +33,8 @@ var WebViewState = keyMirror({
ERROR: null,
});
+type Event = Object;
+
/**
* Renders a native WebView.
*/
@@ -51,6 +53,7 @@ var WebView = React.createClass({
onNavigationStateChange: PropTypes.func,
startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
style: View.propTypes.style,
+ onShouldStartLoadWithRequest: PropTypes.func,
html: deprecatedPropType(
PropTypes.string,
@@ -195,6 +198,15 @@ var WebView = React.createClass({
console.warn('WebView: `source.body` is not supported when using GET.');
}
+ var onShouldOverrideUrlLoading = this.props.onShouldStartLoadWithRequest
+ && ((event: Event) => {
+ var shouldOverride = !this.props.onShouldStartLoadWithRequest(event.nativeEvent);
+ UIManager.dispatchViewManagerCommandSync(
+ this.getWebViewHandle(),
+ UIManager.RCTWebView.Commands.shouldOverrideWithResult,
+ [shouldOverride]);
+ });
+
var webView =
;
return (
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java
index b5939823acf242..d73b543f5d4f8d 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java
@@ -585,8 +585,7 @@ public void onCancel() {
}
}
- public void dispatchCommand(int reactTag, int commandId, @Nullable ReadableArray args) {
- UiThreadUtil.assertOnUiThread();
+ private void dispatchCommandCommon(int reactTag, int commandId, @Nullable ReadableArray args) {
View view = mTagsToViews.get(reactTag);
if (view == null) {
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
viewManager.receiveCommand(view, commandId, args);
}
+ public void dispatchCommandSync(int reactTag, int commandId, @Nullable ReadableArray args) {
+ dispatchCommandCommon(reactTag, commandId, args);
+ }
+
+ public void dispatchCommand(int reactTag, int commandId, @Nullable ReadableArray args) {
+ UiThreadUtil.assertOnUiThread();
+ dispatchCommandCommon(reactTag, commandId, args);
+ }
+
+
/**
* Show a {@link PopupMenu}.
*
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java
index b407c416a5a9f7..981459762b8e86 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java
@@ -538,6 +538,11 @@ public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArra
mOperationsQueue.enqueueDispatchCommand(reactTag, commandId, commandArgs);
}
+ public void dispatchViewManagerCommandSync(int reactTag, int commandId, ReadableArray commandArgs) {
+ assertViewExists(reactTag, "dispatchViewManagerCommandSync");
+ mOperationsQueue.executeDispatchCommand(reactTag, commandId, commandArgs);
+ }
+
/**
* Show a PopupMenu.
*
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java
index ddeb94e6db2ef3..6598a676dc1145 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java
@@ -356,6 +356,11 @@ public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArra
mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs);
}
+ @ReactMethod
+ public void dispatchViewManagerCommandSync(int reactTag, int commandId, ReadableArray commandArgs) {
+ mUIImplementation.dispatchViewManagerCommandSync(reactTag, commandId, commandArgs);
+ }
+
/**
* Show a PopupMenu.
*
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java
index b3b931e0f8ef52..6f2ab709b51ca4 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java
@@ -236,6 +236,10 @@ public DispatchCommandOperation(int tag, int command, @Nullable ReadableArray ar
public void execute() {
mNativeViewHierarchyManager.dispatchCommand(mTag, mCommand, mArgs);
}
+
+ public void executeSync() {
+ mNativeViewHierarchyManager.dispatchCommandSync(mTag, mCommand, mArgs);
+ }
}
private final class ShowPopupMenuOperation extends ViewOperation {
@@ -583,6 +587,14 @@ public void enqueueDispatchCommand(
mOperations.add(new DispatchCommandOperation(reactTag, commandId, commandArgs));
}
+ public void executeDispatchCommand(
+ int reactTag,
+ int commandId,
+ ReadableArray commandArgs) {
+ DispatchCommandOperation operation = new DispatchCommandOperation(reactTag, commandId, commandArgs);
+ operation.executeSync();
+ }
+
public void enqueueUpdateExtraData(int reactTag, Object extraData) {
mOperations.add(new UpdateViewExtraData(reactTag, extraData));
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java
index 40c2845c8adb52..43484abd2500e4 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java
@@ -63,6 +63,10 @@ public boolean canCoalesce() {
return true;
}
+ public boolean isSync() {
+ return false;
+ }
+
/**
* Given two events, coalesce them into a single event that will be sent to JS instead of two
* separate events. By default, just chooses the one the is more recent.
diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java
index 537f1bfb46ba46..bdcedadb9a51e4 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/EventDispatcher.java
@@ -112,6 +112,32 @@ public EventDispatcher(ReactApplicationContext reactContext) {
public void dispatchEvent(Event event) {
Assertions.assertCondition(event.isInitialized(), "Dispatched event hasn't been initialized");
synchronized (mEventsStagingLock) {
+
+ if (event.isSync()) {
+ synchronized (mEventsToDispatchLock) {
+ addEventToEventsToDispatch(event);
+ mReactContext.runOnJSQueueThread(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (mEventsToDispatchLock) {
+ for (int eventIdx = 0; eventIdx < mEventsToDispatchSize; eventIdx++) {
+ Event event = mEventsToDispatch[eventIdx];
+ if (event == null) {
+ continue;
+ }
+
+ event.dispatch(mRCTEventEmitter);
+ event.dispose();
+
+ }
+ clearEventsToDispatch();
+ }
+ }
+ });
+ }
+ return;
+ }
+
mEventStaging.add(event);
Systrace.startAsyncFlow(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java
index 616418bba35b37..896d3a9b315ea4 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/ReactWebViewManager.java
@@ -17,6 +17,7 @@
import android.graphics.Bitmap;
import android.os.Build;
+import android.os.ConditionVariable;
import android.text.TextUtils;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -24,6 +25,7 @@
import com.facebook.catalyst.views.webview.events.TopLoadingErrorEvent;
import com.facebook.catalyst.views.webview.events.TopLoadingFinishEvent;
import com.facebook.catalyst.views.webview.events.TopLoadingStartEvent;
+import com.facebook.react.views.webview.events.ShouldOverrideUrlLoadingEvent;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactContext;
@@ -40,6 +42,7 @@
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.EventDispatcher;
+import com.facebook.react.common.MapBuilder;
/**
* Manages instances of {@link WebView}
@@ -53,6 +56,7 @@
* - topLoadingFinish
* - topLoadingStart
* - topLoadingError
+ * - topShouldOverrideUrlLoading
*
* Each event will carry the following properties:
* - target - view's react tag
@@ -74,6 +78,7 @@ public class ReactWebViewManager extends SimpleViewManager {
public static final int COMMAND_GO_BACK = 1;
public static final int COMMAND_GO_FORWARD = 2;
public static final int COMMAND_RELOAD = 3;
+ public static final int COMMAND_SHOULD_OVERRIDE_WITH_RESULT = 4;
// Use `webView.loadUrl("about:blank")` to reliably reset the view
// state and release page resources (including any running JavaScript).
@@ -143,6 +148,23 @@ public void doUpdateVisitedHistory(WebView webView, String url, boolean isReload
createWebViewEvent(webView, url)));
}
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView webView, String url) {
+ ReactWebView view = ((ReactWebView)webView);
+ view.mShouldOverrideUrlLoadingResult = false;
+ dispatchEvent(
+ webView,
+ new ShouldOverrideUrlLoadingEvent(
+ webView.getId(),
+ SystemClock.nanoTime(),
+ createWebViewEvent(webView, url)));
+
+ view.mShouldOverrideUrlLoadingConditionVariable.close();
+ view.mShouldOverrideUrlLoadingConditionVariable.block(250);
+
+ return view.mShouldOverrideUrlLoadingResult;
+ }
+
private void emitFinishEvent(WebView webView, String url) {
dispatchEvent(
webView,
@@ -180,6 +202,9 @@ private WritableMap createWebViewEvent(WebView webView, String url) {
private static class ReactWebView extends WebView implements LifecycleEventListener {
private @Nullable String injectedJS;
+ protected ConditionVariable mShouldOverrideUrlLoadingConditionVariable;
+ protected boolean mShouldOverrideUrlLoadingResult;
+
/**
* WebView must be created with an context of the current activity
*
@@ -189,6 +214,7 @@ private static class ReactWebView extends WebView implements LifecycleEventListe
*/
public ReactWebView(ThemedReactContext reactContext) {
super(reactContext);
+ mShouldOverrideUrlLoadingConditionVariable = new ConditionVariable();
}
@Override
@@ -222,6 +248,11 @@ private void cleanupCallbacksAndDestroy() {
setWebViewClient(null);
destroy();
}
+
+ private void shouldOverrideWithResult(ReadableArray args) {
+ this.mShouldOverrideUrlLoadingResult = args.getBoolean(0);
+ this.mShouldOverrideUrlLoadingConditionVariable.open();
+ }
}
public ReactWebViewManager() {
@@ -342,7 +373,8 @@ protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
return MapBuilder.of(
"goBack", COMMAND_GO_BACK,
"goForward", COMMAND_GO_FORWARD,
- "reload", COMMAND_RELOAD);
+ "reload", COMMAND_RELOAD,
+ "shouldOverrideWithResult", COMMAND_SHOULD_OVERRIDE_WITH_RESULT);
}
@Override
@@ -357,6 +389,9 @@ public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray
case COMMAND_RELOAD:
root.reload();
break;
+ case COMMAND_SHOULD_OVERRIDE_WITH_RESULT:
+ ((ReactWebView) root).shouldOverrideWithResult(args);
+ break;
}
}
@@ -366,4 +401,11 @@ public void onDropViewInstance(WebView webView) {
((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((ReactWebView) webView);
((ReactWebView) webView).cleanupCallbacksAndDestroy();
}
+
+ @Override
+ public @Nullable Map getExportedCustomDirectEventTypeConstants() {
+ return MapBuilder.builder()
+ .put("topShouldOverrideUrlLoading", MapBuilder.of("registrationName", "onShouldOverrideUrlLoading"))
+ .build();
+ }
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/ShouldOverrideUrlLoadingEvent.java b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/ShouldOverrideUrlLoadingEvent.java
new file mode 100644
index 00000000000000..949769e006c8fc
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/views/webview/events/ShouldOverrideUrlLoadingEvent.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.views.webview.events;
+
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.uimanager.events.Event;
+import com.facebook.react.uimanager.events.RCTEventEmitter;
+
+/**
+ * Event emitted when loading has started
+ */
+public class ShouldOverrideUrlLoadingEvent extends Event {
+
+ public static final String EVENT_NAME = "topShouldOverrideUrlLoading";
+ private WritableMap mEventData;
+
+ public ShouldOverrideUrlLoadingEvent(int viewId, long timestampMs, WritableMap eventData) {
+ super(viewId, timestampMs);
+ mEventData = eventData;
+ }
+
+ @Override
+ public String getEventName() {
+ return EVENT_NAME;
+ }
+
+ @Override
+ public boolean canCoalesce() {
+ return false;
+ }
+
+ @Override
+ public boolean isSync() {
+ return true;
+ }
+
+ @Override
+ public short getCoalescingKey() {
+ // All events for a given view can be coalesced.
+ return 0;
+ }
+
+ @Override
+ public void dispatch(RCTEventEmitter rctEventEmitter) {
+ rctEventEmitter.receiveEvent(getViewTag(), getEventName(), mEventData);
+ }
+}