Home | History | Annotate | Download | only in activity_log
      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 
     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).
     82   void disable() { enabled_ = false; }
     83 
     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;
     89   }
     90 
     91   Action::InjectionType injection_type() const { return injection_type_; }
     92 
     93   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 
    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 
    122 ActivityLogObserver::~ActivityLogObserver() {}
    123 
    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 
    151 TestAdNetworkDatabase::TestAdNetworkDatabase() : ad_network_url1_(kAdNetwork1),
    152                                                  ad_network_url2_(kAdNetwork2) {
    153 }
    154 
    155 TestAdNetworkDatabase::~TestAdNetworkDatabase() {}
    156 
    157 bool TestAdNetworkDatabase::IsAdNetwork(const GURL& url) const {
    158   return url == ad_network_url1_ || url == ad_network_url2_;
    159 }
    160 
    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 
    191   const base::FilePath& test_data_dir() { return test_data_dir_; }
    192 
    193   ExtensionTestMessageListener* listener() { return listener_.get(); }
    194 
    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 
    212 AdInjectionBrowserTest::AdInjectionBrowserTest() : stage_(BEFORE_RESET) {
    213 }
    214 
    215 AdInjectionBrowserTest::~AdInjectionBrowserTest() {
    216 }
    217 
    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 
    242 void AdInjectionBrowserTest::TearDownOnMainThread() {
    243   observer_.reset(NULL);
    244   listener_.reset(NULL);
    245   ActivityLog::GetInstance(profile())->SetWatchdogAppActiveForTesting(false);
    246 
    247   ExtensionBrowserTest::TearDownOnMainThread();
    248 }
    249 
    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 
    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 
    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 
    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.
    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