diff options
author | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-24 04:58:52 +0000 |
---|---|---|
committer | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-24 04:58:52 +0000 |
commit | 625d9f8fc428a36e29e6868d3a9bc9f33245d810 (patch) | |
tree | 625bd090aa1b5684ff258ea2799c5b6afb1e1bbe | |
parent | f8116b6fd136c5c86eaaede373b0363e77e08646 (diff) | |
download | chromium_src-625d9f8fc428a36e29e6868d3a9bc9f33245d810.zip chromium_src-625d9f8fc428a36e29e6868d3a9bc9f33245d810.tar.gz chromium_src-625d9f8fc428a36e29e6868d3a9bc9f33245d810.tar.bz2 |
Web Intent Picker UI (implemented as a constrained dialog, linux only)
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/7648061
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@97990 0039d316-1c4b-4281-b951-d872f2087c98
17 files changed, 1087 insertions, 0 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index fc11849..6b8b0a5 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -13375,6 +13375,9 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_REGISTER_INTENT_HANDLER_DENY" desc="Text to show for the deny button for the register intent handler request infobar."> No </message> + <message name="IDS_CHOOSE_INTENT_HANDLER_MESSAGE" desc="Text to display when user is choosing a intent handler to respond to a web intent."> + Complete action using: + </message> <!-- PDF with unsupported feature Info Bar --> <message name="IDS_PDF_INFOBAR_QUESTION_READER_INSTALLED" desc="Question asked on the info bar when a user views a PDF with an unsupported feature and they have Adobe Reader installed."> diff --git a/chrome/browser/ui/cocoa/web_intent_picker_cocoa.mm b/chrome/browser/ui/cocoa/web_intent_picker_cocoa.mm new file mode 100644 index 0000000..fe96858 --- /dev/null +++ b/chrome/browser/ui/cocoa/web_intent_picker_cocoa.mm @@ -0,0 +1,14 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/intents/web_intent_picker.h" +#include "chrome/browser/ui/intents/web_intent_picker_delegate.h" +#include "content/browser/tab_contents/tab_contents.h" + +// static +WebIntentPicker* WebIntentPicker::Create(TabContents* tab_contents, + WebIntentPickerDelegate* delegate) { + // TODO(binji) Implement. See http://crbug.com/93915. + return NULL; +} diff --git a/chrome/browser/ui/gtk/web_intent_picker_gtk.cc b/chrome/browser/ui/gtk/web_intent_picker_gtk.cc new file mode 100644 index 0000000..90c7901 --- /dev/null +++ b/chrome/browser/ui/gtk/web_intent_picker_gtk.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/gtk/web_intent_picker_gtk.h" + +#include "chrome/browser/favicon/favicon_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/gtk/custom_button.h" +#include "chrome/browser/ui/gtk/gtk_theme_service.h" +#include "chrome/browser/ui/gtk/gtk_util.h" +#include "chrome/browser/ui/intents/web_intent_picker_controller.h" +#include "chrome/browser/ui/intents/web_intent_picker_delegate.h" +#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/common/content_notification_types.h" +#include "googleurl/src/gurl.h" +#include "grit/generated_resources.h" +#include "grit/ui_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/gtk_util.h" +#include "ui/gfx/image/image.h" + +namespace { + +GtkThemeService *GetThemeService(TabContents* tab_contents) { + Profile* profile = Profile::FromBrowserContext( + tab_contents->browser_context()); + return GtkThemeService::GetFrom(profile); +} + +} // namespace + +// static +WebIntentPicker* WebIntentPicker::Create(TabContents* tab_contents, + WebIntentPickerDelegate* delegate) { + return new WebIntentPickerGtk(tab_contents, delegate); +} + +WebIntentPickerGtk::WebIntentPickerGtk(TabContents* tab_contents, + WebIntentPickerDelegate* delegate) + : tab_contents_(tab_contents), + delegate_(delegate), + window_(NULL) { + DCHECK(delegate_ != NULL); + root_.Own(gtk_vbox_new(FALSE, gtk_util::kContentAreaBorder)); + GtkWidget* hbox = gtk_hbox_new(FALSE, 0); + GtkWidget* label = gtk_label_new( + l10n_util::GetStringUTF8(IDS_CHOOSE_INTENT_HANDLER_MESSAGE).c_str()); + gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); + + close_button_.reset( + CustomDrawButton::CloseButton(GetThemeService(tab_contents_))); + g_signal_connect(close_button_->widget(), + "clicked", + G_CALLBACK(OnCloseButtonClickThunk), + this); + gtk_widget_set_can_focus(close_button_->widget(), FALSE); + gtk_box_pack_end(GTK_BOX(hbox), close_button_->widget(), FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(root_.get()), hbox, FALSE, FALSE, 0); + + button_hbox_ = gtk_hbox_new(FALSE, gtk_util::kContentAreaBorder); + gtk_box_pack_start(GTK_BOX(root_.get()), button_hbox_, TRUE, TRUE, 0); +} + +WebIntentPickerGtk::~WebIntentPickerGtk() { + DCHECK(window_ == NULL) << "Should have closed window before destroying."; +} + +void WebIntentPickerGtk::SetServiceURLs(const std::vector<GURL>& urls) { + for (size_t i = 0; i < urls.size(); ++i) { + GtkWidget* button = gtk_button_new(); + gtk_widget_set_tooltip_text(button, urls[i].spec().c_str()); + gtk_box_pack_start(GTK_BOX(button_hbox_), button, FALSE, FALSE, 0); + g_signal_connect(button, + "clicked", + G_CALLBACK(OnServiceButtonClickThunk), + this); + } + + gtk_widget_show_all(button_hbox_); +} + +void WebIntentPickerGtk::SetServiceIcon(size_t index, const SkBitmap& icon) { + if (icon.empty()) + return; + + GList* button_list = gtk_container_get_children(GTK_CONTAINER(button_hbox_)); + GtkButton* button = GTK_BUTTON(g_list_nth_data(button_list, index)); + DCHECK(button != NULL) << "Invalid index."; + + GdkPixbuf* icon_pixbuf = gfx::GdkPixbufFromSkBitmap(&icon); + gtk_button_set_image(button, gtk_image_new_from_pixbuf(icon_pixbuf)); + g_object_unref(icon_pixbuf); +} + +void WebIntentPickerGtk::SetDefaultServiceIcon(size_t index) { + GList* button_list = gtk_container_get_children(GTK_CONTAINER(button_hbox_)); + GtkButton* button = GTK_BUTTON(g_list_nth_data(button_list, index)); + DCHECK(button != NULL) << "Invalid index."; + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + GdkPixbuf* icon_pixbuf = rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON); + gtk_button_set_image(button, gtk_image_new_from_pixbuf(icon_pixbuf)); +} + +void WebIntentPickerGtk::Show() { + DCHECK(window_ == NULL) << "Show already called."; + window_ = tab_contents_->CreateConstrainedDialog(this); +} + +void WebIntentPickerGtk::Close() { + DCHECK(window_ != NULL) << "Show not called."; + window_->CloseConstrainedWindow(); + window_ = NULL; +} + +GtkWidget* WebIntentPickerGtk::GetWidgetRoot() { + return root_.get(); +} + +GtkWidget* WebIntentPickerGtk::GetFocusWidget() { + return root_.get(); +} + +void WebIntentPickerGtk::DeleteDelegate() { + MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +void WebIntentPickerGtk::OnCloseButtonClick(GtkWidget* button) { + delegate_->OnCancelled(); +} + +void WebIntentPickerGtk::OnServiceButtonClick(GtkWidget* button) { + GList* button_list = gtk_container_get_children(GTK_CONTAINER(button_hbox_)); + gint index = g_list_index(button_list, button); + DCHECK(index != -1); + + delegate_->OnServiceChosen(static_cast<size_t>(index)); +} diff --git a/chrome/browser/ui/gtk/web_intent_picker_gtk.h b/chrome/browser/ui/gtk/web_intent_picker_gtk.h new file mode 100644 index 0000000..b85fd80 --- /dev/null +++ b/chrome/browser/ui/gtk/web_intent_picker_gtk.h @@ -0,0 +1,76 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_GTK_WEB_INTENT_PICKER_GTK_H_ +#define CHROME_BROWSER_UI_GTK_WEB_INTENT_PICKER_GTK_H_ +#pragma once + +#include <vector> + +#include <gtk/gtk.h> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/ui/gtk/constrained_window_gtk.h" +#include "chrome/browser/ui/intents/web_intent_picker.h" +#include "ui/base/gtk/gtk_signal.h" + +class CustomDrawButton; +class GURL; +class TabContents; +class WebIntentController; +class WebIntentPickerDelegate; + +// Gtk implementation of WebIntentPicker. +class WebIntentPickerGtk : public WebIntentPicker, + public ConstrainedDialogDelegate { + public: + WebIntentPickerGtk(TabContents* tab_contents, + WebIntentPickerDelegate* delegate); + virtual ~WebIntentPickerGtk(); + + // WebIntentPicker implementation. + virtual void SetServiceURLs(const std::vector<GURL>& urls) OVERRIDE; + virtual void SetServiceIcon(size_t index, const SkBitmap& icon) OVERRIDE; + virtual void SetDefaultServiceIcon(size_t index) OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Close() OVERRIDE; + + // ConstrainedDialogDelegate implementation. + virtual GtkWidget* GetWidgetRoot() OVERRIDE; + virtual GtkWidget* GetFocusWidget() OVERRIDE; + virtual void DeleteDelegate() OVERRIDE; + + private: + // Callback when a service button is clicked. + CHROMEGTK_CALLBACK_0(WebIntentPickerGtk, void, OnServiceButtonClick); + // Callback when close button is clicked. + CHROMEGTK_CALLBACK_0(WebIntentPickerGtk, void, OnCloseButtonClick); + + // A weak pointer to the tab contents on which to display the picker UI. + TabContents* tab_contents_; + + // A weak pointer to the WebIntentPickerDelegate to notify when the user + // chooses a service or cancels. + WebIntentPickerDelegate* delegate_; + + // The root GtkWidget of the dialog. + ui::OwnedWidgetGtk root_; + + // A weak pointer to the hbox that contains the buttons used to choose the + // service. + GtkWidget* button_hbox_; + + // A button to close the dialog delegate. + scoped_ptr<CustomDrawButton> close_button_; + + // The displayed constrained window. Technically owned by the TabContents + // page, but this object tells it when to destroy. + ConstrainedWindow* window_; + + DISALLOW_COPY_AND_ASSIGN(WebIntentPickerGtk); +}; + +#endif // CHROME_BROWSER_UI_GTK_WEB_INTENT_PICKER_GTK_H_ diff --git a/chrome/browser/ui/intents/web_intent_constrained_dialog_factory.cc b/chrome/browser/ui/intents/web_intent_constrained_dialog_factory.cc new file mode 100644 index 0000000..0aa6bbb --- /dev/null +++ b/chrome/browser/ui/intents/web_intent_constrained_dialog_factory.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/intents/web_intent_constrained_dialog_factory.h" + +#include "chrome/browser/ui/intents/web_intent_picker.h" +#include "content/browser/tab_contents/constrained_window.h" +#include "content/browser/tab_contents/tab_contents.h" + +WebIntentConstrainedDialogFactory::WebIntentConstrainedDialogFactory() + : picker_(NULL) { +} + +WebIntentConstrainedDialogFactory::~WebIntentConstrainedDialogFactory() { + CloseDialog(); +} + +WebIntentPicker* WebIntentConstrainedDialogFactory::Create( + TabContents* tab_contents, + WebIntentPickerDelegate* delegate) { + // Only allow one picker per factory. + DCHECK(picker_ == NULL); + + picker_ = WebIntentPicker::Create(tab_contents, delegate); + + // TODO(binji) Remove this check when there are implementations of the picker + // for windows and mac. + if (picker_ == NULL) + return NULL; + + picker_->Show(); + + return picker_; +} + +void WebIntentConstrainedDialogFactory::ClosePicker(WebIntentPicker* picker) { + DCHECK(picker == picker_); + CloseDialog(); +} + +void WebIntentConstrainedDialogFactory::CloseDialog() { + if (picker_) { + picker_->Close(); + picker_ = NULL; + } +} diff --git a/chrome/browser/ui/intents/web_intent_constrained_dialog_factory.h b/chrome/browser/ui/intents/web_intent_constrained_dialog_factory.h new file mode 100644 index 0000000..5efe89f --- /dev/null +++ b/chrome/browser/ui/intents/web_intent_constrained_dialog_factory.h @@ -0,0 +1,40 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_INTENTS_WEB_INTENT_CONSTRAINED_DIALOG_FACTORY_H_ +#define CHROME_BROWSER_UI_INTENTS_WEB_INTENT_CONSTRAINED_DIALOG_FACTORY_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "chrome/browser/ui/intents/web_intent_picker_factory.h" + +class TabContents; +class WebIntentPickerDelegate; + +// A factory for creating a constrained dialog as the intent picker UI for a +// given TabContents. +class WebIntentConstrainedDialogFactory : public WebIntentPickerFactory { + public: + WebIntentConstrainedDialogFactory(); + virtual ~WebIntentConstrainedDialogFactory(); + + // WebIntentPickerFactory implementation. + virtual WebIntentPicker* Create( + TabContents* tab_contents, + WebIntentPickerDelegate* delegate) OVERRIDE; + virtual void ClosePicker(WebIntentPicker* picker) OVERRIDE; + + private: + // Closes the dialog and destroys it. + void CloseDialog(); + + // A weak pointer to the currently active picker, or NULL if there is no + // active picker. + WebIntentPicker* picker_; + + DISALLOW_COPY_AND_ASSIGN(WebIntentConstrainedDialogFactory); +}; + +#endif // CHROME_BROWSER_UI_INTENTS_WEB_INTENT_CONSTRAINED_DIALOG_FACTORY_H_ diff --git a/chrome/browser/ui/intents/web_intent_picker.h b/chrome/browser/ui/intents/web_intent_picker.h new file mode 100644 index 0000000..af2cb19 --- /dev/null +++ b/chrome/browser/ui/intents/web_intent_picker.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_H_ +#define CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_H_ +#pragma once + +#include <vector> + +class GURL; +class SkBitmap; +class TabContents; +class WebIntentPickerDelegate; + +// Base class for the web intent picker dialog. +class WebIntentPicker { + public: + class Delegate; + + // Platform specific factory function. + static WebIntentPicker* Create(TabContents* tab_contents, + WebIntentPickerDelegate* delegate); + + // Initalizes this picker with the |urls|. + virtual void SetServiceURLs(const std::vector<GURL>& urls) = 0; + + // Sets the icon for a service at |index|. + virtual void SetServiceIcon(size_t index, const SkBitmap& icon) = 0; + + // Sets the icon for a service at |index| to be the default favicon. + virtual void SetDefaultServiceIcon(size_t index) = 0; + + // Shows the UI for this picker. + virtual void Show() = 0; + + // Hides the UI for this picker, and destroys its UI. + virtual void Close() = 0; + + protected: + virtual ~WebIntentPicker() {} +}; + +#endif // CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_H_ diff --git a/chrome/browser/ui/intents/web_intent_picker_controller.cc b/chrome/browser/ui/intents/web_intent_picker_controller.cc new file mode 100644 index 0000000..d348e47 --- /dev/null +++ b/chrome/browser/ui/intents/web_intent_picker_controller.cc @@ -0,0 +1,264 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/intents/web_intent_picker_controller.h" + +#include <vector> + +#include "chrome/browser/favicon/favicon_service.h" +#include "chrome/browser/intents/web_intent_data.h" +#include "chrome/browser/intents/web_intents_registry.h" +#include "chrome/browser/intents/web_intents_registry_factory.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/intents/web_intent_picker.h" +#include "chrome/browser/ui/intents/web_intent_picker_factory.h" +#include "chrome/browser/webdata/web_data_service.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/common/notification_source.h" +#include "ui/gfx/codec/png_codec.h" + +namespace { + +// Gets the favicon service for the profile in |tab_contents|. +FaviconService* GetFaviconService(TabContents* tab_contents) { + Profile* profile = Profile::FromBrowserContext( + tab_contents->browser_context()); + return profile->GetFaviconService(Profile::EXPLICIT_ACCESS); +} + +// Gets the web intents registry for the profile in |tab_contents|. +WebIntentsRegistry* GetWebIntentsRegistry(TabContents* tab_contents) { + Profile* profile = Profile::FromBrowserContext( + tab_contents->browser_context()); + return WebIntentsRegistryFactory::GetForProfile(profile); +} + +} // namespace + +// A class that asynchronously fetches web intent data from the web intents +// registry. +class WebIntentPickerController::WebIntentDataFetcher + : public WebIntentsRegistry::Consumer { + public: + WebIntentDataFetcher(WebIntentPickerController* controller, + WebIntentsRegistry* web_intents_registry); + ~WebIntentDataFetcher(); + + // Asynchronously fetches WebIntentData for the given |action| |type| pair. + void Fetch(const string16& action, const string16& type); + + // Cancels the WebDataService request. + void Cancel(); + + private: + // WebIntentsRegistry::Consumer implementation. + virtual void OnIntentsQueryDone( + WebIntentsRegistry::QueryID, + const std::vector<WebIntentData>& intents) OVERRIDE; + + // A weak pointer to the picker controller. + WebIntentPickerController* controller_; + + // A weak pointer to the web intents registry. + WebIntentsRegistry* web_intents_registry_; + + // The ID of the current web intents request. The value will be -1 if + // there is no request in flight. + WebIntentsRegistry::QueryID query_id_; +}; + +// A class that asynchronously fetches favicons for a vector of URLs. +class WebIntentPickerController::FaviconFetcher { + public: + FaviconFetcher(WebIntentPickerController* controller, + FaviconService *favicon_service); + ~FaviconFetcher(); + + // Asynchronously fetches favicons for the each URL in |urls|. + void Fetch(const std::vector<GURL>& urls); + + // Cancels all favicon requests. + void Cancel(); + + private: + // Callback called when a favicon is received. + void OnFaviconDataAvailable(FaviconService::Handle handle, + history::FaviconData favicon_data); + + // A weak pointer to the picker controller. + WebIntentPickerController* controller_; + + // A weak pointer to the favicon service. + FaviconService* favicon_service_; + + // The Consumer to handle asynchronous favicon requests. + CancelableRequestConsumerTSimple<size_t> load_consumer_; + + DISALLOW_COPY_AND_ASSIGN(FaviconFetcher); +}; + +WebIntentPickerController::WebIntentPickerController( + TabContents* tab_contents, + WebIntentPickerFactory* factory) + : tab_contents_(tab_contents), + picker_factory_(factory), + web_intent_data_fetcher_( + new WebIntentDataFetcher(this, + GetWebIntentsRegistry(tab_contents))), + favicon_fetcher_( + new FaviconFetcher(this, GetFaviconService(tab_contents))), + picker_(NULL), + pending_async_count_(0) { + NavigationController* controller = &tab_contents->controller(); + registrar_.Add(this, content::NOTIFICATION_LOAD_START, + Source<NavigationController>(controller)); + registrar_.Add(this, content::NOTIFICATION_TAB_CLOSING, + Source<NavigationController>(controller)); +} + +WebIntentPickerController::~WebIntentPickerController() { +} + +void WebIntentPickerController::ShowDialog(const string16& action, + const string16& type) { + if (picker_ != NULL) + return; + + picker_ = picker_factory_->Create(tab_contents_, this); + + // TODO(binji) Remove this check when there are implementations of the picker + // for windows and mac. + if (picker_ == NULL) + return; + + web_intent_data_fetcher_->Fetch(action, type); +} + +void WebIntentPickerController::Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == content::NOTIFICATION_LOAD_START || + type == content::NOTIFICATION_TAB_CLOSING); + ClosePicker(); +} + +void WebIntentPickerController::OnServiceChosen(size_t index) { + DCHECK(index < urls_.size()); + + // TODO(binji) Send the chosen service back to the renderer. + LOG(INFO) << "Chose index: " << index << " url: " << urls_[index]; + ClosePicker(); +} + +void WebIntentPickerController::OnCancelled() { + // TODO(binji) Tell the renderer that the intent was cancelled. + ClosePicker(); +} + +void WebIntentPickerController::OnWebIntentDataAvailable( + const std::vector<WebIntentData>& intent_data) { + urls_.clear(); + for (size_t i = 0; i < intent_data.size(); ++i) { + urls_.push_back(intent_data[i].service_url); + } + + // Tell the picker to initialize N urls to the default favicon + picker_->SetServiceURLs(urls_); + favicon_fetcher_->Fetch(urls_); + pending_async_count_--; +} + +void WebIntentPickerController::OnFaviconDataAvailable( + size_t index, + const SkBitmap& icon_bitmap) { + picker_->SetServiceIcon(index, icon_bitmap); + pending_async_count_--; +} + +void WebIntentPickerController::OnFaviconDataUnavailable(size_t index) { + picker_->SetDefaultServiceIcon(index); + pending_async_count_--; +} + +void WebIntentPickerController::ClosePicker() { + if (picker_) { + picker_factory_->ClosePicker(picker_); + picker_ = NULL; + } +} + +WebIntentPickerController::WebIntentDataFetcher::WebIntentDataFetcher( + WebIntentPickerController* controller, + WebIntentsRegistry* web_intents_registry) + : controller_(controller), + web_intents_registry_(web_intents_registry), + query_id_(-1) { +} + +WebIntentPickerController::WebIntentDataFetcher::~WebIntentDataFetcher() { +} + +void WebIntentPickerController::WebIntentDataFetcher::Fetch( + const string16& action, + const string16& type) { + DCHECK(query_id_ == -1) << "Fetch already in process."; + controller_->pending_async_count_++; + query_id_ = web_intents_registry_->GetIntentProviders(action, this); +} + +void WebIntentPickerController::WebIntentDataFetcher::OnIntentsQueryDone( + WebIntentsRegistry::QueryID, + const std::vector<WebIntentData>& intents) { + controller_->OnWebIntentDataAvailable(intents); + query_id_ = -1; +} + +WebIntentPickerController::FaviconFetcher::FaviconFetcher( + WebIntentPickerController* controller, + FaviconService* favicon_service) + : controller_(controller), + favicon_service_(favicon_service) { +} + +WebIntentPickerController::FaviconFetcher::~FaviconFetcher() { +} + +void WebIntentPickerController::FaviconFetcher::Fetch( + const std::vector<GURL>& urls) { + if (!favicon_service_) + return; + + for (size_t index = 0; index < urls.size(); ++index) { + controller_->pending_async_count_++; + FaviconService::Handle handle = favicon_service_->GetFaviconForURL( + urls[index], + history::FAVICON, + &load_consumer_, + NewCallback(this, &WebIntentPickerController::FaviconFetcher:: + OnFaviconDataAvailable)); + load_consumer_.SetClientData(favicon_service_, handle, index); + } +} + +void WebIntentPickerController::FaviconFetcher::Cancel() { + load_consumer_.CancelAllRequests(); +} + +void WebIntentPickerController::FaviconFetcher::OnFaviconDataAvailable( + FaviconService::Handle handle, + history::FaviconData favicon_data) { + size_t index = load_consumer_.GetClientDataForCurrentRequest(); + if (favicon_data.is_valid()) { + SkBitmap icon_bitmap; + + if (gfx::PNGCodec::Decode(favicon_data.image_data->front(), + favicon_data.image_data->size(), + &icon_bitmap)) { + controller_->OnFaviconDataAvailable(index, icon_bitmap); + return; + } + } + + controller_->OnFaviconDataUnavailable(index); +} diff --git a/chrome/browser/ui/intents/web_intent_picker_controller.h b/chrome/browser/ui/intents/web_intent_picker_controller.h new file mode 100644 index 0000000..369878c --- /dev/null +++ b/chrome/browser/ui/intents/web_intent_picker_controller.h @@ -0,0 +1,97 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_CONTROLLER_H_ +#define CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_CONTROLLER_H_ +#pragma once + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "chrome/browser/ui/intents/web_intent_picker.h" +#include "chrome/browser/ui/intents/web_intent_picker_delegate.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" + +class FaviconService; +class GURL; +class SkBitmap; +class TabContents; +class WebDataService; +class WebIntentPickerFactory; +struct WebIntentData; + +// Controls the creation of the WebIntentPicker UI and forwards the user's +// intent handler choice back to the TabContents object. +class WebIntentPickerController : public NotificationObserver, + public WebIntentPickerDelegate { + public: + // Takes ownership of |factory|. + WebIntentPickerController(TabContents* tab_contents, + WebIntentPickerFactory* factory); + virtual ~WebIntentPickerController(); + + // Shows the web intent picker, given the intent |action| and mime-type + // |type|. + void ShowDialog(const string16& action, const string16& type); + + protected: + // NotificationObserver implementation. + virtual void Observe(int type, + const NotificationSource& source, + const NotificationDetails& details) OVERRIDE; + + // WebIntentPickerDelegate implementation. + virtual void OnServiceChosen(size_t index) OVERRIDE; + virtual void OnCancelled() OVERRIDE; + + private: + friend class WebIntentPickerControllerTest; + class WebIntentDataFetcher; + class FaviconFetcher; + + int pending_async_count() const { return pending_async_count_; } + + // Called from the WebIntentDataFetcher when intent data is available. + void OnWebIntentDataAvailable(const std::vector<WebIntentData>& intent_data); + + // Called from the FaviconDataFetcher when a favicon is available. + void OnFaviconDataAvailable(size_t index, const SkBitmap& icon_bitmap); + + // Called from the FaviconDataFetcher when a favicon is not available. + void OnFaviconDataUnavailable(size_t index); + + // Closes the currently active picker. + void ClosePicker(); + + // A weak pointer to the tab contents that the picker is displayed on. + TabContents* tab_contents_; + + // A notification registrar, listening for notifications when the tab closes + // to close the picker ui. + NotificationRegistrar registrar_; + + // A factory to create a new picker. + scoped_ptr<WebIntentPickerFactory> picker_factory_; + + // A helper class to fetch web intent data asynchronously. + scoped_ptr<WebIntentDataFetcher> web_intent_data_fetcher_; + + // A helper class to fetch favicon data asynchronously. + scoped_ptr<FaviconFetcher> favicon_fetcher_; + + // A weak pointer to the picker this controller controls. + WebIntentPicker* picker_; + + // A list of URLs to display in the UI. + std::vector<GURL> urls_; + + // A count of the outstanding asynchronous calls. + int pending_async_count_; + + DISALLOW_COPY_AND_ASSIGN(WebIntentPickerController); +}; + +#endif // CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_CONTROLLER_H_ diff --git a/chrome/browser/ui/intents/web_intent_picker_controller_unittest.cc b/chrome/browser/ui/intents/web_intent_picker_controller_unittest.cc new file mode 100644 index 0000000..89a2e14 --- /dev/null +++ b/chrome/browser/ui/intents/web_intent_picker_controller_unittest.cc @@ -0,0 +1,273 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/favicon/favicon_service.h" +#include "chrome/browser/intents/web_intent_data.h" +#include "chrome/browser/intents/web_intents_registry.h" +#include "chrome/browser/intents/web_intents_registry_factory.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/intents/web_intent_picker.h" +#include "chrome/browser/ui/intents/web_intent_picker_controller.h" +#include "chrome/browser/ui/intents/web_intent_picker_factory.h" +#include "chrome/browser/ui/tab_contents/test_tab_contents_wrapper.h" +#include "chrome/browser/webdata/web_data_service.h" +#include "chrome/test/base/testing_profile.h" +#include "content/browser/browser_thread.h" +#include "content/browser/tab_contents/constrained_window.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/browser/tab_contents/test_tab_contents.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "ui/gfx/codec/png_codec.h" + +using testing::_; +using testing::AtMost; +using testing::DoAll; +using testing::Invoke; +using testing::Return; +using testing::SaveArg; + +namespace { + +const string16 kAction1(ASCIIToUTF16("http://www.example.com/share")); +const string16 kAction2(ASCIIToUTF16("http://www.example.com/foobar")); +const string16 kType(ASCIIToUTF16("image/png")); +const GURL kServiceURL1("http://www.google.com"); +const GURL kServiceURL2("http://www.chromium.org"); +const GURL kServiceURL3("http://www.test.com"); + +// Fill the given bmp with valid png data. +void FillDataToBitmap(int w, int h, SkBitmap* bmp) { + bmp->setConfig(SkBitmap::kARGB_8888_Config, w, h); + bmp->allocPixels(); + + unsigned char* src_data = + reinterpret_cast<unsigned char*>(bmp->getAddr32(0, 0)); + for (int i = 0; i < w * h; i++) { + src_data[i * 4 + 0] = static_cast<unsigned char>(i % 255); + src_data[i * 4 + 1] = static_cast<unsigned char>(i % 255); + src_data[i * 4 + 2] = static_cast<unsigned char>(i % 255); + src_data[i * 4 + 3] = static_cast<unsigned char>(i % 255); + } +} + +// Fill the given data buffer with valid png data. +void FillBitmap(int w, int h, std::vector<unsigned char>* output) { + SkBitmap bitmap; + FillDataToBitmap(w, h, &bitmap); + gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, output); +} + +GURL MakeFaviconURL(const GURL& url) { + return GURL(url.spec() + "/favicon.png"); +} + +MATCHER_P(VectorIsOfSize, n, "") { + return arg.size() == static_cast<size_t>(n); +} + +} // namespace + +class WebIntentPickerMock : public WebIntentPicker { + public: + MOCK_METHOD1(SetServiceURLs, void(const std::vector<GURL>& urls)); + MOCK_METHOD2(SetServiceIcon, void(size_t index, const SkBitmap& icon)); + MOCK_METHOD1(SetDefaultServiceIcon, void(size_t index)); + MOCK_METHOD0(Show, void(void)); + MOCK_METHOD0(Close, void(void)); +}; + +class WebIntentPickerFactoryMock : public WebIntentPickerFactory { + public: + MOCK_METHOD2(Create, + WebIntentPicker*(TabContents* tab_contents, + WebIntentPickerDelegate* delegate)); + MOCK_METHOD1(ClosePicker, void(WebIntentPicker* picker)); +}; + +class TestWebIntentPickerController : public WebIntentPickerController { + public: + TestWebIntentPickerController(TabContents* tab_contents, + WebIntentPickerFactory* factory) + : WebIntentPickerController(tab_contents, factory) { + } + + MOCK_METHOD1(OnServiceChosen, void(size_t index)); + MOCK_METHOD0(OnCancelled, void(void)); + + // helper functions to forward to the base class. + void BaseOnServiceChosen(size_t index) { + WebIntentPickerController::OnServiceChosen(index); + } + + void BaseOnCancelled() { + WebIntentPickerController::OnCancelled(); + } +}; + +class WebIntentPickerControllerTest : public TabContentsWrapperTestHarness { + public: + WebIntentPickerControllerTest() + : ui_thread_(BrowserThread::UI, &message_loop_), + db_thread_(BrowserThread::DB, &message_loop_), + picker_factory_(NULL), + delegate_(NULL) { + } + + virtual void SetUp() { + TabContentsWrapperTestHarness::SetUp(); + + profile_->CreateFaviconService(); + profile_->CreateWebDataService(true); + web_data_service_ = profile_->GetWebDataService(Profile::EXPLICIT_ACCESS); + favicon_service_ = profile_->GetFaviconService(Profile::EXPLICIT_ACCESS); + WebIntentsRegistry *registry = + WebIntentsRegistryFactory::GetForProfile(profile_.get()); + registry->Initialize(web_data_service_); + + picker_factory_ = new WebIntentPickerFactoryMock(); + controller_.reset(new TestWebIntentPickerController(contents(), + picker_factory_)); + } + + virtual void TearDown() { + controller_.reset(); + + TabContentsWrapperTestHarness::TearDown(); + } + + protected: + void AddWebIntentService(const string16& action, + const GURL& service_url) { + WebIntentData web_intent_data; + web_intent_data.action = action; + web_intent_data.type = kType; + web_intent_data.service_url = service_url; + web_data_service_->AddWebIntent(web_intent_data); + } + + void AddFaviconForURL(const GURL& url) { + std::vector<unsigned char> image_data; + FillBitmap(16, 16, &image_data); + + favicon_service_->SetFavicon(url, + MakeFaviconURL(url), + image_data, + history::FAVICON); + } + + void SetPickerExpectations(int expected_service_count, + int expected_default_favicons) { + EXPECT_CALL(*picker_factory_, Create(_, _)). + WillOnce(DoAll(SaveArg<1>(&delegate_), Return(&picker_))); + EXPECT_CALL(picker_, + SetServiceURLs(VectorIsOfSize(expected_service_count))). + Times(1); + EXPECT_CALL(picker_, SetDefaultServiceIcon(_)). + Times(expected_default_favicons); + } + + void CheckPendingAsync() { + if (controller_->pending_async_count() > 0) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&WebIntentPickerControllerTest::CheckPendingAsync, + base::Unretained(this))); + return; + } + + MessageLoop::current()->Quit(); + } + + void WaitForDialogToShow() { + CheckPendingAsync(); + MessageLoop::current()->Run(); + } + + BrowserThread ui_thread_; + BrowserThread db_thread_; + WebIntentPickerMock picker_; + WebIntentPickerFactoryMock* picker_factory_; // controller_ takes ownership. + scoped_ptr<TestWebIntentPickerController> controller_; + WebIntentPickerDelegate* delegate_; + WebDataService* web_data_service_; + FaviconService* favicon_service_; +}; + +TEST_F(WebIntentPickerControllerTest, ShowDialogWith3Services) { + SetPickerExpectations(3, 3); + AddWebIntentService(kAction1, kServiceURL1); + AddWebIntentService(kAction1, kServiceURL2); + AddWebIntentService(kAction1, kServiceURL3); + + controller_->ShowDialog(kAction1, kType); + WaitForDialogToShow(); +} + +TEST_F(WebIntentPickerControllerTest, ShowDialogWithNoServices) { + SetPickerExpectations(0, 0); + + EXPECT_CALL(picker_, SetServiceIcon(_, _)).Times(0); + + controller_->ShowDialog(kAction1, kType); + WaitForDialogToShow(); +} + +// TODO(binji) SetServiceIcon isn't called unless I create the HistoryService, +// but when I do, the test hangs... +TEST_F(WebIntentPickerControllerTest, DISABLED_ShowFavicon) { + SetPickerExpectations(3, 2); + AddWebIntentService(kAction1, kServiceURL1); + AddWebIntentService(kAction1, kServiceURL2); + AddWebIntentService(kAction1, kServiceURL3); + AddFaviconForURL(kServiceURL1); + AddFaviconForURL(kServiceURL3); + + EXPECT_CALL(picker_, SetServiceIcon(0, _)).Times(1); + EXPECT_CALL(picker_, SetDefaultServiceIcon(1)).Times(1); + EXPECT_CALL(picker_, SetServiceIcon(2, _)).Times(1); + + controller_->ShowDialog(kAction1, kType); + WaitForDialogToShow(); +} + +TEST_F(WebIntentPickerControllerTest, ChooseService) { + SetPickerExpectations(2, 2); + AddWebIntentService(kAction1, kServiceURL1); + AddWebIntentService(kAction1, kServiceURL2); + + EXPECT_CALL(*controller_, OnServiceChosen(0)) + .WillOnce(Invoke(controller_.get(), + &TestWebIntentPickerController::BaseOnServiceChosen)); + EXPECT_CALL(*controller_, OnCancelled()) + .Times(0); + EXPECT_CALL(*picker_factory_, ClosePicker(_)); + + controller_->ShowDialog(kAction1, kType); + WaitForDialogToShow(); + delegate_->OnServiceChosen(0); +} + +TEST_F(WebIntentPickerControllerTest, Cancel) { + SetPickerExpectations(2, 2); + AddWebIntentService(kAction1, kServiceURL1); + AddWebIntentService(kAction1, kServiceURL2); + + EXPECT_CALL(*controller_, OnServiceChosen(0)) + .Times(0); + EXPECT_CALL(*controller_, OnCancelled()) + .WillOnce(Invoke(controller_.get(), + &TestWebIntentPickerController::BaseOnCancelled)); + EXPECT_CALL(*picker_factory_, ClosePicker(_)); + + controller_->ShowDialog(kAction1, kType); + WaitForDialogToShow(); + delegate_->OnCancelled(); +} diff --git a/chrome/browser/ui/intents/web_intent_picker_delegate.h b/chrome/browser/ui/intents/web_intent_picker_delegate.h new file mode 100644 index 0000000..e540e62 --- /dev/null +++ b/chrome/browser/ui/intents/web_intent_picker_delegate.h @@ -0,0 +1,20 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_DELEGATE_H_ +#define CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_DELEGATE_H_ +#pragma once + +// A class used to notify the delegate when the user has chosen a web intent +// service. +class WebIntentPickerDelegate { + public: + // Callback called when the user has chosen a service. + virtual void OnServiceChosen(size_t index) = 0; + + // Callback called when the user cancels out of the dialog. + virtual void OnCancelled() = 0; +}; + +#endif // CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_DELEGATE_H_ diff --git a/chrome/browser/ui/intents/web_intent_picker_factory.h b/chrome/browser/ui/intents/web_intent_picker_factory.h new file mode 100644 index 0000000..716bac5 --- /dev/null +++ b/chrome/browser/ui/intents/web_intent_picker_factory.h @@ -0,0 +1,26 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_FACTORY_H_ +#define CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_FACTORY_H_ +#pragma once + +class TabContents; +class WebIntentPicker; +class WebIntentPickerDelegate; + +// Interface to create and close the WebIntentPicker UI. +class WebIntentPickerFactory { + public: + virtual ~WebIntentPickerFactory() {} + + // Creates a new WebIntentPicker. The picker is owned by the factory. + virtual WebIntentPicker* Create(TabContents* tab_contents, + WebIntentPickerDelegate* delegate) = 0; + + // Closes and destroys the picker. + virtual void ClosePicker(WebIntentPicker* picker) = 0; +}; + +#endif // CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_FACTORY_H_ diff --git a/chrome/browser/ui/tab_contents/tab_contents_wrapper.cc b/chrome/browser/ui/tab_contents/tab_contents_wrapper.cc index a8d396d..7f92a88 100644 --- a/chrome/browser/ui/tab_contents/tab_contents_wrapper.cc +++ b/chrome/browser/ui/tab_contents/tab_contents_wrapper.cc @@ -54,6 +54,8 @@ #include "chrome/browser/ui/blocked_content/blocked_content_tab_helper.h" #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h" #include "chrome/browser/ui/find_bar/find_tab_helper.h" +#include "chrome/browser/ui/intents/web_intent_constrained_dialog_factory.h" +#include "chrome/browser/ui/intents/web_intent_picker_controller.h" #include "chrome/browser/ui/search_engines/search_engine_tab_helper.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper_delegate.h" #include "chrome/common/chrome_notification_types.h" @@ -156,6 +158,9 @@ TabContentsWrapper::TabContentsWrapper(TabContents* contents) content_settings_.reset(new TabSpecificContentSettings(contents)); translate_tab_helper_.reset(new TranslateTabHelper(contents)); print_view_manager_.reset(new printing::PrintViewManager(this)); + web_intent_picker_controller_.reset(new WebIntentPickerController( + contents, + new WebIntentConstrainedDialogFactory())); // Create the per-tab observers. external_protocol_observer_.reset(new ExternalProtocolObserver(contents)); @@ -645,6 +650,8 @@ void TabContentsWrapper::OnWebIntentDispatch(const IPC::Message& message, << "\ntype=" << UTF16ToASCII(type) << "\nrenderer_id=" << message.routing_id() << "\nid=" << intent_id; + + web_intent_picker_controller_->ShowDialog(action, type); } void TabContentsWrapper::OnSnapshot(const SkBitmap& bitmap) { diff --git a/chrome/browser/ui/tab_contents/tab_contents_wrapper.h b/chrome/browser/ui/tab_contents/tab_contents_wrapper.h index 77e2d4c..fdceb25 100644 --- a/chrome/browser/ui/tab_contents/tab_contents_wrapper.h +++ b/chrome/browser/ui/tab_contents/tab_contents_wrapper.h @@ -59,6 +59,7 @@ class TabContentsWrapperDelegate; class TabSpecificContentSettings; class ThumbnailGenerator; class TranslateTabHelper; +class WebIntentPickerController; namespace safe_browsing { class ClientSideDetectionHost; @@ -195,6 +196,10 @@ class TabContentsWrapper : public TabContentsObserver, return restore_tab_helper_.get(); } + WebIntentPickerController* web_intent_picker_controller() { + return web_intent_picker_controller_.get(); + } + // Overrides ----------------------------------------------------------------- // TabContentsObserver overrides: @@ -327,6 +332,9 @@ class TabContentsWrapper : public TabContentsObserver, // Handles print job for this contents. scoped_ptr<printing::PrintViewManager> print_view_manager_; + // Handles displaying a web intents picker to the user. + scoped_ptr<WebIntentPickerController> web_intent_picker_controller_; + // Handles IPCs related to SafeBrowsing client-side phishing detection. scoped_ptr<safe_browsing::ClientSideDetectionHost> safebrowsing_detection_host_; diff --git a/chrome/browser/ui/views/web_intent_picker_view.cc b/chrome/browser/ui/views/web_intent_picker_view.cc new file mode 100644 index 0000000..fe96858 --- /dev/null +++ b/chrome/browser/ui/views/web_intent_picker_view.cc @@ -0,0 +1,14 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/intents/web_intent_picker.h" +#include "chrome/browser/ui/intents/web_intent_picker_delegate.h" +#include "content/browser/tab_contents/tab_contents.h" + +// static +WebIntentPicker* WebIntentPicker::Create(TabContents* tab_contents, + WebIntentPickerDelegate* delegate) { + // TODO(binji) Implement. See http://crbug.com/93915. + return NULL; +} diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index fc6b188..1888e54 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -2698,6 +2698,7 @@ 'browser/ui/cocoa/view_id_util.h', 'browser/ui/cocoa/view_id_util.mm', 'browser/ui/cocoa/view_resizer.h', + 'browser/ui/cocoa/web_intent_picker_cocoa.mm', 'browser/ui/cocoa/window_restore_utils.h', 'browser/ui/cocoa/window_restore_utils.mm', 'browser/ui/cocoa/window_size_autosaver.h', @@ -2944,9 +2945,18 @@ 'browser/ui/gtk/update_recommended_dialog.h', 'browser/ui/gtk/view_id_util.cc', 'browser/ui/gtk/view_id_util.h', + 'browser/ui/gtk/web_intent_picker_gtk.cc', + 'browser/ui/gtk/web_intent_picker_gtk.h', 'browser/ui/input_window_dialog.h', 'browser/ui/input_window_dialog_gtk.cc', 'browser/ui/input_window_dialog_win.cc', + 'browser/ui/intents/web_intent_constrained_dialog_factory.cc', + 'browser/ui/intents/web_intent_constrained_dialog_factory.h', + 'browser/ui/intents/web_intent_picker.h', + 'browser/ui/intents/web_intent_picker_controller.cc', + 'browser/ui/intents/web_intent_picker_controller.h', + 'browser/ui/intents/web_intent_picker_delegate.h', + 'browser/ui/intents/web_intent_picker_factory.h', 'browser/ui/login/login_model.h', 'browser/ui/login/login_prompt.cc', 'browser/ui/login/login_prompt.h', @@ -3397,6 +3407,7 @@ 'browser/ui/views/update_recommended_message_box.h', 'browser/ui/views/user_data_dir_dialog.cc', 'browser/ui/views/user_data_dir_dialog.h', + 'browser/ui/views/web_intent_picker_view.cc', 'browser/ui/views/window.cc', 'browser/ui/views/window.h', 'browser/ui/views/wrench_menu.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index c30bb85..4079279 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1869,6 +1869,7 @@ 'browser/ui/gtk/reload_button_gtk_unittest.cc', 'browser/ui/gtk/status_icons/status_tray_gtk_unittest.cc', 'browser/ui/gtk/tabs/tab_renderer_gtk_unittest.cc', + 'browser/ui/intents/web_intent_picker_controller_unittest.cc', 'browser/ui/login/login_prompt_unittest.cc', 'browser/ui/omnibox/omnibox_view_unittest.cc', 'browser/ui/panels/panel_browser_window_cocoa_unittest.mm', |