summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbinji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-20 17:35:06 +0000
committerbinji@chromium.org <binji@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-03-20 17:35:06 +0000
commit775f19f371aa19e7c1d947aaeb8eca43cb281c71 (patch)
tree4a41ad4167b378713aca99844fa13f2283441840
parent7b7c1bf968198e5d8f98593d9edab11cbb74fb26 (diff)
downloadchromium_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
-rw-r--r--chrome/browser/intents/web_intents_registry.cc87
-rw-r--r--chrome/browser/intents/web_intents_registry.h13
-rw-r--r--chrome/browser/intents/web_intents_registry_unittest.cc31
-rw-r--r--chrome/browser/ui/intents/web_intent_picker.h6
-rw-r--r--chrome/browser/ui/intents/web_intent_picker_controller.cc141
-rw-r--r--chrome/browser/ui/intents/web_intent_picker_controller.h30
-rw-r--r--chrome/browser/ui/intents/web_intent_picker_controller_browsertest.cc161
-rw-r--r--chrome/browser/ui/intents/web_intent_picker_delegate.h4
-rw-r--r--chrome/browser/ui/intents/web_intent_picker_model.cc2
-rw-r--r--chrome/browser/ui/intents/web_intent_picker_model.h14
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);
};