Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1963)

Unified Diff: chrome/browser/chromeos/arc/intent_helper/arc_external_protocol_dialog.cc

Issue 2540013004: Allow an external URL with FROM_API qualifier to be forwarded to ARC (Closed)
Patch Set: address comment Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/chromeos/arc/intent_helper/arc_external_protocol_dialog.cc
diff --git a/chrome/browser/chromeos/arc/intent_helper/arc_external_protocol_dialog.cc b/chrome/browser/chromeos/arc/intent_helper/arc_external_protocol_dialog.cc
index cdf3a3aa6bfc15925130ceb502005aef0a01471c..db9007d2c5b02ed4d6c0e25b9bf534d01977668f 100644
--- a/chrome/browser/chromeos/arc/intent_helper/arc_external_protocol_dialog.cc
+++ b/chrome/browser/chromeos/arc/intent_helper/arc_external_protocol_dialog.cc
@@ -7,6 +7,7 @@
#include <memory>
#include "base/bind.h"
+#include "base/lazy_instance.h"
#include "base/memory/ref_counted.h"
#include "chrome/browser/chromeos/arc/intent_helper/arc_navigation_throttle.h"
#include "chrome/browser/chromeos/external_protocol_dialog.h"
@@ -37,6 +38,11 @@ constexpr uint32_t kMinVersionForHandleUrl = 2;
constexpr uint32_t kMinVersionForRequestUrlHandlerList = 2;
constexpr uint32_t kMinVersionForAddPreferredPackage = 7;
+// TODO(yusukes|djacobo): Find a better way to detect a request loop and remove
+// the global variables.
+base::LazyInstance<GURL> g_last_url = LAZY_INSTANCE_INITIALIZER;
+ui::PageTransition g_last_page_transition;
+
// Shows the Chrome OS' original external protocol dialog as a fallback.
void ShowFallbackExternalProtocolDialog(int render_process_host_id,
int routing_id,
@@ -96,6 +102,7 @@ void HandleUrlInArc(int render_process_host_id,
// A helper function called by GetAction().
GetActionResult GetActionInternal(
const GURL& original_url,
+ bool always_ask_user,
const mojom::IntentHandlerInfoPtr& handler,
std::pair<GURL, std::string>* out_url_and_package) {
if (handler->fallback_url.has_value()) {
@@ -112,24 +119,28 @@ GetActionResult GetActionInternal(
<< " in Chrome. Falling back to ARC...";
}
// |fallback_url| which Chrome doesn't support is passed (e.g. market:).
- return GetActionResult::HANDLE_URL_IN_ARC;
+ return always_ask_user ? GetActionResult::ASK_USER
+ : GetActionResult::HANDLE_URL_IN_ARC;
}
// Unlike |handler->fallback_url|, the |original_url| should always be handled
// in ARC since it's external to Chrome.
*out_url_and_package = std::make_pair(original_url, handler->package_name);
- return GetActionResult::HANDLE_URL_IN_ARC;
+ return always_ask_user ? GetActionResult::ASK_USER
+ : GetActionResult::HANDLE_URL_IN_ARC;
}
// Gets an action that should be done when ARC has the |handlers| for the
// |original_url| and the user selects |selected_app_index|. When the user
// hasn't selected any app, |selected_app_index| must be set to
-// |handlers.size()|.
+// |handlers.size()|. When |always_ask_user| is true, the function never
+// returns HANDLE_URL_IN_ARC.
//
// When the returned action is either OPEN_URL_IN_CHROME or HANDLE_URL_IN_ARC,
// |out_url_and_package| is filled accordingly.
GetActionResult GetAction(
const GURL& original_url,
+ bool always_ask_user,
const std::vector<mojom::IntentHandlerInfoPtr>& handlers,
size_t selected_app_index,
std::pair<GURL, std::string>* out_url_and_package) {
@@ -143,7 +154,8 @@ GetActionResult GetAction(
// If |handlers| has only one element and its package is "Chrome", open
// the fallback URL in the current tab without showing the dialog.
if (handlers.size() == 1) {
- if (GetActionInternal(original_url, handlers[0], out_url_and_package) ==
+ if (GetActionInternal(original_url, always_ask_user, handlers[0],
+ out_url_and_package) ==
GetActionResult::OPEN_URL_IN_CHROME) {
return GetActionResult::OPEN_URL_IN_CHROME;
}
@@ -158,7 +170,8 @@ GetActionResult GetAction(
continue;
// A preferred activity is found. Decide how to open it, either in Chrome
// or ARC.
- return GetActionInternal(original_url, handler, out_url_and_package);
+ return GetActionInternal(original_url, always_ask_user, handler,
+ out_url_and_package);
}
// Ask the user to pick one.
return GetActionResult::ASK_USER;
@@ -166,7 +179,9 @@ GetActionResult GetAction(
// The user has already made the selection. Decide how to open it, either in
// Chrome or ARC.
- return GetActionInternal(original_url, handlers[selected_app_index],
+ DCHECK(!always_ask_user)
+ << "|always_ask_user| must be false when |selected_app_index| is valid.";
+ return GetActionInternal(original_url, false, handlers[selected_app_index],
out_url_and_package);
}
@@ -174,12 +189,13 @@ GetActionResult GetAction(
bool HandleUrl(int render_process_host_id,
int routing_id,
const GURL& url,
+ bool always_ask_user,
const std::vector<mojom::IntentHandlerInfoPtr>& handlers,
size_t selected_app_index,
GetActionResult* out_result) {
std::pair<GURL, std::string> url_and_package;
- const GetActionResult result =
- GetAction(url, handlers, selected_app_index, &url_and_package);
+ const GetActionResult result = GetAction(
+ url, always_ask_user, handlers, selected_app_index, &url_and_package);
if (out_result)
*out_result = result;
@@ -208,7 +224,7 @@ GURL GetUrlToNavigateOnDeactivate(
const GURL empty_url;
for (size_t i = 0; i < handlers.size(); ++i) {
std::pair<GURL, std::string> url_and_package;
- if (GetActionInternal(empty_url, handlers[i], &url_and_package) ==
+ if (GetActionInternal(empty_url, false, handlers[i], &url_and_package) ==
GetActionResult::OPEN_URL_IN_CHROME) {
DCHECK(url_and_package.first.SchemeIsHTTPOrHTTPS());
return url_and_package.first;
@@ -253,8 +269,13 @@ void OnIntentPickerClosed(int render_process_host_id,
ArcNavigationThrottle::CloseReason::JUST_ONCE_PRESSED ||
close_reason ==
ArcNavigationThrottle::CloseReason::ALWAYS_PRESSED) {
- if (selected_app_index == handlers.size())
+ if (selected_app_index == handlers.size()) {
close_reason = ArcNavigationThrottle::CloseReason::ERROR;
+ } else {
+ // The user has made a selection. Clear g_last_* variables.
+ g_last_url.Get() = GURL();
+ g_last_page_transition = ui::PageTransition();
+ }
}
switch (close_reason) {
@@ -268,7 +289,7 @@ void OnIntentPickerClosed(int render_process_host_id,
}
case ArcNavigationThrottle::CloseReason::JUST_ONCE_PRESSED: {
// Launch the selected app.
- HandleUrl(render_process_host_id, routing_id, url, handlers,
+ HandleUrl(render_process_host_id, routing_id, url, false, handlers,
selected_app_index, nullptr);
break;
}
@@ -331,6 +352,7 @@ void OnAppIconsReceived(
void OnUrlHandlerList(int render_process_host_id,
int routing_id,
const GURL& url,
+ bool always_ask_user,
std::vector<mojom::IntentHandlerInfoPtr> handlers) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@@ -346,8 +368,8 @@ void OnUrlHandlerList(int render_process_host_id,
// Check if the |url| should be handled right away without showing the UI.
GetActionResult result;
- if (HandleUrl(render_process_host_id, routing_id, url, handlers,
- handlers.size(), &result)) {
+ if (HandleUrl(render_process_host_id, routing_id, url, always_ask_user,
+ handlers, handlers.size(), &result)) {
if (result == GetActionResult::HANDLE_URL_IN_ARC) {
ArcNavigationThrottle::RecordUma(
ArcNavigationThrottle::CloseReason::PREFERRED_ACTIVITY_FOUND,
@@ -374,6 +396,29 @@ void OnUrlHandlerList(int render_process_host_id,
routing_id, url, base::Passed(&handlers)));
}
+// Returns true if the |url| is safe to be forwarded to ARC without showing the
+// disambig dialog when there is a preferred app on ARC for the |url|. Note that
+// this function almost always returns true (i.e. "safe") except for very rare
+// situations mentioned below.
+// TODO(yusukes|djacobo): Find a better way to detect a request loop and remove
+// these heuristics.
+bool IsSafeToRedirectToArcWithoutUserConfirmation(
+ const GURL& url,
+ ui::PageTransition page_transition,
+ const GURL& last_url,
+ ui::PageTransition last_page_transition) {
+ // Return "safe" unless both transition flags are FROM_API because the only
+ // unsafe situation we know is infinite tab creation loop with FROM_API
+ // (b/30125340).
+ if (!(page_transition & ui::PAGE_TRANSITION_FROM_API) ||
+ !(last_page_transition & ui::PAGE_TRANSITION_FROM_API)) {
+ return true;
+ }
+
+ // Return "safe" unless both URLs are for the same app.
+ return url.scheme() != last_url.scheme();
+}
+
} // namespace
bool RunArcExternalProtocolDialog(const GURL& url,
@@ -384,10 +429,31 @@ bool RunArcExternalProtocolDialog(const GURL& url,
// This function is for external protocols that Chrome cannot handle.
DCHECK(!url.SchemeIsHTTPOrHTTPS()) << url;
- if (ShouldIgnoreNavigation(page_transition, true /* allow_form_submit */,
+ const bool always_ask_user = !IsSafeToRedirectToArcWithoutUserConfirmation(
+ url, page_transition, g_last_url.Get(), g_last_page_transition);
+ LOG_IF(WARNING, always_ask_user)
+ << "RunArcExternalProtocolDialog: repeatedly handling external protocol "
+ << "redirection to " << url
+ << " started from API: last_url=" << g_last_url.Get();
+
+ // This function is called only on the UI thread. Updating g_last_* variables
+ // without a lock is safe.
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+ g_last_url.Get() = url;
+ g_last_page_transition = page_transition;
+
+ // For external protocol navigation, always ignore the FROM_API qualifier.
+ // We sometimes do need to forward a request with FROM_API to ARC, or
+ // AppAuth may not work (b/33208965). This is safe as long as we properly
+ // use |always_ask_user|.
+ const ui::PageTransition masked_page_transition =
+ MaskOutPageTransition(page_transition, ui::PAGE_TRANSITION_FROM_API);
+
+ if (ShouldIgnoreNavigation(masked_page_transition,
+ true /* allow_form_submit */,
true /* allow_client_redirect */)) {
LOG(WARNING) << "RunArcExternalProtocolDialog: ignoring " << url
- << " with PageTransition=" << page_transition;
+ << " with PageTransition=" << masked_page_transition;
return false;
}
@@ -406,17 +472,18 @@ bool RunArcExternalProtocolDialog(const GURL& url,
// Show ARC version of the dialog, which is IntentPickerBubbleView. To show
// the bubble view, we need to ask ARC for a handler list first.
instance->RequestUrlHandlerList(
- url.spec(),
- base::Bind(OnUrlHandlerList, render_process_host_id, routing_id, url));
+ url.spec(), base::Bind(OnUrlHandlerList, render_process_host_id,
+ routing_id, url, always_ask_user));
return true;
}
GetActionResult GetActionForTesting(
const GURL& original_url,
+ bool always_ask_user,
const std::vector<mojom::IntentHandlerInfoPtr>& handlers,
size_t selected_app_index,
std::pair<GURL, std::string>* out_url_and_package) {
- return GetAction(original_url, handlers, selected_app_index,
+ return GetAction(original_url, always_ask_user, handlers, selected_app_index,
out_url_and_package);
}
@@ -425,4 +492,13 @@ GURL GetUrlToNavigateOnDeactivateForTesting(
return GetUrlToNavigateOnDeactivate(handlers);
}
+bool IsSafeToRedirectToArcWithoutUserConfirmationForTesting(
+ const GURL& url,
+ ui::PageTransition page_transition,
+ const GURL& last_url,
+ ui::PageTransition last_page_transition) {
+ return IsSafeToRedirectToArcWithoutUserConfirmation(
+ url, page_transition, last_url, last_page_transition);
+}
+
} // namespace arc

Powered by Google App Engine
This is Rietveld 408576698