1 // Copyright (c) 2011 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 "chrome/browser/extensions/extension_service.h"
8 #include "chrome/browser/profiles/profile.h"
9 #include "chrome/common/extensions/extension.h"
10 #include "chrome/common/extensions/url_pattern.h"
11 #include "content/browser/browser_thread.h"
12 #include "content/browser/renderer_host/global_request_id.h"
13 #include "content/browser/renderer_host/resource_dispatcher_host_request_info.h"
14 #include "content/common/notification_service.h"
15 #include "net/url_request/url_request.h"
16
UserScriptListener()17 UserScriptListener::UserScriptListener()
18 : resource_queue_(NULL),
19 user_scripts_ready_(false) {
20 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
21
22 registrar_.Add(this, NotificationType::EXTENSION_LOADED,
23 NotificationService::AllSources());
24 registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
25 NotificationService::AllSources());
26 registrar_.Add(this, NotificationType::USER_SCRIPTS_UPDATED,
27 NotificationService::AllSources());
28 AddRef(); // Will be balanced in Cleanup().
29 }
30
Initialize(ResourceQueue * resource_queue)31 void UserScriptListener::Initialize(ResourceQueue* resource_queue) {
32 resource_queue_ = resource_queue;
33 }
34
ShouldDelayRequest(net::URLRequest * request,const ResourceDispatcherHostRequestInfo & request_info,const GlobalRequestID & request_id)35 bool UserScriptListener::ShouldDelayRequest(
36 net::URLRequest* request,
37 const ResourceDispatcherHostRequestInfo& request_info,
38 const GlobalRequestID& request_id) {
39 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
40
41 // If it's a frame load, then we need to check the URL against the list of
42 // user scripts to see if we need to wait.
43 if (request_info.resource_type() != ResourceType::MAIN_FRAME &&
44 request_info.resource_type() != ResourceType::SUB_FRAME) {
45 return false;
46 }
47
48 if (user_scripts_ready_)
49 return false;
50
51 for (URLPatterns::iterator it = url_patterns_.begin();
52 it != url_patterns_.end(); ++it) {
53 if ((*it).MatchesUrl(request->url())) {
54 // One of the user scripts wants to inject into this request, but the
55 // script isn't ready yet. Delay the request.
56 delayed_request_ids_.push_front(request_id);
57 return true;
58 }
59 }
60
61 return false;
62 }
63
WillShutdownResourceQueue()64 void UserScriptListener::WillShutdownResourceQueue() {
65 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
66 resource_queue_ = NULL;
67
68 BrowserThread::PostTask(
69 BrowserThread::UI, FROM_HERE,
70 NewRunnableMethod(this, &UserScriptListener::Cleanup));
71 }
72
~UserScriptListener()73 UserScriptListener::~UserScriptListener() {
74 }
75
StartDelayedRequests()76 void UserScriptListener::StartDelayedRequests() {
77 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
78
79 user_scripts_ready_ = true;
80
81 if (resource_queue_) {
82 for (DelayedRequests::iterator it = delayed_request_ids_.begin();
83 it != delayed_request_ids_.end(); ++it) {
84 resource_queue_->StartDelayedRequest(this, *it);
85 }
86 }
87
88 delayed_request_ids_.clear();
89 }
90
AppendNewURLPatterns(const URLPatterns & new_patterns)91 void UserScriptListener::AppendNewURLPatterns(const URLPatterns& new_patterns) {
92 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
93
94 user_scripts_ready_ = false;
95 url_patterns_.insert(url_patterns_.end(),
96 new_patterns.begin(), new_patterns.end());
97 }
98
ReplaceURLPatterns(const URLPatterns & patterns)99 void UserScriptListener::ReplaceURLPatterns(const URLPatterns& patterns) {
100 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
101 url_patterns_ = patterns;
102 }
103
Cleanup()104 void UserScriptListener::Cleanup() {
105 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
106 registrar_.RemoveAll();
107 Release();
108 }
109
CollectURLPatterns(const Extension * extension,URLPatterns * patterns)110 void UserScriptListener::CollectURLPatterns(const Extension* extension,
111 URLPatterns* patterns) {
112 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
113
114 const UserScriptList& scripts = extension->content_scripts();
115 for (UserScriptList::const_iterator iter = scripts.begin();
116 iter != scripts.end(); ++iter) {
117 patterns->insert(patterns->end(),
118 (*iter).url_patterns().begin(),
119 (*iter).url_patterns().end());
120 }
121 }
122
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)123 void UserScriptListener::Observe(NotificationType type,
124 const NotificationSource& source,
125 const NotificationDetails& details) {
126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
127
128 switch (type.value) {
129 case NotificationType::EXTENSION_LOADED: {
130 const Extension* extension = Details<const Extension>(details).ptr();
131 if (extension->content_scripts().empty())
132 return; // no new patterns from this extension.
133
134 URLPatterns new_patterns;
135 CollectURLPatterns(Details<const Extension>(details).ptr(),
136 &new_patterns);
137 if (!new_patterns.empty()) {
138 BrowserThread::PostTask(
139 BrowserThread::IO, FROM_HERE,
140 NewRunnableMethod(
141 this, &UserScriptListener::AppendNewURLPatterns, new_patterns));
142 }
143 break;
144 }
145
146 case NotificationType::EXTENSION_UNLOADED: {
147 const Extension* unloaded_extension =
148 Details<UnloadedExtensionInfo>(details)->extension;
149 if (unloaded_extension->content_scripts().empty())
150 return; // no patterns to delete for this extension.
151
152 // Clear all our patterns and reregister all the still-loaded extensions.
153 URLPatterns new_patterns;
154 ExtensionService* service =
155 Source<Profile>(source).ptr()->GetExtensionService();
156 for (ExtensionList::const_iterator it = service->extensions()->begin();
157 it != service->extensions()->end(); ++it) {
158 if (*it != unloaded_extension)
159 CollectURLPatterns(*it, &new_patterns);
160 }
161 BrowserThread::PostTask(
162 BrowserThread::IO, FROM_HERE,
163 NewRunnableMethod(
164 this, &UserScriptListener::ReplaceURLPatterns, new_patterns));
165 break;
166 }
167
168 case NotificationType::USER_SCRIPTS_UPDATED: {
169 BrowserThread::PostTask(
170 BrowserThread::IO, FROM_HERE,
171 NewRunnableMethod(this, &UserScriptListener::StartDelayedRequests));
172 break;
173 }
174
175 default:
176 NOTREACHED();
177 }
178 }
179