summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorlazyboy <lazyboy@chromium.org>2015-12-09 15:39:35 -0800
committerCommit bot <commit-bot@chromium.org>2015-12-09 23:40:51 +0000
commit90c1469d03da00e99c0b6ff6a9871c65fa2b3e4c (patch)
treef3572f3c54f5e913d0e0a9855ec874df97de6888
parent84b9c43f19b18bbd7ce85b664fa8a98f4a53dbf6 (diff)
downloadchromium_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}
-rw-r--r--chrome/browser/extensions/extension_disabled_ui_browsertest.cc5
-rw-r--r--chrome/test/data/extensions/permissions_increase/v2/background.html1
-rw-r--r--chrome/test/data/extensions/permissions_increase/v2/background.js11
-rw-r--r--extensions/browser/api/runtime/runtime_api.cc82
-rw-r--r--extensions/browser/api/runtime/runtime_api.h8
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_;