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