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

Side by Side Diff: chrome/browser/chromeos/arc/arc_navigation_throttle.cc

Issue 2441563002: arc: Create intermediate directories in c/b/c/arc (Closed)
Patch Set: Re-rebase to ToT Created 4 years, 1 month 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 unified diff | Download patch
OLDNEW
(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 // Returns true if |handlers| contain one or more apps. When this function is
62 // called from OnAppCandidatesReceived, |handlers| always contain Chrome (aka
63 // intent_helper), but the function doesn't treat it as an app.
64 bool IsAppAvailable(const mojo::Array<mojom::IntentHandlerInfoPtr>& handlers) {
65 return handlers.size() > 1 || (handlers.size() == 1 &&
66 !ArcIntentHelperBridge::IsIntentHelperPackage(
67 handlers[0]->package_name));
68 }
69
70 // Searches for a preferred app in |handlers| and returns its index. If not
71 // found, returns |handlers.size()|.
72 size_t FindPreferredApp(
73 const mojo::Array<mojom::IntentHandlerInfoPtr>& handlers,
74 const GURL& url_for_logging) {
75 for (size_t i = 0; i < handlers.size(); ++i) {
76 if (!handlers[i]->is_preferred)
77 continue;
78 if (ArcIntentHelperBridge::IsIntentHelperPackage(
79 handlers[i]->package_name)) {
80 // If Chrome browser was selected as the preferred app, we shouldn't
81 // create a throttle.
82 DVLOG(1)
83 << "Chrome browser is selected as the preferred app for this URL: "
84 << url_for_logging;
85 }
86 return i;
87 }
88 return handlers.size(); // not found
89 }
90
91 // Swaps Chrome app with any app in row |kMaxAppResults-1| iff its index is
92 // bigger, thus ensuring the user can always see Chrome without scrolling.
93 // When swap is needed, fills |out_indices| and returns true. If |handlers|
94 // do not have Chrome, returns false.
95 bool IsSwapElementsNeeded(
96 const mojo::Array<mojom::IntentHandlerInfoPtr>& handlers,
97 std::pair<size_t, size_t>* out_indices) {
98 size_t chrome_app_index = 0;
99 for (size_t i = 0; i < handlers.size(); ++i) {
100 if (ArcIntentHelperBridge::IsIntentHelperPackage(
101 handlers[i]->package_name)) {
102 chrome_app_index = i;
103 break;
104 }
105 }
106 if (chrome_app_index < ArcNavigationThrottle::kMaxAppResults)
107 return false;
108
109 *out_indices = std::make_pair(ArcNavigationThrottle::kMaxAppResults - 1,
110 chrome_app_index);
111 return true;
112 }
113
114 } // namespace
115
116 ArcNavigationThrottle::ArcNavigationThrottle(
117 content::NavigationHandle* navigation_handle,
118 const ShowIntentPickerCallback& show_intent_picker_cb)
119 : content::NavigationThrottle(navigation_handle),
120 show_intent_picker_callback_(show_intent_picker_cb),
121 previous_user_action_(CloseReason::INVALID),
122 weak_ptr_factory_(this) {}
123
124 ArcNavigationThrottle::~ArcNavigationThrottle() = default;
125
126 content::NavigationThrottle::ThrottleCheckResult
127 ArcNavigationThrottle::WillStartRequest() {
128 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
129 // We must not handle navigations started from the context menu.
130 if (navigation_handle()->WasStartedFromContextMenu())
131 return content::NavigationThrottle::PROCEED;
132 return HandleRequest();
133 }
134
135 content::NavigationThrottle::ThrottleCheckResult
136 ArcNavigationThrottle::WillRedirectRequest() {
137 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
138
139 switch (previous_user_action_) {
140 case CloseReason::ERROR:
141 case CloseReason::DIALOG_DEACTIVATED:
142 // User dismissed the dialog, or some error occurred before. Don't
143 // repeatedly pop up the dialog.
144 return content::NavigationThrottle::PROCEED;
145
146 case CloseReason::ALWAYS_PRESSED:
147 case CloseReason::JUST_ONCE_PRESSED:
148 case CloseReason::PREFERRED_ACTIVITY_FOUND:
149 // Should never get here - if the user selected one of these previously,
150 // Chrome should not see a redirect.
151 NOTREACHED();
152
153 case CloseReason::INVALID:
154 // No picker has previously been popped up for this - continue.
155 break;
156 }
157 return HandleRequest();
158 }
159
160 content::NavigationThrottle::ThrottleCheckResult
161 ArcNavigationThrottle::HandleRequest() {
162 const GURL& url = navigation_handle()->GetURL();
163
164 // Always handle http(s) <form> submissions in Chrome for two reasons: 1) we
165 // don't have a way to send POST data to ARC, and 2) intercepting http(s) form
166 // submissions is not very important because such submissions are usually
167 // done within the same domain. ShouldOverrideUrlLoading() below filters out
168 // such submissions anyway.
169 constexpr bool kAllowFormSubmit = false;
170
171 if (ShouldIgnoreNavigation(navigation_handle()->GetPageTransition(),
172 kAllowFormSubmit))
173 return content::NavigationThrottle::PROCEED;
174
175 const GURL referrer_url = navigation_handle()->GetReferrer().url;
176 const GURL current_url = navigation_handle()->GetURL();
177 const GURL last_committed_url =
178 navigation_handle()->GetWebContents()->GetLastCommittedURL();
179
180 // For navigations from http to https we clean up the Referrer as part of the
181 // sanitization proccess, however we may still have access to the last
182 // committed URL. On the other hand, navigations started within a new tab
183 // (e.g. due to target="_blank") will keep no track of any previous entries
184 // and so GetLastCommittedURL() can be seen empty sometimes, this is why we
185 // use one or the other accordingly. Also we don't use GetVisibleURL() since
186 // it may contain a still non-committed URL (i.e. it can be the same as
187 // GetURL()).
188 const GURL previous_url =
189 referrer_url.is_empty() ? last_committed_url : referrer_url;
190
191 if (!ShouldOverrideUrlLoading(previous_url, current_url))
192 return content::NavigationThrottle::PROCEED;
193
194 ArcServiceManager* arc_service_manager = ArcServiceManager::Get();
195 DCHECK(arc_service_manager);
196 scoped_refptr<LocalActivityResolver> local_resolver =
197 arc_service_manager->activity_resolver();
198 if (local_resolver->ShouldChromeHandleUrl(url)) {
199 // Allow navigation to proceed if there isn't an android app that handles
200 // the given URL.
201 return content::NavigationThrottle::PROCEED;
202 }
203
204 auto* instance = ArcIntentHelperBridge::GetIntentHelperInstance(
205 "RequestUrlHandlerList", kMinVersionForRequestUrlHandlerList);
206 if (!instance)
207 return content::NavigationThrottle::PROCEED;
208 instance->RequestUrlHandlerList(
209 url.spec(), base::Bind(&ArcNavigationThrottle::OnAppCandidatesReceived,
210 weak_ptr_factory_.GetWeakPtr()));
211 return content::NavigationThrottle::DEFER;
212 }
213
214 // We received the array of app candidates to handle this URL (even the Chrome
215 // app is included).
216 void ArcNavigationThrottle::OnAppCandidatesReceived(
217 mojo::Array<mojom::IntentHandlerInfoPtr> handlers) {
218 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
219 if (!IsAppAvailable(handlers)) {
220 // This scenario shouldn't be accesed as ArcNavigationThrottle is created
221 // iff there are ARC apps which can actually handle the given URL.
222 DVLOG(1) << "There are no app candidates for this URL: "
223 << navigation_handle()->GetURL();
224 navigation_handle()->Resume();
225 return;
226 }
227
228 // If one of the apps is marked as preferred, use it right away without
229 // showing the UI.
230 const size_t index =
231 FindPreferredApp(handlers, navigation_handle()->GetURL());
232 if (index != handlers.size()) {
233 const std::string package_name = handlers[index]->package_name;
234 OnIntentPickerClosed(std::move(handlers), package_name,
235 CloseReason::PREFERRED_ACTIVITY_FOUND);
236 return;
237 }
238
239 std::pair<size_t, size_t> indices;
240 if (IsSwapElementsNeeded(handlers, &indices))
241 std::swap(handlers[indices.first], handlers[indices.second]);
242
243 scoped_refptr<ActivityIconLoader> icon_loader = GetIconLoader();
244 if (!icon_loader) {
245 LOG(ERROR) << "Cannot get an instance of ActivityIconLoader";
246 navigation_handle()->Resume();
247 return;
248 }
249 std::vector<ActivityIconLoader::ActivityName> activities;
250 for (const auto& handler : handlers)
251 activities.emplace_back(handler->package_name, handler->activity_name);
252 icon_loader->GetActivityIcons(
253 activities,
254 base::Bind(&ArcNavigationThrottle::OnAppIconsReceived,
255 weak_ptr_factory_.GetWeakPtr(), base::Passed(&handlers)));
256 }
257
258 void ArcNavigationThrottle::OnAppIconsReceived(
259 mojo::Array<mojom::IntentHandlerInfoPtr> handlers,
260 std::unique_ptr<ActivityIconLoader::ActivityToIconsMap> icons) {
261 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
262 std::vector<AppInfo> app_info;
263
264 for (const auto& handler : handlers) {
265 gfx::Image icon;
266 const ActivityIconLoader::ActivityName activity(handler->package_name,
267 handler->activity_name);
268 const auto it = icons->find(activity);
269
270 app_info.emplace_back(
271 AppInfo(it != icons->end() ? it->second.icon20 : gfx::Image(),
272 handler->package_name, handler->name));
273 }
274
275 show_intent_picker_callback_.Run(
276 navigation_handle()->GetWebContents(), app_info,
277 base::Bind(&ArcNavigationThrottle::OnIntentPickerClosed,
278 weak_ptr_factory_.GetWeakPtr(), base::Passed(&handlers)));
279 }
280
281 void ArcNavigationThrottle::OnIntentPickerClosed(
282 mojo::Array<mojom::IntentHandlerInfoPtr> handlers,
283 const std::string& selected_app_package,
284 CloseReason close_reason) {
285 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
286 const GURL& url = navigation_handle()->GetURL();
287 content::NavigationHandle* handle = navigation_handle();
288 previous_user_action_ = close_reason;
289
290 // Make sure that the instance at least supports HandleUrl.
291 auto* instance = ArcIntentHelperBridge::GetIntentHelperInstance(
292 "HandleUrl", kMinVersionForHandleUrl);
293 // Since we are selecting an app by its package name, we need to locate it
294 // on the |handlers| structure before sending the IPC to ARC.
295 const size_t selected_app_index = GetAppIndex(handlers, selected_app_package);
296 if (!instance) {
297 close_reason = CloseReason::ERROR;
298 } else if (close_reason == CloseReason::JUST_ONCE_PRESSED ||
299 close_reason == CloseReason::ALWAYS_PRESSED ||
300 close_reason == CloseReason::PREFERRED_ACTIVITY_FOUND) {
301 if (selected_app_index == handlers.size())
302 close_reason = CloseReason::ERROR;
303 }
304
305 switch (close_reason) {
306 case CloseReason::ERROR:
307 case CloseReason::DIALOG_DEACTIVATED: {
308 // If the user fails to select an option from the list, or the UI returned
309 // an error or if |selected_app_index| is not a valid index, then resume
310 // the navigation in Chrome.
311 DVLOG(1) << "User didn't select a valid option, resuming navigation.";
312 handle->Resume();
313 break;
314 }
315 case CloseReason::ALWAYS_PRESSED: {
316 // Call AddPreferredPackage if it is supported. Reusing the same
317 // |instance| is okay.
318 if (ArcIntentHelperBridge::GetIntentHelperInstance(
319 "AddPreferredPackage", kMinVersionForAddPreferredPackage)) {
320 instance->AddPreferredPackage(
321 handlers[selected_app_index]->package_name);
322 }
323 // fall through.
324 }
325 case CloseReason::JUST_ONCE_PRESSED:
326 case CloseReason::PREFERRED_ACTIVITY_FOUND: {
327 if (ArcIntentHelperBridge::IsIntentHelperPackage(
328 handlers[selected_app_index]->package_name)) {
329 handle->Resume();
330 } else {
331 instance->HandleUrl(url.spec(), selected_app_package);
332 handle->CancelDeferredNavigation(
333 content::NavigationThrottle::CANCEL_AND_IGNORE);
334 if (handle->GetWebContents()->GetController().IsInitialNavigation())
335 handle->GetWebContents()->Close();
336 }
337 break;
338 }
339 case CloseReason::INVALID: {
340 NOTREACHED();
341 return;
342 }
343 }
344
345 UMA_HISTOGRAM_ENUMERATION("Arc.IntentHandlerAction",
346 static_cast<int>(close_reason),
347 static_cast<int>(CloseReason::SIZE));
348 }
349
350 // static
351 size_t ArcNavigationThrottle::GetAppIndex(
352 const mojo::Array<mojom::IntentHandlerInfoPtr>& handlers,
353 const std::string& selected_app_package) {
354 for (size_t i = 0; i < handlers.size(); ++i) {
355 if (handlers[i]->package_name == selected_app_package)
356 return i;
357 }
358 return handlers.size();
359 }
360
361 // static
362 bool ArcNavigationThrottle::ShouldOverrideUrlLoadingForTesting(
363 const GURL& previous_url,
364 const GURL& current_url) {
365 return ShouldOverrideUrlLoading(previous_url, current_url);
366 }
367
368 // static
369 bool ArcNavigationThrottle::IsAppAvailableForTesting(
370 const mojo::Array<mojom::IntentHandlerInfoPtr>& handlers) {
371 return IsAppAvailable(handlers);
372 }
373
374 // static
375 size_t ArcNavigationThrottle::FindPreferredAppForTesting(
376 const mojo::Array<mojom::IntentHandlerInfoPtr>& handlers) {
377 return FindPreferredApp(handlers, GURL());
378 }
379
380 // static
381 bool ArcNavigationThrottle::IsSwapElementsNeededForTesting(
382 const mojo::Array<mojom::IntentHandlerInfoPtr>& handlers,
383 std::pair<size_t, size_t>* out_indices) {
384 return IsSwapElementsNeeded(handlers, out_indices);
385 }
386
387 } // namespace arc
OLDNEW
« no previous file with comments | « chrome/browser/chromeos/arc/arc_navigation_throttle.h ('k') | chrome/browser/chromeos/arc/arc_navigation_throttle_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698