OLD | NEW |
| (Empty) |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/browser/chromeos/arc/arc_navigation_throttle.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/logging.h" | |
11 #include "base/memory/ref_counted.h" | |
12 #include "base/metrics/histogram_macros.h" | |
13 #include "components/arc/arc_bridge_service.h" | |
14 #include "components/arc/arc_service_manager.h" | |
15 #include "components/arc/intent_helper/arc_intent_helper_bridge.h" | |
16 #include "components/arc/intent_helper/local_activity_resolver.h" | |
17 #include "components/arc/intent_helper/page_transition_util.h" | |
18 #include "content/public/browser/browser_thread.h" | |
19 #include "content/public/browser/navigation_controller.h" | |
20 #include "content/public/browser/navigation_handle.h" | |
21 #include "content/public/browser/web_contents.h" | |
22 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | |
23 #include "ui/base/page_transition_types.h" | |
24 #include "url/gurl.h" | |
25 | |
26 namespace arc { | |
27 | |
28 namespace { | |
29 | |
30 constexpr uint32_t kMinVersionForHandleUrl = 2; | |
31 constexpr uint32_t kMinVersionForRequestUrlHandlerList = 2; | |
32 constexpr uint32_t kMinVersionForAddPreferredPackage = 7; | |
33 | |
34 scoped_refptr<ActivityIconLoader> GetIconLoader() { | |
35 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
36 ArcServiceManager* arc_service_manager = ArcServiceManager::Get(); | |
37 return arc_service_manager ? arc_service_manager->icon_loader() : nullptr; | |
38 } | |
39 | |
40 // Compares the host name of the referrer and target URL to decide whether | |
41 // the navigation needs to be overriden. | |
42 bool ShouldOverrideUrlLoading(const GURL& previous_url, | |
43 const GURL& current_url) { | |
44 // When the navigation is initiated in a web page where sending a referrer | |
45 // is disabled, |previous_url| can be empty. In this case, we should open | |
46 // it in the desktop browser. | |
47 if (!previous_url.is_valid() || previous_url.is_empty()) | |
48 return false; | |
49 | |
50 // Also check |current_url| just in case. | |
51 if (!current_url.is_valid() || current_url.is_empty()) { | |
52 DVLOG(1) << "Unexpected URL: " << current_url << ", opening it in Chrome."; | |
53 return false; | |
54 } | |
55 | |
56 return !net::registry_controlled_domains::SameDomainOrHost( | |
57 current_url, previous_url, | |
58 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); | |
59 } | |
60 | |
61 } // namespace | |
62 | |
63 ArcNavigationThrottle::ArcNavigationThrottle( | |
64 content::NavigationHandle* navigation_handle, | |
65 const ShowIntentPickerCallback& show_intent_picker_cb) | |
66 : content::NavigationThrottle(navigation_handle), | |
67 show_intent_picker_callback_(show_intent_picker_cb), | |
68 previous_user_action_(CloseReason::INVALID), | |
69 weak_ptr_factory_(this) {} | |
70 | |
71 ArcNavigationThrottle::~ArcNavigationThrottle() = default; | |
72 | |
73 content::NavigationThrottle::ThrottleCheckResult | |
74 ArcNavigationThrottle::WillStartRequest() { | |
75 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
76 // We must not handle navigations started from the context menu. | |
77 if (navigation_handle()->WasStartedFromContextMenu()) | |
78 return content::NavigationThrottle::PROCEED; | |
79 return HandleRequest(); | |
80 } | |
81 | |
82 content::NavigationThrottle::ThrottleCheckResult | |
83 ArcNavigationThrottle::WillRedirectRequest() { | |
84 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
85 | |
86 switch (previous_user_action_) { | |
87 case CloseReason::ERROR: | |
88 case CloseReason::DIALOG_DEACTIVATED: | |
89 // User dismissed the dialog, or some error occurred before. Don't | |
90 // repeatedly pop up the dialog. | |
91 return content::NavigationThrottle::PROCEED; | |
92 | |
93 case CloseReason::ALWAYS_PRESSED: | |
94 case CloseReason::JUST_ONCE_PRESSED: | |
95 case CloseReason::PREFERRED_ACTIVITY_FOUND: | |
96 // Should never get here - if the user selected one of these previously, | |
97 // Chrome should not see a redirect. | |
98 NOTREACHED(); | |
99 | |
100 case CloseReason::INVALID: | |
101 // No picker has previously been popped up for this - continue. | |
102 break; | |
103 } | |
104 return HandleRequest(); | |
105 } | |
106 | |
107 content::NavigationThrottle::ThrottleCheckResult | |
108 ArcNavigationThrottle::HandleRequest() { | |
109 const GURL& url = navigation_handle()->GetURL(); | |
110 | |
111 // Always handle http(s) <form> submissions in Chrome for two reasons: 1) we | |
112 // don't have a way to send POST data to ARC, and 2) intercepting http(s) form | |
113 // submissions is not very important because such submissions are usually | |
114 // done within the same domain. ShouldOverrideUrlLoading() below filters out | |
115 // such submissions anyway. | |
116 constexpr bool kAllowFormSubmit = false; | |
117 | |
118 if (ShouldIgnoreNavigation(navigation_handle()->GetPageTransition(), | |
119 kAllowFormSubmit)) | |
120 return content::NavigationThrottle::PROCEED; | |
121 | |
122 const GURL previous_url = navigation_handle()->GetReferrer().url; | |
123 const GURL current_url = navigation_handle()->GetURL(); | |
124 if (!ShouldOverrideUrlLoading(previous_url, current_url)) | |
125 return content::NavigationThrottle::PROCEED; | |
126 | |
127 ArcServiceManager* arc_service_manager = ArcServiceManager::Get(); | |
128 DCHECK(arc_service_manager); | |
129 scoped_refptr<LocalActivityResolver> local_resolver = | |
130 arc_service_manager->activity_resolver(); | |
131 if (local_resolver->ShouldChromeHandleUrl(url)) { | |
132 // Allow navigation to proceed if there isn't an android app that handles | |
133 // the given URL. | |
134 return content::NavigationThrottle::PROCEED; | |
135 } | |
136 | |
137 auto* instance = ArcIntentHelperBridge::GetIntentHelperInstance( | |
138 "RequestUrlHandlerList", kMinVersionForRequestUrlHandlerList); | |
139 if (!instance) | |
140 return content::NavigationThrottle::PROCEED; | |
141 instance->RequestUrlHandlerList( | |
142 url.spec(), base::Bind(&ArcNavigationThrottle::OnAppCandidatesReceived, | |
143 weak_ptr_factory_.GetWeakPtr())); | |
144 return content::NavigationThrottle::DEFER; | |
145 } | |
146 | |
147 // We received the array of app candidates to handle this URL (even the Chrome | |
148 // app is included). | |
149 void ArcNavigationThrottle::OnAppCandidatesReceived( | |
150 mojo::Array<mojom::IntentHandlerInfoPtr> handlers) { | |
151 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
152 if (handlers.empty() || | |
153 (handlers.size() == 1 && ArcIntentHelperBridge::IsIntentHelperPackage( | |
154 handlers[0]->package_name))) { | |
155 // This scenario shouldn't be accesed as ArcNavigationThrottle is created | |
156 // iff there are ARC apps which can actually handle the given URL. | |
157 DVLOG(1) << "There are no app candidates for this URL: " | |
158 << navigation_handle()->GetURL().spec(); | |
159 navigation_handle()->Resume(); | |
160 return; | |
161 } | |
162 | |
163 // If one of the apps is marked as preferred, use it right away without | |
164 // showing the UI. | |
165 for (size_t i = 0; i < handlers.size(); ++i) { | |
166 if (!handlers[i]->is_preferred) | |
167 continue; | |
168 if (ArcIntentHelperBridge::IsIntentHelperPackage( | |
169 handlers[i]->package_name)) { | |
170 // If Chrome browser was selected as the preferred app, we should't | |
171 // create a throttle. | |
172 DVLOG(1) | |
173 << "Chrome browser is selected as the preferred app for this URL: " | |
174 << navigation_handle()->GetURL().spec(); | |
175 } | |
176 OnIntentPickerClosed(std::move(handlers), i, | |
177 CloseReason::PREFERRED_ACTIVITY_FOUND); | |
178 return; | |
179 } | |
180 | |
181 // Swap Chrome app with any app in row |kMaxAppResults-1| iff its index is | |
182 // bigger, thus ensuring the user can always see Chrome without scrolling. | |
183 size_t chrome_app_index = 0; | |
184 for (size_t i = 0; i < handlers.size(); ++i) { | |
185 if (ArcIntentHelperBridge::IsIntentHelperPackage( | |
186 handlers[i]->package_name)) { | |
187 chrome_app_index = i; | |
188 break; | |
189 } | |
190 } | |
191 | |
192 if (chrome_app_index >= kMaxAppResults) | |
193 std::swap(handlers[kMaxAppResults - 1], handlers[chrome_app_index]); | |
194 | |
195 scoped_refptr<ActivityIconLoader> icon_loader = GetIconLoader(); | |
196 if (!icon_loader) { | |
197 LOG(ERROR) << "Cannot get an instance of ActivityIconLoader"; | |
198 navigation_handle()->Resume(); | |
199 return; | |
200 } | |
201 std::vector<ActivityIconLoader::ActivityName> activities; | |
202 for (const auto& handler : handlers) { | |
203 activities.emplace_back(handler->package_name, handler->activity_name); | |
204 } | |
205 icon_loader->GetActivityIcons( | |
206 activities, | |
207 base::Bind(&ArcNavigationThrottle::OnAppIconsReceived, | |
208 weak_ptr_factory_.GetWeakPtr(), base::Passed(&handlers))); | |
209 } | |
210 | |
211 void ArcNavigationThrottle::OnAppIconsReceived( | |
212 mojo::Array<mojom::IntentHandlerInfoPtr> handlers, | |
213 std::unique_ptr<ActivityIconLoader::ActivityToIconsMap> icons) { | |
214 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
215 std::vector<NameAndIcon> app_info; | |
216 | |
217 for (const auto& handler : handlers) { | |
218 gfx::Image icon; | |
219 const ActivityIconLoader::ActivityName activity(handler->package_name, | |
220 handler->activity_name); | |
221 const auto it = icons->find(activity); | |
222 | |
223 app_info.emplace_back( | |
224 handler->name, it != icons->end() ? it->second.icon20 : gfx::Image()); | |
225 } | |
226 | |
227 show_intent_picker_callback_.Run( | |
228 navigation_handle()->GetWebContents(), app_info, | |
229 base::Bind(&ArcNavigationThrottle::OnIntentPickerClosed, | |
230 weak_ptr_factory_.GetWeakPtr(), base::Passed(&handlers))); | |
231 } | |
232 | |
233 void ArcNavigationThrottle::OnIntentPickerClosed( | |
234 mojo::Array<mojom::IntentHandlerInfoPtr> handlers, | |
235 size_t selected_app_index, | |
236 CloseReason close_reason) { | |
237 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
238 const GURL& url = navigation_handle()->GetURL(); | |
239 content::NavigationHandle* handle = navigation_handle(); | |
240 | |
241 previous_user_action_ = close_reason; | |
242 | |
243 // Make sure that the instance at least supports HandleUrl. | |
244 auto* instance = ArcIntentHelperBridge::GetIntentHelperInstance( | |
245 "HandleUrl", kMinVersionForHandleUrl); | |
246 if (!instance || selected_app_index >= handlers.size()) | |
247 close_reason = CloseReason::ERROR; | |
248 | |
249 switch (close_reason) { | |
250 case CloseReason::ERROR: | |
251 case CloseReason::DIALOG_DEACTIVATED: { | |
252 // If the user fails to select an option from the list, or the UI returned | |
253 // an error or if |selected_app_index| is not a valid index, then resume | |
254 // the navigation in Chrome. | |
255 DVLOG(1) << "User didn't select a valid option, resuming navigation."; | |
256 handle->Resume(); | |
257 break; | |
258 } | |
259 case CloseReason::ALWAYS_PRESSED: { | |
260 // Call AddPreferredPackage if it is supported. Reusing the same | |
261 // |instance| is okay. | |
262 if (ArcIntentHelperBridge::GetIntentHelperInstance( | |
263 "AddPreferredPackage", kMinVersionForAddPreferredPackage)) { | |
264 instance->AddPreferredPackage( | |
265 handlers[selected_app_index]->package_name); | |
266 } | |
267 // fall through. | |
268 } | |
269 case CloseReason::JUST_ONCE_PRESSED: | |
270 case CloseReason::PREFERRED_ACTIVITY_FOUND: { | |
271 if (ArcIntentHelperBridge::IsIntentHelperPackage( | |
272 handlers[selected_app_index]->package_name)) { | |
273 handle->Resume(); | |
274 } else { | |
275 instance->HandleUrl(url.spec(), | |
276 handlers[selected_app_index]->package_name); | |
277 handle->CancelDeferredNavigation( | |
278 content::NavigationThrottle::CANCEL_AND_IGNORE); | |
279 if (handle->GetWebContents()->GetController().IsInitialNavigation()) | |
280 handle->GetWebContents()->Close(); | |
281 } | |
282 break; | |
283 } | |
284 case CloseReason::INVALID: { | |
285 NOTREACHED(); | |
286 return; | |
287 } | |
288 } | |
289 | |
290 UMA_HISTOGRAM_ENUMERATION("Arc.IntentHandlerAction", | |
291 static_cast<int>(close_reason), | |
292 static_cast<int>(CloseReason::SIZE)); | |
293 } | |
294 | |
295 // static | |
296 bool ArcNavigationThrottle::ShouldOverrideUrlLoadingForTesting( | |
297 const GURL& previous_url, | |
298 const GURL& current_url) { | |
299 return ShouldOverrideUrlLoading(previous_url, current_url); | |
300 } | |
301 | |
302 } // namespace arc | |
OLD | NEW |