diff options
author | lazyboy <lazyboy@chromium.org> | 2015-12-09 15:39:35 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-12-09 23:40:51 +0000 |
commit | 90c1469d03da00e99c0b6ff6a9871c65fa2b3e4c (patch) | |
tree | f3572f3c54f5e913d0e0a9855ec874df97de6888 | |
parent | 84b9c43f19b18bbd7ce85b664fa8a98f4a53dbf6 (diff) | |
download | chromium_src-90c1469d03da00e99c0b6ff6a9871c65fa2b3e4c.zip chromium_src-90c1469d03da00e99c0b6ff6a9871c65fa2b3e4c.tar.gz chromium_src-90c1469d03da00e99c0b6ff6a9871c65fa2b3e4c.tar.bz2 |
Dispatch runtime.onInstalled event if an extension is re-enabled after
it was disabled due to permission escalation.
BUG=561660
Test=Provide an update of an extension that has more permissions
than the previous one (e.g. "bookmarks"). Once the new version
is installed, notice that there's a warning in chrome hotdog
menu to accept new permissions. Upon accepting the permissions,
observe chrome.runtime.onInstalled handler fires on the
new version of the extension.
See https://github.com/lazyboy/chromium/tree/master/tests/extensions/update_flow
for detailed repro steps.
Review URL: https://codereview.chromium.org/1499493003
Cr-Commit-Position: refs/heads/master@{#364215}
5 files changed, 97 insertions, 10 deletions
diff --git a/chrome/browser/extensions/extension_disabled_ui_browsertest.cc b/chrome/browser/extensions/extension_disabled_ui_browsertest.cc index 7c16a03..9b8bb66 100644 --- a/chrome/browser/extensions/extension_disabled_ui_browsertest.cc +++ b/chrome/browser/extensions/extension_disabled_ui_browsertest.cc @@ -26,6 +26,7 @@ #include "extensions/browser/extension_system.h" #include "extensions/browser/test_extension_registry_observer.h" #include "extensions/common/extension.h" +#include "extensions/test/extension_test_message_listener.h" #include "net/url_request/test_url_request_interceptor.h" #include "sync/protocol/extension_specifics.pb.h" #include "sync/protocol/sync.pb.h" @@ -129,10 +130,14 @@ IN_PROC_BROWSER_TEST_F(ExtensionDisabledGlobalErrorTest, AcceptPermissions) { ASSERT_TRUE(GetExtensionDisabledGlobalError()); const size_t size_before = registry_->enabled_extensions().size(); + ExtensionTestMessageListener listener("v2.onInstalled", false); + listener.set_failure_message("FAILED"); service_->GrantPermissionsAndEnableExtension(extension); EXPECT_EQ(size_before + 1, registry_->enabled_extensions().size()); EXPECT_EQ(0u, registry_->disabled_extensions().size()); ASSERT_FALSE(GetExtensionDisabledGlobalError()); + // Expect onInstalled event to fire. + EXPECT_TRUE(listener.WaitUntilSatisfied()); } // Tests uninstalling an extension that was disabled due to higher permissions. diff --git a/chrome/test/data/extensions/permissions_increase/v2/background.html b/chrome/test/data/extensions/permissions_increase/v2/background.html index 741876f..b0d7464 100644 --- a/chrome/test/data/extensions/permissions_increase/v2/background.html +++ b/chrome/test/data/extensions/permissions_increase/v2/background.html @@ -1 +1,2 @@ Dummy file. +<script src="background.js"></script> diff --git a/chrome/test/data/extensions/permissions_increase/v2/background.js b/chrome/test/data/extensions/permissions_increase/v2/background.js new file mode 100644 index 0000000..7a8794a --- /dev/null +++ b/chrome/test/data/extensions/permissions_increase/v2/background.js @@ -0,0 +1,11 @@ +// Copyright 2015 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. + +chrome.runtime.onInstalled.addListener(function(detail) { + if (detail.previousVersion === '1') { + chrome.test.sendMessage('v2.onInstalled'); + } else { + chrome.test.sendMessage('FAILED'); + } +}); diff --git a/extensions/browser/api/runtime/runtime_api.cc b/extensions/browser/api/runtime/runtime_api.cc index a7e6ef4..1473a43 100644 --- a/extensions/browser/api/runtime/runtime_api.cc +++ b/extensions/browser/api/runtime/runtime_api.cc @@ -62,6 +62,15 @@ const char kUpdatesDisabledError[] = "Autoupdate is not enabled."; // A preference key storing the url loaded when an extension is uninstalled. const char kUninstallUrl[] = "uninstall_url"; +// A preference key storing the information about an extension that was +// installed but not loaded. We keep the pending info here so that we can send +// chrome.runtime.onInstalled event during the extension load. +const char kPrefPendingOnInstalledEventDispatchInfo[] = + "pending_on_installed_event_dispatch_info"; + +// Previously installed version number. +const char kPrefPreviousVersion[] = "previous_version"; + // The name of the directory to be returned by getPackageDirectoryEntry. This // particular value does not matter to user code, but is chosen for consistency // with the equivalent Pepper API. @@ -182,6 +191,15 @@ void RuntimeAPI::Observe(int type, void RuntimeAPI::OnExtensionLoaded(content::BrowserContext* browser_context, const Extension* extension) { + base::Version previous_version; + if (ReadPendingOnInstallInfoFromPref(extension->id(), &previous_version)) { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent, + browser_context_, extension->id(), previous_version, false)); + RemovePendingOnInstallInfoFromPref(extension->id()); + } + if (!dispatch_chrome_updated_event_) return; @@ -200,22 +218,19 @@ void RuntimeAPI::OnExtensionWillBeInstalled( const Extension* extension, bool is_update, const std::string& old_name) { - Version old_version = delegate_->GetPreviousExtensionVersion(extension); - - // Dispatch the onInstalled event. - base::MessageLoop::current()->PostTask( - FROM_HERE, - base::Bind(&RuntimeEventRouter::DispatchOnInstalledEvent, - browser_context_, - extension->id(), - old_version, - false)); + // This extension might be disabled before it has a chance to load, e.g. if + // the extension increased its permissions. So instead of trying to send the + // onInstalled event here, we remember the fact in prefs and fire the event + // when the extension is actually loaded. + StorePendingOnInstallInfoToPref(extension); } void RuntimeAPI::OnExtensionUninstalled( content::BrowserContext* browser_context, const Extension* extension, UninstallReason reason) { + RemovePendingOnInstallInfoFromPref(extension->id()); + RuntimeEventRouter::OnExtensionUninstalled( browser_context_, extension->id(), reason); } @@ -237,6 +252,53 @@ void RuntimeAPI::OnBackgroundHostStartup(const Extension* extension) { RuntimeEventRouter::DispatchOnStartupEvent(browser_context_, extension->id()); } +bool RuntimeAPI::ReadPendingOnInstallInfoFromPref( + const ExtensionId& extension_id, + base::Version* previous_version) { + ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context_); + DCHECK(prefs); + + const base::DictionaryValue* info = nullptr; + if (!prefs->ReadPrefAsDictionary( + extension_id, kPrefPendingOnInstalledEventDispatchInfo, &info)) { + return false; + } + + std::string previous_version_string; + info->GetString(kPrefPreviousVersion, &previous_version_string); + // |previous_version_string| can be empty. + *previous_version = base::Version(previous_version_string); + return true; +} + +void RuntimeAPI::RemovePendingOnInstallInfoFromPref( + const ExtensionId& extension_id) { + ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context_); + DCHECK(prefs); + + prefs->UpdateExtensionPref(extension_id, + kPrefPendingOnInstalledEventDispatchInfo, nullptr); +} + +void RuntimeAPI::StorePendingOnInstallInfoToPref(const Extension* extension) { + ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context_); + DCHECK(prefs); + + // |pending_on_install_info| currently only contains a version string. Instead + // of making the pref hold a plain string, we store it as a dictionary value + // so that we can add more stuff to it in the future if necessary. + scoped_ptr<base::DictionaryValue> pending_on_install_info( + new base::DictionaryValue()); + base::Version previous_version = + delegate_->GetPreviousExtensionVersion(extension); + pending_on_install_info->SetString( + kPrefPreviousVersion, + previous_version.IsValid() ? previous_version.GetString() : ""); + prefs->UpdateExtensionPref(extension->id(), + kPrefPendingOnInstalledEventDispatchInfo, + pending_on_install_info.release()); +} + void RuntimeAPI::ReloadExtension(const std::string& extension_id) { delegate_->ReloadExtension(extension_id); } diff --git a/extensions/browser/api/runtime/runtime_api.h b/extensions/browser/api/runtime/runtime_api.h index 28ff852..a9ed93f 100644 --- a/extensions/browser/api/runtime/runtime_api.h +++ b/extensions/browser/api/runtime/runtime_api.h @@ -93,6 +93,14 @@ class RuntimeAPI : public BrowserContextKeyedAPI, // ProcessManagerObserver implementation: void OnBackgroundHostStartup(const Extension* extension) override; + // Pref related functions that deals with info about installed extensions that + // has not been loaded yet. + // Used to send chrome.runtime.onInstalled event upon loading the extensions. + bool ReadPendingOnInstallInfoFromPref(const ExtensionId& extension_id, + base::Version* previous_version); + void RemovePendingOnInstallInfoFromPref(const ExtensionId& extension_id); + void StorePendingOnInstallInfoToPref(const Extension* extension); + scoped_ptr<RuntimeAPIDelegate> delegate_; content::BrowserContext* browser_context_; |