Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / user_script_listener.cc
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