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