diff options
author | asargent <asargent@chromium.org> | 2014-10-09 14:27:57 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-10-09 21:28:17 +0000 |
commit | 7cc29ce343a235f9067545ddd19ba2d982c4595f (patch) | |
tree | 7245596bfb1ab50817302de6fea3c2a781bfecea | |
parent | f7bcf0f223a64972ba7b4f53d0c618d9649a882b (diff) | |
download | chromium_src-7cc29ce343a235f9067545ddd19ba2d982c4595f.zip chromium_src-7cc29ce343a235f9067545ddd19ba2d982c4595f.tar.gz chromium_src-7cc29ce343a235f9067545ddd19ba2d982c4595f.tar.bz2 |
Fix extension content verification handling of ./ in icon paths
When extension manifests contain icon paths with a leading ./, we
were failing to process them properly
BUG=410666
Review URL: https://codereview.chromium.org/630243002
Cr-Commit-Position: refs/heads/master@{#298979}
19 files changed, 227 insertions, 14 deletions
diff --git a/chrome/browser/extensions/content_verifier_browsertest.cc b/chrome/browser/extensions/content_verifier_browsertest.cc index c6ebcd9..1b7bf2a 100644 --- a/chrome/browser/extensions/content_verifier_browsertest.cc +++ b/chrome/browser/extensions/content_verifier_browsertest.cc @@ -87,6 +87,82 @@ class JobDelegate : public ContentVerifyJob::TestDelegate { bool fail_next_done_; }; +class JobObserver : public ContentVerifyJob::TestObserver { + public: + JobObserver(); + virtual ~JobObserver(); + + // Call this to add an expected job result. + void ExpectJobResult(const std::string& extension_id, + const base::FilePath& relative_path, + bool expected_to_fail); + + // Wait to see expected jobs. Returns true if we saw all jobs finish as + // expected, or false if any job completed with non-expected success/failure + // status. + bool WaitForExpectedJobs(); + + // ContentVerifyJob::TestObserver interface + virtual void JobStarted(const std::string& extension_id, + const base::FilePath& relative_path) OVERRIDE; + + virtual void JobFinished(const std::string& extension_id, + const base::FilePath& relative_path, + bool failed) OVERRIDE; + + private: + typedef std::pair<std::string, base::FilePath> ExtensionFile; + typedef std::map<ExtensionFile, bool> ExpectedJobs; + ExpectedJobs expected_jobs_; + scoped_refptr<content::MessageLoopRunner> loop_runner_; + bool saw_expected_job_results_; +}; + +void JobObserver::ExpectJobResult(const std::string& extension_id, + const base::FilePath& relative_path, + bool expected_to_fail) { + expected_jobs_.insert(std::make_pair( + ExtensionFile(extension_id, relative_path), expected_to_fail)); +} + +JobObserver::JobObserver() : saw_expected_job_results_(false) { +} + +JobObserver::~JobObserver() { +} + +bool JobObserver::WaitForExpectedJobs() { + if (!expected_jobs_.empty()) { + loop_runner_ = new content::MessageLoopRunner(); + loop_runner_->Run(); + } + return saw_expected_job_results_; +} + +void JobObserver::JobStarted(const std::string& extension_id, + const base::FilePath& relative_path) { +} + +void JobObserver::JobFinished(const std::string& extension_id, + const base::FilePath& relative_path, + bool failed) { + ExpectedJobs::iterator i = expected_jobs_.find(ExtensionFile( + extension_id, relative_path.NormalizePathSeparatorsTo('/'))); + if (i != expected_jobs_.end()) { + if (failed != i->second) { + saw_expected_job_results_ = false; + if (loop_runner_.get()) + loop_runner_->Quit(); + } + expected_jobs_.erase(i); + if (expected_jobs_.empty()) { + saw_expected_job_results_ = true; + if (loop_runner_.get()) + loop_runner_->Quit(); + } + } +} + } // namespace class ContentVerifierTest : public ExtensionBrowserTest { @@ -104,6 +180,15 @@ class ContentVerifierTest : public ExtensionBrowserTest { // Setup our unload observer and JobDelegate, and install a test extension. virtual void SetUpOnMainThread() override { ExtensionBrowserTest::SetUpOnMainThread(); + } + + virtual void TearDownOnMainThread() override { + ContentVerifyJob::SetDelegateForTests(NULL); + ContentVerifyJob::SetObserverForTests(NULL); + ExtensionBrowserTest::TearDownOnMainThread(); + } + + virtual void OpenPageAndWaitForUnload() { unload_observer_.reset( new UnloadObserver(ExtensionRegistry::Get(profile()))); const Extension* extension = InstallExtensionFromWebstore( @@ -113,14 +198,6 @@ class ContentVerifierTest : public ExtensionBrowserTest { page_url_ = extension->GetResourceURL("page.html"); delegate_.set_id(id_); ContentVerifyJob::SetDelegateForTests(&delegate_); - } - - virtual void TearDownOnMainThread() override { - ContentVerifyJob::SetDelegateForTests(NULL); - ExtensionBrowserTest::TearDownOnMainThread(); - } - - virtual void OpenPageAndWaitForUnload() { AddTabAtIndex(1, page_url_, ui::PAGE_TRANSITION_LINK); unload_observer_->WaitForUnload(id_); ExtensionPrefs* prefs = ExtensionPrefs::Get(profile()); @@ -149,4 +226,33 @@ IN_PROC_BROWSER_TEST_F(ContentVerifierTest, FailOnDone) { OpenPageAndWaitForUnload(); } +IN_PROC_BROWSER_TEST_F(ContentVerifierTest, DotSlashPaths) { + JobObserver job_observer; + ContentVerifyJob::SetObserverForTests(&job_observer); + std::string id = "hoipipabpcoomfapcecilckodldhmpgl"; + + job_observer.ExpectJobResult( + id, base::FilePath(FILE_PATH_LITERAL("background.js")), false); + job_observer.ExpectJobResult( + id, base::FilePath(FILE_PATH_LITERAL("page.html")), false); + job_observer.ExpectJobResult( + id, base::FilePath(FILE_PATH_LITERAL("page.js")), false); + job_observer.ExpectJobResult( + id, base::FilePath(FILE_PATH_LITERAL("dir/page2.html")), false); + job_observer.ExpectJobResult( + id, base::FilePath(FILE_PATH_LITERAL("page2.js")), false); + + // Install a test extension we copied from the webstore that has actual + // signatures, and contains image paths with leading "./". + const Extension* extension = InstallExtensionFromWebstore( + test_data_dir_.AppendASCII("content_verifier/dot_slash_paths.crx"), 1); + + ASSERT_TRUE(extension); + ASSERT_EQ(extension->id(), id); + + EXPECT_TRUE(job_observer.WaitForExpectedJobs()); + + ContentVerifyJob::SetObserverForTests(NULL); +} + } // namespace extensions diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths.crx b/chrome/test/data/extensions/content_verifier/dot_slash_paths.crx Binary files differnew file mode 100644 index 0000000..f7ec7fb --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths.crx diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths/_metadata/verified_contents.json b/chrome/test/data/extensions/content_verifier/dot_slash_paths/_metadata/verified_contents.json new file mode 100644 index 0000000..9e2927d --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths/_metadata/verified_contents.json @@ -0,0 +1 @@ +[{"description":"treehash per file","signed_content":{"payload":"eyJjb250ZW50X2hhc2hlcyI6W3siYmxvY2tfc2l6ZSI6NDA5NiwiZGlnZXN0Ijoic2hhMjU2IiwiZmlsZXMiOlt7InBhdGgiOiJiYWNrZ3JvdW5kLmpzIiwicm9vdF9oYXNoIjoidnZESEk3ZWFVdUYyWElGNzdjNE9vSk9pc0NTNFVoYmFtbTRVQTlLSS0yTSJ9LHsicGF0aCI6ImRpci9wYWdlMi5odG1sIiwicm9vdF9oYXNoIjoiVGNvanllU2xHMlpVOXQ4elVwVzJvQVp4cGlnb0FlZThMZkZZQmdJWWxzcyJ9LHsicGF0aCI6Imljb24ucG5nIiwicm9vdF9oYXNoIjoiWDdrMEFjLXJuc0NDc2ZteTJxejFRRnlWNnRtVkIyVUd0QmNDRDdrTHo2SSJ9LHsicGF0aCI6Imljb24yLnBuZyIsInJvb3RfaGFzaCI6ImlFc2xCdmd2dTVEaXcwbVM0enR6TVd5QnZSUGZJQkpjeS04QnJreEp0ZTgifSx7InBhdGgiOiJpY29uMy5wbmciLCJyb290X2hhc2giOiJiRWZMU1JmYWd3MlBGeGJfZDM3R1RtaV90Y3ZwMTdraHdubHQzcmhBQkgwIn0seyJwYXRoIjoiaW1nL3Rlc3QvaWNvbjQucG5nIiwicm9vdF9oYXNoIjoieWQxUV9xMGVSb21UZE5BQll1UVpaUFg1ZXlXN3JpZ0hWSzdhVWdNTHBJdyJ9LHsicGF0aCI6ImltZy90ZXN0L2ljb241LnBuZyIsInJvb3RfaGFzaCI6InhxUGdOQXU5RW1YQ25Td0xtTk9VZGZkX19CZENCN0o4SWM2WHcxNXRSeFUifSx7ImNhbm9uaWNhbF9qc29uX3Jvb3RfaGFzaCI6InczaHhzeUpJZ3ZLN2RaM1ZfSGdZWkpJZkEzLW5ReXhNYkduMTNnazIzT00iLCJwYXRoIjoibWFuaWZlc3QuanNvbiIsInJvb3RfaGFzaCI6ImhOdE1vSC1fUnRCY2U0TkVibG1sYW1ycUttTW4wNWFOdlQwZ2dBaHBqX1UifSx7InBhdGgiOiJwYWdlLmh0bWwiLCJyb290X2hhc2giOiJNQlhPN0s3aXdzaHByamdLbERjVGRXRXViVlJ6bENNbXF3N1dTZUNSMkRzIn0seyJwYXRoIjoicGFnZS5qcyIsInJvb3RfaGFzaCI6IjNWQnFaUG5iWXVZblBKaTZMeVRzenJZVDh1MjRXNkFNTWZDVkNQRlhuSzQifSx7InBhdGgiOiJwYWdlMi5qcyIsInJvb3RfaGFzaCI6IlRtaV9JM0w1UkdsYWRDMlk0OXZZNllENmhndzh5aXdvc215ZXJUREVVNEEifV0sImZvcm1hdCI6InRyZWVoYXNoIiwiaGFzaF9ibG9ja19zaXplIjo0MDk2fV0sIml0ZW1faWQiOiJob2lwaXBhYnBjb29tZmFwY2VjaWxja29kbGRobXBnbCIsIml0ZW1fdmVyc2lvbiI6IjAuOCIsInByb3RvY29sX3ZlcnNpb24iOjF9","signatures":[{"header":{"kid":"publisher"},"protected":"eyJhbGciOiJSUzI1NiJ9","signature":"nsuqgedzxEY0hfCqN9xHmKrIhXgIFljzTg8hFTiOnh2HhKr3I1HwhcBDbVSQ9Q0sYXW-lQTXHWR8cfcwABk4UbSQfhBa2Z2y9qoe-nyApf49anPVPG2SHbtBujYPF9qjSL0uGQpOFUSvICTiaEHl5pTUv1DvMOyiQ17JytqHaHF_towTgbSq-_b_D_8sK755S58L0BuJUhc31EJZ5llIwA1BlC8omQDpbch8QSzUQ6E0lLzK7MVkQRhVeK4bo5iMkbkQhPoTJDDFzAYwN2eafucfaZSWAADzhEp5oZbof-2QPzsuBkdOPD3F-LJ0cZ7JnW8shm1d6J9kD9PKxcreFw"},{"header":{"kid":"webstore"},"protected":"eyJhbGciOiJSUzI1NiJ9","signature":"CTCIYz86Sh043zJuIpW246A1IH64nZ2mc8zFZk4JqlWNBrkzQi38QRmSM5fKtwGPRjxLKXLsJ1LMV4HRIQHbAAJ3r4DoGF85vpJ3YgnAmFLt3uEfKC4qu8SWDVY5Ad5vZbGf1BjvTljqZJVqPjEvSQmWaNYqTKQs_3Sviil994Nq6M9tChkcQzUjZOBQSz0MTBXeYtSkla80V13VvUJ204vv_hIg0GW3x0opQ70r8qXa25aEB1zPNUavi844IgUM5fdLirMprYCjxn47Qby7MxEIchX3b9cQDiOhqFcqluD5b_s3e3fXTkAaHnc4e5GSv2SPZb10GQH97U-_5gC8tw"}]}}]
\ No newline at end of file diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths/background.js b/chrome/test/data/extensions/content_verifier/dot_slash_paths/background.js new file mode 100644 index 0000000..e8fd254 --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths/background.js @@ -0,0 +1,9 @@ +// 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. + +chrome.tabs.create({url:"./page.html"}); +chrome.tabs.create({url:"dir/page2.html"}); + +if (chrome.test) + chrome.test.sendMessage("background ok"); diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths/dir/page2.html b/chrome/test/data/extensions/content_verifier/dot_slash_paths/dir/page2.html new file mode 100644 index 0000000..a6aeb6c --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths/dir/page2.html @@ -0,0 +1,2 @@ + +<script src="../page2.js"></script> diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths/icon.png b/chrome/test/data/extensions/content_verifier/dot_slash_paths/icon.png Binary files differnew file mode 100644 index 0000000..88d7762 --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths/icon.png diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths/icon2.png b/chrome/test/data/extensions/content_verifier/dot_slash_paths/icon2.png Binary files differnew file mode 100644 index 0000000..64f52e2 --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths/icon2.png diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths/icon3.png b/chrome/test/data/extensions/content_verifier/dot_slash_paths/icon3.png Binary files differnew file mode 100644 index 0000000..93b2155 --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths/icon3.png diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths/img/test/icon4.png b/chrome/test/data/extensions/content_verifier/dot_slash_paths/img/test/icon4.png Binary files differnew file mode 100644 index 0000000..160fb7c --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths/img/test/icon4.png diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths/img/test/icon5.png b/chrome/test/data/extensions/content_verifier/dot_slash_paths/img/test/icon5.png Binary files differnew file mode 100644 index 0000000..cc63de6 --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths/img/test/icon5.png diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths/manifest.json b/chrome/test/data/extensions/content_verifier/dot_slash_paths/manifest.json new file mode 100644 index 0000000..20f715b --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths/manifest.json @@ -0,0 +1,19 @@ +{ +"update_url": "https://clients2.google.com/service/update2/crx", + + "name": "Content Verification ./ paths Test", + "version": "0.8", + "manifest_version": 2, + "background": { + "scripts": ["./background.js"] + }, + "icons": { + "128": "./icon.png", + "64": "/icon2.png", + "32": "img/test/icon4.png", + "16": "./img/test/icon5.png" + }, + "browser_action": { + "default_icon": "./icon3.png" + } +} diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths/page.html b/chrome/test/data/extensions/content_verifier/dot_slash_paths/page.html new file mode 100644 index 0000000..dcb3248f --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths/page.html @@ -0,0 +1 @@ +<script src="./page.js"></script> diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths/page.js b/chrome/test/data/extensions/content_verifier/dot_slash_paths/page.js new file mode 100644 index 0000000..d5a1e69 --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths/page.js @@ -0,0 +1,6 @@ +// 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. + +if (chrome.test) + chrome.test.sendMessage("page ok"); diff --git a/chrome/test/data/extensions/content_verifier/dot_slash_paths/page2.js b/chrome/test/data/extensions/content_verifier/dot_slash_paths/page2.js new file mode 100644 index 0000000..9eb5b1d --- /dev/null +++ b/chrome/test/data/extensions/content_verifier/dot_slash_paths/page2.js @@ -0,0 +1,6 @@ +// 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. + +if (chrome.test) + chrome.test.sendMessage("page2 ok"); diff --git a/extensions/browser/content_verifier.cc b/extensions/browser/content_verifier.cc index 9418fe3..8c05f12 100644 --- a/extensions/browser/content_verifier.cc +++ b/extensions/browser/content_verifier.cc @@ -8,6 +8,7 @@ #include "base/files/file_path.h" #include "base/stl_util.h" +#include "base/strings/string_util.h" #include "content/public/browser/browser_thread.h" #include "extensions/browser/content_hash_fetcher.h" #include "extensions/browser/content_hash_reader.h" @@ -107,6 +108,28 @@ void ContentVerifier::VerifyFailed(const std::string& extension_id, } } +static base::FilePath MakeImagePathRelative(const base::FilePath& path) { + if (path.ReferencesParent()) + return base::FilePath(); + + std::vector<base::FilePath::StringType> parts; + path.GetComponents(&parts); + if (parts.empty()) + return base::FilePath(); + + // Remove the first component if it is '.' or '/' or '//'. + const base::FilePath::StringType separators( + base::FilePath::kSeparators, base::FilePath::kSeparatorsLength); + if (!parts[0].empty() && + (parts[0] == base::FilePath::kCurrentDirectory || + parts[0].find_first_not_of(separators) == std::string::npos)) + parts.erase(parts.begin()); + + // Note that elsewhere we always normalize path separators to '/' so this + // should work for all platforms. + return base::FilePath(JoinString(parts, '/')); +} + void ContentVerifier::OnExtensionLoaded( content::BrowserContext* browser_context, const Extension* extension) { @@ -115,9 +138,21 @@ void ContentVerifier::OnExtensionLoaded( ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension); if (mode != ContentVerifierDelegate::NONE) { + // The browser image paths from the extension may not be relative (eg + // they might have leading '/' or './'), so we strip those to make + // comparing to actual relative paths work later on. + std::set<base::FilePath> original_image_paths = + delegate_->GetBrowserImagePaths(extension); + + scoped_ptr<std::set<base::FilePath>> image_paths( + new std::set<base::FilePath>); + for (const auto& path : original_image_paths) { + image_paths->insert(MakeImagePathRelative(path)); + } + scoped_ptr<ContentVerifierIOData::ExtensionData> data( new ContentVerifierIOData::ExtensionData( - delegate_->GetBrowserImagePaths(extension), + image_paths.Pass(), extension->version() ? *extension->version() : base::Version())); content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, @@ -196,7 +231,7 @@ bool ContentVerifier::ShouldVerifyAnyPaths( if (!data) return false; - const std::set<base::FilePath>& browser_images = data->browser_image_paths; + const std::set<base::FilePath>& browser_images = *(data->browser_image_paths); base::FilePath locales_dir = extension_root.Append(kLocaleFolder); scoped_ptr<std::set<std::string> > all_locales; diff --git a/extensions/browser/content_verifier_io_data.cc b/extensions/browser/content_verifier_io_data.cc index e797488..102c95b 100644 --- a/extensions/browser/content_verifier_io_data.cc +++ b/extensions/browser/content_verifier_io_data.cc @@ -9,9 +9,9 @@ namespace extensions { ContentVerifierIOData::ExtensionData::ExtensionData( - const std::set<base::FilePath>& browser_image_paths, + scoped_ptr<std::set<base::FilePath>> browser_image_paths, const base::Version& version) { - this->browser_image_paths = browser_image_paths; + this->browser_image_paths = browser_image_paths.Pass(); this->version = version; } @@ -27,6 +27,7 @@ ContentVerifierIOData::~ContentVerifierIOData() { void ContentVerifierIOData::AddData(const std::string& extension_id, scoped_ptr<ExtensionData> data) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + CHECK(data->browser_image_paths.get()); data_map_[extension_id] = linked_ptr<ExtensionData>(data.release()); } diff --git a/extensions/browser/content_verifier_io_data.h b/extensions/browser/content_verifier_io_data.h index 64596f5..92e8248 100644 --- a/extensions/browser/content_verifier_io_data.h +++ b/extensions/browser/content_verifier_io_data.h @@ -23,10 +23,10 @@ class ContentVerifierIOData : public base::RefCountedThreadSafe<ContentVerifierIOData> { public: struct ExtensionData { - std::set<base::FilePath> browser_image_paths; + scoped_ptr<std::set<base::FilePath>> browser_image_paths; base::Version version; - ExtensionData(const std::set<base::FilePath>& browser_image_paths, + ExtensionData(scoped_ptr<std::set<base::FilePath>> browser_image_paths, const base::Version& version); ~ExtensionData(); }; diff --git a/extensions/browser/content_verify_job.cc b/extensions/browser/content_verify_job.cc index e7e8cab..651dcae 100644 --- a/extensions/browser/content_verify_job.cc +++ b/extensions/browser/content_verify_job.cc @@ -20,6 +20,7 @@ namespace extensions { namespace { ContentVerifyJob::TestDelegate* g_test_delegate = NULL; +ContentVerifyJob::TestObserver* g_test_observer = NULL; class ScopedElapsedTimer { public: @@ -60,6 +61,9 @@ ContentVerifyJob::~ContentVerifyJob() { void ContentVerifyJob::Start() { DCHECK(thread_checker_.CalledOnValidThread()); + if (g_test_observer) + g_test_observer->JobStarted(hash_reader_->extension_id(), + hash_reader_->relative_path()); base::PostTaskAndReplyWithResult( content::BrowserThread::GetBlockingPool(), FROM_HERE, @@ -130,6 +134,10 @@ void ContentVerifyJob::DoneReading() { done_reading_ = true; if (hashes_ready_ && !FinishBlock()) DispatchFailureCallback(HASH_MISMATCH); + + if (!failed_ && g_test_observer) + g_test_observer->JobFinished( + hash_reader_->extension_id(), hash_reader_->relative_path(), failed_); } bool ContentVerifyJob::FinishBlock() { @@ -182,6 +190,11 @@ void ContentVerifyJob::SetDelegateForTests(TestDelegate* delegate) { g_test_delegate = delegate; } +// static +void ContentVerifyJob::SetObserverForTests(TestObserver* observer) { + g_test_observer = observer; +} + void ContentVerifyJob::DispatchFailureCallback(FailureReason reason) { DCHECK(!failed_); failed_ = true; @@ -192,6 +205,9 @@ void ContentVerifyJob::DispatchFailureCallback(FailureReason reason) { failure_callback_.Run(reason); failure_callback_.Reset(); } + if (g_test_observer) + g_test_observer->JobFinished( + hash_reader_->extension_id(), hash_reader_->relative_path(), failed_); } } // namespace extensions diff --git a/extensions/browser/content_verify_job.h b/extensions/browser/content_verify_job.h index 3ec9f85..67b7f05 100644 --- a/extensions/browser/content_verify_job.h +++ b/extensions/browser/content_verify_job.h @@ -78,7 +78,18 @@ class ContentVerifyJob : public base::RefCountedThreadSafe<ContentVerifyJob> { virtual FailureReason DoneReading(const std::string& extension_id) = 0; }; + class TestObserver { + public: + virtual void JobStarted(const std::string& extension_id, + const base::FilePath& relative_path) = 0; + + virtual void JobFinished(const std::string& extension_id, + const base::FilePath& relative_path, + bool failed) = 0; + }; + static void SetDelegateForTests(TestDelegate* delegate); + static void SetObserverForTests(TestObserver* observer); private: DISALLOW_COPY_AND_ASSIGN(ContentVerifyJob); |