Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(712)

Side by Side Diff: extensions/renderer/script_injection_manager.cc

Issue 321993003: Refactor renderer-side script injection (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Missing files Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright 2014 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 "extensions/renderer/script_injection_manager.h"
6
7 #include "base/bind.h"
8 #include "base/memory/weak_ptr.h"
9 #include "base/metrics/histogram.h"
10 #include "base/values.h"
11 #include "content/public/renderer/render_view.h"
12 #include "content/public/renderer/render_view_observer.h"
13 #include "extensions/common/extension.h"
14 #include "extensions/common/extension_messages.h"
15 #include "extensions/common/extension_set.h"
16 #include "extensions/common/feature_switch.h"
17 #include "extensions/renderer/extension_helper.h"
18 #include "extensions/renderer/programmatic_script_injection.h"
19 #include "extensions/renderer/script_context.h"
20 #include "ipc/ipc_message_macros.h"
21 #include "third_party/WebKit/public/web/WebFrame.h"
22 #include "third_party/WebKit/public/web/WebLocalFrame.h"
23 #include "third_party/WebKit/public/web/WebView.h"
24 #include "url/gurl.h"
25
26 namespace extensions {
27
28 namespace {
29
30 // The length of time to wait after the DOM is complete to try and run user
31 // scripts.
32 const int kScriptIdleTimeoutInMs = 200;
33
34 const int kInvalidRequestId = -1;
35
36 // The id of the next pending injection.
37 int64 g_next_pending_id = 0;
38
39 // Removes any injections with the given |web_frame| from |injections|.
40 void RemoveInjectionsWithFrame(ScopedVector<ScriptInjection>* injections,
41 blink::WebFrame* web_frame) {
42 for (ScopedVector<ScriptInjection>::iterator iter = injections->begin();
43 iter != injections->end();) {
44 if ((*iter)->web_frame() == web_frame)
45 iter = injections->erase(iter);
46 else
47 ++iter;
48 }
49 }
50
51 // Removes any injections associated with an extension in |extension_ids|.
52 void RemoveInjectionsWithExtensionId(
53 ScopedVector<ScriptInjection>* injections,
54 const std::set<std::string>& extension_ids) {
55 for (ScopedVector<ScriptInjection>::iterator iter = injections->begin();
56 iter != injections->end();) {
57 if (extension_ids.count((*iter)->extension_id()) > 0)
58 iter = injections->erase(iter);
59 else
60 ++iter;
61 }
62 }
63
64 // Log information about a given script run.
65 void LogScriptsRun(blink::WebFrame* frame,
66 UserScript::RunLocation location,
67 const ScriptInjection::ScriptsRunInfo& info) {
68 // Notify the browser if any extensions are now executing scripts.
69 if (!info.executing_scripts.empty()) {
70 content::RenderView* render_view =
71 content::RenderView::FromWebView(frame->view());
72 render_view->Send(new ExtensionHostMsg_ContentScriptsExecuting(
73 render_view->GetRoutingID(),
74 info.executing_scripts,
75 render_view->GetPageId(),
76 ScriptContext::GetDataSourceURLForFrame(frame)));
77 }
78
79 switch (location) {
80 case UserScript::DOCUMENT_START:
81 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_CssCount", info.num_css);
82 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectStart_ScriptCount",
83 info.num_js);
84 if (info.num_css || info.num_js)
85 UMA_HISTOGRAM_TIMES("Extensions.InjectStart_Time",
86 info.timer.Elapsed());
87 break;
88 case UserScript::DOCUMENT_END:
89 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectEnd_ScriptCount", info.num_js);
90 if (info.num_js)
91 UMA_HISTOGRAM_TIMES("Extensions.InjectEnd_Time", info.timer.Elapsed());
92 break;
93 case UserScript::DOCUMENT_IDLE:
94 UMA_HISTOGRAM_COUNTS_100("Extensions.InjectIdle_ScriptCount",
95 info.num_js);
96 if (info.num_js)
97 UMA_HISTOGRAM_TIMES("Extensions.InjectIdle_Time", info.timer.Elapsed());
98 break;
99 case UserScript::RUN_DEFERRED:
100 // TODO(rdevlin.cronin): Add histograms.
101 break;
102 case UserScript::UNDEFINED:
103 case UserScript::RUN_LOCATION_LAST:
104 NOTREACHED();
105 }
106 }
107
108 } // namespace
109
110 class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver {
111 public:
112 RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager);
113 virtual ~RVOHelper();
114
115 private:
116 // RenderViewObserver implementation.
117 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
118 virtual void DidCreateDocumentElement(blink::WebLocalFrame* frame) OVERRIDE;
119 virtual void DidFinishDocumentLoad(blink::WebLocalFrame* frame) OVERRIDE;
120 virtual void DidFinishLoad(blink::WebLocalFrame* frame) OVERRIDE;
121 virtual void DidStartProvisionalLoad(blink::WebLocalFrame* frame) OVERRIDE;
122 virtual void FrameDetached(blink::WebFrame* frame) OVERRIDE;
123 virtual void OnDestruct() OVERRIDE;
124
125 virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params);
126 virtual void OnPermitScriptInjection(int64 request_id);
127
128 // Tells the ScriptInjectionManager to run tasks associated with
129 // document_idle.
130 void RunIdle(blink::WebFrame* frame);
131
132 ScriptInjectionManager* manager_;
133 base::WeakPtrFactory<RVOHelper> weak_factory_;
134 };
135
136 ScriptInjectionManager::RVOHelper::RVOHelper(
137 content::RenderView* render_view,
138 ScriptInjectionManager* manager)
139 : content::RenderViewObserver(render_view),
140 manager_(manager),
141 weak_factory_(this) {
142 }
143
144 ScriptInjectionManager::RVOHelper::~RVOHelper() {
145 }
146
147 bool ScriptInjectionManager::RVOHelper::OnMessageReceived(
148 const IPC::Message& message) {
149 bool handled = true;
150 IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RVOHelper, message)
151 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode)
152 IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection,
153 OnPermitScriptInjection)
154 IPC_MESSAGE_UNHANDLED(handled = false)
155 IPC_END_MESSAGE_MAP()
156 return handled;
157 }
158
159 void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement(
160 blink::WebLocalFrame* frame) {
161 manager_->InjectScripts(frame, UserScript::DOCUMENT_START);
162 }
163
164 void ScriptInjectionManager::RVOHelper::DidFinishDocumentLoad(
165 blink::WebLocalFrame* frame) {
166 manager_->InjectScripts(frame, UserScript::DOCUMENT_END);
167 base::MessageLoop::current()->PostDelayedTask(
168 FROM_HERE,
169 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
170 weak_factory_.GetWeakPtr(),
171 frame),
172 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs));
173 }
174
175 void ScriptInjectionManager::RVOHelper::DidFinishLoad(
176 blink::WebLocalFrame* frame) {
177 // Ensure that running scripts does not keep any progress UI running.
178 base::MessageLoop::current()->PostTask(
179 FROM_HERE,
180 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
181 weak_factory_.GetWeakPtr(),
182 frame));
183 }
184
185 void ScriptInjectionManager::RVOHelper::DidStartProvisionalLoad(
186 blink::WebLocalFrame* frame) {
187 weak_factory_.InvalidateWeakPtrs();
188 manager_->InvalidateForFrame(frame);
189 }
190
191 void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) {
192 weak_factory_.InvalidateWeakPtrs();
193 manager_->InvalidateForFrame(frame);
194 }
195
196 void ScriptInjectionManager::RVOHelper::OnDestruct() {
197 manager_->RemoveObserver(this);
198 }
199
200 void ScriptInjectionManager::RVOHelper::OnExecuteCode(
201 const ExtensionMsg_ExecuteCode_Params& params) {
202 manager_->HandleExecuteCode(params, render_view());
203 }
204
205 void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection(
206 int64 request_id) {
207 manager_->HandlePermitScriptInjection(request_id);
208 }
209
210 void ScriptInjectionManager::RVOHelper::RunIdle(blink::WebFrame* frame) {
211 manager_->InjectScripts(frame, UserScript::DOCUMENT_IDLE);
212 }
213
214 ScriptInjectionManager::FrameStatus::FrameStatus()
215 : current_location(UserScript::UNDEFINED), has_run_idle(false) {
216 }
217
218 ScriptInjectionManager::ScriptInjectionManager(const ExtensionSet* extensions)
219 : extensions_(extensions),
220 user_script_injection_list_(new UserScriptInjectionList()),
221 user_script_injection_list_observer_(this) {
222 user_script_injection_list_observer_.Add(user_script_injection_list_.get());
223 }
224
225 ScriptInjectionManager::~ScriptInjectionManager() {
226 }
227
228 void ScriptInjectionManager::OnRenderViewCreated(
229 content::RenderView* render_view) {
230 rvo_helpers_.push_back(new RVOHelper(render_view, this));
231 }
232
233 void ScriptInjectionManager::OnUserScriptsUpdated(
234 const std::set<std::string>& changed_extensions,
235 const std::vector<UserScript*>& scripts) {
236 RemoveInjectionsWithExtensionId(&injections_awaiting_permission_,
237 changed_extensions);
238 RemoveInjectionsWithExtensionId(&injections_awaiting_location_,
239 changed_extensions);
240 }
241
242 void ScriptInjectionManager::RemoveObserver(RVOHelper* helper) {
243 ScopedVector<RVOHelper>::iterator iter = rvo_helpers_.begin();
244 for (; iter != rvo_helpers_.end() && *iter != helper; ++iter)
245 ; // intentionally empty.
246 if (iter != rvo_helpers_.end())
247 rvo_helpers_.erase(iter);
248 }
249
250 void ScriptInjectionManager::InvalidateForFrame(blink::WebFrame* frame) {
251 RemoveInjectionsWithFrame(&injections_awaiting_location_, frame);
252 RemoveInjectionsWithFrame(&injections_awaiting_permission_, frame);
253 frame_statuses_.erase(frame);
254 }
255
256 void ScriptInjectionManager::InjectScripts(
257 blink::WebFrame* frame, UserScript::RunLocation run_location) {
258 FrameStatus& frame_status = frame_statuses_[frame];
259 frame_status.current_location = run_location;
260 if (run_location == UserScript::DOCUMENT_IDLE) {
261 if (frame_status.has_run_idle)
262 return;
263 frame_status.has_run_idle = true;
264 }
265
266 ScriptInjection::ScriptsRunInfo scripts_run_info;
267 for (ScopedVector<ScriptInjection>::iterator iter =
268 injections_awaiting_location_.begin();
269 iter != injections_awaiting_location_.end();) {
270 if ((*iter)->run_location() == run_location) {
271 scoped_ptr<ScriptInjection> injection(*iter);
272 iter = injections_awaiting_location_.weak_erase(iter);
273
274 // Since extension info is sent separately from user script info, they can
275 // be out of sync. We just ignore this situation.
276 const Extension* extension =
277 extensions_->GetByID(injection->extension_id());
278 if (!extension)
279 injection->ExtensionNotFound();
280 else if (injection->Allowed(extension))
281 injection->Inject(&scripts_run_info, extension);
282 } else {
283 ++iter;
284 }
285 }
286
287 GURL document_url = UserScriptInjectionList::GetDocumentUrlForFrame(frame);
288 ScopedVector<ScriptInjection> user_script_injections;
289 int tab_id = ExtensionHelper::Get(content::RenderView::FromWebView(
290 frame->top()->view()))->tab_id();
291 user_script_injection_list_->GetInjections(&user_script_injections,
292 frame,
293 tab_id,
294 run_location,
295 document_url,
296 extensions_);
297 for (ScopedVector<ScriptInjection>::iterator iter =
298 user_script_injections.begin();
299 iter != user_script_injections.end();) {
300 scoped_ptr<ScriptInjection> injection(*iter);
301 iter = user_script_injections.weak_erase(iter);
302
303 const Extension* extension =
304 extensions_->GetByID(injection->extension_id());
305 if (!extension) {
306 injection->ExtensionNotFound();
307 continue;
308 }
309
310 if (injection->Allowed(extension))
311 injection->Inject(&scripts_run_info, extension);
312 else
313 RequestPermissionOrInject(injection.Pass(), &scripts_run_info, extension);
314 }
315
316 LogScriptsRun(frame, run_location, scripts_run_info);
317 }
318
319 void ScriptInjectionManager::RequestPermissionOrInject(
320 scoped_ptr<ScriptInjection> injection,
321 ScriptInjection::ScriptsRunInfo* scripts_run_info,
322 const Extension* extension) {
323 content::RenderView* render_view =
324 content::RenderView::FromWebView(injection->web_frame()->top()->view());
325
326 // By default, we allow injection.
327 bool should_inject = true;
328 int64 request_id = kInvalidRequestId;
329 int page_id = render_view->GetPageId();
330
331 if (FeatureSwitch::scripts_require_action()->IsEnabled()) {
332 should_inject = false;
333 request_id = g_next_pending_id++;
334 injection->set_request_id(request_id);
335 injections_awaiting_permission_.push_back(injection.release());
336 }
337
338 render_view->Send(new ExtensionHostMsg_RequestScriptInjectionPermission(
339 render_view->GetRoutingID(), extension->id(), page_id, request_id));
340
341 // Since delaying script injection is hidden behind a flag, inject the script
342 // immediately if the feature is disabled.
343 if (should_inject)
344 injection->Inject(scripts_run_info, extension);
345 }
346
347 void ScriptInjectionManager::HandleExecuteCode(
348 const ExtensionMsg_ExecuteCode_Params& params,
349 content::RenderView* render_view) {
350 blink::WebFrame* main_frame = render_view->GetWebView()->mainFrame();
351 if (!main_frame) {
352 render_view->Send(
353 new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(),
354 params.request_id,
355 "No main frame",
356 -1,
357 GURL(std::string()),
358 base::ListValue()));
359 return;
360 }
361
362 scoped_ptr<ScriptInjection> injection(new ProgrammaticScriptInjection(
363 main_frame, params, ExtensionHelper::Get(render_view)->tab_id()));
364
365 if (injection->run_location() >
366 frame_statuses_[main_frame].current_location) {
367 injections_awaiting_location_.push_back(injection.release());
368 return;
369 }
370
371 const Extension* extension = extensions_->GetByID(injection->extension_id());
372 ScriptInjection::ScriptsRunInfo scripts_run_info;
373 if (!extension)
374 injection->ExtensionNotFound();
375 else if (injection->Allowed(extension))
376 injection->Inject(&scripts_run_info, extension);
377 }
378
379 void ScriptInjectionManager::HandlePermitScriptInjection(int request_id) {
380 ScopedVector<ScriptInjection>::iterator iter =
381 injections_awaiting_permission_.begin();
382 for (; iter != injections_awaiting_permission_.end(); ++iter) {
383 if ((*iter)->request_id() == request_id)
384 break;
385 }
386 if (iter == injections_awaiting_permission_.end())
387 return;
388
389 scoped_ptr<ScriptInjection> injection(*iter);
390 injections_awaiting_permission_.weak_erase(iter);
391
392 const Extension* extension = extensions_->GetByID(injection->extension_id());
393 if (!extension) {
394 injection->ExtensionNotFound();
395 return;
396 }
397
398 ScriptInjection::ScriptsRunInfo scripts_run_info;
399 injection->Inject(&scripts_run_info, extension);
400 LogScriptsRun(
401 injection->web_frame(), UserScript::RUN_DEFERRED, scripts_run_info);
402 }
403
404 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698