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