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 "chrome/browser/extensions/user_script_listener.h"
      6 
      7 #include "base/bind.h"
      8 #include "chrome/browser/chrome_notification_types.h"
      9 #include "chrome/browser/profiles/profile.h"
     10 #include "chrome/common/extensions/manifest_handlers/content_scripts_handler.h"
     11 #include "content/public/browser/browser_thread.h"
     12 #include "content/public/browser/notification_service.h"
     13 #include "content/public/browser/resource_controller.h"
     14 #include "content/public/browser/resource_throttle.h"
     15 #include "extensions/browser/extension_registry.h"
     16 #include "extensions/common/extension.h"
     17 #include "extensions/common/url_pattern.h"
     18 #include "net/url_request/url_request.h"
     19 
     20 using content::BrowserThread;
     21 using content::ResourceThrottle;
     22 using content::ResourceType;
     23 
     24 namespace extensions {
     25 
     26 class UserScriptListener::Throttle
     27     : public ResourceThrottle,
     28       public base::SupportsWeakPtr<UserScriptListener::Throttle> {
     29  public:
     30   Throttle() : should_defer_(true), did_defer_(false) {
     31   }
     32 
     33   void Resume() {
     34     DCHECK(should_defer_);
     35     should_defer_ = false;
     36     // Only resume the request if |this| has deferred it.
     37     if (did_defer_)
     38       controller()->Resume();
     39   }
     40 
     41   // ResourceThrottle implementation:
     42   virtual void WillStartRequest(bool* defer) OVERRIDE {
     43     // Only defer requests if Resume has not yet been called.
     44     if (should_defer_) {
     45       *defer = true;
     46       did_defer_ = true;
     47     }
     48   }
     49 
     50   virtual const char* GetNameForLogging() const OVERRIDE {
     51     return "UserScriptListener::Throttle";
     52   }
     53 
     54  private:
     55   bool should_defer_;
     56   bool did_defer_;
     57 };
     58 
     59 struct UserScriptListener::ProfileData {
     60   // True if the user scripts contained in |url_patterns| are ready for
     61   // injection.
     62   bool user_scripts_ready;
     63 
     64   // A list of URL patterns that have will have user scripts applied to them.
     65   URLPatterns url_patterns;
     66 
     67   ProfileData() : user_scripts_ready(false) {}
     68 };
     69 
     70 UserScriptListener::UserScriptListener()
     71     : user_scripts_ready_(false) {
     72   DCHECK_CURRENTLY_ON(BrowserThread::UI);
     73 
     74   registrar_.Add(this,
     75                  extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
     76                  content::NotificationService::AllSources());
     77   registrar_.Add(this,
     78                  extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
     79                  content::NotificationService::AllSources());
     80   registrar_.Add(this,
     81                  extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
     82                  content::NotificationService::AllSources());
     83   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
     84                  content::NotificationService::AllSources());
     85 }
     86 
     87 ResourceThrottle* UserScriptListener::CreateResourceThrottle(
     88     const GURL& url,
     89     ResourceType resource_type) {
     90   if (!ShouldDelayRequest(url, resource_type))
     91     return NULL;
     92 
     93   Throttle* throttle = new Throttle();
     94   throttles_.push_back(throttle->AsWeakPtr());
     95   return throttle;
     96 }
     97 
     98 UserScriptListener::~UserScriptListener() {
     99 }
    100 
    101 bool UserScriptListener::ShouldDelayRequest(const GURL& url,
    102                                             ResourceType resource_type) {
    103   DCHECK_CURRENTLY_ON(BrowserThread::IO);
    104 
    105   // If it's a frame load, then we need to check the URL against the list of
    106   // user scripts to see if we need to wait.
    107   if (resource_type != content::RESOURCE_TYPE_MAIN_FRAME &&
    108       resource_type != content::RESOURCE_TYPE_SUB_FRAME)
    109     return false;
    110 
    111   // Note: we could delay only requests made by the profile who is causing the
    112   // delay, but it's a little more complicated to associate requests with the
    113   // right profile. Since this is a rare case, we'll just take the easy way
    114   // out.
    115   if (user_scripts_ready_)
    116     return false;
    117 
    118   for (ProfileDataMap::const_iterator pt = profile_data_.begin();
    119        pt != profile_data_.end(); ++pt) {
    120     for (URLPatterns::const_iterator it = pt->second.url_patterns.begin();
    121          it != pt->second.url_patterns.end(); ++it) {
    122       if ((*it).MatchesURL(url)) {
    123         // One of the user scripts wants to inject into this request, but the
    124         // script isn't ready yet. Delay the request.
    125         return true;
    126       }
    127     }
    128   }
    129 
    130   return false;
    131 }
    132 
    133 void UserScriptListener::StartDelayedRequests() {
    134   WeakThrottleList::const_iterator it;
    135   for (it = throttles_.begin(); it != throttles_.end(); ++it) {
    136     if (it->get())
    137       (*it)->Resume();
    138   }
    139   throttles_.clear();
    140 }
    141 
    142 void UserScriptListener::CheckIfAllUserScriptsReady() {
    143   DCHECK_CURRENTLY_ON(BrowserThread::IO);
    144   bool was_ready = user_scripts_ready_;
    145 
    146   user_scripts_ready_ = true;
    147   for (ProfileDataMap::const_iterator it = profile_data_.begin();
    148        it != profile_data_.end(); ++it) {
    149     if (!it->second.user_scripts_ready)
    150       user_scripts_ready_ = false;
    151   }
    152 
    153   if (user_scripts_ready_ && !was_ready)
    154     StartDelayedRequests();
    155 }
    156 
    157 void UserScriptListener::UserScriptsReady(void* profile_id) {
    158   DCHECK_CURRENTLY_ON(BrowserThread::IO);
    159 
    160   profile_data_[profile_id].user_scripts_ready = true;
    161   CheckIfAllUserScriptsReady();
    162 }
    163 
    164 void UserScriptListener::ProfileDestroyed(void* profile_id) {
    165   DCHECK_CURRENTLY_ON(BrowserThread::IO);
    166   profile_data_.erase(profile_id);
    167 
    168   // We may have deleted the only profile we were waiting on.
    169   CheckIfAllUserScriptsReady();
    170 }
    171 
    172 void UserScriptListener::AppendNewURLPatterns(void* profile_id,
    173                                               const URLPatterns& new_patterns) {
    174   DCHECK_CURRENTLY_ON(BrowserThread::IO);
    175 
    176   user_scripts_ready_ = false;
    177 
    178   ProfileData& data = profile_data_[profile_id];
    179   data.user_scripts_ready = false;
    180 
    181   data.url_patterns.insert(data.url_patterns.end(),
    182                            new_patterns.begin(), new_patterns.end());
    183 }
    184 
    185 void UserScriptListener::ReplaceURLPatterns(void* profile_id,
    186                                             const URLPatterns& patterns) {
    187   DCHECK_CURRENTLY_ON(BrowserThread::IO);
    188 
    189   ProfileData& data = profile_data_[profile_id];
    190   data.url_patterns = patterns;
    191 }
    192 
    193 void UserScriptListener::CollectURLPatterns(const Extension* extension,
    194                                             URLPatterns* patterns) {
    195   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    196 
    197   const UserScriptList& scripts =
    198       ContentScriptsInfo::GetContentScripts(extension);
    199   for (UserScriptList::const_iterator iter = scripts.begin();
    200        iter != scripts.end(); ++iter) {
    201     patterns->insert(patterns->end(),
    202                      (*iter).url_patterns().begin(),
    203                      (*iter).url_patterns().end());
    204   }
    205 }
    206 
    207 void UserScriptListener::Observe(int type,
    208                                  const content::NotificationSource& source,
    209                                  const content::NotificationDetails& details) {
    210   DCHECK_CURRENTLY_ON(BrowserThread::UI);
    211 
    212   switch (type) {
    213     case extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: {
    214       Profile* profile = content::Source<Profile>(source).ptr();
    215       const Extension* extension =
    216           content::Details<const Extension>(details).ptr();
    217       if (ContentScriptsInfo::GetContentScripts(extension).empty())
    218         return;  // no new patterns from this extension.
    219 
    220       URLPatterns new_patterns;
    221       CollectURLPatterns(extension, &new_patterns);
    222       if (!new_patterns.empty()) {
    223         BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
    224             &UserScriptListener::AppendNewURLPatterns, this,
    225             profile, new_patterns));
    226       }
    227       break;
    228     }
    229 
    230     case extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: {
    231       Profile* profile = content::Source<Profile>(source).ptr();
    232       const Extension* unloaded_extension =
    233           content::Details<UnloadedExtensionInfo>(details)->extension;
    234       if (ContentScriptsInfo::GetContentScripts(unloaded_extension).empty())
    235         return;  // no patterns to delete for this extension.
    236 
    237       // Clear all our patterns and reregister all the still-loaded extensions.
    238       const ExtensionSet& extensions =
    239           ExtensionRegistry::Get(profile)->enabled_extensions();
    240       URLPatterns new_patterns;
    241       for (ExtensionSet::const_iterator it = extensions.begin();
    242            it != extensions.end(); ++it) {
    243         if (it->get() != unloaded_extension)
    244           CollectURLPatterns(it->get(), &new_patterns);
    245       }
    246       BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
    247           &UserScriptListener::ReplaceURLPatterns, this,
    248           profile, new_patterns));
    249       break;
    250     }
    251 
    252     case extensions::NOTIFICATION_USER_SCRIPTS_UPDATED: {
    253       Profile* profile = content::Source<Profile>(source).ptr();
    254       BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
    255           &UserScriptListener::UserScriptsReady, this, profile));
    256       break;
    257     }
    258 
    259     case chrome::NOTIFICATION_PROFILE_DESTROYED: {
    260       Profile* profile = content::Source<Profile>(source).ptr();
    261       BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
    262           &UserScriptListener::ProfileDestroyed, this, profile));
    263       break;
    264     }
    265 
    266     default:
    267       NOTREACHED();
    268   }
    269 }
    270 
    271 }  // namespace extensions
    272