diff options
author | mihaip@chromium.org <mihaip@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-29 22:14:37 +0000 |
---|---|---|
committer | mihaip@chromium.org <mihaip@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-08-29 22:14:37 +0000 |
commit | 8915f3407209106b732b65dae883b75b35e6b086 (patch) | |
tree | 2c860479ee69dcbfa99158d43ca0ca40ee3ba420 /chrome | |
parent | 82c7bfb8ca07b27406154548434eca6dc111b097 (diff) | |
download | chromium_src-8915f3407209106b732b65dae883b75b35e6b086.zip chromium_src-8915f3407209106b732b65dae883b75b35e6b086.tar.gz chromium_src-8915f3407209106b732b65dae883b75b35e6b086.tar.bz2 |
Add WebstoreInlineInstaller (downloads store data, shows the install UI, and starts the install).
The flow is:
1. Fetch store metadata as JSON (using URLFetcher)
2. Parse response in utility process (via SafeWebstoreResponseParser)
3. Parse manifest and get icon data using WebstoreInstallHelper
4. Show install UI
5. Whitelist extension ID for download and start download
Still missing are a way of informing the page that the inline install succeeded
or failed.
Also removes ExtensionTabHelper::GetCustomFrameNativeWindow, since it wasn't
actually overriding anything (the method was removed from
ExtensionFunctionDispatcher::Delegate by r74835).
R=asargent@chromium.org
Review URL: http://codereview.chromium.org/7741037
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@98712 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
19 files changed, 627 insertions, 115 deletions
diff --git a/chrome/browser/extensions/extension_install_dialog.cc b/chrome/browser/extensions/extension_install_dialog.cc new file mode 100644 index 0000000..a58e259 --- /dev/null +++ b/chrome/browser/extensions/extension_install_dialog.cc @@ -0,0 +1,74 @@ +// 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/extensions/extension_install_dialog.h" + +#include "base/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "chrome/common/extensions/extension.h" + +// A flag used for SetExtensionInstallDialogForManifestAutoConfirmForTests +enum AutoConfirmForTest { + DO_NOT_SKIP = 0, + PROCEED, + ABORT +}; +AutoConfirmForTest auto_confirm_for_tests = DO_NOT_SKIP; + +void ShowExtensionInstallDialogForManifest( + Profile *profile, + ExtensionInstallUI::Delegate* delegate, + const DictionaryValue* manifest, + const std::string& id, + const std::string& localized_name, + SkBitmap* icon, + ExtensionInstallUI::PromptType type, + scoped_refptr<Extension>* dummy_extension) { + scoped_ptr<DictionaryValue> localized_manifest; + if (!localized_name.empty()) { + localized_manifest.reset(manifest->DeepCopy()); + localized_manifest->SetString(extension_manifest_keys::kName, + localized_name); + } + + std::string init_errors; + *dummy_extension = Extension::CreateWithId( + FilePath(), + Extension::INTERNAL, + localized_manifest.get() ? *localized_manifest.get() : *manifest, + Extension::NO_FLAGS, + id, + &init_errors); + if (!dummy_extension->get()) { + return; + } + + if (icon->empty()) + icon = const_cast<SkBitmap*>(&Extension::GetDefaultIcon( + (*dummy_extension)->is_app())); + + // In tests, we may have setup to proceed or abort without putting up the real + // confirmation dialog. + if (auto_confirm_for_tests != DO_NOT_SKIP) { + if (auto_confirm_for_tests == PROCEED) + delegate->InstallUIProceed(); + else + delegate->InstallUIAbort(true); + return; + } + + ShowExtensionInstallDialog(profile, + delegate, + dummy_extension->get(), + icon, + (*dummy_extension)->GetPermissionMessageStrings(), + ExtensionInstallUI::INSTALL_PROMPT); + return; +} + +void SetExtensionInstallDialogForManifestAutoConfirmForTests( + bool should_proceed) { + auto_confirm_for_tests = should_proceed ? PROCEED : ABORT; +} diff --git a/chrome/browser/extensions/extension_install_dialog.h b/chrome/browser/extensions/extension_install_dialog.h index 2c0dcc1..4cc67fc 100644 --- a/chrome/browser/extensions/extension_install_dialog.h +++ b/chrome/browser/extensions/extension_install_dialog.h @@ -8,6 +8,7 @@ #include <vector> +#include "base/memory/ref_counted.h" #include "base/string16.h" #include "chrome/browser/extensions/extension_install_ui.h" @@ -15,6 +16,10 @@ class Extension; class Profile; class SkBitmap; +namespace base { +class DictionaryValue; +} + // The implementations of this function are platform-specific. void ShowExtensionInstallDialog(Profile* profile, ExtensionInstallUI::Delegate* delegate, @@ -23,4 +28,25 @@ void ShowExtensionInstallDialog(Profile* profile, const std::vector<string16>& permissions, ExtensionInstallUI::PromptType type); +// Wrapper around ShowExtensionInstallDialog that shows the install dialog for +// a given manifest (that corresponds to an extension about to be installed with +// ID |id|). If the name in the manifest is a localized placeholder, it may be +// overidden with |localized_name| (which may be empty). The Extension instance +// that's parsed is returned via |dummy_extension|. +void ShowExtensionInstallDialogForManifest( + Profile *profile, + ExtensionInstallUI::Delegate* delegate, + const base::DictionaryValue* manifest, + const std::string& id, + const std::string& localized_name, + SkBitmap* icon, + ExtensionInstallUI::PromptType type, + scoped_refptr<Extension>* dummy_extension); + +// For use only in tests - sets a flag that makes invocations of +// ShowExtensionInstallDialogForManifest skip putting up a real dialog, and +// instead act as if the dialog choice was to proceed or abort. +void SetExtensionInstallDialogForManifestAutoConfirmForTests( + bool should_proceed); + #endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_INSTALL_DIALOG_H_ diff --git a/chrome/browser/extensions/extension_tab_helper.cc b/chrome/browser/extensions/extension_tab_helper.cc index 024b4eb..6a2a391 100644 --- a/chrome/browser/extensions/extension_tab_helper.cc +++ b/chrome/browser/extensions/extension_tab_helper.cc @@ -6,6 +6,7 @@ #include "base/command_line.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/webstore_inline_installer.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sessions/restore_tab_helper.h" #include "chrome/browser/ui/browser.h" @@ -154,15 +155,9 @@ void ExtensionTabHelper::OnInlineWebstoreInstall( return; } - // For now there is no inline installation UI, we just open the item's Web - // Store page in a new tab. - GURL webstore_item_url = - GURL(extension_urls::GetWebstoreItemDetailURLPrefix() + webstore_item_id); - GetBrowser()->OpenURL(OpenURLParams( - webstore_item_url, - GetBrowser()->GetSelectedTabContents()->GetURL(), - NEW_FOREGROUND_TAB, - PageTransition::AUTO_BOOKMARK)); + scoped_refptr<WebstoreInlineInstaller> installer(new WebstoreInlineInstaller( + tab_contents(), webstore_item_id, this)); + installer->BeginInstall(); } void ExtensionTabHelper::OnRequest( @@ -213,22 +208,17 @@ Browser* ExtensionTabHelper::GetBrowser() { return NULL; } -TabContents* ExtensionTabHelper::GetAssociatedTabContents() const { - return tab_contents(); +void ExtensionTabHelper::OnInlineInstallSuccess() { + Send(new ExtensionMsg_InlineWebstoreInstallResponse(routing_id(), true, "")); } -gfx::NativeWindow ExtensionTabHelper::GetCustomFrameNativeWindow() { - if (GetBrowser()) - return NULL; +void ExtensionTabHelper::OnInlineInstallFailure(const std::string& error) { + Send(new ExtensionMsg_InlineWebstoreInstallResponse( + routing_id(), false, error)); +} - // If there was no browser associated with the function dispatcher delegate, - // then this WebUI may be hosted in an ExternalTabContainer, and a framing - // window will be accessible through the tab_contents. - TabContentsDelegate* tab_contents_delegate = tab_contents()->delegate(); - if (tab_contents_delegate) - return tab_contents_delegate->GetFrameNativeWindow(); - else - return NULL; +TabContents* ExtensionTabHelper::GetAssociatedTabContents() const { + return tab_contents(); } gfx::NativeView ExtensionTabHelper::GetNativeViewOfHost() { diff --git a/chrome/browser/extensions/extension_tab_helper.h b/chrome/browser/extensions/extension_tab_helper.h index b5f1684..1b1b5ad 100644 --- a/chrome/browser/extensions/extension_tab_helper.h +++ b/chrome/browser/extensions/extension_tab_helper.h @@ -9,6 +9,7 @@ #include "content/browser/tab_contents/tab_contents_observer.h" #include "chrome/browser/extensions/extension_function_dispatcher.h" #include "chrome/browser/extensions/image_loading_tracker.h" +#include "chrome/browser/extensions/webstore_inline_installer.h" #include "chrome/common/web_apps.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -23,7 +24,8 @@ struct LoadCommittedDetails; // Per-tab extension helper. Also handles non-extension apps. class ExtensionTabHelper : public TabContentsObserver, public ExtensionFunctionDispatcher::Delegate, - public ImageLoadingTracker::Observer { + public ImageLoadingTracker::Observer, + public WebstoreInlineInstaller::Delegate { public: explicit ExtensionTabHelper(TabContentsWrapper* wrapper); virtual ~ExtensionTabHelper(); @@ -85,13 +87,12 @@ class ExtensionTabHelper : public TabContentsObserver, virtual void DidNavigateMainFramePostCommit( const content::LoadCommittedDetails& details, const ViewHostMsg_FrameNavigate_Params& params) OVERRIDE; - virtual bool OnMessageReceived(const IPC::Message& message); + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; // ExtensionFunctionDispatcher::Delegate overrides. - virtual Browser* GetBrowser(); - virtual gfx::NativeView GetNativeViewOfHost(); - virtual gfx::NativeWindow GetCustomFrameNativeWindow(); - virtual TabContents* GetAssociatedTabContents() const; + virtual Browser* GetBrowser() OVERRIDE; + virtual gfx::NativeView GetNativeViewOfHost() OVERRIDE; + virtual TabContents* GetAssociatedTabContents() const OVERRIDE; // Message handlers. void OnDidGetApplicationInfo(int32 page_id, const WebApplicationInfo& info); @@ -107,7 +108,11 @@ class ExtensionTabHelper : public TabContentsObserver, // ImageLoadingTracker::Observer. virtual void OnImageLoaded(SkBitmap* image, const ExtensionResource& resource, - int index); + int index) OVERRIDE; + + // WebstoreInlineInstaller::Delegate. + virtual void OnInlineInstallSuccess() OVERRIDE; + virtual void OnInlineInstallFailure(const std::string& error) OVERRIDE; // Data for app extensions --------------------------------------------------- diff --git a/chrome/browser/extensions/extension_webstore_private_api.cc b/chrome/browser/extensions/extension_webstore_private_api.cc index c9f8d1c..aacc504 100644 --- a/chrome/browser/extensions/extension_webstore_private_api.cc +++ b/chrome/browser/extensions/extension_webstore_private_api.cc @@ -57,15 +57,6 @@ const char kUserGestureRequiredError[] = ProfileSyncService* test_sync_service = NULL; bool ignore_user_gesture_for_tests = false; -// A flag used for BeginInstallWithManifest::SetAutoConfirmForTests. -enum AutoConfirmForTest { - DO_NOT_SKIP = 0, - PROCEED, - ABORT -}; -AutoConfirmForTest auto_confirm_for_tests = DO_NOT_SKIP; - - // Returns either the test sync service, or the real one from |profile|. ProfileSyncService* GetSyncService(Profile* profile) { if (test_sync_service) @@ -259,62 +250,27 @@ void BeginInstallWithManifestFunction::SetIgnoreUserGestureForTests( ignore_user_gesture_for_tests = ignore; } -void BeginInstallWithManifestFunction::SetAutoConfirmForTests( - bool should_proceed) { - auto_confirm_for_tests = should_proceed ? PROCEED : ABORT; -} - void BeginInstallWithManifestFunction::OnWebstoreParseSuccess( const SkBitmap& icon, DictionaryValue* parsed_manifest) { CHECK(parsed_manifest); icon_ = icon; parsed_manifest_.reset(parsed_manifest); - // If we were passed a localized name to use in the dialog, create a copy - // of the original manifest and replace the name in it. - scoped_ptr<DictionaryValue> localized_manifest; - if (!localized_name_.empty()) { - localized_manifest.reset(parsed_manifest->DeepCopy()); - localized_manifest->SetString(extension_manifest_keys::kName, - localized_name_); - } - - // Create a dummy extension and show the extension install confirmation - // dialog. - std::string init_errors; - dummy_extension_ = Extension::CreateWithId( - FilePath(), - Extension::INTERNAL, - localized_manifest.get() ? *localized_manifest.get() : *parsed_manifest, - Extension::NO_FLAGS, + ShowExtensionInstallDialogForManifest( + profile(), + this, + parsed_manifest, id_, - &init_errors); + localized_name_, + &icon_, + ExtensionInstallUI::INSTALL_PROMPT, + &dummy_extension_); if (!dummy_extension_.get()) { OnWebstoreParseFailure(WebstoreInstallHelper::Delegate::MANIFEST_ERROR, kInvalidManifestError); return; } - if (icon_.empty()) - icon_ = Extension::GetDefaultIcon(dummy_extension_->is_app()); - - // In tests, we may have setup to proceed or abort without putting up the real - // confirmation dialog. - if (auto_confirm_for_tests != DO_NOT_SKIP) { - if (auto_confirm_for_tests == PROCEED) - this->InstallUIProceed(); - else - this->InstallUIAbort(true); - return; - } - - ShowExtensionInstallDialog(profile(), - this, - dummy_extension_.get(), - &icon_, - dummy_extension_->GetPermissionMessageStrings(), - ExtensionInstallUI::INSTALL_PROMPT); - // Control flow finishes up in InstallUIProceed or InstallUIAbort. } diff --git a/chrome/browser/extensions/extension_webstore_private_api.h b/chrome/browser/extensions/extension_webstore_private_api.h index 035311f..8edc6a2 100644 --- a/chrome/browser/extensions/extension_webstore_private_api.h +++ b/chrome/browser/extensions/extension_webstore_private_api.h @@ -78,11 +78,6 @@ class BeginInstallWithManifestFunction // the normal requirement that it is called during a user gesture. static void SetIgnoreUserGestureForTests(bool ignore); - // For use only in tests - sets a flag that makes invocations of - // beginInstallWithManifest skip putting up a real dialog, and instead act - // as if the dialog choice was to proceed or abort. - static void SetAutoConfirmForTests(bool should_proceed); - // Implementing WebstoreInstallHelper::Delegate interface. virtual void OnWebstoreParseSuccess( const SkBitmap& icon, diff --git a/chrome/browser/extensions/extension_webstore_private_apitest.cc b/chrome/browser/extensions/extension_webstore_private_apitest.cc index 52fe488..90b76b2 100644 --- a/chrome/browser/extensions/extension_webstore_private_apitest.cc +++ b/chrome/browser/extensions/extension_webstore_private_apitest.cc @@ -4,6 +4,7 @@ #include "base/stringprintf.h" #include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_install_dialog.h" #include "chrome/browser/extensions/extension_install_ui.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_webstore_private_api.h" @@ -32,7 +33,7 @@ class ExtensionWebstorePrivateApiTest : public ExtensionApiTest { host_resolver()->AddRule("www.example.com", "127.0.0.1"); ASSERT_TRUE(test_server()->Start()); BeginInstallWithManifestFunction::SetIgnoreUserGestureForTests(true); - BeginInstallWithManifestFunction::SetAutoConfirmForTests(true); + SetExtensionInstallDialogForManifestAutoConfirmForTests(true); ExtensionInstallUI::DisableFailureUIForTests(); } @@ -80,7 +81,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebstorePrivateApiTest, InstallLocalized) { // Now test the case where the user cancels the confirmation dialog. IN_PROC_BROWSER_TEST_F(ExtensionWebstorePrivateApiTest, InstallCancelled) { - BeginInstallWithManifestFunction::SetAutoConfirmForTests(false); + SetExtensionInstallDialogForManifestAutoConfirmForTests(false); ASSERT_TRUE(RunInstallTest("cancelled.html", "extension.crx")); } diff --git a/chrome/browser/extensions/webstore_inline_install_browsertest.cc b/chrome/browser/extensions/webstore_inline_install_browsertest.cc index 314256f..e2b5092 100644 --- a/chrome/browser/extensions/webstore_inline_install_browsertest.cc +++ b/chrome/browser/extensions/webstore_inline_install_browsertest.cc @@ -3,72 +3,103 @@ // found in the LICENSE file. #include "base/command_line.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" #include "chrome/browser/extensions/extension_host.h" +#include "chrome/browser/extensions/extension_install_dialog.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/webstore_inline_installer.h" #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/ui/browser.h" +#include "chrome/common/chrome_notification_types.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/common/content_notification_types.h" #include "googleurl/src/gurl.h" +#include "net/base/host_port_pair.h" #include "net/base/mock_host_resolver.h" +const char kWebstoreDomain[] = "cws.com"; +const char kAppDomain[] = "app.com"; + class WebstoreInlineInstallTest : public InProcessBrowserTest { public: virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { EnableDOMAutomation(); + // 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(); + test_gallery_url_ = base::StringPrintf( + "http://%s:%d/files/extensions/api_test/webstore_inline_install", + kWebstoreDomain, host_port.port()); command_line->AppendSwitchASCII( - switches::kAppsGalleryURL, "http://cws.com"); + switches::kAppsGalleryURL, test_gallery_url_); + + GURL crx_url = GenerateTestServerUrl(kWebstoreDomain, "extension.crx"); + CommandLine::ForCurrentProcess()->AppendSwitchASCII( + switches::kAppsGalleryUpdateURL, crx_url.spec()); + command_line->AppendSwitch(switches::kEnableInlineWebstoreInstall); } virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { - host_resolver()->AddRule("cws.com", "127.0.0.1"); - host_resolver()->AddRule("app.com", "127.0.0.1"); - ASSERT_TRUE(test_server()->Start()); + host_resolver()->AddRule(kWebstoreDomain, "127.0.0.1"); + host_resolver()->AddRule(kAppDomain, "127.0.0.1"); } protected: - GURL GetPageUrl(const std::string& page_filename) { + GURL GenerateTestServerUrl(const std::string& domain, + const std::string& page_filename) { GURL page_url = test_server()->GetURL( "files/extensions/api_test/webstore_inline_install/" + page_filename); GURL::Replacements replace_host; - std::string host_str("app.com"); - replace_host.SetHostStr(host_str); + replace_host.SetHostStr(domain); return page_url.ReplaceComponents(replace_host); } + + std::string test_gallery_url_; }; IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallTest, Install) { - ui_test_utils::NavigateToURL(browser(), GetPageUrl("install.html")); + SetExtensionInstallDialogForManifestAutoConfirmForTests(true); + + ui_test_utils::WindowedNotificationObserver load_signal( + chrome::NOTIFICATION_EXTENSION_LOADED, + Source<Profile>(browser()->profile())); + + ui_test_utils::NavigateToURL( + browser(), GenerateTestServerUrl(kAppDomain, "install.html")); bool result = false; + std::string script = StringPrintf("runTest('%s')", test_gallery_url_.c_str()); ASSERT_TRUE(ui_test_utils::ExecuteJavaScriptAndExtractBool( browser()->GetSelectedTabContents()->render_view_host(), L"", - L"runTest()", &result)); + UTF8ToWide(script), &result)); EXPECT_TRUE(result); - // The "inline" UI right now is just the store entry in a new tab. - if (browser()->tabstrip_model()->count() == 1) { - ui_test_utils::WaitForNewTab(browser()); - } + load_signal.Wait(); - TabContents* tab_contents = browser()->GetSelectedTabContents(); - EXPECT_EQ( - GURL("http://cws.com/detail/mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"), - tab_contents->GetURL()); + const Extension* extension = browser()->profile()->GetExtensionService()-> + GetExtensionById("ecglahbcnmdpdciemllbhojghbkagdje", false); + EXPECT_TRUE(extension != NULL); } IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallTest, FindLink) { - ui_test_utils::NavigateToURL(browser(), GetPageUrl("find_link.html")); + ui_test_utils::NavigateToURL( + browser(), GenerateTestServerUrl(kAppDomain, "find_link.html")); bool result = false; + std::string script = StringPrintf("runTest('%s')", test_gallery_url_.c_str()); ASSERT_TRUE(ui_test_utils::ExecuteJavaScriptAndExtractBool( browser()->GetSelectedTabContents()->render_view_host(), L"", - L"runTest()", &result)); + UTF8ToWide(script), &result)); EXPECT_TRUE(result); } diff --git a/chrome/browser/extensions/webstore_inline_installer.cc b/chrome/browser/extensions/webstore_inline_installer.cc new file mode 100644 index 0000000..f13c94f --- /dev/null +++ b/chrome/browser/extensions/webstore_inline_installer.cc @@ -0,0 +1,293 @@ +// 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/extensions/webstore_inline_installer.h" + +#include <vector> + +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/extensions/crx_installer.h" +#include "chrome/browser/extensions/extension_install_dialog.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/chrome_utility_messages.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/browser/utility_process_host.h" +#include "googleurl/src/gurl.h" +#include "net/base/escape.h" +#include "net/url_request/url_request_status.h" + +const char kManifestKey[] = "manifest"; +const char kIconUrlKey[] = "icon_url"; +const char kLocalizedNameKey[] = "localized_name"; + +const char kInvalidWebstoreItemId[] = "Invalid webstore item ID"; +const char kWebstoreRequestError[] = "Could not fetch data from webstore"; +const char kInvalidWebstoreResponseError[] = "Invalid webstore reponse"; +const char kInvalidManifestError[] = "Invalid manifest"; +const char kUserCancelledError[] = "User cancelled install"; + +class SafeWebstoreResponseParser : public UtilityProcessHost::Client { + public: + SafeWebstoreResponseParser(WebstoreInlineInstaller *client, + const std::string& webstore_data) + : client_(client), + webstore_data_(webstore_data), + utility_host_(NULL) {} + + void Start() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + NewRunnableMethod(this, + &SafeWebstoreResponseParser::StartWorkOnIOThread)); + } + + void StartWorkOnIOThread() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + utility_host_ = new UtilityProcessHost(this, BrowserThread::IO); + utility_host_->Send(new ChromeUtilityMsg_ParseJSON(webstore_data_)); + } + + // Implementing pieces of the UtilityProcessHost::Client interface. + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(SafeWebstoreResponseParser, message) + IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Succeeded, + OnJSONParseSucceeded) + IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseJSON_Failed, + OnJSONParseFailed) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; + } + + void OnJSONParseSucceeded(const ListValue& wrapper) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + Value* value = NULL; + CHECK(wrapper.Get(0, &value)); + if (value->IsType(Value::TYPE_DICTIONARY)) { + parsed_webstore_data_.reset( + static_cast<DictionaryValue*>(value)->DeepCopy()); + } else { + error_ = kInvalidWebstoreResponseError; + } + + ReportResults(); + } + + virtual void OnJSONParseFailed(const std::string& error_message) { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + error_ = error_message; + ReportResults(); + } + + void ReportResults() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // The utility_host_ will take care of deleting itself after this call. + utility_host_ = NULL; + + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + NewRunnableMethod(this, + &SafeWebstoreResponseParser::ReportResultOnUIThread)); + } + + void ReportResultOnUIThread() { + CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (error_.empty() && parsed_webstore_data_.get()) { + client_->OnWebstoreResponseParseSuccess(parsed_webstore_data_.release()); + } else { + client_->OnWebstoreResponseParseFailure(error_); + } + } + + private: + virtual ~SafeWebstoreResponseParser() {} + + WebstoreInlineInstaller* client_; + + std::string webstore_data_; + + UtilityProcessHost* utility_host_; + + std::string error_; + scoped_ptr<DictionaryValue> parsed_webstore_data_; +}; + +WebstoreInlineInstaller::WebstoreInlineInstaller(TabContents* tab_contents, + std::string webstore_item_id, + Delegate* delegate) + : tab_contents_(tab_contents), + id_(webstore_item_id), + delegate_(delegate) {} + +WebstoreInlineInstaller::~WebstoreInlineInstaller() { +} + +void WebstoreInlineInstaller::BeginInstall() { + AddRef(); // Balanced in CompleteInstall. + + if (!Extension::IdIsValid(id_)) { + CompleteInstall(kInvalidWebstoreItemId); + return; + } + + GURL webstore_data_url(extension_urls::GetWebstoreItemJsonDataURL(id_)); + + webstore_data_url_fetcher_.reset( + new URLFetcher(webstore_data_url, URLFetcher::GET, this)); + Profile* profile = Profile::FromBrowserContext( + tab_contents_->browser_context()); + webstore_data_url_fetcher_->set_request_context( + profile->GetRequestContext()); + webstore_data_url_fetcher_->Start(); +} + +void WebstoreInlineInstaller::OnURLFetchComplete(const URLFetcher* source) { + CHECK_EQ(webstore_data_url_fetcher_.get(), source); + + if (!webstore_data_url_fetcher_->status().is_success() || + webstore_data_url_fetcher_->response_code() != 200) { + CompleteInstall(kWebstoreRequestError); + return; + } + + std::string webstore_json_data; + webstore_data_url_fetcher_->GetResponseAsString(&webstore_json_data); + webstore_data_url_fetcher_.reset(); + + scoped_refptr<SafeWebstoreResponseParser> parser = + new SafeWebstoreResponseParser(this, webstore_json_data); + // The parser will call us back via OnWebstoreResponseParseSucces or + // OnWebstoreResponseParseFailure. + parser->Start(); +} + +void WebstoreInlineInstaller::OnWebstoreResponseParseSuccess( + DictionaryValue* webstore_data) { + webstore_data_.reset(webstore_data); + + std::string manifest; + if (!webstore_data->GetString(kManifestKey, &manifest)) { + CompleteInstall(kInvalidWebstoreResponseError); + return; + } + + // Localized name is optional. + if (webstore_data->HasKey(kLocalizedNameKey) && + !webstore_data->GetString(kLocalizedNameKey, &localized_name_)) { + CompleteInstall(kInvalidWebstoreResponseError); + return; + } + + // Icon URL is optional. + GURL icon_url; + if (webstore_data->HasKey(kIconUrlKey)) { + std::string icon_url_string; + if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) { + CompleteInstall(kInvalidWebstoreResponseError); + return; + } + icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve( + icon_url_string); + if (!icon_url.is_valid()) { + CompleteInstall(kInvalidWebstoreResponseError); + return; + } + } + + scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper( + this, + manifest, + "", // We don't have any icon data. + icon_url, + Profile::FromBrowserContext(tab_contents_->browser_context())-> + GetRequestContext()); + // The helper will call us back via OnWebstoreParseSucces or + // OnWebstoreParseFailure. + helper->Start(); +} + +void WebstoreInlineInstaller::OnWebstoreResponseParseFailure( + const std::string& error) { + CompleteInstall(error); +} + +void WebstoreInlineInstaller::OnWebstoreParseSuccess( + const SkBitmap& icon, + base::DictionaryValue* manifest) { + manifest_.reset(manifest); + icon_ = icon; + + Profile* profile = Profile::FromBrowserContext( + tab_contents_->browser_context()); + scoped_refptr<Extension> dummy_extension; + ShowExtensionInstallDialogForManifest(profile, + this, + manifest, + id_, + localized_name_, + &icon_, + ExtensionInstallUI::INSTALL_PROMPT, + &dummy_extension); + + if (!dummy_extension.get()) { + CompleteInstall(kInvalidManifestError); + return; + } + + // Control flow finishes up in InstallUIProceed or InstallUIAbort. +} + +void WebstoreInlineInstaller::OnWebstoreParseFailure( + InstallHelperResultCode result_code, + const std::string& error_message) { + CompleteInstall(error_message); +} + +void WebstoreInlineInstaller::InstallUIProceed() { + CrxInstaller::WhitelistEntry* entry = new CrxInstaller::WhitelistEntry; + + entry->parsed_manifest.reset(manifest_.get()->DeepCopy()); + entry->localized_name = localized_name_; + entry->use_app_installed_bubble = true; + CrxInstaller::SetWhitelistEntry(id_, entry); + + GURL install_url(extension_urls::GetWebstoreInstallUrl( + id_, g_browser_process->GetApplicationLocale())); + + NavigationController& controller = tab_contents_->controller(); + // TODO(mihaip): we pretend like the referrer is the gallery in order to pass + // the checks in ExtensionService::IsDownloadFromGallery. We should instead + // pass the real referrer, track that this is an inline install in the + // whitelist entry and look that up when checking that this is a valid + // download. + GURL referrer(extension_urls::GetWebstoreItemDetailURLPrefix() + id_); + controller.LoadURL(install_url, referrer, PageTransition::LINK); + + // TODO(mihaip): the success message should happen later, when the extension + // is actually downloaded and installed (when NOTIFICATION_EXTENSION_INSTALLED + // or NOTIFICATION_EXTENSION_INSTALL_ERROR fire). + CompleteInstall(""); +} + +void WebstoreInlineInstaller::InstallUIAbort(bool user_initiated) { + CompleteInstall(kUserCancelledError); +} + +void WebstoreInlineInstaller::CompleteInstall(const std::string& error) { + if (error.empty()) { + delegate_->OnInlineInstallSuccess(); + } else { + delegate_->OnInlineInstallFailure(error); + } + Release(); // Matches the AddRef in BeginInstall. +} diff --git a/chrome/browser/extensions/webstore_inline_installer.h b/chrome/browser/extensions/webstore_inline_installer.h new file mode 100644 index 0000000..8d0606c --- /dev/null +++ b/chrome/browser/extensions/webstore_inline_installer.h @@ -0,0 +1,100 @@ +// 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_EXTENSIONS_WEBSTORE_INLINE_INSTALLER_H_ +#define CHROME_BROWSER_EXTENSIONS_WEBSTORE_INLINE_INSTALLER_H_ +#pragma once + +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "chrome/browser/extensions/extension_install_ui.h" +#include "chrome/browser/extensions/webstore_install_helper.h" +#include "content/common/url_fetcher.h" +#include "third_party/skia/include/core/SkBitmap.h" + +class TabContents; +class SafeWebstoreResponseParser; + +// Manages inline installs requested by a page (downloads and parses metadata +// from the webstore, shows the install UI, starts the download once the user +// confirms). Clients must implement the WebstoreInlineInstaller::Delegate +// interface to be notified when the inline install completes (successfully or +// not). +class WebstoreInlineInstaller + : public base::RefCountedThreadSafe<WebstoreInlineInstaller>, + public ExtensionInstallUI::Delegate, + public URLFetcher::Delegate, + public WebstoreInstallHelper::Delegate { + public: + class Delegate { + public: + virtual void OnInlineInstallSuccess() = 0; + virtual void OnInlineInstallFailure(const std::string& error) = 0; + }; + + WebstoreInlineInstaller(TabContents* tab_contents, + std::string webstore_item_id, + Delegate* d); + void BeginInstall(); + + private: + friend class base::RefCountedThreadSafe<WebstoreInlineInstaller>; + friend class SafeWebstoreResponseParser; + + virtual ~WebstoreInlineInstaller(); + + // Several delegate/client inteface implementations follow. The normal flow + // (for successful installs) is: + // + // 1. BeginInstall: starts the fetch of data from the webstore + // 2. OnURLFetchComplete: starts the parsing of data from the webstore + // 3. OnWebstoreResponseParseSuccess: starts the parsing of the manifest and + // fetching of icon data. + // 4. OnWebstoreParseSuccess: shows the install UI + // 5. InstallUIProceed: initiates the .crx download/install + // + // All flows (whether successful or not) end up in CompleteInstall, which + // informs our delegate of success/failure. + + // UrlFetcher::Delegate interface implementation. + virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE; + + // Client callbacks for SafeWebstoreResponseParser when parsing is complete. + void OnWebstoreResponseParseSuccess(DictionaryValue* webstore_data); + void OnWebstoreResponseParseFailure(const std::string& error); + + // WebstoreInstallHelper::Delegate interface implementation. + virtual void OnWebstoreParseSuccess( + const SkBitmap& icon, + base::DictionaryValue* parsed_manifest) OVERRIDE; + virtual void OnWebstoreParseFailure( + InstallHelperResultCode result_code, + const std::string& error_message) OVERRIDE; + + // ExtensionInstallUI::Delegate interface implementation. + virtual void InstallUIProceed() OVERRIDE; + virtual void InstallUIAbort(bool user_initiated) OVERRIDE; + + void CompleteInstall(const std::string& error); + + TabContents* tab_contents_; + std::string id_; + Delegate* delegate_; + + // For fetching webstore JSON data. + scoped_ptr<URLFetcher> webstore_data_url_fetcher_; + + // Extracted from the webstore JSON data response. + std::string localized_name_; + scoped_ptr<DictionaryValue> webstore_data_; + scoped_ptr<DictionaryValue> manifest_; + SkBitmap icon_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(WebstoreInlineInstaller); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_WEBSTORE_INLINE_INSTALLER_H_ diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 6cb181c..393d983 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -995,6 +995,7 @@ 'browser/extensions/extension_input_module_constants.h', 'browser/extensions/extension_input_ui_api.cc', 'browser/extensions/extension_input_ui_api.h', + 'browser/extensions/extension_install_dialog.cc', 'browser/extensions/extension_install_dialog.h', 'browser/extensions/extension_install_ui.cc', 'browser/extensions/extension_install_ui.h', @@ -1155,6 +1156,8 @@ 'browser/extensions/user_script_listener.h', 'browser/extensions/user_script_master.cc', 'browser/extensions/user_script_master.h', + 'browser/extensions/webstore_inline_installer.cc', + 'browser/extensions/webstore_inline_installer.h', 'browser/extensions/webstore_install_helper.cc', 'browser/extensions/webstore_install_helper.h', 'browser/external_protocol/external_protocol_handler.cc', diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc index 19b9632..96289ea 100644 --- a/chrome/common/extensions/extension_constants.cc +++ b/chrome/common/extensions/extension_constants.cc @@ -425,6 +425,10 @@ std::string GetWebstoreItemDetailURLPrefix() { return GetWebstoreLaunchURL() + "/detail/"; } +GURL GetWebstoreItemJsonDataURL(const std::string& extension_id) { + return GURL(GetWebstoreItemDetailURLPrefix() + extension_id + "?output=json"); +} + const char* kGalleryUpdateHttpUrl = "http://clients2.google.com/service/update2/crx"; const char* kGalleryUpdateHttpsUrl = diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h index 925b38f..ba14dd5 100644 --- a/chrome/common/extensions/extension_constants.h +++ b/chrome/common/extensions/extension_constants.h @@ -281,6 +281,10 @@ namespace extension_urls { // to get the item detail URL. std::string GetWebstoreItemDetailURLPrefix(); + // Returns the URL used to get webstore data (ratings, manifest, icon URL, + // etc.) about an extension from the webstore as JSON. + GURL GetWebstoreItemJsonDataURL(const std::string& extension_id); + // Return the update URL used by gallery/webstore extensions/apps. The // |secure| parameter will be ignored if the update URL is overriden with // --apps-gallery-update-url. diff --git a/chrome/common/extensions/extension_messages.h b/chrome/common/extensions/extension_messages.h index df009b4..f09841a 100644 --- a/chrome/common/extensions/extension_messages.h +++ b/chrome/common/extensions/extension_messages.h @@ -317,3 +317,8 @@ IPC_MESSAGE_ROUTED1(ExtensionHostMsg_InstallApplication, // Sent by the renderer to implement chrome.webstore.install(). IPC_MESSAGE_ROUTED1(ExtensionHostMsg_InlineWebstoreInstall, std::string /* Web Store item ID */) + +// Send to renderer once the installation mentioned above is complete. +IPC_MESSAGE_ROUTED2(ExtensionMsg_InlineWebstoreInstallResponse, + bool /* whether the install was successful */, + std::string /* error */) diff --git a/chrome/renderer/extensions/extension_helper.cc b/chrome/renderer/extensions/extension_helper.cc index 506e4aa..a3df162c 100644 --- a/chrome/renderer/extensions/extension_helper.cc +++ b/chrome/renderer/extensions/extension_helper.cc @@ -107,6 +107,18 @@ void ExtensionHelper::InlineWebstoreInstall(std::string webstore_item_id) { routing_id(), webstore_item_id)); } +void ExtensionHelper::OnInlineWebstoreInstallResponse( + bool success, + const std::string& error) { + // TODO(mihaip): dispatch these as events to the the WebFrame that initiated + // the inline install. + if (success) { + VLOG(1) << "Inline install succeeded."; + } else { + VLOG(1) << "Inline install failed: " << error; + } +} + bool ExtensionHelper::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(ExtensionHelper, message) @@ -118,6 +130,8 @@ bool ExtensionHelper::OnMessageReceived(const IPC::Message& message) { OnUpdateBrowserWindowId) IPC_MESSAGE_HANDLER(ExtensionMsg_NotifyRenderViewType, OnNotifyRendererViewType) + IPC_MESSAGE_HANDLER(ExtensionMsg_InlineWebstoreInstallResponse, + OnInlineWebstoreInstallResponse) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; diff --git a/chrome/renderer/extensions/extension_helper.h b/chrome/renderer/extensions/extension_helper.h index caffc17..fa1fb61 100644 --- a/chrome/renderer/extensions/extension_helper.h +++ b/chrome/renderer/extensions/extension_helper.h @@ -68,6 +68,7 @@ class ExtensionHelper : public RenderViewObserver, void OnGetApplicationInfo(int page_id); void OnNotifyRendererViewType(ViewType::Type view_type); void OnUpdateBrowserWindowId(int window_id); + void OnInlineWebstoreInstallResponse(bool success, const std::string& error); // Callback triggered when we finish downloading the application definition // file. diff --git a/chrome/test/data/extensions/api_test/webstore_inline_install/ecglahbcnmdpdciemllbhojghbkagdje b/chrome/test/data/extensions/api_test/webstore_inline_install/detail/ecglahbcnmdpdciemllbhojghbkagdje index 2782241..2782241 100644 --- a/chrome/test/data/extensions/api_test/webstore_inline_install/ecglahbcnmdpdciemllbhojghbkagdje +++ b/chrome/test/data/extensions/api_test/webstore_inline_install/detail/ecglahbcnmdpdciemllbhojghbkagdje diff --git a/chrome/test/data/extensions/api_test/webstore_inline_install/find_link.html b/chrome/test/data/extensions/api_test/webstore_inline_install/find_link.html index 32efae4..95710d2 100644 --- a/chrome/test/data/extensions/api_test/webstore_inline_install/find_link.html +++ b/chrome/test/data/extensions/api_test/webstore_inline_install/find_link.html @@ -21,7 +21,7 @@ } } - function runTest() { + function runTest(galleryUrl) { // Definitely no link. checkNoLinkFound(NO_LINK_EXCEPTION); @@ -32,7 +32,7 @@ // Wrong type, right URL. linkNode.rel = 'stylesheet'; - linkNode.href = 'http://cws.com/detail/mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm'; + linkNode.href = galleryUrl + '/detail/mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm'; checkNoLinkFound(NO_LINK_EXCEPTION); // Right type, wrong URL. @@ -42,17 +42,17 @@ // Non-item CWS URL linkNode.href = - 'http://cws.com/someotherpage/mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm'; + galleryUrl + '/someotherpage/mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm'; checkNoLinkFound(INVALID_URL_EXCEPTION); // Invalid ID linkNode.rel = 'chrome-webstore-item'; - linkNode.href = 'http://cws.com/detail/abc'; + linkNode.href = galleryUrl + '/detail/abc'; checkNoLinkFound(INVALID_URL_EXCEPTION); // Extra CWS URL parameters linkNode.href = - 'http://cws.com/detail/mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm?foo=bar'; + galleryUrl + '/detail/mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm?foo=bar'; checkNoLinkFound(INVALID_URL_EXCEPTION); // Successful installation is tested elsewhere diff --git a/chrome/test/data/extensions/api_test/webstore_inline_install/install.html b/chrome/test/data/extensions/api_test/webstore_inline_install/install.html index 2c7278b..73121f2 100644 --- a/chrome/test/data/extensions/api_test/webstore_inline_install/install.html +++ b/chrome/test/data/extensions/api_test/webstore_inline_install/install.html @@ -1,12 +1,22 @@ <!DOCTYPE html> <html> <head> - <link rel="chrome-webstore-item" href="http://cws.com/detail/mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"> + <link rel="chrome-webstore-item"> </head> <body> <script> - function runTest() { - chrome.webstore.install(); + function runTest(galleryUrl) { + // Link URL has to be generated dynamically in order to include the right + // port number. The ID corresponds to the data in the "extension" directory. + document.getElementsByTagName('link')[0].href = + galleryUrl + '/detail/ecglahbcnmdpdciemllbhojghbkagdje'; + + try { + chrome.webstore.install(); + } catch (e) { + window.domAutomationController.send(false); + throw e; + } window.domAutomationController.send(true); } </script> |