Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / extensions / activity_log / ad_injection_browsertest.cc
bloba28702041f8486239c637e285315ea238e1a5e85
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/files/file_path.h"
6 #include "base/scoped_observer.h"
7 #include "base/strings/string_number_conversions.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "chrome/browser/extensions/activity_log/activity_actions.h"
11 #include "chrome/browser/extensions/activity_log/activity_log.h"
12 #include "chrome/browser/extensions/activity_log/ad_network_database.h"
13 #include "chrome/browser/extensions/extension_browsertest.h"
14 #include "chrome/test/base/ui_test_utils.h"
15 #include "extensions/common/extension.h"
16 #include "extensions/test/extension_test_message_listener.h"
17 #include "net/test/embedded_test_server/embedded_test_server.h"
18 #include "net/test/embedded_test_server/http_response.h"
19 #include "url/gurl.h"
21 namespace net {
22 namespace test_server {
23 struct HttpRequest;
27 namespace extensions {
29 namespace {
31 // The "ad network" that we are using. Any src or href equal to this should be
32 // considered an ad network.
33 const char kAdNetwork1[] = "http://www.known-ads.adnetwork";
34 const char kAdNetwork2[] = "http://www.also-known-ads.adnetwork";
36 // The current stage of the test.
37 enum Stage {
38 BEFORE_RESET, // We are about to reset the page.
39 RESETTING, // We are resetting the page.
40 TESTING // The reset is complete, and we are testing.
43 // The string sent by the test to indicate that the page reset will begin.
44 const char kResetBeginString[] = "Page Reset Begin";
45 // The string sent by the test to indicate that page reset is complete.
46 const char kResetEndString[] = "Page Reset End";
47 // The string sent by the test to indicate a JS error was caught in the test.
48 const char kJavascriptErrorString[] = "Testing Error";
49 // The string sent by the test to indicate that we have concluded the full test.
50 const char kTestCompleteString[] = "Test Complete";
52 std::string InjectionTypeToString(Action::InjectionType type) {
53 switch (type) {
54 case Action::NO_AD_INJECTION:
55 return "No Ad Injection";
56 case Action::INJECTION_NEW_AD:
57 return "Injection New Ad";
58 case Action::INJECTION_REMOVED_AD:
59 return "Injection Removed Ad";
60 case Action::INJECTION_REPLACED_AD:
61 return "Injection Replaced Ad";
62 case Action::INJECTION_LIKELY_NEW_AD:
63 return "Injection Likely New Ad";
64 case Action::INJECTION_LIKELY_REPLACED_AD:
65 return "Injection Likely Replaced Ad";
66 case Action::NUM_INJECTION_TYPES:
67 return "Num Injection Types";
69 return std::string();
72 // An implementation of ActivityLog::Observer that, for every action, sends it
73 // through Action::DidInjectAd(). This will keep track of the observed
74 // injections, and can be enabled or disabled as needed (for instance, this
75 // should be disabled while we are resetting the page).
76 class ActivityLogObserver : public ActivityLog::Observer {
77 public:
78 explicit ActivityLogObserver(content::BrowserContext* context);
79 virtual ~ActivityLogObserver();
81 // Disable the observer (e.g., to reset the page).
82 void disable() { enabled_ = false; }
84 // Enable the observer, resetting the state.
85 void enable() {
86 injection_type_ = Action::NO_AD_INJECTION;
87 found_multiple_injections_ = false;
88 enabled_ = true;
91 Action::InjectionType injection_type() const { return injection_type_; }
93 bool found_multiple_injections() const { return found_multiple_injections_; }
95 private:
96 void OnExtensionActivity(scoped_refptr<Action> action) override;
98 ScopedObserver<ActivityLog, ActivityLog::Observer> scoped_observer_;
100 // The associated BrowserContext.
101 content::BrowserContext* context_;
103 // The type of the last injection.
104 Action::InjectionType injection_type_;
106 // Whether or not we found multiple injection types (which shouldn't happen).
107 bool found_multiple_injections_;
109 // Whether or not the observer is enabled.
110 bool enabled_;
113 ActivityLogObserver::ActivityLogObserver(content::BrowserContext* context)
114 : scoped_observer_(this),
115 context_(context),
116 injection_type_(Action::NO_AD_INJECTION),
117 found_multiple_injections_(false),
118 enabled_(false) {
119 ActivityLog::GetInstance(context_)->AddObserver(this);
122 ActivityLogObserver::~ActivityLogObserver() {}
124 void ActivityLogObserver::OnExtensionActivity(scoped_refptr<Action> action) {
125 if (!enabled_)
126 return;
128 Action::InjectionType type =
129 action->DidInjectAd(NULL /* no rappor service */);
130 if (type != Action::NO_AD_INJECTION) {
131 if (injection_type_ != Action::NO_AD_INJECTION)
132 found_multiple_injections_ = true;
133 injection_type_ = type;
137 // A mock for the AdNetworkDatabase. This simply says that the URL
138 // http://www.known-ads.adnetwork is an ad network, and nothing else is.
139 class TestAdNetworkDatabase : public AdNetworkDatabase {
140 public:
141 TestAdNetworkDatabase();
142 ~TestAdNetworkDatabase() override;
144 private:
145 bool IsAdNetwork(const GURL& url) const override;
147 GURL ad_network_url1_;
148 GURL ad_network_url2_;
151 TestAdNetworkDatabase::TestAdNetworkDatabase() : ad_network_url1_(kAdNetwork1),
152 ad_network_url2_(kAdNetwork2) {
155 TestAdNetworkDatabase::~TestAdNetworkDatabase() {}
157 bool TestAdNetworkDatabase::IsAdNetwork(const GURL& url) const {
158 return url == ad_network_url1_ || url == ad_network_url2_;
161 scoped_ptr<net::test_server::HttpResponse> HandleRequest(
162 const net::test_server::HttpRequest& request) {
163 scoped_ptr<net::test_server::BasicHttpResponse> response(
164 new net::test_server::BasicHttpResponse());
165 response->set_code(net::HTTP_OK);
166 return response.Pass();
169 } // namespace
171 class AdInjectionBrowserTest : public ExtensionBrowserTest {
172 protected:
173 AdInjectionBrowserTest();
174 ~AdInjectionBrowserTest() override;
176 void SetUpOnMainThread() override;
177 void TearDownOnMainThread() override;
179 // Handle the "Reset Begin" stage of the test.
180 testing::AssertionResult HandleResetBeginStage();
182 // Handle the "Reset End" stage of the test.
183 testing::AssertionResult HandleResetEndStage();
185 // Handle the "Testing" stage of the test.
186 testing::AssertionResult HandleTestingStage(const std::string& message);
188 // Handle a JS error encountered in a test.
189 testing::AssertionResult HandleJSError(const std::string& message);
191 const base::FilePath& test_data_dir() { return test_data_dir_; }
193 ExtensionTestMessageListener* listener() { return listener_.get(); }
195 ActivityLogObserver* observer() { return observer_.get(); }
197 private:
198 // The name of the last completed test; used in case of unexpected failure for
199 // debugging.
200 std::string last_test_;
202 // A listener for any messages from our ad-injecting extension.
203 scoped_ptr<ExtensionTestMessageListener> listener_;
205 // An observer to be alerted when we detect ad injection.
206 scoped_ptr<ActivityLogObserver> observer_;
208 // The current stage of the test.
209 Stage stage_;
212 AdInjectionBrowserTest::AdInjectionBrowserTest() : stage_(BEFORE_RESET) {
215 AdInjectionBrowserTest::~AdInjectionBrowserTest() {
218 void AdInjectionBrowserTest::SetUpOnMainThread() {
219 ExtensionBrowserTest::SetUpOnMainThread();
221 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
222 embedded_test_server()->RegisterRequestHandler(base::Bind(&HandleRequest));
224 test_data_dir_ =
225 test_data_dir_.AppendASCII("activity_log").AppendASCII("ad_injection");
226 observer_.reset(new ActivityLogObserver(profile()));
228 // We use a listener in order to keep the actions in the Javascript test
229 // synchronous. At the end of each stage, the test will send us a message
230 // with the stage and status, and will not advance until we reply with
231 // a message.
232 listener_.reset(new ExtensionTestMessageListener(true /* will reply */));
234 // Enable the activity log for this test.
235 ActivityLog::GetInstance(profile())->SetWatchdogAppActiveForTesting(true);
237 // Set the ad network database.
238 AdNetworkDatabase::SetForTesting(
239 scoped_ptr<AdNetworkDatabase>(new TestAdNetworkDatabase));
242 void AdInjectionBrowserTest::TearDownOnMainThread() {
243 observer_.reset(NULL);
244 listener_.reset(NULL);
245 ActivityLog::GetInstance(profile())->SetWatchdogAppActiveForTesting(false);
247 ExtensionBrowserTest::TearDownOnMainThread();
250 testing::AssertionResult AdInjectionBrowserTest::HandleResetBeginStage() {
251 if (stage_ != BEFORE_RESET) {
252 return testing::AssertionFailure()
253 << "In incorrect stage. Last Test: " << last_test_;
256 // Stop looking for ad injection, since some of the reset could be considered
257 // ad injection.
258 observer()->disable();
259 stage_ = RESETTING;
260 return testing::AssertionSuccess();
263 testing::AssertionResult AdInjectionBrowserTest::HandleResetEndStage() {
264 if (stage_ != RESETTING) {
265 return testing::AssertionFailure()
266 << "In incorrect stage. Last test: " << last_test_;
269 // Look for ad injection again, now that the reset is over.
270 observer()->enable();
271 stage_ = TESTING;
272 return testing::AssertionSuccess();
275 testing::AssertionResult AdInjectionBrowserTest::HandleTestingStage(
276 const std::string& message) {
277 if (stage_ != TESTING) {
278 return testing::AssertionFailure()
279 << "In incorrect stage. Last test: " << last_test_;
282 // The format for a testing message is:
283 // "<test_name>:<expected_change>"
284 // where <test_name> is the name of the test and <expected_change> is
285 // either -1 for no ad injection (to test against false positives) or the
286 // number corresponding to ad_detection::InjectionType.
287 size_t sep = message.find(':');
288 int expected_change = -1;
289 if (sep == std::string::npos ||
290 !base::StringToInt(message.substr(sep + 1), &expected_change) ||
291 (expected_change < Action::NO_AD_INJECTION ||
292 expected_change >= Action::NUM_INJECTION_TYPES)) {
293 return testing::AssertionFailure()
294 << "Invalid message received for testing stage: " << message;
297 last_test_ = message.substr(0, sep);
299 Action::InjectionType expected_injection =
300 static_cast<Action::InjectionType>(expected_change);
301 std::string error;
302 if (observer()->found_multiple_injections()) {
303 error = "Found multiple injection types. "
304 "Only one injection is expected per test.";
305 } else if (expected_injection != observer()->injection_type()) {
306 // We need these static casts, because size_t is different on different
307 // architectures, and printf becomes unhappy.
308 error = base::StringPrintf(
309 "Incorrect Injection Found: Expected: %s, Actual: %s",
310 InjectionTypeToString(expected_injection).c_str(),
311 InjectionTypeToString(observer()->injection_type()).c_str());
314 stage_ = BEFORE_RESET;
316 if (!error.empty()) {
317 return testing::AssertionFailure()
318 << "Error in Test '" << last_test_ << "': " << error;
321 return testing::AssertionSuccess();
324 testing::AssertionResult AdInjectionBrowserTest::HandleJSError(
325 const std::string& message) {
326 // The format for a testing message is:
327 // "Testing Error:<test_name>:<error>"
328 // where <test_name> is the name of the test and <error> is the error which
329 // was encountered.
330 size_t first_sep = message.find(':');
331 size_t second_sep = message.find(':', first_sep + 1);
332 if (first_sep == std::string::npos || second_sep == std::string::npos) {
333 return testing::AssertionFailure()
334 << "Invalid message received: " << message;
337 std::string test_name =
338 message.substr(first_sep + 1, second_sep - first_sep - 1);
339 std::string test_err = message.substr(second_sep + 1);
341 // We set the stage here, so that subsequent tests don't fail.
342 stage_ = BEFORE_RESET;
344 return testing::AssertionFailure() << "Javascript Error in test '"
345 << test_name << "': " << test_err;
348 // This is the primary Ad-Injection browser test. It loads an extension that
349 // has a content script that, in turn, injects ads left, right, and center.
350 // The content script waits after each injection for a response from this
351 // browsertest, in order to ensure synchronicity. After each injection, the
352 // content script cleans up after itself. For significantly more detailed
353 // comments, see
354 // chrome/test/data/extensions/activity_log/ad_injection/content_script.js.
355 IN_PROC_BROWSER_TEST_F(AdInjectionBrowserTest, DetectAdInjections) {
356 const Extension* extension = LoadExtension(test_data_dir_);
357 ASSERT_TRUE(extension);
359 ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL("/"));
361 std::string message;
362 while (message != "TestComplete") {
363 listener()->WaitUntilSatisfied();
364 message = listener()->message();
365 if (message == kResetBeginString) {
366 ASSERT_TRUE(HandleResetBeginStage());
367 } else if (message == kResetEndString) {
368 ASSERT_TRUE(HandleResetEndStage());
369 } else if (!message.compare(
370 0, strlen(kJavascriptErrorString), kJavascriptErrorString)) {
371 EXPECT_TRUE(HandleJSError(message));
372 } else if (message == kTestCompleteString) {
373 break; // We're done!
374 } else { // We're in some kind of test.
375 EXPECT_TRUE(HandleTestingStage(message));
378 // In all cases (except for "Test Complete", in which case we already
379 // break'ed), we reply with a continue message.
380 listener()->Reply("Continue");
381 listener()->Reset();
385 // If this test grows, we should consolidate it and AdInjectionBrowserTest.
386 class ExecuteScriptAdInjectionBrowserTest : public ExtensionBrowserTest {
387 protected:
388 void SetUpOnMainThread() override;
389 void TearDownOnMainThread() override;
392 void ExecuteScriptAdInjectionBrowserTest::SetUpOnMainThread() {
393 ExtensionBrowserTest::SetUpOnMainThread();
395 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
396 embedded_test_server()->RegisterRequestHandler(base::Bind(&HandleRequest));
398 // Enable the activity log for this test.
399 ActivityLog::GetInstance(profile())->SetWatchdogAppActiveForTesting(true);
401 // Set the ad network database.
402 AdNetworkDatabase::SetForTesting(
403 scoped_ptr<AdNetworkDatabase>(new TestAdNetworkDatabase));
406 void ExecuteScriptAdInjectionBrowserTest::TearDownOnMainThread() {
407 ActivityLog::GetInstance(profile())->SetWatchdogAppActiveForTesting(false);
408 ExtensionBrowserTest::TearDownOnMainThread();
411 // Test that using chrome.tabs.executeScript doesn't circumvent our detection.
412 // Since each type of injection is tested more thoroughly in the test above,
413 // this test just needs to make sure that we detect anything from executeScript.
414 IN_PROC_BROWSER_TEST_F(ExecuteScriptAdInjectionBrowserTest,
415 ExecuteScriptAdInjection) {
416 const Extension* extension =
417 LoadExtension(test_data_dir_.AppendASCII("activity_log")
418 .AppendASCII("execute_script_ad_injection"));
419 ASSERT_TRUE(extension);
421 ExtensionTestMessageListener listener(false); // Won't reply.
422 listener.set_extension_id(extension->id());
423 ActivityLogObserver observer(profile());
424 observer.enable();
426 ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL("/"));
428 // The extension sends a "Done" message when the script has executed.
429 listener.WaitUntilSatisfied();
430 EXPECT_EQ("Done", listener.message());
432 // We should have injected an ad.
433 EXPECT_EQ(Action::INJECTION_NEW_AD, observer.injection_type());
434 EXPECT_FALSE(observer.found_multiple_injections());
437 // TODO(rdevlin.cronin): We test a good amount of ways of injecting ads with
438 // the above test, but more is better in testing.
439 // See crbug.com/357204.
441 } // namespace extensions