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.
5 #include "chrome/browser/extensions/user_script_listener.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"
20 using content::BrowserThread;
21 using content::ResourceThrottle;
22 using content::ResourceType;
24 namespace extensions {
26 class UserScriptListener::Throttle
27 : public ResourceThrottle,
28 public base::SupportsWeakPtr<UserScriptListener::Throttle> {
30 Throttle() : should_defer_(true), did_defer_(false) {
34 DCHECK(should_defer_);
35 should_defer_ = false;
36 // Only resume the request if |this| has deferred it.
38 controller()->Resume();
41 // ResourceThrottle implementation:
42 void WillStartRequest(bool* defer) override {
43 // Only defer requests if Resume has not yet been called.
50 const char* GetNameForLogging() const override {
51 return "UserScriptListener::Throttle";
59 struct UserScriptListener::ProfileData {
60 // True if the user scripts contained in |url_patterns| are ready for
62 bool user_scripts_ready;
64 // A list of URL patterns that have will have user scripts applied to them.
65 URLPatterns url_patterns;
67 ProfileData() : user_scripts_ready(false) {}
70 UserScriptListener::UserScriptListener()
71 : user_scripts_ready_(false) {
72 DCHECK_CURRENTLY_ON(BrowserThread::UI);
75 extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
76 content::NotificationService::AllSources());
78 extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
79 content::NotificationService::AllSources());
81 extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
82 content::NotificationService::AllSources());
83 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
84 content::NotificationService::AllSources());
87 ResourceThrottle* UserScriptListener::CreateResourceThrottle(
89 ResourceType resource_type) {
90 if (!ShouldDelayRequest(url, resource_type))
93 Throttle* throttle = new Throttle();
94 throttles_.push_back(throttle->AsWeakPtr());
98 UserScriptListener::~UserScriptListener() {
101 bool UserScriptListener::ShouldDelayRequest(const GURL& url,
102 ResourceType resource_type) {
103 DCHECK_CURRENTLY_ON(BrowserThread::IO);
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)
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
115 if (user_scripts_ready_)
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.
133 void UserScriptListener::StartDelayedRequests() {
134 WeakThrottleList::const_iterator it;
135 for (it = throttles_.begin(); it != throttles_.end(); ++it) {
142 void UserScriptListener::CheckIfAllUserScriptsReady() {
143 DCHECK_CURRENTLY_ON(BrowserThread::IO);
144 bool was_ready = user_scripts_ready_;
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;
153 if (user_scripts_ready_ && !was_ready)
154 StartDelayedRequests();
157 void UserScriptListener::UserScriptsReady(void* profile_id) {
158 DCHECK_CURRENTLY_ON(BrowserThread::IO);
160 profile_data_[profile_id].user_scripts_ready = true;
161 CheckIfAllUserScriptsReady();
164 void UserScriptListener::ProfileDestroyed(void* profile_id) {
165 DCHECK_CURRENTLY_ON(BrowserThread::IO);
166 profile_data_.erase(profile_id);
168 // We may have deleted the only profile we were waiting on.
169 CheckIfAllUserScriptsReady();
172 void UserScriptListener::AppendNewURLPatterns(void* profile_id,
173 const URLPatterns& new_patterns) {
174 DCHECK_CURRENTLY_ON(BrowserThread::IO);
176 user_scripts_ready_ = false;
178 ProfileData& data = profile_data_[profile_id];
179 data.user_scripts_ready = false;
181 data.url_patterns.insert(data.url_patterns.end(),
182 new_patterns.begin(), new_patterns.end());
185 void UserScriptListener::ReplaceURLPatterns(void* profile_id,
186 const URLPatterns& patterns) {
187 DCHECK_CURRENTLY_ON(BrowserThread::IO);
189 ProfileData& data = profile_data_[profile_id];
190 data.url_patterns = patterns;
193 void UserScriptListener::CollectURLPatterns(const Extension* extension,
194 URLPatterns* patterns) {
195 DCHECK_CURRENTLY_ON(BrowserThread::UI);
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());
207 void UserScriptListener::Observe(int type,
208 const content::NotificationSource& source,
209 const content::NotificationDetails& details) {
210 DCHECK_CURRENTLY_ON(BrowserThread::UI);
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.
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));
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.
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);
246 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
247 &UserScriptListener::ReplaceURLPatterns, this,
248 profile, new_patterns));
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));
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));
271 } // namespace extensions