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