summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/extensions/extension_updater.cc560
-rw-r--r--chrome/browser/extensions/extension_updater.h181
-rw-r--r--chrome/browser/extensions/extension_updater_unittest.cc448
-rw-r--r--chrome/browser/extensions/extensions_service.cc58
-rw-r--r--chrome/browser/extensions/extensions_service.h23
-rw-r--r--chrome/browser/extensions/extensions_service_unittest.cc31
-rw-r--r--chrome/browser/profile.cc3
-rw-r--r--chrome/chrome.gyp3
-rw-r--r--chrome/common/chrome_switches.cc3
-rw-r--r--chrome/common/chrome_switches.h1
10 files changed, 1273 insertions, 38 deletions
diff --git a/chrome/browser/extensions/extension_updater.cc b/chrome/browser/extensions/extension_updater.cc
new file mode 100644
index 0000000..c14281c
--- /dev/null
+++ b/chrome/browser/extensions/extension_updater.cc
@@ -0,0 +1,560 @@
+// Copyright (c) 2009 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_updater.h"
+
+#include "base/logging.h"
+#include "base/file_util.h"
+#include "base/file_version_info.h"
+#include "base/string_util.h"
+#include "base/thread.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_error_reporter.h"
+#include "chrome/common/libxml_utils.h"
+#include "googleurl/src/gurl.h"
+#include "net/url_request/url_request_status.h"
+#include "libxml/tree.h"
+
+const char* ExtensionUpdater::kExpectedGupdateProtocol = "2.0";
+const char* ExtensionUpdater::kExpectedGupdateXmlns =
+ "http://www.google.com/update2/response";
+
+// For sanity checking on update frequency - enforced in release mode only.
+static const int kMinUpdateFrequencySeconds = 60 * 60; // 1 hour
+static const int kMaxUpdateFrequencySeconds = 60 * 60 * 24 * 7; // 7 days
+
+// A utility class to do file handling on the file I/O thread.
+class ExtensionUpdaterFileHandler
+ : public base::RefCountedThreadSafe<ExtensionUpdaterFileHandler> {
+ public:
+ ExtensionUpdaterFileHandler(MessageLoop* updater_loop,
+ MessageLoop* file_io_loop)
+ : updater_loop_(updater_loop), file_io_loop_(file_io_loop) {}
+
+ // Writes crx file data into a tempfile, and calls back the updater.
+ void WriteTempFile(const std::string& extension_id, const std::string& data,
+ scoped_refptr<ExtensionUpdater> updater) {
+ // Make sure we're running in the right thread.
+ DCHECK(MessageLoop::current() == file_io_loop_);
+
+ FilePath path;
+ if (!file_util::CreateTemporaryFileName(&path)) {
+ LOG(WARNING) << "Failed to create temporary file path";
+ return;
+ }
+ if (file_util::WriteFile(path, data.c_str(), data.length()) !=
+ static_cast<int>(data.length())) {
+ // TODO(asargent) - It would be nice to back off updating alltogether if
+ // the disk is full. (http://crbug.com/12763).
+ LOG(ERROR) << "Failed to write temporary file";
+ file_util::Delete(path, false);
+ return;
+ }
+
+ // The ExtensionUpdater is now responsible for cleaning up the temp file
+ // from disk.
+ updater_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ updater.get(), &ExtensionUpdater::OnCRXFileWritten, extension_id,
+ path));
+ }
+
+ void DeleteFile(const FilePath& path) {
+ DCHECK(MessageLoop::current() == file_io_loop_);
+ if (!file_util::Delete(path, false)) {
+ LOG(WARNING) << "Failed to delete temp file " << path.value();
+ }
+ }
+
+ private:
+ // The MessageLoop we use to call back the ExtensionUpdater.
+ MessageLoop* updater_loop_;
+
+ // The MessageLoop we should be operating on for file operations.
+ MessageLoop* file_io_loop_;
+};
+
+
+ExtensionUpdater::ExtensionUpdater(ExtensionUpdateService* service,
+ int frequency_seconds,
+ MessageLoop* file_io_loop)
+ : service_(service), frequency_seconds_(frequency_seconds),
+ file_io_loop_(file_io_loop),
+ file_handler_(new ExtensionUpdaterFileHandler(MessageLoop::current(),
+ file_io_loop_)) {
+ Init();
+}
+
+void ExtensionUpdater::Init() {
+ // Unless we're in a unit test, expect that the file_io_loop_ is on the
+ // browser file thread.
+ if (g_browser_process->file_thread() != NULL) {
+ DCHECK(file_io_loop_ == g_browser_process->file_thread()->message_loop());
+ }
+
+ DCHECK_GE(frequency_seconds_, 5);
+ DCHECK(frequency_seconds_ <= kMaxUpdateFrequencySeconds);
+#ifdef NDEBUG
+ // In Release mode we enforce that update checks don't happen too often.
+ frequency_seconds_ = std::max(frequency_seconds_, kMinUpdateFrequencySeconds);
+#endif
+ frequency_seconds_ = std::min(frequency_seconds_, kMaxUpdateFrequencySeconds);
+}
+
+ExtensionUpdater::~ExtensionUpdater() {}
+
+void ExtensionUpdater::Start() {
+ // TODO(asargent) Make sure update schedules work across browser restarts by
+ // reading/writing update frequency in profile/settings. *But*, make sure to
+ // wait a reasonable amount of time after browser startup to do the first
+ // check during a given run of the browser. Also remember to update the header
+ // comments when this is implemented. (http://crbug.com/12545).
+ timer_.Start(base::TimeDelta::FromSeconds(frequency_seconds_), this,
+ &ExtensionUpdater::TimerFired);
+}
+
+void ExtensionUpdater::Stop() {
+ timer_.Stop();
+ manifest_fetcher_.reset();
+ extension_fetcher_.reset();
+ manifests_pending_.clear();
+ extensions_pending_.clear();
+}
+
+void ExtensionUpdater::OnURLFetchComplete(
+ const URLFetcher* source, const GURL& url, const URLRequestStatus& status,
+ int response_code, const ResponseCookies& cookies,
+ const std::string& data) {
+
+ if (source == manifest_fetcher_.get()) {
+ OnManifestFetchComplete(url, status, response_code, data);
+ } else if (source == extension_fetcher_.get()) {
+ OnCRXFetchComplete(url, status, response_code, data);
+ } else {
+ NOTREACHED();
+ }
+}
+
+void ExtensionUpdater::OnManifestFetchComplete(const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const std::string& data) {
+ // We want to try parsing the manifest, and if it indicates updates are
+ // available, we want to fire off requests to fetch those updates.
+ if (status.status() == URLRequestStatus::SUCCESS && response_code == 200) {
+ ScopedVector<ParseResult> parsed;
+ // TODO(asargent) - We should do the xml parsing in a sandboxed process.
+ // (http://crbug.com/12677).
+ if (Parse(data, &parsed.get())) {
+ std::vector<int> updates = DetermineUpdates(parsed.get());
+ for (size_t i = 0; i < updates.size(); i++) {
+ ParseResult* update = parsed[updates[i]];
+ FetchUpdatedExtension(update->extension_id, update->crx_url);
+ }
+ }
+ } else {
+ // TODO(asargent) Do exponential backoff here. (http://crbug.com/12546).
+ LOG(INFO) << "Failed to fetch manifst '" << url.possibly_invalid_spec() <<
+ "' response code:" << response_code;
+ }
+ manifest_fetcher_.reset();
+
+ // If we have any pending manifest requests, fire off the next one.
+ if (!manifests_pending_.empty()) {
+ GURL url = manifests_pending_.front();
+ manifests_pending_.pop_front();
+ StartUpdateCheck(url);
+ }
+}
+
+void ExtensionUpdater::OnCRXFetchComplete(const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const std::string& data) {
+ if (url != current_extension_fetch_.url) {
+ LOG(ERROR) << "Called with unexpected url:'" << url.spec()
+ << "' expected:'" << current_extension_fetch_.url.spec() << "'";
+ NOTREACHED();
+ } else if (status.status() == URLRequestStatus::SUCCESS &&
+ response_code == 200) {
+ // Successfully fetched - now write crx to a file so we can have the
+ // ExtensionsService install it.
+ file_io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ file_handler_.get(), &ExtensionUpdaterFileHandler::WriteTempFile,
+ current_extension_fetch_.id, data, this));
+ } else {
+ // TODO(asargent) do things like exponential backoff, handling
+ // 503 Service Unavailable / Retry-After headers, etc. here.
+ // (http://crbug.com/12546).
+ LOG(INFO) << "Failed to fetch extension '" <<
+ url.possibly_invalid_spec() << "' response code:" << response_code;
+ }
+ extension_fetcher_.reset();
+ current_extension_fetch_ = ExtensionFetch();
+
+ // If there are any pending downloads left, start one.
+ if (extensions_pending_.size() > 0) {
+ ExtensionFetch next = extensions_pending_.front();
+ extensions_pending_.pop_front();
+ FetchUpdatedExtension(next.id, next.url);
+ }
+}
+
+void ExtensionUpdater::OnCRXFileWritten(const std::string& id,
+ const FilePath& path) {
+ // TODO(asargent) - Instead of calling InstallExtension here, we should have
+ // an UpdateExtension method in ExtensionsService and rely on it to check
+ // that the extension is still installed, and still an older version than
+ // what we're handing it.
+ // (http://crbug.com/12764).
+ ExtensionInstallCallback* callback =
+ NewCallback(this, &ExtensionUpdater::OnExtensionInstallFinished);
+ service_->UpdateExtension(id, path, false, callback);
+}
+
+void ExtensionUpdater::OnExtensionInstallFinished(const FilePath& path,
+ Extension* extension) {
+ // Have the file_handler_ delete the temp file on the file I/O thread.
+ file_io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ file_handler_.get(), &ExtensionUpdaterFileHandler::DeleteFile, path));
+}
+
+static const char* kURLEncodedEquals = "%3D"; // '='
+static const char* kURLEncodedAnd = "%26"; // '&'
+
+void ExtensionUpdater::TimerFired() {
+ // Generate a set of update urls for loaded extensions.
+ std::set<GURL> urls;
+ const ExtensionList* extensions = service_->extensions();
+ for (ExtensionList::const_iterator iter = extensions->begin();
+ iter != extensions->end(); ++iter) {
+ Extension* extension = (*iter);
+ const GURL& update_url = extension->update_url();
+ if (update_url.is_empty() || extension->id().empty()) {
+ continue;
+ }
+
+ DCHECK(update_url.is_valid());
+ DCHECK(!update_url.has_ref());
+
+ // Append extension information to the url.
+ std::string full_url_string = update_url.spec();
+ full_url_string.append(update_url.has_query() ? "&" : "?");
+ full_url_string.append("x=");
+
+ full_url_string.append("id");
+ full_url_string.append(kURLEncodedEquals);
+ full_url_string.append(extension->id());
+
+ const Version* version = extension->version();
+ DCHECK(version);
+ full_url_string.append("v");
+ full_url_string.append(kURLEncodedEquals);
+ full_url_string.append(version->GetString());
+ full_url_string.append(kURLEncodedAnd);
+
+ GURL full_url(full_url_string);
+ if (!full_url.is_valid()) {
+ LOG(ERROR) << "invalid url: " << full_url.possibly_invalid_spec();
+ NOTREACHED();
+ } else {
+ urls.insert(full_url);
+ }
+ }
+
+ // Now do an update check for each url we found.
+ for (std::set<GURL>::iterator iter = urls.begin(); iter != urls.end();
+ ++iter) {
+ // StartUpdateCheck makes sure the url isn't already downloading or
+ // scheduled, so we don't need to check before calling it.
+ StartUpdateCheck(*iter);
+ }
+ timer_.Reset();
+}
+
+static void ManifestParseError(const char* details, ...) {
+ va_list args;
+ va_start(args, details);
+ std::string message("Extension update manifest parse error: ");
+ StringAppendV(&message, details, args);
+ ExtensionErrorReporter::GetInstance()->ReportError(message, false);
+}
+
+// Checks whether a given node's name matches |expected_name| and
+// |expected_namespace|.
+static bool TagNameEquals(const xmlNode* node, const char* expected_name,
+ const xmlNs* expected_namespace) {
+ if (node->ns != expected_namespace) {
+ return false;
+ }
+ return 0 == strcmp(expected_name, reinterpret_cast<const char*>(node->name));
+}
+
+// Returns child nodes of |root| with name |name| in namespace |xml_namespace|.
+static std::vector<xmlNode*> GetChildren(xmlNode* root, xmlNs* xml_namespace,
+ const char* name) {
+ std::vector<xmlNode*> result;
+ for (xmlNode* child = root->children; child != NULL; child = child->next) {
+ if (!TagNameEquals(child, name, xml_namespace)) {
+ continue;
+ }
+ result.push_back(child);
+ }
+ return result;
+}
+
+// Returns the value of a named attribute, or the empty string.
+static std::string GetAttribute(xmlNode* node, const char* attribute_name) {
+ const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name);
+ for (xmlAttr* attr = node->properties; attr != NULL; attr = attr->next) {
+ if (!xmlStrcmp(attr->name, name) && attr->children &&
+ attr->children->content) {
+ return std::string(reinterpret_cast<const char*>(
+ attr->children->content));
+ }
+ }
+ return std::string();
+}
+
+// This is used for the xml parser to report errors. This assumes the context
+// is a pointer to a std::string where the error message should be appended.
+static void XmlErrorFunc(void *context, const char *message, ...) {
+ va_list args;
+ va_start(args, message);
+ std::string* error = static_cast<std::string*>(context);
+ StringAppendV(error, message, args);
+}
+
+// Utility class for cleaning up xml parser state when leaving a scope.
+class ScopedXmlParserCleanup {
+ public:
+ ScopedXmlParserCleanup() : document_(NULL) {}
+ ~ScopedXmlParserCleanup() {
+ if (document_)
+ xmlFreeDoc(document_);
+ xmlCleanupParser();
+ }
+ void set_document(xmlDocPtr document) {
+ document_ = document;
+ }
+
+ private:
+ xmlDocPtr document_;
+};
+
+// Returns a pointer to the xmlNs on |node| with the |expected_href|, or
+// NULL if there isn't one with that href.
+static xmlNs* GetNamespace(xmlNode* node, const char* expected_href) {
+ const xmlChar* href = reinterpret_cast<const xmlChar*>(expected_href);
+ for (xmlNs* ns = node->ns; ns != NULL; ns = ns->next) {
+ if (ns->href && !xmlStrcmp(ns->href, href)) {
+ return ns;
+ }
+ }
+ return NULL;
+}
+
+// This is a separate sub-class so that we can have access to the private
+// ParseResult struct, but avoid making the .h file include the xml api headers.
+class ExtensionUpdater::ParseHelper {
+ public:
+ // Helper function for ExtensionUpdater::Parse that reads in values for a
+ // single <app> tag. It returns a boolean indicating success or failure.
+ static bool ParseSingleAppTag(xmlNode* app_node, xmlNs* xml_namespace,
+ ParseResult* result) {
+ // Read the extension id.
+ result->extension_id = GetAttribute(app_node, "appid");
+ if (result->extension_id.length() == 0) {
+ ManifestParseError("Missing appid on app node");
+ return false;
+ }
+
+ // Get the updatecheck node.
+ std::vector<xmlNode*> updates = GetChildren(app_node, xml_namespace,
+ "updatecheck");
+ if (updates.size() > 1) {
+ ManifestParseError("Too many updatecheck tags on app (expecting only 1)");
+ return false;
+ }
+ if (updates.size() == 0) {
+ ManifestParseError("Missing updatecheck on app");
+ return false;
+ }
+ xmlNode *updatecheck = updates[0];
+
+ // Find the url to the crx file.
+ result->crx_url = GURL(GetAttribute(updatecheck, "codebase"));
+ if (!result->crx_url.is_valid()) {
+ ManifestParseError("Invalid codebase url");
+ return false;
+ }
+
+ // Get the version.
+ std::string tmp = GetAttribute(updatecheck, "version");
+ if (tmp.length() == 0) {
+ ManifestParseError("Missing version for updatecheck");
+ return false;
+ }
+ result->version.reset(Version::GetVersionFromString(tmp));
+ if (!result->version.get()) {
+ ManifestParseError("Invalid version");
+ return false;
+ }
+
+ // Get the minimum browser version (not required).
+ tmp = GetAttribute(updatecheck, "prodversionmin");
+ if (tmp.length()) {
+ result->browser_min_version.reset(Version::GetVersionFromString(tmp));
+ if (!result->browser_min_version.get()) {
+ ManifestParseError("Invalid prodversionmin");
+ return false;
+ }
+ }
+ return true;
+ }
+};
+
+bool ExtensionUpdater::Parse(const std::string& manifest_xml,
+ ParseResultList* results) {
+ std::string xml_errors;
+ ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc);
+ ScopedXmlParserCleanup xml_cleanup;
+
+ xmlDocPtr document = xmlParseDoc(
+ reinterpret_cast<const xmlChar*>(manifest_xml.c_str()));
+ if (!document) {
+ ManifestParseError(xml_errors.c_str());
+ return false;
+ }
+ xml_cleanup.set_document(document);
+
+ xmlNode *root = xmlDocGetRootElement(document);
+ if (!root) {
+ ManifestParseError("Missing root node");
+ return false;
+ }
+
+ // Look for the required namespace declaration.
+ xmlNs* gupdate_ns = GetNamespace(root, kExpectedGupdateXmlns);
+ if (!gupdate_ns) {
+ ManifestParseError("Missing or incorrect xmlns on gupdate tag");
+ return false;
+ }
+
+ if (!TagNameEquals(root, "gupdate", gupdate_ns)) {
+ ManifestParseError("Missing gupdate tag");
+ return false;
+ }
+
+ // Check for the gupdate "protocol" attribute.
+ if (GetAttribute(root, "protocol") != kExpectedGupdateProtocol) {
+ ManifestParseError("Missing/incorrect protocol on gupdate tag "
+ "(expected '%s')", kExpectedGupdateProtocol);
+ return false;
+ }
+
+ // Parse each of the <app> tags.
+ ScopedVector<ParseResult> tmp_results;
+ std::vector<xmlNode*> apps = GetChildren(root, gupdate_ns, "app");
+ for (unsigned int i = 0; i < apps.size(); i++) {
+ ParseResult* current = new ParseResult();
+ tmp_results.push_back(current);
+ if (!ParseHelper::ParseSingleAppTag(apps[i], gupdate_ns, current)) {
+ return false;
+ }
+ }
+ tmp_results.release(results);
+
+ return true;
+}
+
+std::vector<int> ExtensionUpdater::DetermineUpdates(
+ const ParseResultList& possible_updates) {
+
+ std::vector<int> result;
+
+ // This will only get set if one of possible_updates specifies
+ // browser_min_version.
+ scoped_ptr<Version> browser_version;
+
+ for (size_t i = 0; i < possible_updates.size(); i++) {
+ ParseResult* update = possible_updates[i];
+
+ Extension* extension = service_->GetExtensionById(update->extension_id);
+ if (!extension)
+ continue;
+
+ // If the update version is the same or older than what's already installed,
+ // we don't want it.
+ if (update->version.get()->CompareTo(*(extension->version())) <= 0)
+ continue;
+
+ // If the update specifies a browser minimum version, do we qualify?
+ if (update->browser_min_version.get()) {
+ // First determine the browser version if we haven't already.
+ if (!browser_version.get()) {
+ scoped_ptr<FileVersionInfo> version_info(
+ FileVersionInfo::CreateFileVersionInfoForCurrentModule());
+ if (version_info.get()) {
+ browser_version.reset(Version::GetVersionFromString(
+ version_info->product_version()));
+ }
+ }
+ if (browser_version.get() &&
+ update->browser_min_version->CompareTo(*browser_version.get()) > 0) {
+ // TODO(asargent) - We may want this to show up in the extensions UI
+ // eventually. (http://crbug.com/12547).
+ LOG(WARNING) << "Updated version of extension " << extension->id() <<
+ " available, but requires chrome version " <<
+ update->browser_min_version->GetString();
+ continue;
+ }
+ }
+ result.push_back(i);
+ }
+ return result;
+}
+
+void ExtensionUpdater::StartUpdateCheck(const GURL& url) {
+ if (std::find(manifests_pending_.begin(), manifests_pending_.end(), url) !=
+ manifests_pending_.end()) {
+ return; // already scheduled
+ }
+
+ if (manifest_fetcher_.get() != NULL) {
+ if (manifest_fetcher_->url() != url) {
+ manifests_pending_.push_back(url);
+ }
+ } else {
+ manifest_fetcher_.reset(
+ URLFetcher::Create(kManifestFetcherId, url, URLFetcher::GET, this));
+ manifest_fetcher_->set_request_context(Profile::GetDefaultRequestContext());
+ manifest_fetcher_->Start();
+ }
+}
+
+void ExtensionUpdater::FetchUpdatedExtension(const std::string& id, GURL url) {
+ for (std::deque<ExtensionFetch>::const_iterator iter =
+ extensions_pending_.begin();
+ iter != extensions_pending_.end(); ++iter) {
+ if (iter->id == id || iter->url == url) {
+ return; // already scheduled
+ }
+ }
+
+ if (extension_fetcher_.get() != NULL) {
+ if (extension_fetcher_->url() != url) {
+ extensions_pending_.push_back(ExtensionFetch(id, url));
+ }
+ } else {
+ extension_fetcher_.reset(
+ URLFetcher::Create(kExtensionFetcherId, url, URLFetcher::GET, this));
+ extension_fetcher_->set_request_context(
+ Profile::GetDefaultRequestContext());
+ extension_fetcher_->Start();
+ current_extension_fetch_ = ExtensionFetch(id, url);
+ }
+}
diff --git a/chrome/browser/extensions/extension_updater.h b/chrome/browser/extensions/extension_updater.h
new file mode 100644
index 0000000..5af2c03
--- /dev/null
+++ b/chrome/browser/extensions/extension_updater.h
@@ -0,0 +1,181 @@
+// Copyright (c) 2009 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_EXTENSION_UPDATER_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_UPDATER_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/scoped_temp_dir.h"
+#include "base/scoped_vector.h"
+#include "base/task.h"
+#include "base/time.h"
+#include "base/timer.h"
+#include "base/version.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/net/url_fetcher.h"
+#include "googleurl/src/gurl.h"
+
+class Extension;
+class ExtensionUpdaterTest;
+class MessageLoop;
+class ExtensionUpdaterFileHandler;
+
+// A class for doing auto-updates of installed Extensions. Used like this:
+//
+// ExtensionUpdater* updater = new ExtensionUpdater(my_extensions_service,
+// update_frequency_secs,
+// file_io_loop);
+// updater.Start();
+// ....
+// updater.Stop();
+class ExtensionUpdater
+ : public URLFetcher::Delegate,
+ public base::RefCountedThreadSafe<ExtensionUpdater> {
+ public:
+ // Holds a pointer to the passed |service|, using it for querying installed
+ // extensions and installing updated ones. The |frequency_seconds| parameter
+ // controls how often update checks are scheduled.
+ ExtensionUpdater(ExtensionUpdateService* service,
+ int frequency_seconds,
+ MessageLoop* file_io_loop);
+
+ virtual ~ExtensionUpdater();
+
+ // Starts the updater running, with the first check scheduled for
+ // |frequency_seconds| from now. Eventually ExtensionUpdater will persist the
+ // time the last check happened, and do the first check |frequency_seconds_|
+ // from then (potentially adding a short wait if the browser just started).
+ // (http://crbug.com/12545).
+ void Start();
+
+ // Stops the updater running, cancelling any outstanding update manifest and
+ // crx downloads. Does not cancel any in-progress installs.
+ void Stop();
+
+ private:
+ friend class ExtensionUpdaterTest;
+ friend class ExtensionUpdaterFileHandler;
+ class ParseHelper;
+
+ // An update manifest looks like this:
+ //
+ // <?xml version='1.0' encoding='UTF-8'?>
+ // <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
+ // <app appid='12345'>
+ // <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'
+ // version='1.2.3.4' prodversionmin='2.0.143.0' />
+ // </app>
+ // </gupdate>
+ //
+ // The "appid" attribute of the <app> tag refers to the unique id of the
+ // extension. The "codebase" attribute of the <updatecheck> tag is the url to
+ // fetch the updated crx file, and the "prodversionmin" attribute refers to
+ // the minimum version of the chrome browser that the update applies to.
+
+ // The result of parsing one <app> tag in an xml update check manifest.
+ struct ParseResult {
+ std::string extension_id;
+ scoped_ptr<Version> version;
+ scoped_ptr<Version> browser_min_version;
+ GURL crx_url;
+ };
+
+ // We need to keep track of the extension id associated with a url when
+ // doing a fetch.
+ struct ExtensionFetch {
+ std::string id;
+ GURL url;
+ ExtensionFetch() : id(""), url() {}
+ ExtensionFetch(std::string i, GURL u) : id(i), url(u) {}
+ };
+
+ // These are needed for unit testing, to help identify the correct mock
+ // URLFetcher objects.
+ static const int kManifestFetcherId = 1;
+ static const int kExtensionFetcherId = 1;
+
+ // Constants for the update manifest.
+ static const char* kExpectedGupdateProtocol;
+ static const char* kExpectedGupdateXmlns;
+
+ // Does common work from constructors.
+ void Init();
+
+ // URLFetcher::Delegate interface.
+ virtual void OnURLFetchComplete(const URLFetcher* source,
+ const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookies,
+ const std::string& data);
+
+ // These do the actual work when a URL fetch completes.
+ virtual void OnManifestFetchComplete(const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const std::string& data);
+ virtual void OnCRXFetchComplete(const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const std::string& data);
+
+ // Called when a crx file has been written into a temp file, and is ready
+ // to be installed.
+ void OnCRXFileWritten(const std::string& id, const FilePath& path);
+
+ // Callback for when ExtensionsService::Install is finished.
+ void OnExtensionInstallFinished(const FilePath& path, Extension* extension);
+
+ // BaseTimer::ReceiverMethod callback.
+ void TimerFired();
+
+ // Begins an update check - called with url to fetch an update manifest.
+ void StartUpdateCheck(const GURL& url);
+
+ // Begins (or queues up) download of an updated extension.
+ void FetchUpdatedExtension(const std::string& id, GURL url);
+
+ typedef std::vector<ParseResult*> ParseResultList;
+
+ // Given a list of potential updates, returns the indices of the ones that are
+ // applicable (are actually a new version, etc.) in |result|.
+ std::vector<int> DetermineUpdates(const ParseResultList& possible_updates);
+
+ // Parses an update manifest xml string into ParseResult data. On success, it
+ // inserts new ParseResult items into |result| and returns true. On failure,
+ // it returns false and puts nothing into |result|.
+ static bool Parse(const std::string& manifest_xml, ParseResultList* result);
+
+ // Outstanding url fetch requests for manifests and updates.
+ scoped_ptr<URLFetcher> manifest_fetcher_;
+ scoped_ptr<URLFetcher> extension_fetcher_;
+
+ // Pending manifests and extensions to be fetched when the appropriate fetcher
+ // is available.
+ std::deque<GURL> manifests_pending_;
+ std::deque<ExtensionFetch> extensions_pending_;
+
+ // The extension currently being fetched (if any).
+ ExtensionFetch current_extension_fetch_;
+
+ // Pointer back to the service that owns this ExtensionUpdater.
+ ExtensionUpdateService* service_;
+
+ base::RepeatingTimer<ExtensionUpdater> timer_;
+ int frequency_seconds_;
+
+ // The MessageLoop where we should do file I/O.
+ MessageLoop* file_io_loop_;
+
+ scoped_refptr<ExtensionUpdaterFileHandler> file_handler_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionUpdater);
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_UPDATER_H_
diff --git a/chrome/browser/extensions/extension_updater_unittest.cc b/chrome/browser/extensions/extension_updater_unittest.cc
new file mode 100644
index 0000000..6b22617
--- /dev/null
+++ b/chrome/browser/extensions/extension_updater_unittest.cc
@@ -0,0 +1,448 @@
+// Copyright (c) 2009 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 "base/file_util.h"
+#include "base/string_util.h"
+#include "chrome/browser/extensions/extension_updater.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/net/test_url_fetcher_factory.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_error_reporter.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+const char *valid_xml =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </app>"
+"</gupdate>";
+
+const char* missing_appid =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' />"
+" </app>"
+"</gupdate>";
+
+const char* invalid_codebase =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+" <updatecheck codebase='example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' />"
+" </app>"
+"</gupdate>";
+
+const char* missing_version =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' />"
+" </app>"
+"</gupdate>";
+
+const char* invalid_version =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' "
+" version='1.2.3.a'/>"
+" </app>"
+"</gupdate>";
+
+const char *uses_namespace_prefix =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<g:gupdate xmlns:g='http://www.google.com/update2/response' protocol='2.0'>"
+" <g:app appid='12345'>"
+" <g:updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </g:app>"
+"</g:gupdate>";
+
+// Includes unrelated <app> tags from other xml namespaces - this should
+// not cause problems.
+const char *similar_tagnames =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response'"
+" xmlns:a='http://a' protocol='2.0'>"
+" <a:app/>"
+" <b:app xmlns:b='http://b' />"
+" <app appid='12345'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </app>"
+"</gupdate>";
+
+
+// Do-nothing base class for further specialized test classes.
+class MockService : public ExtensionUpdateService {
+ public:
+ MockService() {}
+ virtual ~MockService() {}
+
+ virtual const ExtensionList* extensions() const {
+ EXPECT_TRUE(false);
+ return NULL;
+ }
+
+ virtual void UpdateExtension(const std::string& id,
+ const FilePath& extension_path,
+ bool alert_on_error,
+ ExtensionInstallCallback* callback) {
+ EXPECT_TRUE(false);
+ }
+
+ virtual Extension* GetExtensionById(const std::string& id) {
+ EXPECT_TRUE(false);
+ return NULL;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockService);
+};
+
+
+const char* kIdPrefix = "000000000000000000000000000000000000000";
+
+// Creates test extensions and inserts them into list. The name and
+// version are all based on their index.
+void CreateTestExtensions(int count, ExtensionList *list) {
+ for (int i = 1; i <= count; i++) {
+ DictionaryValue input;
+ Extension* e = new Extension();
+ input.SetString(Extension::kVersionKey, StringPrintf("%d.0.0.0", i));
+ input.SetString(Extension::kNameKey, StringPrintf("Extension %d", i));
+ std::string error;
+ EXPECT_TRUE(e->InitFromValue(input, false, &error));
+ list->push_back(e);
+ }
+}
+
+class ServiceForManifestTests : public MockService {
+ public:
+ ServiceForManifestTests() {}
+
+ virtual ~ServiceForManifestTests() {}
+
+ virtual Extension* GetExtensionById(const std::string& id) {
+ for (ExtensionList::iterator iter = extensions_.begin();
+ iter != extensions_.end(); ++iter) {
+ if ((*iter)->id() == id) {
+ return *iter;
+ }
+ }
+ return NULL;
+ }
+
+ virtual const ExtensionList* extensions() const { return &extensions_; }
+
+ void set_extensions(ExtensionList extensions) {
+ extensions_ = extensions;
+ }
+
+ private:
+ ExtensionList extensions_;
+};
+
+class ServiceForDownloadTests : public MockService {
+ public:
+ explicit ServiceForDownloadTests() : callback_(NULL) {}
+ virtual ~ServiceForDownloadTests() {
+ // expect that cleanup happened via FireInstallCallback
+ EXPECT_EQ(NULL, callback_);
+ EXPECT_TRUE(install_path_.empty());
+ }
+
+ virtual void UpdateExtension(const std::string& id,
+ const FilePath& extension_path,
+ bool alert_on_error,
+ ExtensionInstallCallback* callback) {
+ // Since this mock only has support for one oustanding update, ensure
+ // that the callback doesn't need to be run.
+ EXPECT_TRUE(install_path_.empty());
+ EXPECT_EQ(NULL, callback_);
+
+ extension_id_ = id;
+ install_path_ = extension_path;
+ callback_ = callback;
+ }
+
+ void FireInstallCallback() {
+ EXPECT_TRUE(callback_ != NULL);
+ callback_->Run(install_path_, static_cast<Extension*>(NULL));
+ delete callback_;
+ callback_ = NULL;
+ install_path_ = FilePath();
+ }
+
+ const std::string& extension_id() { return extension_id_; }
+ const FilePath& install_path() { return install_path_; }
+
+ private:
+ std::string extension_id_;
+ FilePath install_path_;
+ ExtensionInstallCallback* callback_;
+};
+
+static const int kUpdateFrequencySecs = 15;
+
+// All of our tests that need to use private APIs of ExtensionUpdater live
+// inside this class (which is a friend to ExtensionUpdater).
+class ExtensionUpdaterTest : public testing::Test {
+ public:
+ static void expectParseFailure(const char *xml) {
+ ScopedVector<ExtensionUpdater::ParseResult> result;
+ EXPECT_FALSE(ExtensionUpdater::Parse(xml, &result.get()));
+ }
+
+ // Make a test ParseResult
+ static ExtensionUpdater::ParseResult* MakeParseResult(std::string id,
+ std::string version,
+ std::string url) {
+ ExtensionUpdater::ParseResult *result = new ExtensionUpdater::ParseResult;
+ result->extension_id = id;
+ result->version.reset(Version::GetVersionFromString(version));
+ result->crx_url = GURL(url);
+ return result;
+ }
+
+ static void TestXmlParsing() {
+ ExtensionErrorReporter::Init(false);
+
+ // Test parsing of a number of invalid xml cases
+ expectParseFailure("");
+ expectParseFailure(missing_appid);
+ expectParseFailure(invalid_codebase);
+ expectParseFailure(missing_version);
+ expectParseFailure(invalid_version);
+
+ // Parse some valid XML, and check that all params came out as expected
+ ScopedVector<ExtensionUpdater::ParseResult> results;
+ EXPECT_TRUE(ExtensionUpdater::Parse(valid_xml, &results.get()));
+ EXPECT_FALSE(results->empty());
+ ExtensionUpdater::ParseResult* firstResult = results->at(0);
+ EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
+ firstResult->crx_url);
+ scoped_ptr<Version> version(Version::GetVersionFromString("1.2.3.4"));
+ EXPECT_EQ(0, version->CompareTo(*firstResult->version.get()));
+ version.reset(Version::GetVersionFromString("2.0.143.0"));
+ EXPECT_EQ(0, version->CompareTo(*firstResult->browser_min_version.get()));
+
+ // Parse some xml that uses namespace prefixes.
+ results.reset();
+ EXPECT_TRUE(ExtensionUpdater::Parse(uses_namespace_prefix, &results.get()));
+ EXPECT_TRUE(ExtensionUpdater::Parse(similar_tagnames, &results.get()));
+ }
+
+ static void TestDetermineUpdates() {
+ // Create a set of test extensions
+ ServiceForManifestTests service;
+ ExtensionList tmp;
+ CreateTestExtensions(3, &tmp);
+ service.set_extensions(tmp);
+
+ MessageLoop message_loop;
+ scoped_refptr<ExtensionUpdater> updater =
+ new ExtensionUpdater(&service, kUpdateFrequencySecs, &message_loop);
+
+ // Check passing an empty list of parse results to DetermineUpdates
+ ExtensionUpdater::ParseResultList updates;
+ std::vector<int> updateable = updater->DetermineUpdates(updates);
+ EXPECT_TRUE(updateable.empty());
+
+ // Create two updates - expect that DetermineUpdates will return the first
+ // one (v1.0 installed, v1.1 available) but not the second one (both
+ // installed and available at v2.0).
+ scoped_ptr<Version> one(Version::GetVersionFromString("1.0"));
+ EXPECT_TRUE(tmp[0]->version()->Equals(*one));
+ updates.push_back(MakeParseResult(tmp[0]->id(),
+ "1.1", "http://localhost/e1_1.1.crx"));
+ updates.push_back(MakeParseResult(tmp[1]->id(),
+ tmp[1]->VersionString(), "http://localhost/e2_2.0.crx"));
+ updateable = updater->DetermineUpdates(updates);
+ EXPECT_EQ(1u, updateable.size());
+ EXPECT_EQ(0, updateable[0]);
+ STLDeleteElements(&updates);
+ }
+
+ static void TestMultipleManifestDownloading() {
+ TestURLFetcherFactory factory;
+ TestURLFetcher* fetcher = NULL;
+ URLFetcher::set_factory(&factory);
+ ServiceForDownloadTests service;
+ MessageLoop message_loop;
+ scoped_refptr<ExtensionUpdater> updater =
+ new ExtensionUpdater(&service, kUpdateFrequencySecs, &message_loop);
+
+ GURL url1("http://localhost/manifest1");
+ GURL url2("http://localhost/manifest2");
+
+ // Request 2 update checks - the first should begin immediately and the
+ // second one should be queued up.
+ updater->StartUpdateCheck(url1);
+ updater->StartUpdateCheck(url2);
+
+ std::string manifest_data("invalid xml");
+ fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId);
+ EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
+ fetcher->delegate()->OnURLFetchComplete(
+ fetcher, url1, URLRequestStatus(), 200, ResponseCookies(),
+ manifest_data);
+
+ // Now that the first request is complete, make sure the second one has
+ // been started.
+ fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId);
+ EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
+ fetcher->delegate()->OnURLFetchComplete(
+ fetcher, url2, URLRequestStatus(), 200, ResponseCookies(),
+ manifest_data);
+ }
+
+ static void TestSingleExtensionDownloading() {
+ MessageLoop message_loop;
+ TestURLFetcherFactory factory;
+ TestURLFetcher* fetcher = NULL;
+ URLFetcher::set_factory(&factory);
+ ServiceForDownloadTests service;
+ scoped_refptr<ExtensionUpdater> updater =
+ new ExtensionUpdater(&service, kUpdateFrequencySecs, &message_loop);
+
+ GURL test_url("http://localhost/extension.crx");
+
+ std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+
+ updater->FetchUpdatedExtension(id, test_url);
+
+ // Call back the ExtensionUpdater with a 200 response and some test data
+ std::string extension_data("whatever");
+ fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId);
+ EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
+ fetcher->delegate()->OnURLFetchComplete(
+ fetcher, test_url, URLRequestStatus(), 200, ResponseCookies(),
+ extension_data);
+
+ message_loop.RunAllPending();
+
+ // Expect that ExtensionUpdater asked the mock extensions service to install
+ // a file with the test data for the right id.
+ EXPECT_EQ(id, service.extension_id());
+ FilePath tmpfile_path = service.install_path();
+ EXPECT_FALSE(tmpfile_path.empty());
+ std::string file_contents;
+ EXPECT_TRUE(file_util::ReadFileToString(tmpfile_path, &file_contents));
+ EXPECT_TRUE(extension_data == file_contents);
+
+ service.FireInstallCallback();
+
+ message_loop.RunAllPending();
+
+ // Make sure the temp file is cleaned up
+ EXPECT_FALSE(file_util::PathExists(tmpfile_path));
+
+ URLFetcher::set_factory(NULL);
+ }
+
+ static void TestMultipleExtensionDownloading() {
+ MessageLoopForUI message_loop;
+ TestURLFetcherFactory factory;
+ TestURLFetcher* fetcher = NULL;
+ URLFetcher::set_factory(&factory);
+ ServiceForDownloadTests service;
+ scoped_refptr<ExtensionUpdater> updater =
+ new ExtensionUpdater(&service, kUpdateFrequencySecs, &message_loop);
+
+ GURL url1("http://localhost/extension1.crx");
+ GURL url2("http://localhost/extension2.crx");
+
+ std::string id1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ std::string id2 = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
+
+ // Start two fetches
+ updater->FetchUpdatedExtension(id1, url1);
+ updater->FetchUpdatedExtension(id2, url2);
+
+ // Make the first fetch complete.
+ std::string extension_data1("whatever");
+ fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId);
+ EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
+ fetcher->delegate()->OnURLFetchComplete(
+ fetcher, url1, URLRequestStatus(), 200, ResponseCookies(),
+ extension_data1);
+ message_loop.RunAllPending();
+
+ // Expect that the service was asked to do an install with the right data,
+ // and fire the callback indicating the install finished.
+ FilePath tmpfile_path = service.install_path();
+ EXPECT_FALSE(tmpfile_path.empty());
+ EXPECT_EQ(id1, service.extension_id());
+ service.FireInstallCallback();
+
+ // Make sure the tempfile got cleaned up.
+ message_loop.RunAllPending();
+ EXPECT_FALSE(tmpfile_path.empty());
+ EXPECT_FALSE(file_util::PathExists(tmpfile_path));
+
+ // Make sure the second fetch finished and asked the service to do an
+ // update.
+ std::string extension_data2("whatever2");
+ fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId);
+ EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
+ fetcher->delegate()->OnURLFetchComplete(
+ fetcher, url2, URLRequestStatus(), 200, ResponseCookies(),
+ extension_data2);
+ message_loop.RunAllPending();
+ EXPECT_EQ(id2, service.extension_id());
+ EXPECT_FALSE(service.install_path().empty());
+
+ // Make sure the correct crx contents were passed for the update call.
+ std::string file_contents;
+ EXPECT_TRUE(file_util::ReadFileToString(service.install_path(),
+ &file_contents));
+ EXPECT_TRUE(extension_data2 == file_contents);
+ service.FireInstallCallback();
+ message_loop.RunAllPending();
+ }
+};
+
+// Because we test some private methods of ExtensionUpdater, it's easer for the
+// actual test code to live in ExtenionUpdaterTest methods instead of TEST_F
+// subclasses where friendship with ExtenionUpdater is not inherited.
+
+TEST(ExtensionUpdaterTest, TestXmlParsing) {
+ ExtensionUpdaterTest::TestXmlParsing();
+}
+
+TEST(ExtensionUpdaterTest, TestDetermineUpdates) {
+ ExtensionUpdaterTest::TestDetermineUpdates();
+}
+
+TEST(ExtensionUpdaterTest, TestMultipleManifestDownloading) {
+ ExtensionUpdaterTest::TestMultipleManifestDownloading();
+}
+
+TEST(ExtensionUpdaterTest, TestSingleExtensionDownloading) {
+ ExtensionUpdaterTest::TestSingleExtensionDownloading();
+}
+
+TEST(ExtensionUpdaterTest, TestMultipleExtensionDownloading) {
+ ExtensionUpdaterTest::TestMultipleExtensionDownloading();
+}
+
+
+// TODO(asargent) - (http://crbug.com/12780) add tests for:
+// -prodversionmin (shouldn't update if browser version too old)
+// -manifests & updates arriving out of order / interleaved
+// -Profile::GetDefaultRequestContext() returning null
+// (should not crash, but just do check later)
+// -malformed update url (empty, file://, has query, has a # fragment, etc.)
+// -An extension gets uninstalled while updates are in progress (so it doesn't
+// "come back from the dead")
+// -An extension gets manually updated to v3 while we're downloading v2 (ie
+// you don't get downgraded accidentally)
+// -An update manifest mentions multiple updates
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index 4094ebf..57247cb 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -25,6 +25,7 @@
#include "chrome/browser/extensions/extension_creator.h"
#include "chrome/browser/extensions/extension_browser_event_router.h"
#include "chrome/browser/extensions/extension_process_manager.h"
+#include "chrome/browser/extensions/extension_updater.h"
#include "chrome/browser/extensions/external_extension_provider.h"
#include "chrome/browser/extensions/external_pref_extension_provider.h"
#include "chrome/browser/profile.h"
@@ -88,9 +89,11 @@ class ExtensionsServiceBackend::UnpackerClient
UnpackerClient(ExtensionsServiceBackend* backend,
const FilePath& extension_path,
const std::string& public_key,
- const std::string& expected_id)
+ const std::string& expected_id,
+ bool silent)
: backend_(backend), extension_path_(extension_path),
- public_key_(public_key), expected_id_(expected_id), got_response_(false) {
+ public_key_(public_key), expected_id_(expected_id), got_response_(false),
+ silent_(silent) {
}
// Starts the unpack task. We call back to the backend when the task is done,
@@ -167,7 +170,7 @@ class ExtensionsServiceBackend::UnpackerClient
FilePath extension_dir = temp_extension_path_.DirName().AppendASCII(
ExtensionsServiceBackend::kTempExtensionName);
backend_->OnExtensionUnpacked(extension_path_, extension_dir,
- expected_id_, manifest, images);
+ expected_id_, manifest, images, silent_);
Cleanup();
}
@@ -211,6 +214,9 @@ class ExtensionsServiceBackend::UnpackerClient
// True if we got a response from the utility process and have cleaned up
// already.
bool got_response_;
+
+ // True if the install should be done with no confirmation dialog.
+ bool silent_;
};
ExtensionsService::ExtensionsService(Profile* profile,
@@ -218,7 +224,8 @@ ExtensionsService::ExtensionsService(Profile* profile,
PrefService* prefs,
const FilePath& install_directory,
MessageLoop* frontend_loop,
- MessageLoop* backend_loop)
+ MessageLoop* backend_loop,
+ bool autoupdate_enabled)
: extension_prefs_(new ExtensionPrefs(prefs, install_directory)),
backend_loop_(backend_loop),
install_directory_(install_directory),
@@ -231,6 +238,16 @@ ExtensionsService::ExtensionsService(Profile* profile,
else if (profile->GetPrefs()->GetBoolean(prefs::kEnableExtensions))
extensions_enabled_ = true;
+ // Set up the ExtensionUpdater
+ if (autoupdate_enabled) {
+ int update_frequency = kDefaultUpdateFrequencySeconds;
+ if (command_line->HasSwitch(switches::kExtensionsUpdateFrequency)) {
+ update_frequency = StringToInt(WideToASCII(command_line->GetSwitchValue(
+ switches::kExtensionsUpdateFrequency)));
+ }
+ updater_ = new ExtensionUpdater(this, update_frequency, backend_loop_);
+ }
+
backend_ = new ExtensionsServiceBackend(
install_directory_, g_browser_process->resource_dispatcher_host(),
frontend_loop, extensions_enabled());
@@ -238,6 +255,9 @@ ExtensionsService::ExtensionsService(Profile* profile,
ExtensionsService::~ExtensionsService() {
UnloadAllExtensions();
+ if (updater_.get()) {
+ updater_->Stop();
+ }
}
void ExtensionsService::SetExtensionsEnabled(bool enabled) {
@@ -248,7 +268,7 @@ void ExtensionsService::SetExtensionsEnabled(bool enabled) {
void ExtensionsService::Init() {
DCHECK(!ready_);
- DCHECK(extensions_.size() == 0);
+ DCHECK_EQ(extensions_.size(), 0u);
// Start up the extension event routers.
ExtensionBrowserEventRouter::GetInstance()->Init();
@@ -257,7 +277,7 @@ void ExtensionsService::Init() {
// TODO(erikkay) this should probably be deferred to a future point
// rather than running immediately at startup.
- CheckForUpdates();
+ CheckForExternalUpdates();
// TODO(erikkay) this should probably be deferred as well.
GarbageCollectExtensions();
@@ -332,7 +352,7 @@ void ExtensionsService::LoadAllExtensions() {
new InstalledExtensions(extension_prefs_.get())));
}
-void ExtensionsService::CheckForUpdates() {
+void ExtensionsService::CheckForExternalUpdates() {
// This installs or updates externally provided extensions.
std::set<std::string> killed_extensions;
extension_prefs_->GetKilledExtensionIds(&killed_extensions);
@@ -390,6 +410,9 @@ void ExtensionsService::GarbageCollectExtensions() {
void ExtensionsService::OnLoadedInstalledExtensions() {
ready_ = true;
+ if (updater_.get()) {
+ updater_->Start();
+ }
NotificationService::current()->Notify(
NotificationType::EXTENSIONS_READY,
Source<ExtensionsService>(this),
@@ -559,7 +582,7 @@ void ExtensionsServiceBackend::GarbageCollectExtensions(
// Nothing to clean up if it doesn't exist.
if (!file_util::DirectoryExists(install_directory_))
return;
-
+
FilePath install_directory_absolute(install_directory_);
file_util::AbsolutePath(&install_directory_absolute);
@@ -909,7 +932,7 @@ void ExtensionsServiceBackend::InstallExtension(
frontend_ = frontend;
alert_on_error_ = true;
- InstallOrUpdateExtension(extension_path, std::string());
+ InstallOrUpdateExtension(extension_path, std::string(), false);
}
void ExtensionsServiceBackend::UpdateExtension(const std::string& id,
@@ -920,17 +943,18 @@ void ExtensionsServiceBackend::UpdateExtension(const std::string& id,
frontend_ = frontend;
alert_on_error_ = alert_on_error;
- InstallOrUpdateExtension(extension_path, id);
+ InstallOrUpdateExtension(extension_path, id, true);
}
void ExtensionsServiceBackend::InstallOrUpdateExtension(
- const FilePath& extension_path, const std::string& expected_id) {
+ const FilePath& extension_path, const std::string& expected_id,
+ bool silent) {
std::string actual_public_key;
if (!ValidateSignature(extension_path, &actual_public_key))
return; // Failures reported within ValidateSignature().
UnpackerClient* client = new UnpackerClient(
- this, extension_path, actual_public_key, expected_id);
+ this, extension_path, actual_public_key, expected_id, silent);
client->Start();
}
@@ -1028,7 +1052,8 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
const FilePath& temp_extension_dir,
const std::string expected_id,
const DictionaryValue& manifest,
- const std::vector< Tuple2<SkBitmap, FilePath> >& images) {
+ const std::vector< Tuple2<SkBitmap, FilePath> >& images,
+ bool silent) {
Extension extension;
std::string error;
if (!extension.InitFromValue(manifest,
@@ -1056,8 +1081,9 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
// TODO(extensions): Make better extensions UI. http://crbug.com/12116
- // We don't show the install dialog for themes or external extensions.
- if (!extension.IsTheme() && frontend_->show_extensions_prompts()) {
+ // We don't show the install dialog for themes, updates, or external
+ // extensions.
+ if (!extension.IsTheme() && !silent && frontend_->show_extensions_prompts()) {
#if defined(OS_WIN)
if (!Extension::IsExternalLocation(location) &&
win_util::MessageBox(GetForegroundWindow(),
@@ -1260,7 +1286,7 @@ void ExtensionsServiceBackend::CheckVersionAndInstallExtension(
const std::string& id, const Version* extension_version,
const FilePath& extension_path) {
if (ShouldInstall(id, extension_version))
- InstallOrUpdateExtension(FilePath(extension_path), id);
+ InstallOrUpdateExtension(FilePath(extension_path), id, false);
}
bool ExtensionsServiceBackend::LookupExternalExtension(
diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h
index 5afa547..8ae2759 100644
--- a/chrome/browser/extensions/extensions_service.h
+++ b/chrome/browser/extensions/extensions_service.h
@@ -26,6 +26,7 @@ class Browser;
class DictionaryValue;
class Extension;
class ExtensionsServiceBackend;
+class ExtensionUpdater;
class GURL;
class MessageLoop;
class PrefService;
@@ -95,12 +96,16 @@ class ExtensionsService
// installed to.
static const char* kInstallDirectoryName;
+ // If auto-updates are turned on, default to running every 5 hours.
+ static const int kDefaultUpdateFrequencySeconds = 60 * 60 * 5;
+
ExtensionsService(Profile* profile,
const CommandLine* command_line,
PrefService* prefs,
const FilePath& install_directory,
MessageLoop* frontend_loop,
- MessageLoop* backend_loop);
+ MessageLoop* backend_loop,
+ bool autoupdate_enabled);
virtual ~ExtensionsService();
// Gets the list of currently installed extensions.
@@ -144,7 +149,7 @@ class ExtensionsService
void LoadAllExtensions();
// Check for updates (or potentially new extensions from external providers)
- void CheckForUpdates();
+ void CheckForExternalUpdates();
// Unload the specified extension.
void UnloadExtension(const std::string& extension_id);
@@ -245,6 +250,9 @@ class ExtensionsService
// Is the service ready to go?
bool ready_;
+ // Our extension updater, if updates are turned on.
+ scoped_refptr<ExtensionUpdater> updater_;
+
DISALLOW_COPY_AND_ASSIGN(ExtensionsService);
};
@@ -348,9 +356,10 @@ class ExtensionsServiceBackend
// Install a crx file at |extension_path|. If |expected_id| is not empty, it's
// verified against the extension's manifest before installation. If the
// extension is already installed, install the new version only if its version
- // number is greater than the current installed version.
+ // number is greater than the current installed version. If |silent| is true,
+ // the confirmation dialog will not pop up.
void InstallOrUpdateExtension(const FilePath& extension_path,
- const std::string& expected_id);
+ const std::string& expected_id, bool silent);
// Validates the signature of the extension in |extension_path|. Returns true
// and the public key (in |key|) if the signature validates, false otherwise.
@@ -360,13 +369,15 @@ class ExtensionsServiceBackend
// |temp_extension_dir| by our utility process. If |expected_id| is not
// empty, it's verified against the extension's manifest before installation.
// |manifest| and |images| are parsed information from the extension that
- // we want to write to disk in the browser process.
+ // we want to write to disk in the browser process. If |silent| is true, there
+ // will be no install confirmation dialog.
void OnExtensionUnpacked(
const FilePath& extension_path,
const FilePath& temp_extension_dir,
const std::string expected_id,
const DictionaryValue& manifest,
- const std::vector< Tuple2<SkBitmap, FilePath> >& images);
+ const std::vector< Tuple2<SkBitmap, FilePath> >& images,
+ bool silent);
// Notify the frontend that there was an error loading an extension.
void ReportExtensionLoadError(const FilePath& extension_path,
diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc
index 555655c..ac668e1f 100644
--- a/chrome/browser/extensions/extensions_service_unittest.cc
+++ b/chrome/browser/extensions/extensions_service_unittest.cc
@@ -213,7 +213,8 @@ class ExtensionsServiceTest
prefs_.get(),
extensions_install_dir,
&loop_,
- &loop_);
+ &loop_,
+ false);
service_->SetExtensionsEnabled(true);
service_->set_show_extensions_prompts(false);
@@ -1063,7 +1064,7 @@ TEST_F(ExtensionsServiceTest, LoadExtension) {
FilePath no_manifest = extensions_path
.AppendASCII("bad")
- //.AppendASCII("Extensions")
+ // .AppendASCII("Extensions")
.AppendASCII("cccccccccccccccccccccccccccccccc")
.AppendASCII("1");
service_->LoadExtension(no_manifest);
@@ -1145,7 +1146,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) {
// Reloading extensions should find our externally registered extension
// and install it.
- service_->CheckForUpdates();
+ service_->CheckForExternalUpdates();
loop_.RunAllPending();
ASSERT_EQ(0u, GetErrors().size());
@@ -1172,7 +1173,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) {
reg_provider->UpdateOrAddExtension(good_crx, "1.0.0.1", source_path);
loaded_.clear();
- service_->CheckForUpdates();
+ service_->CheckForExternalUpdates();
loop_.RunAllPending();
ASSERT_EQ(0u, GetErrors().size());
ASSERT_EQ(1u, loaded_.size());
@@ -1192,7 +1193,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) {
ASSERT_FALSE(file_util::PathExists(install_path));
loaded_.clear();
- service_->CheckForUpdates();
+ service_->CheckForExternalUpdates();
loop_.RunAllPending();
ASSERT_EQ(0u, loaded_.size());
ValidatePrefKeyCount(1);
@@ -1205,7 +1206,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) {
prefs_->ScheduleSavePersistentPrefs();
loaded_.clear();
- service_->CheckForUpdates();
+ service_->CheckForExternalUpdates();
loop_.RunAllPending();
ASSERT_EQ(1u, loaded_.size());
ValidatePrefKeyCount(1);
@@ -1249,7 +1250,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) {
// Checking for updates should find our externally registered extension
// and install it.
- service_->CheckForUpdates();
+ service_->CheckForExternalUpdates();
loop_.RunAllPending();
ASSERT_EQ(0u, GetErrors().size());
@@ -1276,7 +1277,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) {
pref_provider->UpdateOrAddExtension(good_crx, "1.0.0.1", source_path);
loaded_.clear();
- service_->CheckForUpdates();
+ service_->CheckForExternalUpdates();
loop_.RunAllPending();
ASSERT_EQ(0u, GetErrors().size());
ASSERT_EQ(1u, loaded_.size());
@@ -1296,7 +1297,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) {
ASSERT_FALSE(file_util::PathExists(install_path));
loaded_.clear();
- service_->CheckForUpdates();
+ service_->CheckForExternalUpdates();
loop_.RunAllPending();
ASSERT_EQ(0u, loaded_.size());
ValidatePrefKeyCount(1);
@@ -1308,7 +1309,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) {
prefs_->ScheduleSavePersistentPrefs();
loaded_.clear();
- service_->CheckForUpdates();
+ service_->CheckForExternalUpdates();
loop_.RunAllPending();
ASSERT_EQ(1u, loaded_.size());
ValidatePrefKeyCount(1);
@@ -1331,7 +1332,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) {
SetExtensionsEnabled(false);
pref_provider->UpdateOrAddExtension(good_crx, "1.0", source_path);
- service_->CheckForUpdates();
+ service_->CheckForExternalUpdates();
loop_.RunAllPending();
ASSERT_EQ(0u, loaded_.size());
@@ -1433,7 +1434,7 @@ TEST(ExtensionsServiceTestSimple, Enabledness) {
// By default, we are disabled.
command_line.reset(new CommandLine(L""));
service = new ExtensionsService(&profile, command_line.get(),
- profile.GetPrefs(), install_dir, &loop, &loop);
+ profile.GetPrefs(), install_dir, &loop, &loop, false);
EXPECT_FALSE(service->extensions_enabled());
service->Init();
loop.RunAllPending();
@@ -1443,7 +1444,7 @@ TEST(ExtensionsServiceTestSimple, Enabledness) {
recorder.set_ready(false);
command_line->AppendSwitch(switches::kEnableExtensions);
service = new ExtensionsService(&profile, command_line.get(),
- profile.GetPrefs(), install_dir, &loop, &loop);
+ profile.GetPrefs(), install_dir, &loop, &loop, false);
EXPECT_TRUE(service->extensions_enabled());
service->Init();
loop.RunAllPending();
@@ -1452,7 +1453,7 @@ TEST(ExtensionsServiceTestSimple, Enabledness) {
recorder.set_ready(false);
profile.GetPrefs()->SetBoolean(prefs::kEnableExtensions, true);
service = new ExtensionsService(&profile, command_line.get(),
- profile.GetPrefs(), install_dir, &loop, &loop);
+ profile.GetPrefs(), install_dir, &loop, &loop, false);
EXPECT_TRUE(service->extensions_enabled());
service->Init();
loop.RunAllPending();
@@ -1461,7 +1462,7 @@ TEST(ExtensionsServiceTestSimple, Enabledness) {
recorder.set_ready(false);
command_line.reset(new CommandLine(L""));
service = new ExtensionsService(&profile, command_line.get(),
- profile.GetPrefs(), install_dir, &loop, &loop);
+ profile.GetPrefs(), install_dir, &loop, &loop, false);
EXPECT_TRUE(service->extensions_enabled());
service->Init();
loop.RunAllPending();
diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc
index 085f814..df95acf 100644
--- a/chrome/browser/profile.cc
+++ b/chrome/browser/profile.cc
@@ -565,7 +565,8 @@ void ProfileImpl::InitExtensions() {
GetPrefs(),
GetPath().AppendASCII(ExtensionsService::kInstallDirectoryName),
MessageLoop::current(),
- g_browser_process->file_thread()->message_loop());
+ g_browser_process->file_thread()->message_loop(),
+ true);
extensions_service_->Init();
}
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index ae6c902..13e7e95 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -935,6 +935,8 @@
'browser/extensions/extension_tabs_module.h',
'browser/extensions/extension_tabs_module_constants.cc',
'browser/extensions/extension_tabs_module_constants.h',
+ 'browser/extensions/extension_updater.cc',
+ 'browser/extensions/extension_updater.h',
'browser/extensions/extensions_service.cc',
'browser/extensions/extensions_service.h',
'browser/extensions/extensions_ui.cc',
@@ -3508,6 +3510,7 @@
'browser/extensions/extension_messages_unittest.cc',
'browser/extensions/extension_process_manager_unittest.cc',
'browser/extensions/extension_ui_unittest.cc',
+ 'browser/extensions/extension_updater_unittest.cc',
'browser/extensions/extensions_service_unittest.cc',
'browser/extensions/user_script_master_unittest.cc',
'browser/find_backend_unittest.cc',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 9ba11a1..cef4dad 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -392,6 +392,9 @@ const wchar_t kEnableUserScripts[] = L"enable-user-scripts";
// Enable extensions.
const wchar_t kEnableExtensions[] = L"enable-extensions";
+// Frequency in seconds for Extensions auto-update.
+const wchar_t kExtensionsUpdateFrequency[] = L"extensions-update-frequency";
+
// Install the extension specified in the argument. This is for MIME type
// handling so that users can double-click on an extension.
const wchar_t kInstallExtension[] = L"install-extension";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index f8f74d3..ef46c6c 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -142,6 +142,7 @@ extern const wchar_t kSdchFilter[];
extern const wchar_t kEnableUserScripts[];
extern const wchar_t kEnableExtensions[];
+extern const wchar_t kExtensionsUpdateFrequency[];
extern const wchar_t kInstallExtension[];
extern const wchar_t kLoadExtension[];
extern const wchar_t kPackExtension[];