Bug 1189957 - Open noHandler results in the context they were loaded in. r=margaret
authorMichael Comella <michael.l.comella@gmail.com>
Fri, 07 Aug 2015 16:19:23 -0700
changeset 289948 4a151c79684431a30b43770e696de98613f8e224
parent 289947 b948eb69b69dd1c7e0bd0b47d7d44796511bac10
child 289949 a5d7ee79a10ce44cc108f649cbe3d4cd4946e6bd
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret
bugs1189957
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1189957 - Open noHandler results in the context they were loaded in. r=margaret Before we loaded the url into the open tab - this approach is more correct. I verified it fixed the issues on the Delta website mentioned in this bug.
mobile/android/base/IntentHelper.java
mobile/android/components/ContentDispatchChooser.js
--- a/mobile/android/base/IntentHelper.java
+++ b/mobile/android/base/IntentHelper.java
@@ -1,18 +1,21 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.util.ActivityResultHandler;
+import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.JSONUtils;
+import org.mozilla.gecko.util.NativeEventListener;
+import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.WebActivityMapper;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.app.Activity;
 import android.content.Intent;
@@ -21,70 +24,82 @@ import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
 import java.net.URLEncoder;
 import java.util.Arrays;
 import java.util.List;
 
-public final class IntentHelper implements GeckoEventListener {
+public final class IntentHelper implements GeckoEventListener,
+                                           NativeEventListener {
+
     private static final String LOGTAG = "GeckoIntentHelper";
     private static final String[] EVENTS = {
         "Intent:GetHandlers",
         "Intent:Open",
         "Intent:OpenForResult",
+        "WebActivity:Open"
+    };
+
+    private static final String[] NATIVE_EVENTS = {
         "Intent:OpenNoHandler",
-        "WebActivity:Open"
     };
 
     // via http://developer.android.com/distribute/tools/promote/linking.html
     private static String MARKET_INTENT_URI_PACKAGE_PREFIX = "market://details?id=";
     private static String EXTRA_BROWSER_FALLBACK_URL = "browser_fallback_url";
 
     /** A partial URI to an error page - the encoded error URI should be appended before loading. */
     private static String UNKNOWN_PROTOCOL_URI_PREFIX = "about:neterror?e=unknownProtocolFound&u=";
 
     private static IntentHelper instance;
 
     private final Activity activity;
 
     private IntentHelper(Activity activity) {
         this.activity = activity;
-        EventDispatcher.getInstance().registerGeckoThreadListener(this, EVENTS);
+        EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this, EVENTS);
+        EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this, NATIVE_EVENTS);
     }
 
     public static IntentHelper init(Activity activity) {
         if (instance == null) {
             instance = new IntentHelper(activity);
         } else {
             Log.w(LOGTAG, "IntentHelper.init() called twice, ignoring.");
         }
 
         return instance;
     }
 
     public static void destroy() {
         if (instance != null) {
-            EventDispatcher.getInstance().unregisterGeckoThreadListener(instance, EVENTS);
+            EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) instance, EVENTS);
+            EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) instance, NATIVE_EVENTS);
             instance = null;
         }
     }
 
     @Override
+    public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) {
+        if (event.equals("Intent:OpenNoHandler")) {
+            openNoHandler(message, callback);
+        }
+    }
+
+    @Override
     public void handleMessage(String event, JSONObject message) {
         try {
             if (event.equals("Intent:GetHandlers")) {
                 getHandlers(message);
             } else if (event.equals("Intent:Open")) {
                 open(message);
             } else if (event.equals("Intent:OpenForResult")) {
                 openForResult(message);
-            } else if (event.equals("Intent:OpenNoHandler")) {
-                openNoHandler(message);
             } else if (event.equals("WebActivity:Open")) {
                 openWebActivity(message);
             }
         } catch (JSONException e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
@@ -128,38 +143,40 @@ public final class IntentHelper implemen
     }
 
     /**
      * Opens a URI without any valid handlers on device. In the best case, a package is specified
      * and we can bring the user directly to the application page in an app market. If a package is
      * not specified and there is a fallback url in the intent extras, we open that url. If neither
      * is present, we alert the user that we were unable to open the link.
      */
-    private void openNoHandler(final JSONObject msg) {
-        final String uri = msg.optString("uri");
+    private void openNoHandler(final NativeJSObject msg, final EventCallback callback) {
+        final String uri = msg.getString("uri");
 
         if (TextUtils.isEmpty(uri)) {
-            openUnknownProtocolErrorPage("");
             Log.w(LOGTAG, "Received empty URL - loading about:neterror");
+            callback.sendError(getUnknownProtocolErrorPageUri(""));
             return;
         }
 
         final Intent intent;
         try {
             // TODO (bug 1173626): This will not handle android-app uris on non 5.1 devices.
             intent = Intent.parseUri(uri, 0);
         } catch (final URISyntaxException e) {
+            String errorUri;
             try {
-                openUnknownProtocolErrorPage(URLEncoder.encode(uri, "UTF-8"));
+                errorUri = getUnknownProtocolErrorPageUri(URLEncoder.encode(uri, "UTF-8"));
             } catch (final UnsupportedEncodingException encodingE) {
-                openUnknownProtocolErrorPage("");
+                errorUri = getUnknownProtocolErrorPageUri("");
             }
 
             // Don't log the exception to prevent leaking URIs.
             Log.w(LOGTAG, "Unable to parse Intent URI - loading about:neterror");
+            callback.sendError(errorUri);
             return;
         }
 
         // For this flow, we follow Chrome's lead:
         //   https://developer.chrome.com/multidevice/android/intents
         //
         // Note on alternative flows: we could get the intent package from a component, however, for
         // security reasons, components are ignored when opening URIs (bug 1168998) so we should
@@ -169,38 +186,41 @@ public final class IntentHelper implemen
         // while this could help the user find a new app, there is not always a correlation in
         // scheme to application name and we could end up steering the user wrong (potentially to
         // malicious software). Better to leave that one alone.
         if (intent.getPackage() != null) {
             final String marketUri = MARKET_INTENT_URI_PACKAGE_PREFIX + intent.getPackage();
             final Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(marketUri));
             marketIntent.addCategory(Intent.CATEGORY_BROWSABLE);
             marketIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+            // (Bug 1192436) We don't know if marketIntent matches any Activities (e.g. non-Play
+            // Store devices). If it doesn't, clicking the link will cause no action to occur.
             activity.startActivity(marketIntent);
+            callback.sendSuccess(null);
 
         } else if (intent.hasExtra(EXTRA_BROWSER_FALLBACK_URL)) {
             final String fallbackUrl = intent.getStringExtra(EXTRA_BROWSER_FALLBACK_URL);
-            Tabs.getInstance().loadUrl(fallbackUrl);
+            callback.sendError(fallbackUrl);
 
         }  else {
-            openUnknownProtocolErrorPage(intent.getData().toString());
             // Don't log the URI to prevent leaking it.
             Log.w(LOGTAG, "Unable to open URI, default case - loading about:neterror");
+            callback.sendError(getUnknownProtocolErrorPageUri(intent.getData().toString()));
         }
     }
 
     /**
-     * Opens about:neterror with the unknownProtocolFound text.
+     * Returns an about:neterror uri with the unknownProtocolFound text as a parameter.
      * @param encodedUri The encoded uri. While the page does not open correctly without specifying
      *                   a uri parameter, it happily accepts the empty String so this argument may
      *                   be the empty String.
      */
-    private void openUnknownProtocolErrorPage(final String encodedUri) {
-        final String errorUri = UNKNOWN_PROTOCOL_URI_PREFIX + encodedUri;
-        Tabs.getInstance().loadUrl(errorUri);
+    private String getUnknownProtocolErrorPageUri(final String encodedUri) {
+        return UNKNOWN_PROTOCOL_URI_PREFIX + encodedUri;
     }
 
     private void openWebActivity(JSONObject message) throws JSONException {
         final Intent intent = WebActivityMapper.getIntentForWebActivity(message.getJSONObject("activity"));
         ActivityHandlerHelper.startIntentForActivity(activity, intent, new ResultHandler(message));
     }
 
     private static class ResultHandler implements ActivityResultHandler {
--- a/mobile/android/components/ContentDispatchChooser.js
+++ b/mobile/android/components/ContentDispatchChooser.js
@@ -58,14 +58,23 @@ ContentDispatchChooser.prototype =
         return;
       }
 
       let msg = {
         type: "Intent:OpenNoHandler",
         uri: aURI.spec,
       };
 
-      Messaging.sendRequest(msg);
+      Messaging.sendRequestForResult(msg).then(() => {
+        // Java opens an app on success: take no action.
+      }, (uri) => {
+        // Java didn't load a page so load the page that Java wants us to load.
+        //
+        // Note: when we load the page here (rather than into the selected tab in
+        // java), we load it in the same context where the uri was specified (e.g.
+        // if it's in an iframe, we load the page in an iframe).
+        window.location.href = uri;
+      });
     }
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentDispatchChooser]);