author | Michael Comella <michael.l.comella@gmail.com> |
Fri, 07 Aug 2015 16:19:23 -0700 | |
changeset 289948 | 4a151c79684431a30b43770e696de98613f8e224 |
parent 289947 | b948eb69b69dd1c7e0bd0b47d7d44796511bac10 |
child 289949 | a5d7ee79a10ce44cc108f649cbe3d4cd4946e6bd |
push id | 5245 |
push user | raliiev@mozilla.com |
push date | Thu, 29 Oct 2015 11:30:51 +0000 |
treeherder | mozilla-beta@dac831dc1bd0 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | margaret |
bugs | 1189957 |
milestone | 43.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
|
mobile/android/base/IntentHelper.java | file | annotate | diff | comparison | revisions | |
mobile/android/components/ContentDispatchChooser.js | file | annotate | diff | comparison | revisions |
--- 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]);