Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2012 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_util.h"
      6 #include "base/json/json_file_value_serializer.h"
      7 #include "base/memory/scoped_ptr.h"
      8 #include "base/message_loop/message_loop.h"
      9 #include "base/threading/thread.h"
     10 #include "chrome/browser/chrome_notification_types.h"
     11 #include "chrome/browser/extensions/extension_service.h"
     12 #include "chrome/browser/extensions/extension_service_test_base.h"
     13 #include "chrome/browser/extensions/unpacked_installer.h"
     14 #include "chrome/browser/extensions/user_script_listener.h"
     15 #include "chrome/common/chrome_paths.h"
     16 #include "chrome/test/base/testing_profile.h"
     17 #include "content/public/browser/notification_service.h"
     18 #include "content/public/browser/resource_controller.h"
     19 #include "content/public/browser/resource_throttle.h"
     20 #include "extensions/browser/extension_registry.h"
     21 #include "net/base/request_priority.h"
     22 #include "net/url_request/url_request.h"
     23 #include "net/url_request/url_request_filter.h"
     24 #include "net/url_request/url_request_interceptor.h"
     25 #include "net/url_request/url_request_test_job.h"
     26 #include "net/url_request/url_request_test_util.h"
     27 #include "testing/gtest/include/gtest/gtest.h"
     28 
     29 using content::ResourceController;
     30 using content::ResourceThrottle;
     31 using content::ResourceType;
     32 
     33 namespace extensions {
     34 
     35 namespace {
     36 
     37 const char kMatchingUrl[] = "http://google.com/";
     38 const char kNotMatchingUrl[] = "http://example.com/";
     39 const char kTestData[] = "Hello, World!";
     40 
     41 class ThrottleController : public base::SupportsUserData::Data,
     42                            public ResourceController {
     43  public:
     44   ThrottleController(net::URLRequest* request, ResourceThrottle* throttle)
     45       : request_(request),
     46         throttle_(throttle) {
     47     throttle_->set_controller_for_testing(this);
     48   }
     49 
     50   // ResourceController implementation:
     51   virtual void Resume() OVERRIDE {
     52     request_->Start();
     53   }
     54   virtual void Cancel() OVERRIDE {
     55     NOTREACHED();
     56   }
     57   virtual void CancelAndIgnore() OVERRIDE {
     58     NOTREACHED();
     59   }
     60   virtual void CancelWithError(int error_code) OVERRIDE {
     61     NOTREACHED();
     62   }
     63 
     64  private:
     65   net::URLRequest* request_;
     66   scoped_ptr<ResourceThrottle> throttle_;
     67 };
     68 
     69 // A simple test net::URLRequestJob. We don't care what it does, only that
     70 // whether it starts and finishes.
     71 class SimpleTestJob : public net::URLRequestTestJob {
     72  public:
     73   SimpleTestJob(net::URLRequest* request,
     74                 net::NetworkDelegate* network_delegate)
     75       : net::URLRequestTestJob(request,
     76                                network_delegate,
     77                                test_headers(),
     78                                kTestData,
     79                                true) {}
     80  private:
     81   virtual ~SimpleTestJob() {}
     82 };
     83 
     84 // Yoinked from extension_manifest_unittest.cc.
     85 base::DictionaryValue* LoadManifestFile(const base::FilePath path,
     86                                         std::string* error) {
     87   EXPECT_TRUE(base::PathExists(path));
     88   JSONFileValueSerializer serializer(path);
     89   return static_cast<base::DictionaryValue*>(
     90       serializer.Deserialize(NULL, error));
     91 }
     92 
     93 scoped_refptr<Extension> LoadExtension(const std::string& filename,
     94                                        std::string* error) {
     95   base::FilePath path;
     96   PathService::Get(chrome::DIR_TEST_DATA, &path);
     97   path = path.
     98       AppendASCII("extensions").
     99       AppendASCII("manifest_tests").
    100       AppendASCII(filename.c_str());
    101   scoped_ptr<base::DictionaryValue> value(LoadManifestFile(path, error));
    102   if (!value)
    103     return NULL;
    104   return Extension::Create(path.DirName(), Manifest::UNPACKED, *value,
    105                            Extension::NO_FLAGS, error);
    106 }
    107 
    108 class SimpleTestJobURLRequestInterceptor
    109     : public net::URLRequestInterceptor {
    110  public:
    111   SimpleTestJobURLRequestInterceptor() {}
    112   virtual ~SimpleTestJobURLRequestInterceptor() {}
    113 
    114   // net::URLRequestJobFactory::ProtocolHandler
    115   virtual net::URLRequestJob* MaybeInterceptRequest(
    116       net::URLRequest* request,
    117       net::NetworkDelegate* network_delegate) const OVERRIDE {
    118     return new SimpleTestJob(request, network_delegate);
    119   }
    120 
    121  private:
    122   DISALLOW_COPY_AND_ASSIGN(SimpleTestJobURLRequestInterceptor);
    123 };
    124 
    125 }  // namespace
    126 
    127 class UserScriptListenerTest : public ExtensionServiceTestBase {
    128  public:
    129   UserScriptListenerTest() {
    130     net::URLRequestFilter::GetInstance()->AddHostnameInterceptor(
    131         "http", "google.com",
    132         scoped_ptr<net::URLRequestInterceptor>(
    133             new SimpleTestJobURLRequestInterceptor()));
    134     net::URLRequestFilter::GetInstance()->AddHostnameInterceptor(
    135         "http", "example.com",
    136         scoped_ptr<net::URLRequestInterceptor>(
    137             new SimpleTestJobURLRequestInterceptor()));
    138   }
    139 
    140   virtual ~UserScriptListenerTest() {
    141     net::URLRequestFilter::GetInstance()->RemoveHostnameHandler("http",
    142                                                                 "google.com");
    143     net::URLRequestFilter::GetInstance()->RemoveHostnameHandler("http",
    144                                                                 "example.com");
    145   }
    146 
    147   virtual void SetUp() OVERRIDE {
    148     ExtensionServiceTestBase::SetUp();
    149 
    150     InitializeEmptyExtensionService();
    151     service_->Init();
    152     base::MessageLoop::current()->RunUntilIdle();
    153 
    154     listener_ = new UserScriptListener();
    155   }
    156 
    157   virtual void TearDown() OVERRIDE {
    158     listener_ = NULL;
    159     base::MessageLoop::current()->RunUntilIdle();
    160     ExtensionServiceTestBase::TearDown();
    161   }
    162 
    163  protected:
    164   scoped_ptr<net::URLRequest> StartTestRequest(
    165       net::URLRequest::Delegate* delegate,
    166       const std::string& url_string,
    167       net::TestURLRequestContext* context) {
    168     GURL url(url_string);
    169     scoped_ptr<net::URLRequest> request(context->CreateRequest(
    170         url, net::DEFAULT_PRIORITY, delegate, NULL));
    171 
    172     ResourceThrottle* throttle = listener_->CreateResourceThrottle(
    173         url, content::RESOURCE_TYPE_MAIN_FRAME);
    174 
    175     bool defer = false;
    176     if (throttle) {
    177       request->SetUserData(NULL,
    178                            new ThrottleController(request.get(), throttle));
    179 
    180       throttle->WillStartRequest(&defer);
    181     }
    182 
    183     if (!defer)
    184       request->Start();
    185 
    186     return request.Pass();
    187   }
    188 
    189   void LoadTestExtension() {
    190     base::FilePath test_dir;
    191     ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
    192     base::FilePath extension_path = test_dir
    193         .AppendASCII("extensions")
    194         .AppendASCII("good")
    195         .AppendASCII("Extensions")
    196         .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
    197         .AppendASCII("1.0.0.0");
    198     UnpackedInstaller::Create(service_)->Load(extension_path);
    199   }
    200 
    201   void UnloadTestExtension() {
    202     ASSERT_FALSE(service_->extensions()->is_empty());
    203     service_->UnloadExtension((*service_->extensions()->begin())->id(),
    204                               UnloadedExtensionInfo::REASON_DISABLE);
    205   }
    206 
    207   scoped_refptr<UserScriptListener> listener_;
    208 };
    209 
    210 namespace {
    211 
    212 TEST_F(UserScriptListenerTest, DelayAndUpdate) {
    213   LoadTestExtension();
    214   base::MessageLoop::current()->RunUntilIdle();
    215 
    216   net::TestDelegate delegate;
    217   net::TestURLRequestContext context;
    218   scoped_ptr<net::URLRequest> request(
    219       StartTestRequest(&delegate, kMatchingUrl, &context));
    220   ASSERT_FALSE(request->is_pending());
    221 
    222   content::NotificationService::current()->Notify(
    223       extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
    224       content::Source<Profile>(profile_.get()),
    225       content::NotificationService::NoDetails());
    226   base::MessageLoop::current()->RunUntilIdle();
    227   EXPECT_EQ(kTestData, delegate.data_received());
    228 }
    229 
    230 TEST_F(UserScriptListenerTest, DelayAndUnload) {
    231   LoadTestExtension();
    232   base::MessageLoop::current()->RunUntilIdle();
    233 
    234   net::TestDelegate delegate;
    235   net::TestURLRequestContext context;
    236   scoped_ptr<net::URLRequest> request(
    237       StartTestRequest(&delegate, kMatchingUrl, &context));
    238   ASSERT_FALSE(request->is_pending());
    239 
    240   UnloadTestExtension();
    241   base::MessageLoop::current()->RunUntilIdle();
    242 
    243   // This is still not enough to start delayed requests. We have to notify the
    244   // listener that the user scripts have been updated.
    245   ASSERT_FALSE(request->is_pending());
    246 
    247   content::NotificationService::current()->Notify(
    248       extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
    249       content::Source<Profile>(profile_.get()),
    250       content::NotificationService::NoDetails());
    251   base::MessageLoop::current()->RunUntilIdle();
    252   EXPECT_EQ(kTestData, delegate.data_received());
    253 }
    254 
    255 TEST_F(UserScriptListenerTest, NoDelayNoExtension) {
    256   net::TestDelegate delegate;
    257   net::TestURLRequestContext context;
    258   scoped_ptr<net::URLRequest> request(
    259       StartTestRequest(&delegate, kMatchingUrl, &context));
    260 
    261   // The request should be started immediately.
    262   ASSERT_TRUE(request->is_pending());
    263 
    264   base::MessageLoop::current()->RunUntilIdle();
    265   EXPECT_EQ(kTestData, delegate.data_received());
    266 }
    267 
    268 TEST_F(UserScriptListenerTest, NoDelayNotMatching) {
    269   LoadTestExtension();
    270   base::MessageLoop::current()->RunUntilIdle();
    271 
    272   net::TestDelegate delegate;
    273   net::TestURLRequestContext context;
    274   scoped_ptr<net::URLRequest> request(
    275       StartTestRequest(&delegate, kNotMatchingUrl, &context));
    276 
    277   // The request should be started immediately.
    278   ASSERT_TRUE(request->is_pending());
    279 
    280   base::MessageLoop::current()->RunUntilIdle();
    281   EXPECT_EQ(kTestData, delegate.data_received());
    282 }
    283 
    284 TEST_F(UserScriptListenerTest, MultiProfile) {
    285   LoadTestExtension();
    286   base::MessageLoop::current()->RunUntilIdle();
    287 
    288   // Fire up a second profile and have it load an extension with a content
    289   // script.
    290   TestingProfile profile2;
    291   std::string error;
    292   scoped_refptr<Extension> extension = LoadExtension(
    293       "content_script_yahoo.json", &error);
    294   ASSERT_TRUE(extension.get());
    295 
    296   extensions::ExtensionRegistry::Get(&profile2)->AddEnabled(extension);
    297 
    298   content::NotificationService::current()->Notify(
    299       extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
    300       content::Source<Profile>(&profile2),
    301       content::Details<Extension>(extension.get()));
    302 
    303   net::TestDelegate delegate;
    304   net::TestURLRequestContext context;
    305   scoped_ptr<net::URLRequest> request(
    306       StartTestRequest(&delegate, kMatchingUrl, &context));
    307   ASSERT_FALSE(request->is_pending());
    308 
    309   // When the first profile's user scripts are ready, the request should still
    310   // be blocked waiting for profile2.
    311   content::NotificationService::current()->Notify(
    312       extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
    313       content::Source<Profile>(profile_.get()),
    314       content::NotificationService::NoDetails());
    315   base::MessageLoop::current()->RunUntilIdle();
    316   ASSERT_FALSE(request->is_pending());
    317   EXPECT_TRUE(delegate.data_received().empty());
    318 
    319   // After profile2 is ready, the request should proceed.
    320   content::NotificationService::current()->Notify(
    321       extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
    322       content::Source<Profile>(&profile2),
    323       content::NotificationService::NoDetails());
    324   base::MessageLoop::current()->RunUntilIdle();
    325   EXPECT_EQ(kTestData, delegate.data_received());
    326 }
    327 
    328 // Test when the script updated notification occurs before the throttle's
    329 // WillStartRequest function is called.  This can occur when there are multiple
    330 // throttles.
    331 TEST_F(UserScriptListenerTest, ResumeBeforeStart) {
    332   LoadTestExtension();
    333   base::MessageLoop::current()->RunUntilIdle();
    334   net::TestDelegate delegate;
    335   net::TestURLRequestContext context;
    336   GURL url(kMatchingUrl);
    337   scoped_ptr<net::URLRequest> request(context.CreateRequest(
    338       url, net::DEFAULT_PRIORITY, &delegate, NULL));
    339 
    340   ResourceThrottle* throttle =
    341       listener_->CreateResourceThrottle(url, content::RESOURCE_TYPE_MAIN_FRAME);
    342   ASSERT_TRUE(throttle);
    343   request->SetUserData(NULL, new ThrottleController(request.get(), throttle));
    344 
    345   ASSERT_FALSE(request->is_pending());
    346 
    347   content::NotificationService::current()->Notify(
    348       extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
    349       content::Source<Profile>(profile_.get()),
    350       content::NotificationService::NoDetails());
    351   base::MessageLoop::current()->RunUntilIdle();
    352 
    353   bool defer = false;
    354   throttle->WillStartRequest(&defer);
    355   ASSERT_FALSE(defer);
    356 }
    357 
    358 }  // namespace
    359 
    360 }  // namespace extensions
    361