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"
22 namespace test_server
{
27 namespace extensions
{
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.
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
) {
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";
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
{
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.
86 injection_type_
= Action::NO_AD_INJECTION
;
87 found_multiple_injections_
= false;
91 Action::InjectionType
injection_type() const { return injection_type_
; }
93 bool found_multiple_injections() const { return found_multiple_injections_
; }
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.
113 ActivityLogObserver::ActivityLogObserver(content::BrowserContext
* context
)
114 : scoped_observer_(this),
116 injection_type_(Action::NO_AD_INJECTION
),
117 found_multiple_injections_(false),
119 ActivityLog::GetInstance(context_
)->AddObserver(this);
122 ActivityLogObserver::~ActivityLogObserver() {}
124 void ActivityLogObserver::OnExtensionActivity(scoped_refptr
<Action
> action
) {
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
{
141 TestAdNetworkDatabase();
142 ~TestAdNetworkDatabase() override
;
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();
171 class AdInjectionBrowserTest
: public ExtensionBrowserTest
{
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(); }
198 // The name of the last completed test; used in case of unexpected failure for
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.
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
));
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
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
258 observer()->disable();
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();
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
);
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
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
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("/"));
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");
385 // If this test grows, we should consolidate it and AdInjectionBrowserTest.
386 class ExecuteScriptAdInjectionBrowserTest
: public ExtensionBrowserTest
{
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());
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