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/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"
20 using content::BrowserThread;
21 using content::ResourceThrottle;
23 namespace extensions {
25 class UserScriptListener::Throttle
26 : public ResourceThrottle,
27 public base::SupportsWeakPtr<UserScriptListener::Throttle> {
29 Throttle() : should_defer_(true), did_defer_(false) {
33 DCHECK(should_defer_);
34 should_defer_ = false;
35 // Only resume the request if |this| has deferred it.
37 controller()->Resume();
40 // ResourceThrottle implementation:
41 virtual void WillStartRequest(bool* defer) OVERRIDE {
42 // Only defer requests if Resume has not yet been called.
49 virtual const char* GetNameForLogging() const OVERRIDE {
50 return "UserScriptListener::Throttle";
58 struct UserScriptListener::ProfileData {
59 // True if the user scripts contained in |url_patterns| are ready for
61 bool user_scripts_ready;
63 // A list of URL patterns that have will have user scripts applied to them.
64 URLPatterns url_patterns;
66 ProfileData() : user_scripts_ready(false) {}
69 UserScriptListener::UserScriptListener()
70 : user_scripts_ready_(false) {
71 DCHECK_CURRENTLY_ON(BrowserThread::UI);
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());
84 ResourceThrottle* UserScriptListener::CreateResourceThrottle(
86 ResourceType::Type resource_type) {
87 if (!ShouldDelayRequest(url, resource_type))
90 Throttle* throttle = new Throttle();
91 throttles_.push_back(throttle->AsWeakPtr());
95 UserScriptListener::~UserScriptListener() {
98 bool UserScriptListener::ShouldDelayRequest(const GURL& url,
99 ResourceType::Type resource_type) {
100 DCHECK_CURRENTLY_ON(BrowserThread::IO);
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)
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
112 if (user_scripts_ready_)
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.
130 void UserScriptListener::StartDelayedRequests() {
131 WeakThrottleList::const_iterator it;
132 for (it = throttles_.begin(); it != throttles_.end(); ++it) {
139 void UserScriptListener::CheckIfAllUserScriptsReady() {
140 DCHECK_CURRENTLY_ON(BrowserThread::IO);
141 bool was_ready = user_scripts_ready_;
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;
150 if (user_scripts_ready_ && !was_ready)
151 StartDelayedRequests();
154 void UserScriptListener::UserScriptsReady(void* profile_id) {
155 DCHECK_CURRENTLY_ON(BrowserThread::IO);
157 profile_data_[profile_id].user_scripts_ready = true;
158 CheckIfAllUserScriptsReady();
161 void UserScriptListener::ProfileDestroyed(void* profile_id) {
162 DCHECK_CURRENTLY_ON(BrowserThread::IO);
163 profile_data_.erase(profile_id);
165 // We may have deleted the only profile we were waiting on.
166 CheckIfAllUserScriptsReady();
169 void UserScriptListener::AppendNewURLPatterns(void* profile_id,
170 const URLPatterns& new_patterns) {
171 DCHECK_CURRENTLY_ON(BrowserThread::IO);
173 user_scripts_ready_ = false;
175 ProfileData& data = profile_data_[profile_id];
176 data.user_scripts_ready = false;
178 data.url_patterns.insert(data.url_patterns.end(),
179 new_patterns.begin(), new_patterns.end());
182 void UserScriptListener::ReplaceURLPatterns(void* profile_id,
183 const URLPatterns& patterns) {
184 DCHECK_CURRENTLY_ON(BrowserThread::IO);
186 ProfileData& data = profile_data_[profile_id];
187 data.url_patterns = patterns;
190 void UserScriptListener::CollectURLPatterns(const Extension* extension,
191 URLPatterns* patterns) {
192 DCHECK_CURRENTLY_ON(BrowserThread::UI);
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());
204 void UserScriptListener::Observe(int type,
205 const content::NotificationSource& source,
206 const content::NotificationDetails& details) {
207 DCHECK_CURRENTLY_ON(BrowserThread::UI);
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.
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));
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.
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);
242 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
243 &UserScriptListener::ReplaceURLPatterns, this,
244 profile, new_patterns));
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));
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));
267 } // namespace extensions