• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
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/browser/extensions/extension_test_message_listener.h"
15 #include "chrome/test/base/ui_test_utils.h"
16 #include "extensions/common/extension.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"
20 
21 namespace net {
22 namespace test_server {
23 struct HttpRequest;
24 }
25 }
26 
27 namespace extensions {
28 
29 namespace {
30 
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";
35 
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.
41 };
42 
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";
51 
InjectionTypeToString(Action::InjectionType type)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";
68   }
69   return std::string();
70 }
71 
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();
80 
81   // Disable the observer (e.g., to reset the page).
disable()82   void disable() { enabled_ = false; }
83 
84   // Enable the observer, resetting the state.
enable()85   void enable() {
86     injection_type_ = Action::NO_AD_INJECTION;
87     found_multiple_injections_ = false;
88     enabled_ = true;
89   }
90 
injection_type() const91   Action::InjectionType injection_type() const { return injection_type_; }
92 
found_multiple_injections() const93   bool found_multiple_injections() const { return found_multiple_injections_; }
94 
95  private:
96   virtual void OnExtensionActivity(scoped_refptr<Action> action) OVERRIDE;
97 
98   ScopedObserver<ActivityLog, ActivityLog::Observer> scoped_observer_;
99 
100   // The associated BrowserContext.
101   content::BrowserContext* context_;
102 
103   // The type of the last injection.
104   Action::InjectionType injection_type_;
105 
106   // Whether or not we found multiple injection types (which shouldn't happen).
107   bool found_multiple_injections_;
108 
109   // Whether or not the observer is enabled.
110   bool enabled_;
111 };
112 
ActivityLogObserver(content::BrowserContext * context)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);
120 }
121 
~ActivityLogObserver()122 ActivityLogObserver::~ActivityLogObserver() {}
123 
OnExtensionActivity(scoped_refptr<Action> action)124 void ActivityLogObserver::OnExtensionActivity(scoped_refptr<Action> action) {
125   if (!enabled_)
126     return;
127 
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;
134   }
135 }
136 
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   virtual ~TestAdNetworkDatabase();
143 
144  private:
145   virtual bool IsAdNetwork(const GURL& url) const OVERRIDE;
146 
147   GURL ad_network_url1_;
148   GURL ad_network_url2_;
149 };
150 
TestAdNetworkDatabase()151 TestAdNetworkDatabase::TestAdNetworkDatabase() : ad_network_url1_(kAdNetwork1),
152                                                  ad_network_url2_(kAdNetwork2) {
153 }
154 
~TestAdNetworkDatabase()155 TestAdNetworkDatabase::~TestAdNetworkDatabase() {}
156 
IsAdNetwork(const GURL & url) const157 bool TestAdNetworkDatabase::IsAdNetwork(const GURL& url) const {
158   return url == ad_network_url1_ || url == ad_network_url2_;
159 }
160 
HandleRequest(const net::test_server::HttpRequest & request)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.PassAs<net::test_server::HttpResponse>();
167 }
168 
169 }  // namespace
170 
171 class AdInjectionBrowserTest : public ExtensionBrowserTest {
172  protected:
173   AdInjectionBrowserTest();
174   virtual ~AdInjectionBrowserTest();
175 
176   virtual void SetUpOnMainThread() OVERRIDE;
177   virtual void TearDownOnMainThread() OVERRIDE;
178 
179   // Handle the "Reset Begin" stage of the test.
180   testing::AssertionResult HandleResetBeginStage();
181 
182   // Handle the "Reset End" stage of the test.
183   testing::AssertionResult HandleResetEndStage();
184 
185   // Handle the "Testing" stage of the test.
186   testing::AssertionResult HandleTestingStage(const std::string& message);
187 
188   // Handle a JS error encountered in a test.
189   testing::AssertionResult HandleJSError(const std::string& message);
190 
test_data_dir()191   const base::FilePath& test_data_dir() { return test_data_dir_; }
192 
listener()193   ExtensionTestMessageListener* listener() { return listener_.get(); }
194 
observer()195   ActivityLogObserver* observer() { return observer_.get(); }
196 
197  private:
198   // The name of the last completed test; used in case of unexpected failure for
199   // debugging.
200   std::string last_test_;
201 
202   // A listener for any messages from our ad-injecting extension.
203   scoped_ptr<ExtensionTestMessageListener> listener_;
204 
205   // An observer to be alerted when we detect ad injection.
206   scoped_ptr<ActivityLogObserver> observer_;
207 
208   // The current stage of the test.
209   Stage stage_;
210 };
211 
AdInjectionBrowserTest()212 AdInjectionBrowserTest::AdInjectionBrowserTest() : stage_(BEFORE_RESET) {
213 }
214 
~AdInjectionBrowserTest()215 AdInjectionBrowserTest::~AdInjectionBrowserTest() {
216 }
217 
SetUpOnMainThread()218 void AdInjectionBrowserTest::SetUpOnMainThread() {
219   ExtensionBrowserTest::SetUpOnMainThread();
220 
221   ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
222   embedded_test_server()->RegisterRequestHandler(base::Bind(&HandleRequest));
223 
224   test_data_dir_ =
225       test_data_dir_.AppendASCII("activity_log").AppendASCII("ad_injection");
226   observer_.reset(new ActivityLogObserver(profile()));
227 
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 */));
233 
234   // Enable the activity log for this test.
235   ActivityLog::GetInstance(profile())->SetWatchdogAppActiveForTesting(true);
236 
237   // Set the ad network database.
238   AdNetworkDatabase::SetForTesting(
239       scoped_ptr<AdNetworkDatabase>(new TestAdNetworkDatabase));
240 }
241 
TearDownOnMainThread()242 void AdInjectionBrowserTest::TearDownOnMainThread() {
243   observer_.reset(NULL);
244   listener_.reset(NULL);
245   ActivityLog::GetInstance(profile())->SetWatchdogAppActiveForTesting(false);
246 
247   ExtensionBrowserTest::TearDownOnMainThread();
248 }
249 
HandleResetBeginStage()250 testing::AssertionResult AdInjectionBrowserTest::HandleResetBeginStage() {
251   if (stage_ != BEFORE_RESET) {
252     return testing::AssertionFailure()
253            << "In incorrect stage. Last Test: " << last_test_;
254   }
255 
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();
261 }
262 
HandleResetEndStage()263 testing::AssertionResult AdInjectionBrowserTest::HandleResetEndStage() {
264   if (stage_ != RESETTING) {
265     return testing::AssertionFailure()
266            << "In incorrect stage. Last test: " << last_test_;
267   }
268 
269   // Look for ad injection again, now that the reset is over.
270   observer()->enable();
271   stage_ = TESTING;
272   return testing::AssertionSuccess();
273 }
274 
HandleTestingStage(const std::string & message)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_;
280   }
281 
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;
295   }
296 
297   last_test_ = message.substr(0, sep);
298 
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());
312   }
313 
314   stage_ = BEFORE_RESET;
315 
316   if (!error.empty()) {
317     return testing::AssertionFailure()
318         << "Error in Test '" << last_test_ << "': " << error;
319   }
320 
321   return testing::AssertionSuccess();
322 }
323 
HandleJSError(const std::string & message)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;
335   }
336 
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);
340 
341   // We set the stage here, so that subsequent tests don't fail.
342   stage_ = BEFORE_RESET;
343 
344   return testing::AssertionFailure() << "Javascript Error in test '"
345                                      << test_name << "': " << test_err;
346 }
347 
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.
IN_PROC_BROWSER_TEST_F(AdInjectionBrowserTest,DetectAdInjections)355 IN_PROC_BROWSER_TEST_F(AdInjectionBrowserTest, DetectAdInjections) {
356   const Extension* extension = LoadExtension(test_data_dir_);
357   ASSERT_TRUE(extension);
358 
359   ui_test_utils::NavigateToURL(browser(), embedded_test_server()->GetURL("/"));
360 
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));
376     }
377 
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();
382   }
383 }
384 
385 // TODO(rdevlin.cronin): We test a good amount of ways of injecting ads with
386 // the above test, but more is better in testing.
387 // See crbug.com/357204.
388 
389 }  // namespace extensions
390