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

Side by Side Diff: chrome/browser/ui/intents/web_intent_picker_controller.cc

Issue 12225076: Delete most web intents code. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebase Created 7 years, 10 months 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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/ui/intents/web_intent_picker_controller.h"
6
7 #include <vector>
8
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/md5.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/time.h"
14 #include "base/utf_string_conversions.h"
15 #include "chrome/browser/download/download_item_model.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/extension_system.h"
18 #include "chrome/browser/extensions/platform_app_launcher.h"
19 #include "chrome/browser/extensions/webstore_installer.h"
20 #include "chrome/browser/intents/cws_intents_registry_factory.h"
21 #include "chrome/browser/intents/default_web_intent_service.h"
22 #include "chrome/browser/intents/intent_service_host.h"
23 #include "chrome/browser/intents/native_services.h"
24 #include "chrome/browser/intents/web_intents_registry_factory.h"
25 #include "chrome/browser/intents/web_intents_reporting.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/tab_contents/tab_util.h"
28 #include "chrome/browser/ui/browser.h"
29 #include "chrome/browser/ui/browser_finder.h"
30 #include "chrome/browser/ui/browser_list.h"
31 #include "chrome/browser/ui/browser_navigator.h"
32 #include "chrome/browser/ui/browser_tabstrip.h"
33 #include "chrome/browser/ui/browser_window.h"
34 #include "chrome/browser/ui/intents/web_intent_icon_loader.h"
35 #include "chrome/browser/ui/intents/web_intent_picker.h"
36 #include "chrome/browser/ui/intents/web_intent_picker_model.h"
37 #include "chrome/browser/ui/tabs/tab_strip_model.h"
38 #include "chrome/browser/ui/web_contents_modal_dialog_manager.h"
39 #include "chrome/browser/webdata/web_data_service.h"
40 #include "chrome/common/url_constants.h"
41 #include "content/public/browser/browser_thread.h"
42 #include "content/public/browser/download_manager.h"
43 #include "content/public/browser/web_contents.h"
44 #include "content/public/browser/web_contents_observer.h"
45 #include "content/public/browser/web_intents_dispatcher.h"
46 #include "ui/gfx/favicon_size.h"
47 #include "ui/gfx/image/image.h"
48
49 using extensions::WebstoreInstaller;
50
51 DEFINE_WEB_CONTENTS_USER_DATA_KEY(WebIntentPickerController);
52
53 namespace {
54
55 // Maximum amount of time to delay displaying dialog while waiting for data.
56 const int kMaxHiddenSetupTimeMs = 200;
57
58 // Minimum amount of time to show waiting dialog, if it is shown.
59 const int kMinThrobberDisplayTimeMs = 800;
60
61 // Gets the web intents registry for the specified profile.
62 WebIntentsRegistry* GetWebIntentsRegistry(Profile* profile) {
63 return WebIntentsRegistryFactory::GetForProfile(profile);
64 }
65
66 // Gets the Chrome web store intents registry for the specified profile.
67 CWSIntentsRegistry* GetCWSIntentsRegistry(Profile* profile) {
68 return CWSIntentsRegistryFactory::GetForProfile(profile);
69 }
70
71 class SourceWindowObserver : content::WebContentsObserver {
72 public:
73 SourceWindowObserver(content::WebContents* web_contents,
74 base::WeakPtr<WebIntentPickerController> controller)
75 : content::WebContentsObserver(web_contents),
76 controller_(controller) {}
77 virtual ~SourceWindowObserver() {}
78
79 // Implement WebContentsObserver
80 virtual void WebContentsDestroyed(
81 content::WebContents* web_contents) OVERRIDE {
82 if (controller_)
83 controller_->SourceWebContentsDestroyed(web_contents);
84 delete this;
85 }
86
87 private:
88 base::WeakPtr<WebIntentPickerController> controller_;
89 };
90
91 // Deletes |service|, presumably called from a dispatcher callback.
92 void DeleteIntentService(
93 web_intents::IntentServiceHost* service,
94 webkit_glue::WebIntentReplyType type) {
95 delete service;
96 }
97
98 } // namespace
99
100 // UMAReporter handles reporting Web Intents events to UMA.
101 class WebIntentPickerController::UMAReporter {
102 public:
103
104 // Resets the service active duration timer to "now".
105 void ResetServiceActiveTimer();
106
107 // Records the duration of time spent using the service. Uses |reply_type|
108 // to distinguish between successful and failed service calls.
109 void RecordServiceActiveDuration(webkit_glue::WebIntentReplyType reply_type);
110
111 private:
112
113 // The time when the user began using the service.
114 base::TimeTicks service_start_time_;
115 };
116
117 void WebIntentPickerController::UMAReporter::ResetServiceActiveTimer() {
118 service_start_time_ = base::TimeTicks::Now();
119 }
120
121 void WebIntentPickerController::UMAReporter::RecordServiceActiveDuration(
122 webkit_glue::WebIntentReplyType reply_type) {
123 if (!service_start_time_.is_null()) {
124 web_intents::RecordServiceActiveDuration(reply_type,
125 base::TimeTicks::Now() - service_start_time_);
126 }
127 }
128
129 WebIntentPickerController::WebIntentPickerController(
130 content::WebContents* web_contents)
131 : dialog_state_(kPickerHidden),
132 web_contents_(web_contents),
133 profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())),
134 picker_(NULL),
135 picker_model_(new WebIntentPickerModel()),
136 uma_reporter_(new UMAReporter()),
137 pending_async_count_(0),
138 pending_registry_calls_count_(0),
139 pending_cws_request_(false),
140 picker_shown_(false),
141 window_disposition_source_(NULL),
142 source_intents_dispatcher_(NULL),
143 intents_dispatcher_(NULL),
144 location_bar_button_indicated_(true),
145 service_tab_(NULL),
146 icon_loader_(NULL),
147 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
148 ALLOW_THIS_IN_INITIALIZER_LIST(timer_factory_(this)),
149 ALLOW_THIS_IN_INITIALIZER_LIST(dispatcher_factory_(this)) {
150 native_services_.reset(new web_intents::NativeServiceFactory());
151 #if defined(TOOLKIT_VIEWS)
152 cancelled_ = true;
153 #endif
154 icon_loader_.reset(
155 new web_intents::IconLoader(profile_, picker_model_.get()));
156 }
157
158 WebIntentPickerController::~WebIntentPickerController() {
159 if (picker_)
160 picker_->InvalidateDelegate();
161 if (webstore_installer_.get())
162 webstore_installer_->InvalidateDelegate();
163 }
164
165 // TODO(gbillock): combine this with ShowDialog.
166 void WebIntentPickerController::SetIntentsDispatcher(
167 content::WebIntentsDispatcher* intents_dispatcher) {
168 // TODO(gbillock): This is to account for multiple dispatches in the same tab.
169 // That is currently not a well-handled case, and this is a band-aid.
170 dispatcher_factory_.InvalidateWeakPtrs();
171 intents_dispatcher_ = intents_dispatcher;
172 intents_dispatcher_->RegisterReplyNotification(
173 base::Bind(&WebIntentPickerController::OnSendReturnMessage,
174 dispatcher_factory_.GetWeakPtr()));
175
176 // Initialize the reporting bucket.
177 const webkit_glue::WebIntentData& intent = intents_dispatcher_->GetIntent();
178 uma_bucket_ = web_intents::ToUMABucket(intent.action, intent.type);
179 }
180
181 // TODO(smckay): rename this "StartActivity".
182 void WebIntentPickerController::ShowDialog(const string16& action,
183 const string16& type) {
184 ShowDialog(kEnableDefaults);
185 }
186
187 void WebIntentPickerController::ReshowDialog() {
188 ShowDialog(kSuppressDefaults);
189 }
190
191 void WebIntentPickerController::ShowDialog(DefaultsUsage suppress_defaults) {
192 web_intents::RecordIntentDispatched(uma_bucket_);
193
194 DCHECK(intents_dispatcher_);
195
196 #if defined(TOOLKIT_VIEWS)
197 cancelled_ = true;
198 #endif
199
200 // Only show a picker once.
201 // TODO(gbillock): There's a hole potentially admitting multiple
202 // in-flight dispatches since we don't create the picker
203 // in this method, but only after calling the registry.
204 if (picker_shown_) {
205 intents_dispatcher_->SendReply(webkit_glue::WebIntentReply(
206 webkit_glue::WEB_INTENT_REPLY_FAILURE,
207 ASCIIToUTF16("Simultaneous intent invocation.")));
208 return;
209 }
210
211 // TODO(binji): Figure out what to do when intents are invoked from incognito
212 // mode.
213 if (profile_->IsOffTheRecord()) {
214 intents_dispatcher_->SendReply(webkit_glue::WebIntentReply(
215 webkit_glue::WEB_INTENT_REPLY_FAILURE, string16()));
216 return;
217 }
218
219 picker_model_->Clear();
220 picker_model_->set_action(intents_dispatcher_->GetIntent().action);
221 picker_model_->set_type(intents_dispatcher_->GetIntent().type);
222
223 // If the intent is explicit, skip showing the picker.
224 const GURL& service = intents_dispatcher_->GetIntent().service;
225 // TODO(gbillock): Decide whether to honor the default suppression flag
226 // here or suppress the control for explicit intents.
227 if (service.is_valid() && !suppress_defaults) {
228 // Get services from the registry to verify a registered extension
229 // page for this action/type if it is permitted to be dispatched. (Also
230 // required to find disposition set by service.)
231 pending_async_count_++;
232 GetWebIntentsRegistry(profile_)->GetIntentServices(
233 picker_model_->action(), picker_model_->type(),
234 base::Bind(
235 &WebIntentPickerController::
236 OnWebIntentServicesAvailableForExplicitIntent,
237 weak_ptr_factory_.GetWeakPtr()));
238 return;
239 }
240
241 // As soon as the dialog is requested, block all input events
242 // on the original tab.
243 WebContentsModalDialogManager* web_contents_modal_dialog_manager =
244 WebContentsModalDialogManager::FromWebContents(web_contents_);
245 web_contents_modal_dialog_manager->BlockWebContentsInteraction(true);
246 SetDialogState(kPickerSetup);
247
248 pending_async_count_++;
249 pending_registry_calls_count_++;
250 GetWebIntentsRegistry(profile_)->GetIntentServices(
251 picker_model_->action(), picker_model_->type(),
252 base::Bind(&WebIntentPickerController::OnWebIntentServicesAvailable,
253 weak_ptr_factory_.GetWeakPtr()));
254
255 GURL invoking_url = web_contents_->GetURL();
256 if (invoking_url.is_valid() && !suppress_defaults) {
257 pending_async_count_++;
258 pending_registry_calls_count_++;
259 GetWebIntentsRegistry(profile_)->GetDefaultIntentService(
260 picker_model_->action(), picker_model_->type(), invoking_url,
261 base::Bind(&WebIntentPickerController::OnWebIntentDefaultsAvailable,
262 weak_ptr_factory_.GetWeakPtr()));
263 }
264
265 pending_cws_request_ = true;
266 pending_async_count_++;
267 GetCWSIntentsRegistry(profile_)->GetIntentServices(
268 picker_model_->action(), picker_model_->type(),
269 base::Bind(&WebIntentPickerController::OnCWSIntentServicesAvailable,
270 weak_ptr_factory_.GetWeakPtr()));
271 }
272
273 void WebIntentPickerController::OnServiceChosen(
274 const GURL& url,
275 webkit_glue::WebIntentServiceData::Disposition disposition,
276 DefaultsUsage suppress_defaults) {
277 web_intents::RecordServiceInvoke(uma_bucket_);
278 uma_reporter_->ResetServiceActiveTimer();
279 ExtensionService* extension_service =
280 extensions::ExtensionSystem::Get(profile_)->extension_service();
281 DCHECK(extension_service);
282
283 #if defined(TOOLKIT_VIEWS)
284 cancelled_ = false;
285 #endif
286
287 // Set the default here. Activating the intent resets the picker model.
288 // TODO(gbillock): we should perhaps couple the model to the dispatcher so
289 // we can re-activate the model on use-another-service.
290 if (!suppress_defaults)
291 SetDefaultServiceForSelection(url);
292
293
294 // TODO(smckay): this basically smells like another disposition.
295 const extensions::Extension* extension =
296 extension_service->GetInstalledApp(url);
297 if (extension && extension->is_platform_app()) {
298 extensions::LaunchPlatformAppWithWebIntent(profile_,
299 extension, intents_dispatcher_, web_contents_);
300 ClosePicker();
301 return;
302 }
303
304 // TODO(smckay): This entire method shold basically be pulled out
305 // into a separate class dedicated to the execution of intents.
306 // The tricky part is with the "INLINE" disposition where we
307 // want to (re)use the picker to handle the intent. A bit of
308 // artful composition + lazy instantiation should make that possible.
309 switch (disposition) {
310 case webkit_glue::WebIntentServiceData::DISPOSITION_NATIVE: {
311 web_intents::IntentServiceHost* service =
312 native_services_->CreateServiceInstance(
313 url, intents_dispatcher_->GetIntent(), web_contents_);
314 DCHECK(service);
315
316 intents_dispatcher_->RegisterReplyNotification(
317 base::Bind(&DeleteIntentService, base::Unretained(service)));
318
319 service->HandleIntent(intents_dispatcher_);
320 break;
321 }
322
323 case webkit_glue::WebIntentServiceData::DISPOSITION_INLINE: {
324 // Set the model to inline disposition. It will notify the picker which
325 // will respond (via OnInlineDispositionWebContentsCreated) with the
326 // WebContents to dispatch the intent to.
327 picker_model_->SetInlineDisposition(url);
328 break;
329 }
330
331 case webkit_glue::WebIntentServiceData::DISPOSITION_WINDOW: {
332 content::WebContents* contents = content::WebContents::Create(
333 content::WebContents::CreateParams(
334 profile_, tab_util::GetSiteInstanceForNewTab(profile_, url)));
335 WebIntentPickerController::CreateForWebContents(contents);
336
337 // Let the controller for the target WebContents know that it is hosting a
338 // web intents service. Suppress if we're not showing the
339 // use-another-service button.
340 if (picker_model_->show_use_another_service()) {
341 WebIntentPickerController::FromWebContents(contents)->
342 SetWindowDispositionSource(web_contents_, intents_dispatcher_);
343 }
344
345 intents_dispatcher_->DispatchIntent(contents);
346 service_tab_ = contents;
347
348 // This call performs all the tab strip manipulation, notifications, etc.
349 // Since we're passing in a target_contents, it assumes that we will
350 // navigate the page ourselves, though.
351 chrome::NavigateParams params(profile_, url,
352 content::PAGE_TRANSITION_LINK);
353 params.target_contents = contents;
354 params.disposition = NEW_FOREGROUND_TAB;
355 params.tabstrip_add_types = TabStripModel::ADD_INHERIT_GROUP;
356 chrome::Navigate(&params);
357
358 service_tab_->GetController().LoadURL(
359 url, content::Referrer(),
360 content::PAGE_TRANSITION_AUTO_BOOKMARK, std::string());
361
362 ClosePicker();
363 break;
364 }
365
366 default:
367 NOTREACHED();
368 break;
369 }
370 }
371
372 content::WebContents*
373 WebIntentPickerController::CreateWebContentsForInlineDisposition(
374 Profile* profile, const GURL& url) {
375 content::WebContents::CreateParams create_params(
376 profile, tab_util::GetSiteInstanceForNewTab(profile, url));
377 content::WebContents* web_contents = content::WebContents::Create(
378 create_params);
379 intents_dispatcher_->DispatchIntent(web_contents);
380 return web_contents;
381 }
382
383 void WebIntentPickerController::SetDefaultServiceForSelection(const GURL& url) {
384 DCHECK(picker_model_.get());
385 if (url == picker_model_->default_service_url())
386 return;
387
388 DefaultWebIntentService record;
389 record.action = picker_model_->action();
390 record.type = picker_model_->type();
391 record.service_url = url.spec();
392 record.user_date = static_cast<int>(floor(base::Time::Now().ToDoubleT()));
393 GetWebIntentsRegistry(profile_)->RegisterDefaultIntentService(record);
394 }
395
396 void WebIntentPickerController::OnExtensionInstallRequested(
397 const std::string& id) {
398 // Create a local copy of |id| since it is a reference to a member on a UI
399 // object, and SetPendingExtensionInstallId triggers an OnModelChanged and
400 // a subsequent rebuild of UI objects.
401 std::string extension_id(id);
402
403 picker_model_->SetPendingExtensionInstallId(extension_id);
404
405 scoped_ptr<WebstoreInstaller::Approval> approval(
406 WebstoreInstaller::Approval::CreateWithInstallPrompt(profile_));
407 // Don't show a bubble pointing to the extension or any other post
408 // installation UI.
409 approval->skip_post_install_ui = true;
410 approval->show_dialog_callback = base::Bind(
411 &WebIntentPickerController::OnShowExtensionInstallDialog,
412 weak_ptr_factory_.GetWeakPtr());
413
414 webstore_installer_ = new WebstoreInstaller(profile_, this,
415 &web_contents_->GetController(), extension_id,
416 approval.Pass(), WebstoreInstaller::FLAG_INLINE_INSTALL);
417
418 pending_async_count_++;
419 webstore_installer_->Start();
420 }
421
422 void WebIntentPickerController::OnExtensionLinkClicked(
423 const std::string& id,
424 WindowOpenDisposition disposition) {
425 // Navigate from source tab.
426 GURL extension_url(extension_urls::GetWebstoreItemDetailURLPrefix() + id);
427 chrome::NavigateParams params(profile_, extension_url,
428 content::PAGE_TRANSITION_LINK);
429 params.disposition =
430 (disposition == CURRENT_TAB) ? NEW_FOREGROUND_TAB : disposition;
431 chrome::Navigate(&params);
432 }
433
434 void WebIntentPickerController::OnSuggestionsLinkClicked(
435 WindowOpenDisposition disposition) {
436 // Navigate from source tab.
437 GURL query_url = extension_urls::GetWebstoreIntentQueryURL(
438 UTF16ToUTF8(picker_model_->action()),
439 UTF16ToUTF8(picker_model_->type()));
440 chrome::NavigateParams params(profile_, query_url,
441 content::PAGE_TRANSITION_LINK);
442 params.disposition =
443 (disposition == CURRENT_TAB) ? NEW_FOREGROUND_TAB : disposition;
444 chrome::Navigate(&params);
445 }
446
447 void WebIntentPickerController::OnUserCancelledPickerDialog() {
448 if (!intents_dispatcher_)
449 return;
450
451 intents_dispatcher_->SendReply(webkit_glue::WebIntentReply(
452 webkit_glue::WEB_INTENT_PICKER_CANCELLED, string16()));
453 web_intents::RecordPickerCancel(uma_bucket_);
454
455 ClosePicker();
456 }
457
458 void WebIntentPickerController::OnChooseAnotherService() {
459 DCHECK(intents_dispatcher_);
460 web_intents::RecordChooseAnotherService(uma_bucket_);
461 intents_dispatcher_->ResetDispatch();
462 picker_model_->SetInlineDisposition(GURL::EmptyGURL());
463 }
464
465 void WebIntentPickerController::OnClosing() {
466 SetDialogState(kPickerHidden);
467 picker_ = NULL;
468 picker_model_->ClearPendingExtensionInstall();
469 CancelDownload();
470 #if defined(TOOLKIT_VIEWS)
471 if (cancelled_)
472 OnUserCancelledPickerDialog();
473 #endif
474 }
475
476 void WebIntentPickerController::OnExtensionDownloadStarted(
477 const std::string& id,
478 content::DownloadItem* item) {
479 DownloadItemModel(item).SetShouldShowInShelf(false);
480 download_id_ = item->GetGlobalId();
481 picker_model_->UpdateExtensionDownloadState(item);
482 }
483
484 void WebIntentPickerController::OnExtensionDownloadProgress(
485 const std::string& id,
486 content::DownloadItem* item) {
487 picker_model_->UpdateExtensionDownloadState(item);
488 }
489
490 void WebIntentPickerController::OnExtensionInstallSuccess(
491 const std::string& extension_id) {
492 webstore_installer_ = NULL; // Release reference.
493
494 // OnExtensionInstallSuccess is called via NotificationService::Notify before
495 // the extension is added to the ExtensionService. Dispatch via PostTask to
496 // allow ExtensionService to update.
497 MessageLoop::current()->PostTask(
498 FROM_HERE,
499 base::Bind(
500 &WebIntentPickerController::DispatchToInstalledExtension,
501 base::Unretained(this),
502 extension_id));
503 }
504
505 void WebIntentPickerController::DispatchToInstalledExtension(
506 const std::string& extension_id) {
507 web_intents::RecordCWSExtensionInstalled(uma_bucket_);
508
509 download_id_ = content::DownloadId();
510 picker_model_->ClearPendingExtensionInstall();
511 if (picker_)
512 picker_->OnExtensionInstallSuccess(extension_id);
513
514 WebIntentsRegistry::IntentServiceList services;
515 GetWebIntentsRegistry(profile_)->GetIntentServicesForExtensionFilter(
516 picker_model_->action(), picker_model_->type(),
517 extension_id,
518 &services);
519
520 // Extension must be registered with registry by now.
521 DCHECK(services.size() > 0);
522
523 // TODO(binji): We're going to need to disambiguate if there are multiple
524 // services. For now, just choose the first.
525 const webkit_glue::WebIntentServiceData& service_data = services[0];
526
527 picker_model_->RemoveSuggestedExtension(extension_id);
528 AddServiceToModel(service_data);
529 OnServiceChosen(service_data.service_url, service_data.disposition,
530 kEnableDefaults);
531 AsyncOperationFinished();
532 }
533
534 void WebIntentPickerController::OnExtensionInstallFailure(
535 const std::string& id,
536 const std::string& error,
537 WebstoreInstaller::FailureReason reason) {
538 webstore_installer_ = NULL; // Release reference.
539
540 // If the user cancelled the install then don't show an error message.
541 if (reason == WebstoreInstaller::FAILURE_REASON_CANCELLED)
542 picker_model_->ClearPendingExtensionInstall();
543 else
544 picker_model_->SetPendingExtensionInstallStatusString(UTF8ToUTF16(error));
545
546 if (picker_)
547 picker_->OnExtensionInstallFailure(id);
548 AsyncOperationFinished();
549 }
550
551 void WebIntentPickerController::OnSendReturnMessage(
552 webkit_glue::WebIntentReplyType reply_type) {
553 ClosePicker();
554 uma_reporter_->RecordServiceActiveDuration(reply_type);
555
556 if (service_tab_ &&
557 reply_type != webkit_glue::WEB_INTENT_SERVICE_CONTENTS_CLOSED) {
558 Browser* browser = chrome::FindBrowserWithWebContents(service_tab_);
559 if (browser) {
560 int index = browser->tab_strip_model()->GetIndexOfWebContents(
561 service_tab_);
562 browser->tab_strip_model()->CloseWebContentsAt(
563 index, TabStripModel::CLOSE_CREATE_HISTORICAL_TAB);
564
565 // Activate source tab.
566 Browser* source_browser =
567 chrome::FindBrowserWithWebContents(web_contents_);
568 if (source_browser) {
569 int source_index = source_browser->tab_strip_model()->
570 GetIndexOfWebContents(web_contents_);
571 source_browser->tab_strip_model()->ActivateTabAt(source_index, false);
572 }
573 }
574 service_tab_ = NULL;
575 }
576
577 intents_dispatcher_ = NULL;
578 }
579
580 void WebIntentPickerController::AddServiceToModel(
581 const webkit_glue::WebIntentServiceData& service) {
582
583 picker_model_->AddInstalledService(
584 service.title,
585 service.service_url,
586 service.disposition);
587
588 icon_loader_->LoadFavicon(service.service_url);
589 }
590
591 void WebIntentPickerController::OnWebIntentServicesAvailable(
592 const std::vector<webkit_glue::WebIntentServiceData>& services) {
593 for (size_t i = 0; i < services.size(); ++i)
594 AddServiceToModel(services[i]);
595
596 RegistryCallsCompleted();
597 AsyncOperationFinished();
598 }
599
600 void WebIntentPickerController::OnWebIntentServicesAvailableForExplicitIntent(
601 const std::vector<webkit_glue::WebIntentServiceData>& services) {
602 DCHECK(intents_dispatcher_);
603 DCHECK(intents_dispatcher_->GetIntent().service.is_valid());
604 for (size_t i = 0; i < services.size(); ++i) {
605 if (services[i].service_url != intents_dispatcher_->GetIntent().service)
606 continue;
607
608 InvokeServiceWithSelection(services[i]);
609 AsyncOperationFinished();
610 return;
611 }
612
613 // No acceptable extension. The intent cannot be dispatched.
614 intents_dispatcher_->SendReply(webkit_glue::WebIntentReply(
615 webkit_glue::WEB_INTENT_REPLY_FAILURE, ASCIIToUTF16(
616 "Explicit extension URL is not available.")));
617
618 AsyncOperationFinished();
619 }
620
621 void WebIntentPickerController::OnWebIntentDefaultsAvailable(
622 const DefaultWebIntentService& default_service) {
623 if (!default_service.service_url.empty()) {
624 // TODO(gbillock): this doesn't belong in the model, but keep it there
625 // for now.
626 picker_model_->set_default_service_url(GURL(default_service.service_url));
627 }
628
629 RegistryCallsCompleted();
630 AsyncOperationFinished();
631 }
632
633 void WebIntentPickerController::RegistryCallsCompleted() {
634 pending_registry_calls_count_--;
635 if (pending_registry_calls_count_ != 0) return;
636
637 if (picker_model_->default_service_url().is_valid()) {
638 // If there's a default service, dispatch to it immediately
639 // without showing the picker.
640 const WebIntentPickerModel::InstalledService* default_service =
641 picker_model_->GetInstalledServiceWithURL(
642 GURL(picker_model_->default_service_url()));
643 if (default_service != NULL) {
644 InvokeService(*default_service);
645 return;
646 }
647 }
648
649 OnPickerEvent(kPickerEventRegistryDataComplete);
650 OnIntentDataArrived();
651 }
652
653 void WebIntentPickerController::OnCWSIntentServicesAvailable(
654 const CWSIntentsRegistry::IntentExtensionList& extensions) {
655 ExtensionServiceInterface* extension_service =
656 extensions::ExtensionSystem::Get(profile_)->extension_service();
657
658 std::vector<WebIntentPickerModel::SuggestedExtension> suggestions;
659 for (size_t i = 0; i < extensions.size(); ++i) {
660 const CWSIntentsRegistry::IntentExtensionInfo& info = extensions[i];
661
662 // Do not include suggestions for already installed extensions.
663 if (extension_service->GetExtensionById(info.id, true))
664 continue;
665
666 suggestions.push_back(WebIntentPickerModel::SuggestedExtension(
667 info.name, info.id, info.average_rating));
668
669 icon_loader_->LoadExtensionIcon(info.icon_url, info.id);
670 }
671
672 picker_model_->AddSuggestedExtensions(suggestions);
673
674 AsyncOperationFinished();
675 pending_cws_request_ = false;
676 OnIntentDataArrived();
677 }
678
679
680 void WebIntentPickerController::OnIntentDataArrived() {
681 DCHECK(picker_model_.get());
682
683 if (!pending_cws_request_ &&
684 pending_registry_calls_count_ == 0)
685 OnPickerEvent(kPickerEventAsyncDataComplete);
686 }
687
688 void WebIntentPickerController::Reset() {
689 // Abandon all callbacks.
690 weak_ptr_factory_.InvalidateWeakPtrs();
691 timer_factory_.InvalidateWeakPtrs();
692
693 // Reset state associated with callbacks.
694 pending_async_count_ = 0;
695 pending_registry_calls_count_ = 0;
696 pending_cws_request_ = false;
697
698 // Reset picker.
699 icon_loader_.reset();
700 picker_model_.reset(new WebIntentPickerModel());
701 icon_loader_.reset(
702 new web_intents::IconLoader(profile_, picker_model_.get()));
703
704 picker_shown_ = false;
705
706 DCHECK(web_contents_);
707 WebContentsModalDialogManager* web_contents_modal_dialog_manager =
708 WebContentsModalDialogManager::FromWebContents(web_contents_);
709 web_contents_modal_dialog_manager->BlockWebContentsInteraction(false);
710 }
711
712 void WebIntentPickerController::OnShowExtensionInstallDialog(
713 const ExtensionInstallPrompt::ShowParams& show_params,
714 ExtensionInstallPrompt::Delegate* delegate,
715 const ExtensionInstallPrompt::Prompt& prompt) {
716 picker_model_->SetPendingExtensionInstallDelegate(delegate);
717 picker_model_->SetPendingExtensionInstallPrompt(prompt);
718 if (picker_)
719 picker_->OnShowExtensionInstallDialog(show_params, delegate, prompt);
720 }
721
722 void WebIntentPickerController::SetWindowDispositionSource(
723 content::WebContents* source,
724 content::WebIntentsDispatcher* dispatcher) {
725 DCHECK(source);
726 DCHECK(dispatcher);
727 location_bar_button_indicated_ = false;
728 window_disposition_source_ = source;
729 if (window_disposition_source_) {
730 // This object is self-deleting when the source WebContents is destroyed.
731 new SourceWindowObserver(window_disposition_source_,
732 weak_ptr_factory_.GetWeakPtr());
733 }
734
735 if (dispatcher) {
736 dispatcher->RegisterReplyNotification(
737 base::Bind(&WebIntentPickerController::SourceDispatcherReplied,
738 weak_ptr_factory_.GetWeakPtr()));
739 }
740 source_intents_dispatcher_ = dispatcher;
741 }
742
743 void WebIntentPickerController::SourceWebContentsDestroyed(
744 content::WebContents* source) {
745 window_disposition_source_ = NULL;
746 // TODO(gbillock): redraw location bar to kill button
747 }
748
749 void WebIntentPickerController::SourceDispatcherReplied(
750 webkit_glue::WebIntentReplyType reply_type) {
751 source_intents_dispatcher_ = NULL;
752 // TODO(gbillock): redraw location bar to kill button
753 }
754
755 bool WebIntentPickerController::ShowLocationBarPickerButton() {
756 return window_disposition_source_ || source_intents_dispatcher_;
757 }
758
759 void WebIntentPickerController::OnPickerEvent(WebIntentPickerEvent event) {
760 switch (event) {
761 case kPickerEventHiddenSetupTimeout:
762 DCHECK(dialog_state_ == kPickerSetup);
763 SetDialogState(kPickerWaiting);
764 break;
765
766 case kPickerEventMaxWaitTimeExceeded:
767 DCHECK(dialog_state_ == kPickerWaiting);
768
769 // If registry data is complete, go to main dialog. Otherwise, wait.
770 if (pending_registry_calls_count_ == 0)
771 SetDialogState(kPickerMain);
772 else
773 SetDialogState(kPickerWaitLong);
774 break;
775
776 case kPickerEventRegistryDataComplete:
777 DCHECK(dialog_state_ == kPickerSetup ||
778 dialog_state_ == kPickerWaiting ||
779 dialog_state_ == kPickerWaitLong);
780
781 // If minimum wait dialog time is exceeded, display main dialog.
782 // Either way, we don't do a thing.
783 break;
784
785 case kPickerEventAsyncDataComplete:
786 DCHECK(dialog_state_ == kPickerSetup ||
787 dialog_state_ == kPickerWaiting ||
788 dialog_state_ == kPickerWaitLong ||
789 dialog_state_ == kPickerInline);
790
791 // In setup state, transition to main dialog. In waiting state, let
792 // timer expire.
793 if (dialog_state_ == kPickerSetup)
794 SetDialogState(kPickerMain);
795 break;
796
797 default:
798 NOTREACHED();
799 break;
800 }
801 }
802
803 void WebIntentPickerController::LocationBarPickerButtonClicked() {
804 DCHECK(web_contents_);
805 if (window_disposition_source_ && source_intents_dispatcher_) {
806 Browser* service_browser =
807 chrome::FindBrowserWithWebContents(web_contents_);
808 if (!service_browser) return;
809
810 Browser* client_browser =
811 chrome::FindBrowserWithWebContents(window_disposition_source_);
812 if (!client_browser)
813 return;
814 int client_index = client_browser->tab_strip_model()->GetIndexOfWebContents(
815 window_disposition_source_);
816 DCHECK(client_index != TabStripModel::kNoTab);
817
818 source_intents_dispatcher_->ResetDispatch();
819
820 WebIntentPickerController* client_controller =
821 WebIntentPickerController::FromWebContents(window_disposition_source_);
822 DCHECK(client_controller);
823
824 // This call deletes this object, so anything below here needs to
825 // use stack variables.
826 chrome::CloseWebContents(service_browser, web_contents_, true);
827
828 // Re-open the other tab and activate the picker.
829 client_browser->window()->Activate();
830 client_browser->tab_strip_model()->ActivateTabAt(client_index, true);
831 // The picker has been Reset() when the new tab is created; need to fully
832 // reload.
833 client_controller->ReshowDialog();
834 }
835 // TODO(gbillock): figure out what we ought to do in this case. Probably
836 // nothing? Refresh the location bar?
837 }
838
839 void WebIntentPickerController::AsyncOperationFinished() {
840 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
841 if (--pending_async_count_ == 0) {
842 if (picker_)
843 picker_->OnPendingAsyncCompleted();
844 }
845 }
846
847 void WebIntentPickerController::InvokeServiceWithSelection(
848 const webkit_glue::WebIntentServiceData& service) {
849 if (picker_shown_) {
850 intents_dispatcher_->SendReply(webkit_glue::WebIntentReply(
851 webkit_glue::WEB_INTENT_REPLY_FAILURE,
852 ASCIIToUTF16("Simultaneous intent invocation.")));
853 return;
854 }
855
856 // TODO(gbillock): this is a bit hacky and exists because in the inline case,
857 // the picker currently assumes it exists.
858 AddServiceToModel(service);
859 picker_model_->set_show_use_another_service(false);
860
861 if (service.disposition ==
862 webkit_glue::WebIntentServiceData::DISPOSITION_INLINE) {
863 picker_model_->SetInlineDisposition(service.service_url);
864 SetDialogState(kPickerInline);
865 return;
866 }
867
868 OnServiceChosen(service.service_url, service.disposition, kSuppressDefaults);
869 }
870
871 void WebIntentPickerController::InvokeService(
872 const WebIntentPickerModel::InstalledService& service) {
873 if (service.disposition ==
874 webkit_glue::WebIntentServiceData::DISPOSITION_INLINE) {
875 // This call will ensure the picker dialog is created and initialized.
876 picker_model_->SetInlineDisposition(service.url);
877 SetDialogState(kPickerInline);
878 return;
879 }
880 OnServiceChosen(service.url, service.disposition, kEnableDefaults);
881 }
882
883 void WebIntentPickerController::SetDialogState(WebIntentPickerState state) {
884 // Ignore events that don't change state.
885 if (state == dialog_state_)
886 return;
887
888 // Any pending timers are abandoned on state changes.
889 timer_factory_.InvalidateWeakPtrs();
890
891 switch (state) {
892 case kPickerSetup:
893 DCHECK_EQ(dialog_state_, kPickerHidden);
894 // Post timer CWS pending
895 MessageLoop::current()->PostDelayedTask(FROM_HERE,
896 base::Bind(&WebIntentPickerController::OnPickerEvent,
897 timer_factory_.GetWeakPtr(),
898 kPickerEventHiddenSetupTimeout),
899 base::TimeDelta::FromMilliseconds(kMaxHiddenSetupTimeMs));
900 break;
901
902 case kPickerWaiting:
903 DCHECK_EQ(dialog_state_, kPickerSetup);
904 // Waiting dialog can be dismissed after minimum wait time.
905 MessageLoop::current()->PostDelayedTask(FROM_HERE,
906 base::Bind(&WebIntentPickerController::OnPickerEvent,
907 timer_factory_.GetWeakPtr(),
908 kPickerEventMaxWaitTimeExceeded),
909 base::TimeDelta::FromMilliseconds(kMinThrobberDisplayTimeMs));
910 break;
911
912 case kPickerWaitLong:
913 DCHECK_EQ(dialog_state_, kPickerWaiting);
914 break;
915
916 case kPickerInline:
917 // Intentional fall-through.
918 case kPickerMain:
919 // No DCHECK - main state can be reached from any state.
920 // Ready to display data.
921 picker_model_->SetWaitingForSuggestions(false);
922 break;
923
924 case kPickerHidden:
925 Reset();
926 break;
927
928 default:
929 NOTREACHED();
930 break;
931
932 }
933
934 dialog_state_ = state;
935
936 // Create picker dialog when changing away from hidden state.
937 if (dialog_state_ != kPickerHidden && dialog_state_ != kPickerSetup)
938 CreatePicker();
939 }
940
941 void WebIntentPickerController::CreatePicker() {
942 // If picker is non-NULL, it was set by a test.
943 if (picker_ == NULL)
944 picker_ = WebIntentPicker::Create(web_contents_, this, picker_model_.get());
945 picker_->SetActionString(WebIntentPicker::GetDisplayStringForIntentAction(
946 picker_model_->action()));
947 web_intents::RecordPickerShow(
948 uma_bucket_, picker_model_->GetInstalledServiceCount());
949 picker_shown_ = true;
950 }
951
952 void WebIntentPickerController::ClosePicker() {
953 SetDialogState(kPickerHidden);
954 if (picker_)
955 picker_->Close();
956 }
957
958 void WebIntentPickerController::CancelDownload() {
959 if (!download_id_.IsValid())
960 return;
961 Profile* profile =
962 Profile::FromBrowserContext(web_contents_->GetBrowserContext());
963 content::DownloadManager* download_manager =
964 content::BrowserContext::GetDownloadManager(profile);
965 if (!download_manager)
966 return;
967 content::DownloadItem* item =
968 download_manager->GetDownload(download_id_.local());
969 if (item)
970 item->Cancel(true);
971 download_id_ = content::DownloadId();
972 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698