diff options
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> |