diff options
author | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-20 17:35:06 +0000 |
---|---|---|
committer | binji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-20 17:35:06 +0000 |
commit | 775f19f371aa19e7c1d947aaeb8eca43cb281c71 (patch) | |
tree | 4a41ad4167b378713aca99844fa13f2283441840 | |
parent | 7b7c1bf968198e5d8f98593d9edab11cbb74fb26 (diff) | |
download | chromium_src-775f19f371aa19e7c1d947aaeb8eca43cb281c71.zip chromium_src-775f19f371aa19e7c1d947aaeb8eca43cb281c71.tar.gz chromium_src-775f19f371aa19e7c1d947aaeb8eca43cb281c71.tar.bz2 |
[Web Intents] Inline installation of extensions in web intents picker.
BUG=113476
TEST=WebIntentsRegistryTest.GetIntentServicesWithExtensionId, WebIntentPickerControllerBrowserTest.ExtensionInstallSuccess
Review URL: https://chromiumcodereview.appspot.com/9595031
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@127716 0039d316-1c4b-4281-b951-d872f2087c98
10 files changed, 395 insertions, 94 deletions
diff --git a/chrome/browser/intents/web_intents_registry.cc b/chrome/browser/intents/web_intents_registry.cc index 87a0985..d58c1af 100644 --- a/chrome/browser/intents/web_intents_registry.cc +++ b/chrome/browser/intents/web_intents_registry.cc @@ -4,16 +4,21 @@ #include "chrome/browser/intents/web_intents_registry.h" +#include "base/bind.h" +#include "base/bind_helpers.h" #include "base/callback.h" #include "base/utf_string_conversions.h" #include "chrome/browser/intents/default_web_intent_service.h" #include "chrome/browser/webdata/web_data_service.h" +#include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_set.h" #include "googleurl/src/gurl.h" #include "net/base/mime_util.h" namespace { +typedef WebIntentsRegistry::IntentServiceList IntentServiceList; + // Compares two mime types for equality. Supports wild cards in both // |type1| and |type2|. Wild cards are of the form '<type>/*' or '*'. bool MimeTypesAreEqual(const string16& type1, const string16& type2) { @@ -25,6 +30,33 @@ bool MimeTypesAreEqual(const string16& type1, const string16& type2) { return net::MatchesMimeType(UTF16ToUTF8(type2), UTF16ToUTF8(type1)); } +// Adds any intent services of |extension| that match |action| to +// |matching_services|. +void AddMatchingServicesForExtension(const Extension& extension, + const string16& action, + IntentServiceList* matching_services) { + const IntentServiceList& services = extension.intents_services(); + for (IntentServiceList::const_iterator i = services.begin(); + i != services.end(); ++i) { + if (action.empty() || action == i->action) + matching_services->push_back(*i); + } +} + +// Removes all services from |matching_services| that do not match |mimetype|. +// Wildcards are supported, of the form '<type>/*' or '*'. +void FilterServicesByMimetype(const string16& mimetype, + IntentServiceList* matching_services) { + // Filter out all services not matching the query type. + IntentServiceList::iterator iter(matching_services->begin()); + while (iter != matching_services->end()) { + if (MimeTypesAreEqual(iter->type, mimetype)) + ++iter; + else + iter = matching_services->erase(iter); + } +} + } // namespace using webkit_glue::WebIntentServiceData; @@ -111,24 +143,14 @@ void WebIntentsRegistry::OnWebDataServiceRequestDone( if (extensions) { for (ExtensionSet::const_iterator i(extensions->begin()); i != extensions->end(); ++i) { - const IntentServiceList& services((*i)->intents_services()); - for (IntentServiceList::const_iterator j(services.begin()); - j != services.end(); ++j) { - if (query->action_.empty() || query->action_ == j->action) - matching_services.push_back(*j); - } + AddMatchingServicesForExtension(**i, query->action_, + &matching_services); } } } // Filter out all services not matching the query type. - IntentServiceList::iterator iter(matching_services.begin()); - while (iter != matching_services.end()) { - if (MimeTypesAreEqual(iter->type, query->type_)) - ++iter; - else - iter = matching_services.erase(iter); - } + FilterServicesByMimetype(query->type_, &matching_services); query->consumer_->OnIntentsQueryDone(query->query_id_, matching_services); delete query; @@ -269,6 +291,45 @@ WebIntentsRegistry::QueryID WebIntentsRegistry::IntentServiceExists( return query->query_id_; } +WebIntentsRegistry::QueryID + WebIntentsRegistry::GetIntentServicesForExtensionFilter( + const string16& action, + const string16& mimetype, + const std::string& extension_id, + Consumer* consumer) { + DCHECK(consumer); + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + scoped_ptr<IntentsQuery> query( + new IntentsQuery(next_query_id_++, consumer, action, mimetype)); + int query_id = query->query_id_; + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind(&WebIntentsRegistry::DoGetIntentServicesForExtensionFilter, + base::Unretained(this), + base::Passed(&query), extension_id)); + + return query_id; +} + +void WebIntentsRegistry::DoGetIntentServicesForExtensionFilter( + scoped_ptr<IntentsQuery> query, + const std::string& extension_id) { + IntentServiceList matching_services; + + if (extension_service_) { + const Extension* extension = + extension_service_->GetExtensionById(extension_id, false); + AddMatchingServicesForExtension(*extension, + query->action_, + &matching_services); + FilterServicesByMimetype(query->type_, &matching_services); + } + + query->consumer_->OnIntentsQueryDone(query->query_id_, matching_services); +} + void WebIntentsRegistry::RegisterDefaultIntentService( const DefaultWebIntentService& default_service) { DCHECK(wds_.get()); diff --git a/chrome/browser/intents/web_intents_registry.h b/chrome/browser/intents/web_intents_registry.h index 473175a..dbb2e08 100644 --- a/chrome/browser/intents/web_intents_registry.h +++ b/chrome/browser/intents/web_intents_registry.h @@ -77,6 +77,15 @@ class WebIntentsRegistry const webkit_glue::WebIntentServiceData& service, const base::Callback<void(bool)>& callback); + // Requests all extension services matching |action|, |mimetype| and + // |extension_id|. + // |mimetype| must conform to definition as outlined for GetIntentServices. + // |consumer| must not be NULL. + QueryID GetIntentServicesForExtensionFilter(const string16& action, + const string16& mimetype, + const std::string& extension_id, + Consumer* consumer); + // Record the given default service entry. virtual void RegisterDefaultIntentService( const DefaultWebIntentService& default_service); @@ -122,6 +131,10 @@ class WebIntentsRegistry WebDataService::Handle h, const WDTypedResult* result); + // Implementation of GetIntentServicesForExtensionFilter. + void DoGetIntentServicesForExtensionFilter(scoped_ptr<IntentsQuery> query, + const std::string& extension_id); + // Map for all in-flight web data requests/intent queries. QueryMap queries_; diff --git a/chrome/browser/intents/web_intents_registry_unittest.cc b/chrome/browser/intents/web_intents_registry_unittest.cc index ef1a1e1..82077fd 100644 --- a/chrome/browser/intents/web_intents_registry_unittest.cc +++ b/chrome/browser/intents/web_intents_registry_unittest.cc @@ -25,6 +25,8 @@ class MockExtensionService: public TestExtensionService { public: virtual ~MockExtensionService() {} MOCK_CONST_METHOD0(extensions, const ExtensionSet*()); + MOCK_CONST_METHOD2(GetExtensionById, + const Extension*(const std::string&, bool)); }; namespace { @@ -93,6 +95,9 @@ class WebIntentsRegistryTest : public testing::Test { registry_.Initialize(wds_, &extension_service_); EXPECT_CALL(extension_service_, extensions()). WillRepeatedly(testing::Return(&extensions_)); + EXPECT_CALL(extension_service_, GetExtensionById(testing::_, testing::_)). + WillRepeatedly( + testing::Invoke(this, &WebIntentsRegistryTest::GetExtensionById)); } virtual void TearDown() { @@ -104,6 +109,17 @@ class WebIntentsRegistryTest : public testing::Test { MessageLoop::current()->Run(); } + const Extension* GetExtensionById(const std::string& extension_id, + testing::Unused) { + for (ExtensionSet::const_iterator iter = extensions_.begin(); + iter != extensions_.end(); ++iter) { + if ((*iter)->id() == extension_id) + return &**iter; + } + + return NULL; + } + MessageLoopForUI message_loop_; content::TestBrowserThread ui_thread_; content::TestBrowserThread db_thread_; @@ -194,6 +210,21 @@ TEST_F(WebIntentsRegistryTest, BasicTests) { EXPECT_EQ(1U, consumer.services_.size()); } +TEST_F(WebIntentsRegistryTest, GetIntentServicesForExtensionFilter) { + extensions_.Insert(LoadAndExpectSuccess("intent_valid.json")); + extensions_.Insert(LoadAndExpectSuccess("intent_valid_2.json")); + ASSERT_EQ(2U, extensions_.size()); + + TestConsumer consumer; + consumer.expected_id_ = registry_.GetIntentServicesForExtensionFilter( + ASCIIToUTF16("http://webintents.org/edit"), + ASCIIToUTF16("image/*"), + (*extensions_.begin())->id(), + &consumer); + consumer.WaitForData(); + ASSERT_EQ(1U, consumer.services_.size()); +} + TEST_F(WebIntentsRegistryTest, GetAllIntents) { webkit_glue::WebIntentServiceData service; service.service_url = GURL("http://google.com"); diff --git a/chrome/browser/ui/intents/web_intent_picker.h b/chrome/browser/ui/intents/web_intent_picker.h index fb95697..646edf0 100644 --- a/chrome/browser/ui/intents/web_intent_picker.h +++ b/chrome/browser/ui/intents/web_intent_picker.h @@ -32,6 +32,12 @@ class WebIntentPicker { // Hides the UI for this picker, and destroys its UI. virtual void Close() = 0; + // Called when an extension is successfully installed via the picker. + virtual void OnExtensionInstallSuccess(const std::string& id) {} + + // Called when an extension installation started via the picker has failed. + virtual void OnExtensionInstallFailure(const std::string& id) {} + // Called when the controller has finished all pending asynchronous // activities. virtual void OnPendingAsyncCompleted() {} diff --git a/chrome/browser/ui/intents/web_intent_picker_controller.cc b/chrome/browser/ui/intents/web_intent_picker_controller.cc index 2f37204..179ce31 100644 --- a/chrome/browser/ui/intents/web_intent_picker_controller.cc +++ b/chrome/browser/ui/intents/web_intent_picker_controller.cc @@ -8,7 +8,7 @@ #include "base/bind.h" #include "base/bind_helpers.h" -#include "chrome/browser/ui/browser.h" +#include "chrome/browser/extensions/webstore_installer.h" #include "chrome/browser/favicon/favicon_service.h" #include "chrome/browser/intents/default_web_intent_service.h" #include "chrome/browser/intents/web_intents_registry_factory.h" @@ -16,6 +16,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/tab_contents/tab_util.h" #include "chrome/browser/tabs/tab_strip_model.h" +#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_navigator.h" #include "chrome/browser/ui/intents/web_intent_picker.h" @@ -24,6 +25,7 @@ #include "chrome/browser/webdata/web_data_service.h" #include "chrome/common/chrome_notification_types.h" #include "content/public/browser/browser_thread.h" +#include "content/public/browser/navigation_controller.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_intents_dispatcher.h" @@ -66,25 +68,77 @@ WebIntentPickerModel::Disposition ConvertDisposition( } } +// Self-deleting trampoline that forwards a WebIntentsRegistry response to a +// callback. +class WebIntentsRegistryTrampoline : public WebIntentsRegistry::Consumer { + public: + typedef std::vector<webkit_glue::WebIntentServiceData> IntentServices; + typedef base::Callback<void(const IntentServices&)> ForwardingCallback; + + explicit WebIntentsRegistryTrampoline(const ForwardingCallback& callback); + ~WebIntentsRegistryTrampoline(); + + // WebIntentsRegistry::Consumer implementation. + virtual void OnIntentsQueryDone( + WebIntentsRegistry::QueryID, + const std::vector<webkit_glue::WebIntentServiceData>& services) OVERRIDE; + virtual void OnIntentsDefaultsQueryDone( + WebIntentsRegistry::QueryID, + const DefaultWebIntentService& default_service) OVERRIDE {} + + private: + // Forwarding callback from |OnIntentsQueryDone|. + ForwardingCallback callback_; +}; + +WebIntentsRegistryTrampoline::WebIntentsRegistryTrampoline( + const ForwardingCallback& callback) + : callback_(callback) { +} + +WebIntentsRegistryTrampoline::~WebIntentsRegistryTrampoline() { +} + +void WebIntentsRegistryTrampoline::OnIntentsQueryDone( + WebIntentsRegistry::QueryID, + const std::vector<webkit_glue::WebIntentServiceData>& services) { + DCHECK(!callback_.is_null()); + callback_.Run(services); + delete this; +} + +// Self-deleting trampoline that forwards A URLFetcher response to a callback. class URLFetcherTrampoline : public content::URLFetcherDelegate { public: - typedef base::Callback<void(const content::URLFetcher* source)> Callback; + typedef base::Callback<void(const content::URLFetcher* source)> + ForwardingCallback; - explicit URLFetcherTrampoline(const Callback& callback) - : callback_(callback) {} - ~URLFetcherTrampoline() {} + explicit URLFetcherTrampoline(const ForwardingCallback& callback); + ~URLFetcherTrampoline(); // content::URLFetcherDelegate implementation. - virtual void OnURLFetchComplete(const content::URLFetcher* source) OVERRIDE { - callback_.Run(source); - delete source; - delete this; - } + virtual void OnURLFetchComplete(const content::URLFetcher* source) OVERRIDE; private: - Callback callback_; + // Fowarding callback from |OnURLFetchComplete|. + ForwardingCallback callback_; }; +URLFetcherTrampoline::URLFetcherTrampoline(const ForwardingCallback& callback) + : callback_(callback) { +} + +URLFetcherTrampoline::~URLFetcherTrampoline() { +} + +void URLFetcherTrampoline::OnURLFetchComplete( + const content::URLFetcher* source) { + DCHECK(!callback_.is_null()); + callback_.Run(source); + delete source; + delete this; +} + } // namespace WebIntentPickerController::WebIntentPickerController( @@ -124,6 +178,8 @@ void WebIntentPickerController::ShowDialog(Browser* browser, return; picker_model_->Clear(); + picker_model_->set_action(action); + picker_model_->set_mimetype(type); // If picker is non-NULL, it was set by a test. if (picker_ == NULL) { @@ -133,8 +189,14 @@ void WebIntentPickerController::ShowDialog(Browser* browser, picker_shown_ = true; pending_async_count_+= 2; - GetWebIntentsRegistry(wrapper_)->GetIntentServices(action, type, this); - GetCWSIntentsRegistry(wrapper_)->GetIntentServices(action, type, + GetWebIntentsRegistry(wrapper_)->GetIntentServices( + action, type, + // WebIntentsRegistryTrampoline is self-deleting. + new WebIntentsRegistryTrampoline( + base::Bind(&WebIntentPickerController::OnWebIntentServicesAvailable, + weak_ptr_factory_.GetWeakPtr()))); + GetCWSIntentsRegistry(wrapper_)->GetIntentServices( + action, type, base::Bind(&WebIntentPickerController::OnCWSIntentServicesAvailable, weak_ptr_factory_.GetWeakPtr())); } @@ -207,6 +269,16 @@ void WebIntentPickerController::OnInlineDispositionWebContentsCreated( intents_dispatcher_->DispatchIntent(web_contents); } +void WebIntentPickerController::OnExtensionInstallRequested( + const std::string& id) { + webstore_installer_ = new WebstoreInstaller( + wrapper_->profile(), this, &wrapper_->web_contents()->GetController(), id, + WebstoreInstaller::FLAG_INLINE_INSTALL); + + pending_async_count_++; + webstore_installer_->Start(); +} + void WebIntentPickerController::OnCancelled() { if (!intents_dispatcher_) return; @@ -227,6 +299,28 @@ void WebIntentPickerController::OnClosing() { picker_ = NULL; } +void WebIntentPickerController::OnExtensionInstallSuccess( + const std::string& id) { + picker_->OnExtensionInstallSuccess(id); + pending_async_count_++; + GetWebIntentsRegistry(wrapper_)->GetIntentServicesForExtensionFilter( + picker_model_->action(), + picker_model_->mimetype(), + id, + new WebIntentsRegistryTrampoline( + base::Bind( + &WebIntentPickerController::OnExtensionInstallServiceAvailable, + weak_ptr_factory_.GetWeakPtr()))); + AsyncOperationFinished(); +} + +void WebIntentPickerController::OnExtensionInstallFailure( + const std::string& id, + const std::string& error) { + picker_->OnExtensionInstallFailure(id); + AsyncOperationFinished(); +} + void WebIntentPickerController::OnSendReturnMessage( webkit_glue::WebIntentReplyType reply_type) { ClosePicker(); @@ -255,8 +349,7 @@ void WebIntentPickerController::OnSendReturnMessage( intents_dispatcher_ = NULL; } -void WebIntentPickerController::OnIntentsQueryDone( - WebIntentsRegistry::QueryID, +void WebIntentPickerController::OnWebIntentServicesAvailable( const std::vector<webkit_glue::WebIntentServiceData>& services) { FaviconService* favicon_service = GetFaviconService(wrapper_); for (size_t i = 0; i < services.size(); ++i) { @@ -279,11 +372,6 @@ void WebIntentPickerController::OnIntentsQueryDone( AsyncOperationFinished(); } -void WebIntentPickerController::OnIntentsDefaultsQueryDone( - WebIntentsRegistry::QueryID, - const DefaultWebIntentService& default_service) { -} - void WebIntentPickerController::OnFaviconDataAvailable( FaviconService::Handle handle, history::FaviconData favicon_data) { size_t index = favicon_consumer_.GetClientDataForCurrentRequest(); @@ -416,6 +504,19 @@ void WebIntentPickerController::OnExtensionIconUnavailable( AsyncOperationFinished(); } +void WebIntentPickerController::OnExtensionInstallServiceAvailable( + const std::vector<webkit_glue::WebIntentServiceData>& services) { + DCHECK(services.size() > 0); + + // TODO(binji): We're going to need to disambiguate if there are multiple + // services. For now, just choose the first. + const webkit_glue::WebIntentServiceData& service_data = services[0]; + OnServiceChosen( + service_data.service_url, + ConvertDisposition(service_data.disposition)); + AsyncOperationFinished(); +} + void WebIntentPickerController::AsyncOperationFinished() { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); if (--pending_async_count_ == 0) { diff --git a/chrome/browser/ui/intents/web_intent_picker_controller.h b/chrome/browser/ui/intents/web_intent_picker_controller.h index ee6ce0e..63112c4 100644 --- a/chrome/browser/ui/intents/web_intent_picker_controller.h +++ b/chrome/browser/ui/intents/web_intent_picker_controller.h @@ -9,9 +9,11 @@ #include <vector> #include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/string16.h" +#include "chrome/browser/extensions/webstore_installer.h" #include "chrome/browser/favicon/favicon_service.h" #include "chrome/browser/intents/web_intents_registry.h" #include "chrome/browser/intents/cws_intents_registry.h" @@ -27,6 +29,7 @@ class GURL; class TabContentsWrapper; class WebIntentPicker; class WebIntentPickerModel; +class WebstoreInstaller; namespace content { class WebContents; @@ -41,7 +44,7 @@ struct WebIntentServiceData; // intent handler choice back to the TabContents object. class WebIntentPickerController : public content::NotificationObserver, public WebIntentPickerDelegate, - public WebIntentsRegistry::Consumer { + public WebstoreInstaller::Delegate { public: // Takes ownership of |factory|. explicit WebIntentPickerController(TabContentsWrapper* wrapper); @@ -69,9 +72,15 @@ class WebIntentPickerController : public content::NotificationObserver, Disposition disposition) OVERRIDE; virtual void OnInlineDispositionWebContentsCreated( content::WebContents* web_contents) OVERRIDE; + virtual void OnExtensionInstallRequested(const std::string& id) OVERRIDE; virtual void OnCancelled() OVERRIDE; virtual void OnClosing() OVERRIDE; + // WebstoreInstaller::Delegate implementation. + virtual void OnExtensionInstallSuccess(const std::string& id) OVERRIDE; + virtual void OnExtensionInstallFailure(const std::string& id, + const std::string& error) OVERRIDE; + private: friend class WebIntentPickerControllerTest; friend class WebIntentPickerControllerBrowserTest; @@ -90,14 +99,8 @@ class WebIntentPickerController : public content::NotificationObserver, } // Called when WebIntentServiceData is returned from the WebIntentsRegistry. - virtual void OnIntentsQueryDone( - WebIntentsRegistry::QueryID, - const std::vector<webkit_glue::WebIntentServiceData>& services) OVERRIDE; - - // Called when the WebIntentsRegistry returns responses to a defaults request. - virtual void OnIntentsDefaultsQueryDone( - WebIntentsRegistry::QueryID, - const DefaultWebIntentService& default_service) OVERRIDE; + void OnWebIntentServicesAvailable( + const std::vector<webkit_glue::WebIntentServiceData>& services); // Called when FaviconData is returned from the FaviconService. void OnFaviconDataAvailable(FaviconService::Handle handle, @@ -126,6 +129,12 @@ class WebIntentPickerController : public content::NotificationObserver, // Called when an extension's icon failed to be decoded or resized. void OnExtensionIconUnavailable(const string16& extension_id); + // When an extension is installed, all that is known is the extension id. + // This callback receives the intent service data for that extension. + // |services| must be a non-empty list. + void OnExtensionInstallServiceAvailable( + const std::vector<webkit_glue::WebIntentServiceData>& services); + // Decrements the |pending_async_count_| and notifies the picker if it // reaches zero. void AsyncOperationFinished(); @@ -167,6 +176,9 @@ class WebIntentPickerController : public content::NotificationObserver, // Request consumer used when asynchronously loading favicons. CancelableRequestConsumerTSimple<size_t> favicon_consumer_; + // Used to install extensions from the Chrome Web Store. + scoped_refptr<WebstoreInstaller> webstore_installer_; + base::WeakPtrFactory<WebIntentPickerController> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(WebIntentPickerController); diff --git a/chrome/browser/ui/intents/web_intent_picker_controller_browsertest.cc b/chrome/browser/ui/intents/web_intent_picker_controller_browsertest.cc index 9dab1d2..28d8973 100644 --- a/chrome/browser/ui/intents/web_intent_picker_controller_browsertest.cc +++ b/chrome/browser/ui/intents/web_intent_picker_controller_browsertest.cc @@ -20,6 +20,7 @@ #include "chrome/browser/ui/intents/web_intent_picker_model_observer.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/browser/webdata/web_data_service.h" +#include "chrome/common/chrome_switches.h" #include "chrome/common/url_constants.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" @@ -27,6 +28,7 @@ #include "content/public/browser/web_intents_dispatcher.h" #include "content/test/test_url_fetcher_factory.h" #include "net/base/escape.h" +#include "net/base/mock_host_resolver.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/gfx/image/image_unittest_util.h" #include "ui/gfx/image/image_util.h" @@ -34,11 +36,13 @@ namespace { -const string16 kAction1(ASCIIToUTF16("http://www.example.com/share")); +const string16 kAction1(ASCIIToUTF16("http://webintents.org/share")); const string16 kAction2(ASCIIToUTF16("http://www.example.com/foobar")); -const string16 kType(ASCIIToUTF16("image/png")); +const string16 kType1(ASCIIToUTF16("image/png")); +const string16 kType2(ASCIIToUTF16("text/*")); const GURL kServiceURL1("http://www.google.com"); const GURL kServiceURL2("http://www.chromium.org"); +const char kDummyExtensionId[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; const char kCWSResponseEmpty[] = "{\"kind\":\"chromewebstore#itemList\",\"total_items\":0,\"start_index\":0," "\"items\":[]}"; @@ -49,34 +53,20 @@ const char kCWSResponseResultFormat[] = "\"start_index\":0," "\"items\":[{" "\"kind\":\"chromewebstore#item\"," - "\"id\":\"nhkckhebbbncbkefhcpcgepcgfaclehe\"," + "\"id\":\"%s\"," "\"type\":\"APPLICATION\"," "\"num_ratings\":0," "\"average_rating\":0.0," "\"manifest\": \"{\\n" - "\\\"update_url\\\":\\" - "\"http://0.tbhome_staging.dserver.download-qa.td.borg.google.com/" - "service/update2/crx\\\",\\n " - "\\\"name\\\": \\\"Sidd's Intent App\\\",\\n " - "\\\"description\\\": \\\"Do stuff\\\",\\n " - "\\\"version\\\": \\\"1.2.19\\\",\\n " - "\\\"app\\\": {\\n " - "\\\"urls\\\": [ \\n ],\\n " - "\\\"launch\\\": {\\n " - "\\\"web_url\\\": \\\"http://siddharthasaha.net/\\\"\\n " - "}\\n " - "},\\n " - "\\\"icons\\\": {\\n \\\"128\\\": \\\"icon128.png\\\"\\n },\\n " - "\\\"permissions\\\":" " [\\n " - "\\\"unlimitedStorage\\\",\\n \\\"notifications\\\"\\n " - "],\\n" - " \\\"intents\\\": {\\n " - "\\\"%s\\\" : {\\n " - "\\\"type\\\" : [\\\"%s\\\"],\\n " - "\\\"path\\\" : \\\"//services/edit\\\",\\n " - "\\\"title\\\" : \\\"Sample Editing Intent\\\",\\n " - "\\\"disposition\\\" : \\\"inline\\\"\\n " - "}\\n " + "\\\"name\\\": \\\"Dummy Share\\\",\\n" + "\\\"version\\\": \\\"1.0.0.0\\\",\\n" + "\\\"intents\\\": {\\n" + "\\\"%s\\\" : {\\n" + "\\\"type\\\" : [\\\"%s\\\"],\\n" + "\\\"path\\\" : \\\"share.html\\\",\\n" + "\\\"title\\\" : \\\"Dummy share!\\\",\\n" + "\\\"disposition\\\": \\\"inline\\\"\\n" + "}\\n" "}\\n" "}\\n\"," "\"family_safe\":true," @@ -107,6 +97,7 @@ class WebIntentPickerMock : public WebIntentPicker, : num_installed_services_(0), num_icons_changed_(0), num_extension_icons_changed_(0), + num_extensions_installed_(0), message_loop_started_(false), pending_async_completed_(false) { } @@ -130,23 +121,35 @@ class WebIntentPickerMock : public WebIntentPicker, WebIntentPickerModel* model, const GURL& url) OVERRIDE {} virtual void Close() OVERRIDE {} - virtual void OnPendingAsyncCompleted() OVERRIDE { - pending_async_completed_ = true; + virtual void OnExtensionInstallSuccess(const std::string& id) OVERRIDE { + num_extensions_installed_++; + } - if (message_loop_started_) - MessageLoop::current()->Quit(); + virtual void OnExtensionInstallFailure(const std::string& id) OVERRIDE { + } + + virtual void OnPendingAsyncCompleted() OVERRIDE { + StopWaiting(); } - void WaitForPendingAsync() { + void Wait() { if (!pending_async_completed_) { message_loop_started_ = true; ui_test_utils::RunMessageLoop(); + pending_async_completed_ = false; } } + void StopWaiting() { + pending_async_completed_ = true; + if (message_loop_started_) + MessageLoop::current()->Quit(); + } + int num_installed_services_; int num_icons_changed_; int num_extension_icons_changed_; + int num_extensions_installed_; bool message_loop_started_; bool pending_async_completed_; }; @@ -183,6 +186,27 @@ class WebIntentPickerControllerBrowserTest : public InProcessBrowserTest { WebIntentPickerControllerBrowserTest() {} + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + // We start the test server now instead of in + // SetUpInProcessBrowserTestFixture so that we can get its port number. + ASSERT_TRUE(test_server()->Start()); + + InProcessBrowserTest::SetUpCommandLine(command_line); + + net::HostPortPair host_port = test_server()->host_port_pair(); + command_line->AppendSwitchASCII( + switches::kAppsGalleryDownloadURL, + base::StringPrintf( + "http://www.example.com:%d/files/extensions/intents/%%s.crx", + host_port.port())); + command_line->AppendSwitchASCII( + switches::kAppsGalleryInstallAutoConfirmForTests, "accept"); + } + + virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { + host_resolver()->AddRule("www.example.com", "127.0.0.1"); + } + virtual void SetUpOnMainThread() OVERRIDE { // The FakeURLFetcherFactory will return a NULL URLFetcher if a request is // created for a URL it doesn't know and there is no default factory. @@ -207,33 +231,36 @@ class WebIntentPickerControllerBrowserTest : public InProcessBrowserTest { void AddWebIntentService(const string16& action, const GURL& service_url) { webkit_glue::WebIntentServiceData service; service.action = action; - service.type = kType; + service.type = kType1; service.service_url = service_url; web_data_service_->AddWebIntentService(service); } void AddCWSExtensionServiceEmpty(const string16& action) { - GURL cws_query_url = CWSIntentsRegistry::BuildQueryURL(action, kType); + GURL cws_query_url = CWSIntentsRegistry::BuildQueryURL(action, kType1); fake_url_fetcher_factory_->SetFakeResponse(cws_query_url.spec(), - kCWSResponseEmpty, true); + kCWSResponseEmpty, true); } - void AddCWSExtensionServiceWithResult(const string16& action) { - GURL cws_query_url = CWSIntentsRegistry::BuildQueryURL(action, kType); + void AddCWSExtensionServiceWithResult(const std::string& extension_id, + const string16& action, + const string16& type) { + GURL cws_query_url = CWSIntentsRegistry::BuildQueryURL(action, type); std::string icon_url; std::string escaped_action = net::EscapePath(UTF16ToUTF8(action)); base::SStringPrintf(&icon_url, kCWSFakeIconURLFormat, - escaped_action.c_str()); + escaped_action.c_str()); std::string response; base::SStringPrintf(&response, kCWSResponseResultFormat, - UTF16ToUTF8(action).c_str(), UTF16ToUTF8(kType).c_str(), - icon_url.c_str()); + extension_id.c_str(), + UTF16ToUTF8(action).c_str(), UTF16ToUTF8(type).c_str(), + icon_url.c_str()); fake_url_fetcher_factory_->SetFakeResponse(cws_query_url.spec(), response, - true); + true); fake_url_fetcher_factory_->SetFakeResponse(icon_url, icon_response_, - true); + true); } void OnSendReturnMessage( @@ -249,6 +276,10 @@ class WebIntentPickerControllerBrowserTest : public InProcessBrowserTest { controller_->OnCancelled(); } + void OnExtensionInstallRequested(const std::string& extension_id) { + controller_->OnExtensionInstallRequested(extension_id); + } + void CreateFakeIcon() { gfx::Image image(gfx::test::CreateImage()); std::vector<unsigned char> image_data; @@ -256,7 +287,7 @@ class WebIntentPickerControllerBrowserTest : public InProcessBrowserTest { DCHECK(result); std::copy(image_data.begin(), image_data.end(), - std::back_inserter(icon_response_)); + std::back_inserter(icon_response_)); } WebIntentPickerMock picker_; @@ -273,8 +304,8 @@ IN_PROC_BROWSER_TEST_F(WebIntentPickerControllerBrowserTest, ChooseService) { AddWebIntentService(kAction1, kServiceURL2); AddCWSExtensionServiceEmpty(kAction1); - controller_->ShowDialog(browser(), kAction1, kType); - picker_.WaitForPendingAsync(); + controller_->ShowDialog(browser(), kAction1, kType1); + picker_.Wait(); EXPECT_EQ(2, picker_.num_installed_services_); EXPECT_EQ(0, picker_.num_icons_changed_); @@ -299,10 +330,10 @@ IN_PROC_BROWSER_TEST_F(WebIntentPickerControllerBrowserTest, FetchExtensionIcon) { AddWebIntentService(kAction1, kServiceURL1); AddWebIntentService(kAction1, kServiceURL2); - AddCWSExtensionServiceWithResult(kAction1); + AddCWSExtensionServiceWithResult(kDummyExtensionId, kAction1, kType1); - controller_->ShowDialog(browser(), kAction1, kType); - picker_.WaitForPendingAsync(); + controller_->ShowDialog(browser(), kAction1, kType1); + picker_.Wait(); EXPECT_EQ(2, picker_.num_installed_services_); EXPECT_EQ(0, picker_.num_icons_changed_); EXPECT_EQ(1, picker_.num_extension_icons_changed_); @@ -313,11 +344,11 @@ IN_PROC_BROWSER_TEST_F(WebIntentPickerControllerBrowserTest, OpenCancelOpen) { AddWebIntentService(kAction1, kServiceURL2); AddCWSExtensionServiceEmpty(kAction1); - controller_->ShowDialog(browser(), kAction1, kType); - picker_.WaitForPendingAsync(); + controller_->ShowDialog(browser(), kAction1, kType1); + picker_.Wait(); OnCancelled(); - controller_->ShowDialog(browser(), kAction1, kType); + controller_->ShowDialog(browser(), kAction1, kType1); OnCancelled(); } @@ -335,8 +366,8 @@ IN_PROC_BROWSER_TEST_F(WebIntentPickerControllerBrowserTest, ASSERT_EQ(2, browser()->tab_count()); EXPECT_EQ(original, browser()->GetSelectedWebContents()->GetURL()); - controller_->ShowDialog(browser(), kAction1, kType); - picker_.WaitForPendingAsync(); + controller_->ShowDialog(browser(), kAction1, kType1); + picker_.Wait(); EXPECT_EQ(1, picker_.num_installed_services_); webkit_glue::WebIntentData intent; @@ -356,3 +387,29 @@ IN_PROC_BROWSER_TEST_F(WebIntentPickerControllerBrowserTest, ASSERT_EQ(2, browser()->tab_count()); EXPECT_EQ(original, browser()->GetSelectedWebContents()->GetURL()); } + +IN_PROC_BROWSER_TEST_F(WebIntentPickerControllerBrowserTest, + ExtensionInstallSuccess) { + const char extension_id[] = "ooodacpbmglpoagccnepcbfhfhpdgddn"; + AddCWSExtensionServiceWithResult(extension_id, kAction1, kType2); + + controller_->ShowDialog(browser(), kAction1, kType2); + picker_.Wait(); + + webkit_glue::WebIntentData intent; + intent.action = kAction1; + intent.type = kType2; + IntentsDispatcherMock dispatcher(intent); + controller_->SetIntentsDispatcher(&dispatcher); + + OnExtensionInstallRequested(extension_id); + picker_.Wait(); + EXPECT_EQ(1, picker_.num_extensions_installed_); + const Extension* extension = browser()->profile()->GetExtensionService()-> + GetExtensionById(extension_id, false); + EXPECT_TRUE(extension); + + // Installing an extension should also choose it. Since this extension uses + // window disposition, it will create a new tab. + ASSERT_EQ(2, browser()->tab_count()); +} diff --git a/chrome/browser/ui/intents/web_intent_picker_delegate.h b/chrome/browser/ui/intents/web_intent_picker_delegate.h index f6ef88f..296dea1 100644 --- a/chrome/browser/ui/intents/web_intent_picker_delegate.h +++ b/chrome/browser/ui/intents/web_intent_picker_delegate.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_UI_INTENTS_WEB_INTENT_PICKER_DELEGATE_H_ #pragma once +#include <string> #include "chrome/browser/ui/intents/web_intent_picker_model.h" namespace content { @@ -29,6 +30,9 @@ class WebIntentPickerDelegate { virtual void OnInlineDispositionWebContentsCreated( content::WebContents* web_contents) = 0; + // Called when the user has chosen to install a suggested extension. + virtual void OnExtensionInstallRequested(const std::string& id) = 0; + // Called when the user cancels out of the dialog, whether by closing it // manually or otherwise purposefully. virtual void OnCancelled() = 0; diff --git a/chrome/browser/ui/intents/web_intent_picker_model.cc b/chrome/browser/ui/intents/web_intent_picker_model.cc index d564566..349b2f6 100644 --- a/chrome/browser/ui/intents/web_intent_picker_model.cc +++ b/chrome/browser/ui/intents/web_intent_picker_model.cc @@ -39,6 +39,8 @@ void WebIntentPickerModel::RemoveInstalledServiceAt(size_t index) { void WebIntentPickerModel::Clear() { DestroyAll(); + action_.clear(); + mimetype_.clear(); inline_disposition_url_ = GURL::EmptyGURL(); if (observer_) observer_->OnModelChanged(this); diff --git a/chrome/browser/ui/intents/web_intent_picker_model.h b/chrome/browser/ui/intents/web_intent_picker_model.h index c560cd9..148a682 100644 --- a/chrome/browser/ui/intents/web_intent_picker_model.h +++ b/chrome/browser/ui/intents/web_intent_picker_model.h @@ -74,6 +74,14 @@ class WebIntentPickerModel { observer_ = observer; } + void set_action(const string16& action) { action_ = action; } + + const string16& action() { return action_; } + + void set_mimetype(const string16& mimetype) { mimetype_ = mimetype; } + + const string16& mimetype() { return mimetype_; } + // Add a new installed service with |title|, |url| and |disposition| to the // picker. void AddInstalledService(const string16& title, @@ -146,6 +154,12 @@ class WebIntentPickerModel { // GURL::EmptyGURL() if none. GURL inline_disposition_url_; + // A cached copy of the action that instantiated the picker. + string16 action_; + + // A cached copy of the mimetype that instantiated the picker. + string16 mimetype_; + DISALLOW_COPY_AND_ASSIGN(WebIntentPickerModel); }; |