diff options
28 files changed, 804 insertions, 110 deletions
diff --git a/chrome/browser/extensions/api/webstore/webstore_api.cc b/chrome/browser/extensions/api/webstore/webstore_api.cc new file mode 100644 index 0000000..a0def2a --- /dev/null +++ b/chrome/browser/extensions/api/webstore/webstore_api.cc @@ -0,0 +1,142 @@ +// Copyright 2014 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/api/webstore/webstore_api.h" + +#include "base/lazy_instance.h" +#include "base/values.h" +#include "chrome/browser/extensions/install_tracker.h" +#include "chrome/browser/extensions/install_tracker_factory.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/extensions/chrome_extension_messages.h" +#include "extensions/browser/extension_system.h" +#include "ipc/ipc_sender.h" + +namespace extensions { + +namespace { + +base::LazyInstance<BrowserContextKeyedAPIFactory<WebstoreAPI> > g_factory = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +struct WebstoreAPI::ObservedInstallInfo { + ObservedInstallInfo(int routing_id, + const std::string& extension_id, + IPC::Sender* ipc_sender); + ~ObservedInstallInfo(); + + int routing_id; + std::string extension_id; + IPC::Sender* ipc_sender; +}; + +WebstoreAPI::ObservedInstallInfo::ObservedInstallInfo( + int routing_id, + const std::string& extension_id, + IPC::Sender* ipc_sender) + : routing_id(routing_id), + extension_id(extension_id), + ipc_sender(ipc_sender) {} + +WebstoreAPI::ObservedInstallInfo::~ObservedInstallInfo() {} + +WebstoreAPI::WebstoreAPI(content::BrowserContext* browser_context) + : browser_context_(browser_context), + install_observer_( + new ScopedObserver<InstallTracker, InstallObserver>(this)) { + install_observer_->Add(InstallTrackerFactory::GetForProfile( + Profile::FromBrowserContext(browser_context))); +} + +WebstoreAPI::~WebstoreAPI() {} + +// static +WebstoreAPI* WebstoreAPI::Get(content::BrowserContext* browser_context) { + return BrowserContextKeyedAPIFactory<WebstoreAPI>::Get(browser_context); +} + +void WebstoreAPI::OnInlineInstallStart(int routing_id, + IPC::Sender* ipc_sender, + const std::string& extension_id, + int listeners_mask) { + if (listeners_mask & api::webstore::INSTALL_STAGE_LISTENER) { + install_stage_listeners_.push_back( + ObservedInstallInfo(routing_id, extension_id, ipc_sender)); + } + + if (listeners_mask & api::webstore::DOWNLOAD_PROGRESS_LISTENER) { + download_progress_listeners_.push_back( + ObservedInstallInfo(routing_id, extension_id, ipc_sender)); + } +} + +void WebstoreAPI::OnInlineInstallFinished(int routing_id, + const std::string& extension_id) { + RemoveListeners(routing_id, extension_id, &download_progress_listeners_); + RemoveListeners(routing_id, extension_id, &install_stage_listeners_); +} + +void WebstoreAPI::OnBeginExtensionDownload(const std::string& extension_id) { + SendInstallMessageIfObserved(extension_id, + api::webstore::INSTALL_STAGE_DOWNLOADING); +} + +void WebstoreAPI::OnDownloadProgress(const std::string& extension_id, + int percent_downloaded) { + for (ObservedInstallInfoList::const_iterator iter = + download_progress_listeners_.begin(); + iter != download_progress_listeners_.end(); + ++iter) { + if (iter->extension_id == extension_id) { + iter->ipc_sender->Send(new ExtensionMsg_InlineInstallDownloadProgress( + iter->routing_id, percent_downloaded)); + } + } +} + +void WebstoreAPI::OnBeginCrxInstall(const std::string& extension_id) { + SendInstallMessageIfObserved(extension_id, + api::webstore::INSTALL_STAGE_INSTALLING); +} + +void WebstoreAPI::OnShutdown() { + install_observer_.reset(); +} + +void WebstoreAPI::Shutdown() {} + +// static +BrowserContextKeyedAPIFactory<WebstoreAPI>* WebstoreAPI::GetFactoryInstance() { + return g_factory.Pointer(); +} + +void WebstoreAPI::SendInstallMessageIfObserved( + const std::string& extension_id, + api::webstore::InstallStage install_stage) { + for (ObservedInstallInfoList::const_iterator iter = + install_stage_listeners_.begin(); + iter != install_stage_listeners_.end(); + ++iter) { + if (iter->extension_id == extension_id) { + iter->ipc_sender->Send(new ExtensionMsg_InlineInstallStageChanged( + iter->routing_id, install_stage)); + } + } +} + +void WebstoreAPI::RemoveListeners(int routing_id, + const std::string& extension_id, + ObservedInstallInfoList* listeners) { + for (ObservedInstallInfoList::iterator iter = listeners->begin(); + iter != listeners->end();) { + if (iter->extension_id == extension_id && iter->routing_id == routing_id) + iter = listeners->erase(iter); + else + ++iter; + } +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/webstore/webstore_api.h b/chrome/browser/extensions/api/webstore/webstore_api.h new file mode 100644 index 0000000..e345491 --- /dev/null +++ b/chrome/browser/extensions/api/webstore/webstore_api.h @@ -0,0 +1,104 @@ +// Copyright 2014 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_API_WEBSTORE_WEBSTORE_API_H_ +#define CHROME_BROWSER_EXTENSIONS_API_WEBSTORE_WEBSTORE_API_H_ + +#include <list> +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/scoped_observer.h" +#include "chrome/browser/extensions/install_observer.h" +#include "chrome/common/extensions/api/webstore/webstore_api_constants.h" +#include "extensions/browser/browser_context_keyed_api_factory.h" +#include "extensions/browser/event_router.h" + +namespace base { +class ListValue; +} + +namespace content { +class BrowserContext; +} + +namespace ipc { +class Sender; +} + +namespace extensions { +class InstallTracker; + +class WebstoreAPI : public BrowserContextKeyedAPI, + public InstallObserver { + public: + explicit WebstoreAPI(content::BrowserContext* browser_context); + virtual ~WebstoreAPI(); + + static WebstoreAPI* Get(content::BrowserContext* browser_context); + + // Called whenever an inline extension install is started. Examines + // |listener_mask| to determine if a download progress or install + // stage listener should be added. + // |routing_id| refers to the id to which we send any return messages; + // |ipc_sender| is the sender through which we send them (typically this + // is the TabHelper which started the inline install). + void OnInlineInstallStart(int routing_id, + IPC::Sender* ipc_sender, + const std::string& extension_id, + int listener_mask); + + // Called when an inline extension install finishes. Removes any listeners + // related to the |routing_id|-|extension_id| pair. + void OnInlineInstallFinished(int routing_id, const std::string& extension_id); + + // BrowserContextKeyedAPI implementation. + static BrowserContextKeyedAPIFactory<WebstoreAPI>* GetFactoryInstance(); + + private: + friend class BrowserContextKeyedAPIFactory<WebstoreAPI>; + + // A simple struct to hold our listeners' information for each observed + // install. + struct ObservedInstallInfo; + typedef std::list<ObservedInstallInfo> ObservedInstallInfoList; + + // Sends an installation stage update message if we are observing + // the extension's install. + void SendInstallMessageIfObserved(const std::string& extension_id, + api::webstore::InstallStage install_stage); + + // Removes listeners for the given |extension_id|-|routing_id| pair from + // |listeners|. + void RemoveListeners(int routing_id, + const std::string& extension_id, + ObservedInstallInfoList* listeners); + + // InstallObserver implementation. + virtual void OnBeginExtensionDownload(const std::string& extension_id) + OVERRIDE; + virtual void OnDownloadProgress(const std::string& extension_id, + int percent_downloaded) OVERRIDE; + virtual void OnBeginCrxInstall(const std::string& extension_id) OVERRIDE; + virtual void OnShutdown() OVERRIDE; + + // BrowserContextKeyedService implementation. + virtual void Shutdown() OVERRIDE; + + // BrowserContextKeyedAPI implementation. + static const char* service_name() { return "WebstoreAPI"; } + static const bool kServiceIsNULLWhileTesting = true; + + ObservedInstallInfoList download_progress_listeners_; + ObservedInstallInfoList install_stage_listeners_; + content::BrowserContext* browser_context_; + scoped_ptr<ScopedObserver<InstallTracker, InstallObserver> > + install_observer_; + + DISALLOW_COPY_AND_ASSIGN(WebstoreAPI); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_WEBSTORE_WEBSTORE_API_H_ diff --git a/chrome/browser/extensions/browser_context_keyed_service_factories.cc b/chrome/browser/extensions/browser_context_keyed_service_factories.cc index 32010e3..7a4a27d 100644 --- a/chrome/browser/extensions/browser_context_keyed_service_factories.cc +++ b/chrome/browser/extensions/browser_context_keyed_service_factories.cc @@ -46,6 +46,7 @@ #include "chrome/browser/extensions/api/web_navigation/web_navigation_api.h" #include "chrome/browser/extensions/api/web_request/web_request_api.h" #include "chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h" +#include "chrome/browser/extensions/api/webstore/webstore_api.h" #include "chrome/browser/extensions/extension_gcm_app_handler.h" #include "chrome/browser/extensions/extension_system_factory.h" #include "chrome/browser/extensions/extension_toolbar_model_factory.h" @@ -144,6 +145,7 @@ void EnsureBrowserContextKeyedServiceFactoriesBuilt() { extensions::WebNavigationAPI::GetFactoryInstance(); extensions::WebRequestAPI::GetFactoryInstance(); extensions::WebrtcAudioPrivateEventService::GetFactoryInstance(); + extensions::WebstoreAPI::GetFactoryInstance(); #if defined(OS_CHROMEOS) file_manager::FileBrowserPrivateAPIFactory::GetInstance(); #endif diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc index 7e03d36..09af468 100644 --- a/chrome/browser/extensions/crx_installer.cc +++ b/chrome/browser/extensions/crx_installer.cc @@ -28,6 +28,8 @@ #include "chrome/browser/extensions/extension_error_reporter.h" #include "chrome/browser/extensions/extension_install_ui.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/install_tracker.h" +#include "chrome/browser/extensions/install_tracker_factory.h" #include "chrome/browser/extensions/permissions_updater.h" #include "chrome/browser/extensions/webstore_installer.h" #include "chrome/browser/profiles/profile.h" @@ -176,6 +178,9 @@ void CrxInstaller::InstallCrx(const base::FilePath& source_file) { if (!service || service->browser_terminating()) return; + InstallTrackerFactory::GetForProfile(profile()) + ->OnBeginCrxInstall(expected_id_); + source_file_ = source_file; scoped_refptr<SandboxedUnpacker> unpacker( diff --git a/chrome/browser/extensions/install_observer.cc b/chrome/browser/extensions/install_observer.cc index d713a09..37c7565 100644 --- a/chrome/browser/extensions/install_observer.cc +++ b/chrome/browser/extensions/install_observer.cc @@ -19,19 +19,4 @@ InstallObserver::ExtensionInstallParams::ExtensionInstallParams( is_platform_app(is_platform_app), is_ephemeral(false) {} -void InstallObserver::OnBeginExtensionInstall( - const ExtensionInstallParams& params) {} -void InstallObserver::OnDownloadProgress(const std::string& extension_id, - int percent_downloaded) {} -void InstallObserver::OnInstallFailure(const std::string& extension_id) {} -void InstallObserver::OnExtensionInstalled(const Extension* extension) {} -void InstallObserver::OnExtensionLoaded(const Extension* extension) {} -void InstallObserver::OnExtensionUnloaded(const Extension* extension) {} -void InstallObserver::OnExtensionUninstalled(const Extension* extension) {} -void InstallObserver::OnDisabledExtensionUpdated(const Extension* extension) {} -void InstallObserver::OnAppInstalledToAppList(const std::string& extension_id) { -} -void InstallObserver::OnAppsReordered() {} -void InstallObserver::OnShutdown() {} - } // namespace extensions diff --git a/chrome/browser/extensions/install_observer.h b/chrome/browser/extensions/install_observer.h index 72a7a09..1823170 100644 --- a/chrome/browser/extensions/install_observer.h +++ b/chrome/browser/extensions/install_observer.h @@ -33,35 +33,44 @@ class InstallObserver { // Called at the beginning of the complete installation process, i.e., this // is called before the extension download begins. - virtual void OnBeginExtensionInstall(const ExtensionInstallParams& params); + virtual void OnBeginExtensionInstall(const ExtensionInstallParams& params) {} + + // Called when the Extension begins the download process. This typically + // happens right after OnBeginExtensionInstall(), unless the extension has + // already been downloaded. + virtual void OnBeginExtensionDownload(const std::string& extension_id) {} // Called whenever the extension download is updated. // Note: Some extensions have multiple modules, so the percent included here // is a simple calculation of: // (finished_files * 100 + current_file_progress) / (total files * 100). virtual void OnDownloadProgress(const std::string& extension_id, - int percent_downloaded); + int percent_downloaded) {} + + // Called when the necessary downloads have completed, and the crx + // installation is due to start. + virtual void OnBeginCrxInstall(const std::string& extension_id) {} // Called if the extension fails to install. - virtual void OnInstallFailure(const std::string& extension_id); + virtual void OnInstallFailure(const std::string& extension_id) {} // Called if the installation succeeds. - virtual void OnExtensionInstalled(const Extension* extension); + virtual void OnExtensionInstalled(const Extension* extension) {} // Called when an extension is [Loaded, Unloaded, Uninstalled] or an app is // installed to the app list. These are simply forwarded from the // chrome::NOTIFICATIONs. - virtual void OnExtensionLoaded(const Extension* extension); - virtual void OnExtensionUnloaded(const Extension* extension); - virtual void OnExtensionUninstalled(const Extension* extension); - virtual void OnDisabledExtensionUpdated(const Extension* extension); - virtual void OnAppInstalledToAppList(const std::string& extension_id); + virtual void OnExtensionLoaded(const Extension* extension) {} + virtual void OnExtensionUnloaded(const Extension* extension) {} + virtual void OnExtensionUninstalled(const Extension* extension) {} + virtual void OnDisabledExtensionUpdated(const Extension* extension) {} + virtual void OnAppInstalledToAppList(const std::string& extension_id) {} // Called when the app list is reordered. - virtual void OnAppsReordered(); + virtual void OnAppsReordered() {} // Notifies observers that the observed object is going away. - virtual void OnShutdown(); + virtual void OnShutdown() {} protected: virtual ~InstallObserver() {} diff --git a/chrome/browser/extensions/install_tracker.cc b/chrome/browser/extensions/install_tracker.cc index 0c79c1c..fc5b504 100644 --- a/chrome/browser/extensions/install_tracker.cc +++ b/chrome/browser/extensions/install_tracker.cc @@ -56,12 +56,22 @@ void InstallTracker::OnBeginExtensionInstall( OnBeginExtensionInstall(params)); } +void InstallTracker::OnBeginExtensionDownload(const std::string& extension_id) { + FOR_EACH_OBSERVER( + InstallObserver, observers_, OnBeginExtensionDownload(extension_id)); +} + void InstallTracker::OnDownloadProgress(const std::string& extension_id, int percent_downloaded) { FOR_EACH_OBSERVER(InstallObserver, observers_, OnDownloadProgress(extension_id, percent_downloaded)); } +void InstallTracker::OnBeginCrxInstall(const std::string& extension_id) { + FOR_EACH_OBSERVER( + InstallObserver, observers_, OnBeginCrxInstall(extension_id)); +} + void InstallTracker::OnInstallFailure( const std::string& extension_id) { FOR_EACH_OBSERVER(InstallObserver, observers_, diff --git a/chrome/browser/extensions/install_tracker.h b/chrome/browser/extensions/install_tracker.h index 3997370..d3101d2 100644 --- a/chrome/browser/extensions/install_tracker.h +++ b/chrome/browser/extensions/install_tracker.h @@ -30,8 +30,10 @@ class InstallTracker : public KeyedService, void OnBeginExtensionInstall( const InstallObserver::ExtensionInstallParams& params); + void OnBeginExtensionDownload(const std::string& extension_id); void OnDownloadProgress(const std::string& extension_id, int percent_downloaded); + void OnBeginCrxInstall(const std::string& extension_id); void OnInstallFailure(const std::string& extension_id); // Overriddes for KeyedService: diff --git a/chrome/browser/extensions/tab_helper.cc b/chrome/browser/extensions/tab_helper.cc index 9eacd19..16eed18 100644 --- a/chrome/browser/extensions/tab_helper.cc +++ b/chrome/browser/extensions/tab_helper.cc @@ -12,7 +12,9 @@ #include "chrome/browser/extensions/activity_log/activity_log.h" #include "chrome/browser/extensions/api/declarative/rules_registry_service.h" #include "chrome/browser/extensions/api/declarative_content/content_rules_registry.h" +#include "chrome/browser/extensions/api/webstore/webstore_api.h" #include "chrome/browser/extensions/bookmark_app_helper.h" +#include "chrome/browser/extensions/crx_installer.h" #include "chrome/browser/extensions/error_console/error_console.h" #include "chrome/browser/extensions/extension_action.h" #include "chrome/browser/extensions/extension_action_manager.h" @@ -362,11 +364,27 @@ void TabHelper::OnDidGetApplicationInfo(int32 page_id, #endif } -void TabHelper::OnInlineWebstoreInstall( - int install_id, - int return_route_id, - const std::string& webstore_item_id, - const GURL& requestor_url) { +void TabHelper::OnInlineWebstoreInstall(int install_id, + int return_route_id, + const std::string& webstore_item_id, + const GURL& requestor_url, + int listeners_mask) { +#if defined(ENABLE_EXTENSIONS) + // Check that the listener is reasonable. We should never get anything other + // than an install stage listener, a download listener, or both. + if ((listeners_mask & ~(api::webstore::INSTALL_STAGE_LISTENER | + api::webstore::DOWNLOAD_PROGRESS_LISTENER)) != 0) { + NOTREACHED(); + return; + } + // Inform the Webstore API that an inline install is happening, in case the + // page requested status updates. + Profile* profile = + Profile::FromBrowserContext(web_contents()->GetBrowserContext()); + WebstoreAPI::Get(profile)->OnInlineInstallStart( + return_route_id, this, webstore_item_id, listeners_mask); +#endif + WebstoreStandaloneInstaller::Callback callback = base::Bind(&TabHelper::OnInlineInstallComplete, base::Unretained(this), install_id, return_route_id); diff --git a/chrome/browser/extensions/tab_helper.h b/chrome/browser/extensions/tab_helper.h index 42603de..dcfa66b 100644 --- a/chrome/browser/extensions/tab_helper.h +++ b/chrome/browser/extensions/tab_helper.h @@ -191,7 +191,8 @@ class TabHelper : public content::WebContentsObserver, void OnInlineWebstoreInstall(int install_id, int return_route_id, const std::string& webstore_item_id, - const GURL& requestor_url); + const GURL& requestor_url, + int listeners_mask); void OnGetAppInstallState(const GURL& requestor_url, int return_route_id, int callback_id); diff --git a/chrome/browser/extensions/webstore_inline_installer_browsertest.cc b/chrome/browser/extensions/webstore_inline_installer_browsertest.cc index 60b6aab..921cb61 100644 --- a/chrome/browser/extensions/webstore_inline_installer_browsertest.cc +++ b/chrome/browser/extensions/webstore_inline_installer_browsertest.cc @@ -167,4 +167,33 @@ IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest, ASSERT_TRUE(extension_service->IsExtensionEnabled(kTestExtensionId)); } +class WebstoreInlineInstallerListenerTest : public WebstoreInlineInstallerTest { + public: + WebstoreInlineInstallerListenerTest() {} + virtual ~WebstoreInlineInstallerListenerTest() {} + + protected: + void RunTest(const std::string& file_name) { + CommandLine::ForCurrentProcess()->AppendSwitchASCII( + switches::kAppsGalleryInstallAutoConfirmForTests, "accept"); + ui_test_utils::NavigateToURL(browser(), + GenerateTestServerUrl(kAppDomain, file_name)); + WebstoreInstallerTest::RunTest("runTest"); + } +}; + +IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerListenerTest, + InstallStageListenerTest) { + RunTest("install_stage_listener.html"); +} + +IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerListenerTest, + DownloadProgressListenerTest) { + RunTest("download_progress_listener.html"); +} + +IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerListenerTest, BothListenersTest) { + RunTest("both_listeners.html"); +} + } // namespace extensions diff --git a/chrome/browser/extensions/webstore_installer.cc b/chrome/browser/extensions/webstore_installer.cc index 5442408..a780425 100644 --- a/chrome/browser/extensions/webstore_installer.cc +++ b/chrome/browser/extensions/webstore_installer.cc @@ -19,6 +19,7 @@ #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/download/download_crx_util.h" #include "chrome/browser/download/download_prefs.h" @@ -84,6 +85,8 @@ const char kInlineInstallSource[] = "inline"; const char kDefaultInstallSource[] = "ondemand"; const char kAppLauncherInstallSource[] = "applauncher"; +const size_t kTimeRemainingMinutesThreshold = 1u; + // Folder for downloading crx files from the webstore. This is used so that the // crx files don't go via the usual downloads folder. const base::FilePath::CharType kWebstoreDownloadFolder[] = @@ -303,10 +306,6 @@ void WebstoreInstaller::Start() { } ExtensionSystem::Get(profile_)->install_verifier()->AddProvisional(ids); - // TODO(crbug.com/305343): Query manifest of dependencises before - // downloading & installing those dependencies. - DownloadNextPendingModule(); - std::string name; if (!approval_->manifest->value()->GetString(manifest_keys::kName, &name)) { NOTREACHED(); @@ -321,6 +320,12 @@ void WebstoreInstaller::Start() { approval_->manifest->is_platform_app()); params.is_ephemeral = approval_->is_ephemeral; tracker->OnBeginExtensionInstall(params); + + tracker->OnBeginExtensionDownload(id_); + + // TODO(crbug.com/305343): Query manifest of dependencies before + // downloading & installing those dependencies. + DownloadNextPendingModule(); } void WebstoreInstaller::Observe(int type, @@ -473,21 +478,15 @@ void WebstoreInstaller::OnDownloadUpdated(DownloadItem* download) { extensions::InstallTrackerFactory::GetForProfile(profile_); tracker->OnDownloadProgress(id_, 100); } + // Stop the progress timer if it's running. + download_progress_timer_.Stop(); break; case DownloadItem::IN_PROGRESS: { if (delegate_ && pending_modules_.size() == 1) { // Only report download progress for the main module to |delegrate_|. delegate_->OnExtensionDownloadProgress(id_, download); } - int percent = download->PercentComplete(); - // Only report progress if precent is more than 0 - if (percent >= 0) { - int finished_modules = total_modules_ - pending_modules_.size(); - percent = (percent + finished_modules * 100) / total_modules_; - extensions::InstallTracker* tracker = - extensions::InstallTrackerFactory::GetForProfile(profile_); - tracker->OnDownloadProgress(id_, percent); - } + UpdateDownloadProgress(); break; } default: @@ -614,6 +613,42 @@ void WebstoreInstaller::StartDownload(const base::FilePath& file) { download_manager->DownloadUrl(params.Pass()); } +void WebstoreInstaller::UpdateDownloadProgress() { + // If the download has gone away, or isn't in progress (in which case we can't + // give a good progress estimate), stop any running timers and return. + if (!download_item_ || + download_item_->GetState() != DownloadItem::IN_PROGRESS) { + download_progress_timer_.Stop(); + return; + } + + int percent = download_item_->PercentComplete(); + // Only report progress if precent is more than 0 + if (percent >= 0) { + int finished_modules = total_modules_ - pending_modules_.size(); + percent = (percent + (finished_modules * 100)) / total_modules_; + extensions::InstallTracker* tracker = + extensions::InstallTrackerFactory::GetForProfile(profile_); + tracker->OnDownloadProgress(id_, percent); + } + + // If there's enough time remaining on the download to warrant an update, + // set the timer (overwriting any current timers). Otherwise, stop the + // timer. + base::TimeDelta time_remaining; + if (download_item_->TimeRemaining(&time_remaining) && + time_remaining > + base::TimeDelta::FromSeconds(kTimeRemainingMinutesThreshold)) { + download_progress_timer_.Start( + FROM_HERE, + base::TimeDelta::FromSeconds(kTimeRemainingMinutesThreshold), + this, + &WebstoreInstaller::UpdateDownloadProgress); + } else { + download_progress_timer_.Stop(); + } +} + void WebstoreInstaller::ReportFailure(const std::string& error, FailureReason reason) { if (delegate_) { diff --git a/chrome/browser/extensions/webstore_installer.h b/chrome/browser/extensions/webstore_installer.h index a8b6ce4..25b2181 100644 --- a/chrome/browser/extensions/webstore_installer.h +++ b/chrome/browser/extensions/webstore_installer.h @@ -12,6 +12,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/supports_user_data.h" +#include "base/timer/timer.h" #include "base/values.h" #include "base/version.h" #include "chrome/browser/extensions/extension_install_prompt.h" @@ -224,6 +225,9 @@ class WebstoreInstaller : public content::NotificationObserver, // Starts downloading the extension to |file_path|. void StartDownload(const base::FilePath& file_path); + // Updates the InstallTracker with the latest download progress. + void UpdateDownloadProgress(); + // Reports an install |error| to the delegate for the given extension if this // managed its installation. This also removes the associated PendingInstall. void ReportFailure(const std::string& error, FailureReason reason); @@ -244,6 +248,10 @@ class WebstoreInstaller : public content::NotificationObserver, // The DownloadItem is owned by the DownloadManager and is valid from when // OnDownloadStarted is called (with no error) until OnDownloadDestroyed(). content::DownloadItem* download_item_; + // Used to periodically update the extension's download status. This will + // trigger at least every second, though sometimes more frequently (depending + // on number of modules, etc). + base::OneShotTimer<WebstoreInstaller> download_progress_timer_; scoped_ptr<Approval> approval_; GURL download_url_; diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index 8843bcf..3d8aa51 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -590,6 +590,8 @@ 'browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.h', 'browser/extensions/api/webstore_private/webstore_private_api.cc', 'browser/extensions/api/webstore_private/webstore_private_api.h', + 'browser/extensions/api/webstore/webstore_api.cc', + 'browser/extensions/api/webstore/webstore_api.h', 'browser/extensions/api/webview/webview_api.cc', 'browser/extensions/api/webview/webview_api.h', 'browser/extensions/app_icon_loader.h', diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi index fab8064..483e98c 100644 --- a/chrome/chrome_common.gypi +++ b/chrome/chrome_common.gypi @@ -161,6 +161,8 @@ 'common/extensions/api/system_indicator/system_indicator_handler.h', 'common/extensions/api/url_handlers/url_handlers_parser.cc', 'common/extensions/api/url_handlers/url_handlers_parser.h', + 'common/extensions/api/webstore/webstore_api_constants.cc', + 'common/extensions/api/webstore/webstore_api_constants.h', 'common/extensions/chrome_extension_messages.h', 'common/extensions/chrome_extensions_client.cc', 'common/extensions/chrome_extensions_client.h', diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index 33c30cd..81485a0 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -84,6 +84,12 @@ const char kAppsGalleryDownloadURL[] = "apps-gallery-download-url"; // confirmation dialog. A value of 'accept' means to always act as if the dialog // was accepted, and 'cancel' means to always act as if the dialog was // cancelled. +// +// TODO (rdevlin.cronin): Remove this. +// This is not a good use of a command-line flag, as it would be equally +// effective as a global boolean. Additionally, this opens up a dangerous way +// for attackers to append a commandline flag and circumvent all user action for +// installing an extension. const char kAppsGalleryInstallAutoConfirmForTests[] = "apps-gallery-install-auto-confirm-for-tests"; diff --git a/chrome/common/extensions/api/webstore.json b/chrome/common/extensions/api/webstore.json index 31cbd2d..38d6de3 100644 --- a/chrome/common/extensions/api/webstore.json +++ b/chrome/common/extensions/api/webstore.json @@ -6,6 +6,40 @@ { "namespace": "webstore", "description": "Use the <code>chrome.webstore</code> API to initiate app and extension installations \"inline\" from your site.", + "types": [ + { + "id": "InstallStage", + "type": "string", + "enum": ["installing", "downloading"], + "description": "Enum used to indicate the stage of the installation process. 'downloading' indicates that the necessary files are being downloaded, and 'installing' indicates that the files are downloaded and are being actively installed." + } + ], // types + "events": [ + { + "name": "onInstallStageChanged", + "description": "Fired when an inline installation enters a new InstallStage. In order to receive notifications about this event, listeners must be registered before the inline installation begins.", + "type": "function", + "parameters": [ + { + "name": "stage", + "$ref": "InstallStage", + "description": "The InstallStage that just began." + } + ] + }, // onInstallStageChanged + { + "name": "onDownloadProgress", + "description": "Fired periodically with the download progress of an inline install. In order to receive notifications about this event, listeners must be registered before the inline installation begins.", + "type": "function", + "parameters": [ + { + "name": "percentDownloaded", + "type": "number", + "description": "The progress of the download, between 0 and 1. 0 indicates no progress; 1.0 indicates complete." + } + ] + } // onDownloadProgress + ], // events "functions": [ { "name": "install", @@ -39,6 +73,6 @@ } ] } // install - ] + ] // functions } // webstore ] diff --git a/chrome/common/extensions/api/webstore/webstore_api_constants.cc b/chrome/common/extensions/api/webstore/webstore_api_constants.cc new file mode 100644 index 0000000..e766bce --- /dev/null +++ b/chrome/common/extensions/api/webstore/webstore_api_constants.cc @@ -0,0 +1,29 @@ +// Copyright 2014 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/common/extensions/api/webstore/webstore_api_constants.h" + +namespace extensions { +namespace api { +namespace webstore { + +// The "downloading" stage begins when the installer starts downloading modules +// for the extension. +const char kInstallStageDownloading[] = "downloading"; + +// The "installing" stage begins once all downloads are complete, and the +// CrxInstaller begins. +const char kInstallStageInstalling[] = "installing"; + +// The method in custom_webstore_bindings.js triggered when we enter a new +// install stage ("downloading" or "installing"). +const char kOnInstallStageChangedMethodName[] = "onInstallStageChanged"; + +// The method in custom_webstore_bindings.js triggered when we update +// download progress. +const char kOnDownloadProgressMethodName[] = "onDownloadProgress"; + +} // namespace webstore +} // namespace api +} // namespace extensions diff --git a/chrome/common/extensions/api/webstore/webstore_api_constants.h b/chrome/common/extensions/api/webstore/webstore_api_constants.h new file mode 100644 index 0000000..445a57b --- /dev/null +++ b/chrome/common/extensions/api/webstore/webstore_api_constants.h @@ -0,0 +1,34 @@ +// Copyright 2014 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_COMMON_EXTENSIONS_API_WEBSTORE_WEBSTORE_API_CONSTANTS_H_ +#define CHROME_COMMON_EXTENSIONS_API_WEBSTORE_WEBSTORE_API_CONSTANTS_H_ + +namespace extensions { +namespace api { +namespace webstore { + +// An enum for listener types. This is used when creating/reading the mask for +// IPC messages. +enum ListenerType { + INSTALL_STAGE_LISTENER = 1, + DOWNLOAD_PROGRESS_LISTENER = 1 << 1 +}; + +// An enum to represent which stage the installation is in. +enum InstallStage { + INSTALL_STAGE_DOWNLOADING = 0, + INSTALL_STAGE_INSTALLING, +}; + +extern const char kInstallStageDownloading[]; +extern const char kInstallStageInstalling[]; +extern const char kOnInstallStageChangedMethodName[]; +extern const char kOnDownloadProgressMethodName[]; + +} // namespace webstore +} // namespace api +} // namespace extensions + +#endif // CHROME_COMMON_EXTENSIONS_API_WEBSTORE_WEBSTORE_API_CONSTANTS_H_ diff --git a/chrome/common/extensions/chrome_extension_messages.h b/chrome/common/extensions/chrome_extension_messages.h index e25e901..2372412 100644 --- a/chrome/common/extensions/chrome_extension_messages.h +++ b/chrome/common/extensions/chrome_extension_messages.h @@ -8,11 +8,15 @@ // // Multiply-included message file, hence no include guard. +#include "chrome/common/extensions/api/webstore/webstore_api_constants.h" #include "chrome/common/web_application_info.h" #include "ipc/ipc_message_macros.h" #define IPC_MESSAGE_START ChromeExtensionMsgStart +IPC_ENUM_TRAITS_MAX_VALUE(extensions::api::webstore::InstallStage, + extensions::api::webstore::INSTALL_STAGE_INSTALLING) + IPC_STRUCT_TRAITS_BEGIN(WebApplicationInfo::IconInfo) IPC_STRUCT_TRAITS_MEMBER(url) IPC_STRUCT_TRAITS_MEMBER(width) @@ -34,6 +38,31 @@ IPC_STRUCT_TRAITS_END() IPC_MESSAGE_ROUTED1(ChromeExtensionMsg_GetApplicationInfo, int32 /* page_id */) +// Sent by the renderer to implement chrome.webstore.install(). +IPC_MESSAGE_ROUTED5(ExtensionHostMsg_InlineWebstoreInstall, + int32 /* install id */, + int32 /* return route id */, + std::string /* Web Store item ID */, + GURL /* requestor URL */, + int /* listeners_mask */) + +// Sent to the renderer if install stage updates were requested for an inline +// install. +IPC_MESSAGE_ROUTED1(ExtensionMsg_InlineInstallStageChanged, + extensions::api::webstore::InstallStage /* stage */) + +// Sent to the renderer if download progress updates were requested for an +// inline install. +IPC_MESSAGE_ROUTED1(ExtensionMsg_InlineInstallDownloadProgress, + int /* percent_downloaded */) + +// Send to renderer once the installation mentioned on +// ExtensionHostMsg_InlineWebstoreInstall is complete. +IPC_MESSAGE_ROUTED3(ExtensionMsg_InlineWebstoreInstallResponse, + int32 /* install id */, + bool /* whether the install was successful */, + std::string /* error */) + // Messages sent from the renderer to the browser. IPC_MESSAGE_ROUTED2(ChromeExtensionHostMsg_DidGetApplicationInfo, diff --git a/chrome/renderer/extensions/webstore_bindings.cc b/chrome/renderer/extensions/webstore_bindings.cc index 21f3b22..08e9d9a 100644 --- a/chrome/renderer/extensions/webstore_bindings.cc +++ b/chrome/renderer/extensions/webstore_bindings.cc @@ -5,11 +5,12 @@ #include "chrome/renderer/extensions/webstore_bindings.h" #include "base/strings/string_util.h" +#include "chrome/common/extensions/api/webstore/webstore_api_constants.h" +#include "chrome/common/extensions/chrome_extension_messages.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/renderer/extensions/chrome_v8_context.h" #include "content/public/renderer/render_view.h" #include "extensions/common/extension.h" -#include "extensions/common/extension_messages.h" #include "grit/renderer_resources.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebElement.h" @@ -32,12 +33,6 @@ namespace { const char kWebstoreLinkRelation[] = "chrome-webstore-item"; -const char kPreferredStoreLinkUrlNotAString[] = - "The Chrome Web Store item link URL parameter must be a string."; -const char kSuccessCallbackNotAFunctionError[] = - "The success callback parameter must be a function."; -const char kFailureCallbackNotAFunctionError[] = - "The failure callback parameter must be a function."; const char kNotInTopFrameError[] = "Chrome Web Store installations can only be started by the top frame."; const char kNotUserGestureError[] = @@ -64,28 +59,30 @@ WebstoreBindings::WebstoreBindings(Dispatcher* dispatcher, void WebstoreBindings::Install( const v8::FunctionCallbackInfo<v8::Value>& args) { - WebFrame* frame = WebFrame::frameForContext(context()->v8_context()); - if (!frame || !frame->view()) - return; - - content::RenderView* render_view = - content::RenderView::FromWebView(frame->view()); + content::RenderView* render_view = GetRenderView(); if (!render_view) return; + // The first two arguments indicate whether or not there are install stage + // or download progress listeners. + int listener_mask = 0; + CHECK(args[0]->IsBoolean()); + if (args[0]->BooleanValue()) + listener_mask |= api::webstore::INSTALL_STAGE_LISTENER; + CHECK(args[1]->IsBoolean()); + if (args[1]->BooleanValue()) + listener_mask |= api::webstore::DOWNLOAD_PROGRESS_LISTENER; + std::string preferred_store_link_url; - if (!args[0]->IsUndefined()) { - if (args[0]->IsString()) { - preferred_store_link_url = std::string(*v8::String::Utf8Value(args[0])); - } else { - args.GetIsolate()->ThrowException(v8::String::NewFromUtf8( - args.GetIsolate(), kPreferredStoreLinkUrlNotAString)); - return; - } + if (!args[2]->IsUndefined()) { + CHECK(args[2]->IsString()); + preferred_store_link_url = std::string(*v8::String::Utf8Value(args[2])); } std::string webstore_item_id; std::string error; + WebFrame* frame = context()->web_frame(); + if (!GetWebstoreItemIdFromFrame( frame, preferred_store_link_url, &webstore_item_id, &error)) { args.GetIsolate()->ThrowException( @@ -94,24 +91,13 @@ void WebstoreBindings::Install( } int install_id = g_next_install_id++; - if (!args[1]->IsUndefined() && !args[1]->IsFunction()) { - args.GetIsolate()->ThrowException(v8::String::NewFromUtf8( - args.GetIsolate(), kSuccessCallbackNotAFunctionError)); - return; - } - if (!args[2]->IsUndefined() && !args[2]->IsFunction()) { - args.GetIsolate()->ThrowException(v8::String::NewFromUtf8( - args.GetIsolate(), kFailureCallbackNotAFunctionError)); - return; - } - - Send(new ExtensionHostMsg_InlineWebstoreInstall( - render_view->GetRoutingID(), - install_id, - GetRoutingID(), - webstore_item_id, - frame->document().url())); + Send(new ExtensionHostMsg_InlineWebstoreInstall(render_view->GetRoutingID(), + install_id, + GetRoutingID(), + webstore_item_id, + frame->document().url(), + listener_mask)); args.GetReturnValue().Set(static_cast<int32_t>(install_id)); } @@ -207,6 +193,10 @@ bool WebstoreBindings::OnMessageReceived(const IPC::Message& message) { IPC_BEGIN_MESSAGE_MAP(WebstoreBindings, message) IPC_MESSAGE_HANDLER(ExtensionMsg_InlineWebstoreInstallResponse, OnInlineWebstoreInstallResponse) + IPC_MESSAGE_HANDLER(ExtensionMsg_InlineInstallStageChanged, + OnInlineInstallStageChanged) + IPC_MESSAGE_HANDLER(ExtensionMsg_InlineInstallDownloadProgress, + OnInlineInstallDownloadProgress) IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message") IPC_END_MESSAGE_MAP() return true; @@ -228,4 +218,35 @@ void WebstoreBindings::OnInlineWebstoreInstallResponse( "webstore", "onInstallResponse", arraysize(argv), argv); } +void WebstoreBindings::OnInlineInstallStageChanged(int stage) { + const char* stage_string = NULL; + api::webstore::InstallStage install_stage = + static_cast<api::webstore::InstallStage>(stage); + switch (install_stage) { + case api::webstore::INSTALL_STAGE_DOWNLOADING: + stage_string = api::webstore::kInstallStageDownloading; + break; + case api::webstore::INSTALL_STAGE_INSTALLING: + stage_string = api::webstore::kInstallStageInstalling; + break; + } + v8::Isolate* isolate = context()->isolate(); + v8::HandleScope handle_scope(isolate); + v8::Context::Scope context_scope(context()->v8_context()); + v8::Handle<v8::Value> argv[] = { + v8::String::NewFromUtf8(isolate, stage_string)}; + context()->module_system()->CallModuleMethod( + "webstore", "onInstallStageChanged", arraysize(argv), argv); +} + +void WebstoreBindings::OnInlineInstallDownloadProgress(int percent_downloaded) { + v8::Isolate* isolate = context()->isolate(); + v8::HandleScope handle_scope(isolate); + v8::Context::Scope context_scope(context()->v8_context()); + v8::Handle<v8::Value> argv[] = { + v8::Number::New(isolate, percent_downloaded / 100.0)}; + context()->module_system()->CallModuleMethod( + "webstore", "onDownloadProgress", arraysize(argv), argv); +} + } // namespace extensions diff --git a/chrome/renderer/extensions/webstore_bindings.h b/chrome/renderer/extensions/webstore_bindings.h index b729150..5df521a 100644 --- a/chrome/renderer/extensions/webstore_bindings.h +++ b/chrome/renderer/extensions/webstore_bindings.h @@ -30,6 +30,10 @@ class WebstoreBindings : public ChromeV8Extension, void OnInlineWebstoreInstallResponse( int install_id, bool success, const std::string& error); + void OnInlineInstallStageChanged(int stage); + + void OnInlineInstallDownloadProgress(int percent_downloaded); + // Extracts a Web Store item ID from a <link rel="chrome-webstore-item" // href="https://chrome.google.com/webstore/detail/id"> node found in the // frame. On success, true will be returned and the |webstore_item_id| diff --git a/chrome/renderer/resources/extensions/webstore_custom_bindings.js b/chrome/renderer/resources/extensions/webstore_custom_bindings.js index 963adaa..c31f40f 100644 --- a/chrome/renderer/resources/extensions/webstore_custom_bindings.js +++ b/chrome/renderer/resources/extensions/webstore_custom_bindings.js @@ -5,15 +5,38 @@ // Custom binding for the webstore API. var webstoreNatives = requireNative('webstore'); +var Event = require('event_bindings').Event; function Installer() { this._pendingInstall = null; + this.onInstallStageChanged = + new Event(null, [{name: 'stage', type: 'string'}], {unmanaged: true}); + this.onDownloadProgress = + new Event(null, [{name: 'progress', type: 'number'}], {unmanaged: true}); } Installer.prototype.install = function(url, onSuccess, onFailure) { if (this._pendingInstall) - throw 'A Chrome Web Store installation is already pending.'; - var installId = webstoreNatives.Install(url, onSuccess, onFailure); + throw new Error('A Chrome Web Store installation is already pending.'); + if (url !== undefined && typeof(url) !== 'string') { + throw new Error( + 'The Chrome Web Store item link URL parameter must be a string.'); + } + if (onSuccess !== undefined && typeof(onSuccess) !== 'function') + throw new Error('The success callback parameter must be a function.'); + if (onFailure !== undefined && typeof(onFailure) !== 'function') + throw new Error('The failure callback parameter must be a function.'); + + // Since we call Install() with a bool for if we have listeners, listeners + // must be set prior to the inline installation starting (this is also + // noted in the Event documentation in + // chrome/common/extensions/api/webstore.json). + var installId = webstoreNatives.Install( + this.onInstallStageChanged.hasListeners(), + this.onDownloadProgress.hasListeners(), + url, + onSuccess, + onFailure); if (installId !== undefined) { this._pendingInstall = { installId: installId, @@ -43,20 +66,32 @@ Installer.prototype.onInstallResponse = function(installId, success, error) { } }; +Installer.prototype.onInstallStageChanged = function(installStage) { + this.onInstallStageChanged.dispatch(installStage); +}; + +Installer.prototype.onDownloadProgress = function(progress) { + this.onDownloadProgress.dispatch(progress); +}; + var installer = new Installer(); var chromeWebstore = { - install: function install(url, onSuccess, onFailure) { + install: function (url, onSuccess, onFailure) { installer.install(url, onSuccess, onFailure); - } + }, + onInstallStageChanged: installer.onInstallStageChanged, + onDownloadProgress: installer.onDownloadProgress }; -// Called by webstore_binding.cc. -function onInstallResponse(installId, success, error) { - installer.onInstallResponse(installId, success, error); -} - -// These must match the names in InstallWebstorebinding in +// This must match the name in InstallWebstoreBindings in // chrome/renderer/extensions/dispatcher.cc. exports.chromeWebstore = chromeWebstore; -exports.onInstallResponse = onInstallResponse; + +// Called by webstore_bindings.cc. +exports.onInstallResponse = + Installer.prototype.onInstallResponse.bind(installer); +exports.onInstallStageChanged = + Installer.prototype.onInstallStageChanged.bind(installer); +exports.onDownloadProgress = + Installer.prototype.onDownloadProgress.bind(installer); diff --git a/chrome/test/data/extensions/api_test/webstore_inline_install/both_listeners.html b/chrome/test/data/extensions/api_test/webstore_inline_install/both_listeners.html new file mode 100644 index 0000000..757e38b --- /dev/null +++ b/chrome/test/data/extensions/api_test/webstore_inline_install/both_listeners.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html> +<head> + <link rel="chrome-webstore-item"> +</head> +<body> +<script> + function runTest(galleryUrl) { + var receivedProgressUpdate = false; + + // 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 { + // Ensure our listener mask works correctly by adding both listeners. + // We don't need to test the fucntionality, as each listener is tested + // individually separately. + chrome.webstore.onDownloadProgress.addListener(function(progress) {}); + chrome.webstore.onInstallStageChanged.addListener(function(stage) {}); + chrome.webstore.install( + undefined, + function() { + window.domAutomationController.send(true); + }, + function(error) { + console.log('Unexpected error: ' + error); + window.domAutomationController.send(false); + }); + // Force a garbage collection, so that if the callbacks aren't being + // retained properly they'll get collected and the test will fail. + window.gc(); + } catch (e) { + console.log('Unexpected exception: ' + e); + console.log(e.stack); + window.domAutomationController.send(false); + throw e; + } + } +</script> + +</body> +</html> diff --git a/chrome/test/data/extensions/api_test/webstore_inline_install/download_progress_listener.html b/chrome/test/data/extensions/api_test/webstore_inline_install/download_progress_listener.html new file mode 100644 index 0000000..b2da0b0 --- /dev/null +++ b/chrome/test/data/extensions/api_test/webstore_inline_install/download_progress_listener.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<head> + <link rel="chrome-webstore-item"> +</head> +<body> +<script> + function runTest(galleryUrl) { + var receivedProgressUpdate = false; + + // 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.onDownloadProgress.addListener(function(progress) { + if (progress < 0 || progress > 1.0) { + console.log('Received invalid download progress: ' + progress); + window.domAutomationController.send(false); + } + receivedProgressUpdate = true; + }); + chrome.webstore.install( + undefined, + function() { + if (!receivedProgressUpdate) { + console.log( + 'Test Failed: Did not receive download progress update.'); + window.domAutomationController.send(false); + } + window.domAutomationController.send(true); + }, + function(error) { + console.log('Unexpected error: ' + error); + window.domAutomationController.send(false); + }); + // Force a garbage collection, so that if the callbacks aren't being + // retained properly they'll get collected and the test will fail. + window.gc(); + } catch (e) { + console.log('Unexpected exception: ' + e); + console.log(e.stack); + window.domAutomationController.send(false); + throw e; + } + } +</script> + +</body> +</html> diff --git a/chrome/test/data/extensions/api_test/webstore_inline_install/install_stage_listener.html b/chrome/test/data/extensions/api_test/webstore_inline_install/install_stage_listener.html new file mode 100644 index 0000000..ba1547a --- /dev/null +++ b/chrome/test/data/extensions/api_test/webstore_inline_install/install_stage_listener.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html> +<head> + <link rel="chrome-webstore-item"> +</head> +<body> +<script> + function runTest(galleryUrl) { + var downloadStarted = false; + var installStarted = false; + + // 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.onInstallStageChanged.addListener(function(stage) { + if (stage == 'installing') { + if (installStarted) { + console.log('Received multiple "installing" stage events.'); + window.domAutomationController.send(false); + } + installStarted = true; + } else if (stage == 'downloading') { + if (downloadStarted) { + console.log('Received multiple "downloading" stage events.'); + window.domAutomationController.send(false); + } + downloadStarted = true; + } else { + console.log('Received unknown install stage event: ' + stage); + window.domAutomationController.send(false); + } + }); + chrome.webstore.install( + undefined, + function() { + if (!downloadStarted || !installStarted) { + console.log('Test Failed'); + if (!downloadStarted) + console.log('Did not receive "downloading" event.'); + if (!installStarted) + console.log('Did not receive "installing" event.'); + window.domAutomationController.send(false); + } + window.domAutomationController.send(true); + }, + function(error) { + console.log('Unexpected error: ' + error); + window.domAutomationController.send(false); + }); + // Force a garbage collection, so that if the callbacks aren't being + // retained properly they'll get collected and the test will fail. + window.gc(); + } catch (e) { + console.log('Unexpected exception: ' + e); + console.log(e.stack); + window.domAutomationController.send(false); + throw e; + } + } +</script> + +</body> +</html> diff --git a/chrome/test/data/extensions/api_test/webstore_inline_install/multiple_install_calls.html b/chrome/test/data/extensions/api_test/webstore_inline_install/multiple_install_calls.html index 3821497..8e52a3f 100644 --- a/chrome/test/data/extensions/api_test/webstore_inline_install/multiple_install_calls.html +++ b/chrome/test/data/extensions/api_test/webstore_inline_install/multiple_install_calls.html @@ -31,7 +31,8 @@ chrome.webstore.install(undefined, callback, callback); } catch (e) { console.log('Ran test, sending response'); - window.domAutomationController.send(e.indexOf('already pending') != -1); + window.domAutomationController.send( + e.message.indexOf('already pending') != -1); } } </script> diff --git a/extensions/common/extension_messages.h b/extensions/common/extension_messages.h index b7ce195..aa69efa 100644 --- a/extensions/common/extension_messages.h +++ b/extensions/common/extension_messages.h @@ -407,13 +407,6 @@ IPC_MESSAGE_CONTROL1(ExtensionMsg_Suspend, IPC_MESSAGE_CONTROL1(ExtensionMsg_CancelSuspend, std::string /* extension_id */) -// Send to renderer once the installation mentioned on -// ExtensionHostMsg_InlineWebstoreInstall is complete. -IPC_MESSAGE_ROUTED3(ExtensionMsg_InlineWebstoreInstallResponse, - int32 /* install id */, - bool /* whether the install was successful */, - std::string /* error */) - // Response to the renderer for ExtensionHostMsg_GetAppInstallState. IPC_MESSAGE_ROUTED2(ExtensionMsg_GetAppInstallStateResponse, std::string /* state */, @@ -573,13 +566,6 @@ IPC_MESSAGE_ROUTED3(ExtensionHostMsg_ContentScriptsExecuting, int32 /* page_id of the _topmost_ frame */, GURL /* url of the _topmost_ frame */) -// Sent by the renderer to implement chrome.webstore.install(). -IPC_MESSAGE_ROUTED4(ExtensionHostMsg_InlineWebstoreInstall, - int32 /* install id */, - int32 /* return route id */, - std::string /* Web Store item ID */, - GURL /* requestor URL */) - // Sent by the renderer when a web page is checking if its app is installed. IPC_MESSAGE_ROUTED3(ExtensionHostMsg_GetAppInstallState, GURL /* requestor_url */, |