Upstream version 11.40.277.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/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"
19
20 using content::BrowserThread;
21 using content::ResourceThrottle;
22 using content::ResourceType;
23
24 namespace extensions {
25
26 class UserScriptListener::Throttle
27     : public ResourceThrottle,
28       public base::SupportsWeakPtr<UserScriptListener::Throttle> {
29  public:
30   Throttle() : should_defer_(true), did_defer_(false) {
31   }
32
33   void Resume() {
34     DCHECK(should_defer_);
35     should_defer_ = false;
36     // Only resume the request if |this| has deferred it.
37     if (did_defer_)
38       controller()->Resume();
39   }
40
41   // ResourceThrottle implementation:
42   void WillStartRequest(bool* defer) override {
43     // Only defer requests if Resume has not yet been called.
44     if (should_defer_) {
45       *defer = true;
46       did_defer_ = true;
47     }
48   }
49
50   const char* GetNameForLogging() const override {
51     return "UserScriptListener::Throttle";
52   }
53
54  private:
55   bool should_defer_;
56   bool did_defer_;
57 };
58
59 struct UserScriptListener::ProfileData {
60   // True if the user scripts contained in |url_patterns| are ready for
61   // injection.
62   bool user_scripts_ready;
63
64   // A list of URL patterns that have will have user scripts applied to them.
65   URLPatterns url_patterns;
66
67   ProfileData() : user_scripts_ready(false) {}
68 };
69
70 UserScriptListener::UserScriptListener()
71     : user_scripts_ready_(false) {
72   DCHECK_CURRENTLY_ON(BrowserThread::UI);
73
74   registrar_.Add(this,
75                  extensions::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
76                  content::NotificationService::AllSources());
77   registrar_.Add(this,
78                  extensions::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
79                  content::NotificationService::AllSources());
80   registrar_.Add(this,
81                  extensions::NOTIFICATION_USER_SCRIPTS_UPDATED,
82                  content::NotificationService::AllSources());
83   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
84                  content::NotificationService::AllSources());
85 }
86
87 ResourceThrottle* UserScriptListener::CreateResourceThrottle(
88     const GURL& url,
89     ResourceType resource_type) {
90   if (!ShouldDelayRequest(url, resource_type))
91     return NULL;
92
93   Throttle* throttle = new Throttle();
94   throttles_.push_back(throttle->AsWeakPtr());
95   return throttle;
96 }
97
98 UserScriptListener::~UserScriptListener() {
99 }
100
101 bool UserScriptListener::ShouldDelayRequest(const GURL& url,
102                                             ResourceType resource_type) {
103   DCHECK_CURRENTLY_ON(BrowserThread::IO);
104
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)
109     return false;
110
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
114   // out.
115   if (user_scripts_ready_)
116     return false;
117
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.
125         return true;
126       }
127     }
128   }
129
130   return false;
131 }
132
133 void UserScriptListener::StartDelayedRequests() {
134   WeakThrottleList::const_iterator it;
135   for (it = throttles_.begin(); it != throttles_.end(); ++it) {
136     if (it->get())
137       (*it)->Resume();
138   }
139   throttles_.clear();
140 }
141
142 void UserScriptListener::CheckIfAllUserScriptsReady() {
143   DCHECK_CURRENTLY_ON(BrowserThread::IO);
144   bool was_ready = user_scripts_ready_;
145
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;
151   }
152
153   if (user_scripts_ready_ && !was_ready)
154     StartDelayedRequests();
155 }
156
157 void UserScriptListener::UserScriptsReady(void* profile_id) {
158   DCHECK_CURRENTLY_ON(BrowserThread::IO);
159
160   profile_data_[profile_id].user_scripts_ready = true;
161   CheckIfAllUserScriptsReady();
162 }
163
164 void UserScriptListener::ProfileDestroyed(void* profile_id) {
165   DCHECK_CURRENTLY_ON(BrowserThread::IO);
166   profile_data_.erase(profile_id);
167
168   // We may have deleted the only profile we were waiting on.
169   CheckIfAllUserScriptsReady();
170 }
171
172 void UserScriptListener::AppendNewURLPatterns(void* profile_id,
173                                               const URLPatterns& new_patterns) {
174   DCHECK_CURRENTLY_ON(BrowserThread::IO);
175
176   user_scripts_ready_ = false;
177
178   ProfileData& data = profile_data_[profile_id];
179   data.user_scripts_ready = false;
180
181   data.url_patterns.insert(data.url_patterns.end(),
182                            new_patterns.begin(), new_patterns.end());
183 }
184
185 void UserScriptListener::ReplaceURLPatterns(void* profile_id,
186                                             const URLPatterns& patterns) {
187   DCHECK_CURRENTLY_ON(BrowserThread::IO);
188
189   ProfileData& data = profile_data_[profile_id];
190   data.url_patterns = patterns;
191 }
192
193 void UserScriptListener::CollectURLPatterns(const Extension* extension,
194                                             URLPatterns* patterns) {
195   DCHECK_CURRENTLY_ON(BrowserThread::UI);
196
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());
204   }
205 }
206
207 void UserScriptListener::Observe(int type,
208                                  const content::NotificationSource& source,
209                                  const content::NotificationDetails& details) {
210   DCHECK_CURRENTLY_ON(BrowserThread::UI);
211
212   switch (type) {
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.
219
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));
226       }
227       break;
228     }
229
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.
236
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);
245       }
246       BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
247           &UserScriptListener::ReplaceURLPatterns, this,
248           profile, new_patterns));
249       break;
250     }
251
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));
256       break;
257     }
258
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));
263       break;
264     }
265
266     default:
267       NOTREACHED();
268   }
269 }
270
271 }  // namespace extensions