summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/extensions/extension_install_dialog.cc74
-rw-r--r--chrome/browser/extensions/extension_install_dialog.h26
-rw-r--r--chrome/browser/extensions/extension_tab_helper.cc34
-rw-r--r--chrome/browser/extensions/extension_tab_helper.h19
-rw-r--r--chrome/browser/extensions/extension_webstore_private_api.cc60
-rw-r--r--chrome/browser/extensions/extension_webstore_private_api.h5
-rw-r--r--chrome/browser/extensions/extension_webstore_private_apitest.cc5
-rw-r--r--chrome/browser/extensions/webstore_inline_install_browsertest.cc69
-rw-r--r--chrome/browser/extensions/webstore_inline_installer.cc293
-rw-r--r--chrome/browser/extensions/webstore_inline_installer.h100
-rw-r--r--chrome/chrome_browser.gypi3
-rw-r--r--chrome/common/extensions/extension_constants.cc4
-rw-r--r--chrome/common/extensions/extension_constants.h4
-rw-r--r--chrome/common/extensions/extension_messages.h5
-rw-r--r--chrome/renderer/extensions/extension_helper.cc14
-rw-r--r--chrome/renderer/extensions/extension_helper.h1
-rw-r--r--chrome/test/data/extensions/api_test/webstore_inline_install/detail/ecglahbcnmdpdciemllbhojghbkagdje (renamed from chrome/test/data/extensions/api_test/webstore_inline_install/ecglahbcnmdpdciemllbhojghbkagdje)0
-rw-r--r--chrome/test/data/extensions/api_test/webstore_inline_install/find_link.html10
-rw-r--r--chrome/test/data/extensions/api_test/webstore_inline_install/install.html16
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>