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