kMaxBlockSize -> kMaxHeaderBlockSize to avoid shadowing warning on VS2015
[chromium-blink-merge.git] / extensions / renderer / script_injection_manager.cc
blobd017a6742eb906abebfe9c9e0835994418c2de3d
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.
5 #include "extensions/renderer/script_injection_manager.h"
7 #include "base/auto_reset.h"
8 #include "base/bind.h"
9 #include "base/memory/weak_ptr.h"
10 #include "base/values.h"
11 #include "content/public/renderer/render_thread.h"
12 #include "content/public/renderer/render_view.h"
13 #include "content/public/renderer/render_view_observer.h"
14 #include "extensions/common/extension.h"
15 #include "extensions/common/extension_messages.h"
16 #include "extensions/common/extension_set.h"
17 #include "extensions/renderer/extension_helper.h"
18 #include "extensions/renderer/programmatic_script_injector.h"
19 #include "extensions/renderer/script_injection.h"
20 #include "extensions/renderer/scripts_run_info.h"
21 #include "ipc/ipc_message_macros.h"
22 #include "third_party/WebKit/public/web/WebDocument.h"
23 #include "third_party/WebKit/public/web/WebFrame.h"
24 #include "third_party/WebKit/public/web/WebLocalFrame.h"
25 #include "third_party/WebKit/public/web/WebView.h"
26 #include "url/gurl.h"
28 namespace extensions {
30 namespace {
32 // The length of time to wait after the DOM is complete to try and run user
33 // scripts.
34 const int kScriptIdleTimeoutInMs = 200;
36 // Returns the RunLocation that follows |run_location|.
37 UserScript::RunLocation NextRunLocation(UserScript::RunLocation run_location) {
38 switch (run_location) {
39 case UserScript::DOCUMENT_START:
40 return UserScript::DOCUMENT_END;
41 case UserScript::DOCUMENT_END:
42 return UserScript::DOCUMENT_IDLE;
43 case UserScript::DOCUMENT_IDLE:
44 return UserScript::RUN_LOCATION_LAST;
45 case UserScript::UNDEFINED:
46 case UserScript::RUN_DEFERRED:
47 case UserScript::BROWSER_DRIVEN:
48 case UserScript::RUN_LOCATION_LAST:
49 break;
51 NOTREACHED();
52 return UserScript::RUN_LOCATION_LAST;
55 } // namespace
57 class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver {
58 public:
59 RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager);
60 ~RVOHelper() override;
62 private:
63 // RenderViewObserver implementation.
64 bool OnMessageReceived(const IPC::Message& message) override;
65 void DidCreateNewDocument(blink::WebLocalFrame* frame) override;
66 void DidCreateDocumentElement(blink::WebLocalFrame* frame) override;
67 void DidFinishDocumentLoad(blink::WebLocalFrame* frame) override;
68 void DidFinishLoad(blink::WebLocalFrame* frame) override;
69 void FrameDetached(blink::WebFrame* frame) override;
70 void OnDestruct() override;
72 virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params);
73 virtual void OnExecuteDeclarativeScript(int tab_id,
74 const ExtensionId& extension_id,
75 int script_id,
76 const GURL& url);
77 virtual void OnPermitScriptInjection(int64 request_id);
79 // Tells the ScriptInjectionManager to run tasks associated with
80 // document_idle.
81 void RunIdle(blink::WebFrame* frame);
83 // Indicate that the given |frame| is no longer valid because it is starting
84 // a new load or closing.
85 void InvalidateFrame(blink::WebFrame* frame);
87 // The owning ScriptInjectionManager.
88 ScriptInjectionManager* manager_;
90 // The set of frames that we are about to notify for DOCUMENT_IDLE. We keep
91 // a set of those that are valid, so we don't notify that an invalid frame
92 // became idle.
93 std::set<blink::WebFrame*> pending_idle_frames_;
95 base::WeakPtrFactory<RVOHelper> weak_factory_;
98 ScriptInjectionManager::RVOHelper::RVOHelper(
99 content::RenderView* render_view,
100 ScriptInjectionManager* manager)
101 : content::RenderViewObserver(render_view),
102 manager_(manager),
103 weak_factory_(this) {
106 ScriptInjectionManager::RVOHelper::~RVOHelper() {
109 bool ScriptInjectionManager::RVOHelper::OnMessageReceived(
110 const IPC::Message& message) {
111 bool handled = true;
112 IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RVOHelper, message)
113 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode)
114 IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection,
115 OnPermitScriptInjection)
116 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteDeclarativeScript,
117 OnExecuteDeclarativeScript)
118 IPC_MESSAGE_UNHANDLED(handled = false)
119 IPC_END_MESSAGE_MAP()
120 return handled;
123 void ScriptInjectionManager::RVOHelper::DidCreateNewDocument(
124 blink::WebLocalFrame* frame) {
125 // A new document is going to be shown, so invalidate the old document state.
126 // Check that the frame's state is known before invalidating the frame,
127 // because it is possible that a script injection was scheduled before the
128 // page was loaded, e.g. by navigating to a javascript: URL before the page
129 // has loaded.
130 if (manager_->frame_statuses_.find(frame) != manager_->frame_statuses_.end())
131 InvalidateFrame(frame);
134 void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement(
135 blink::WebLocalFrame* frame) {
136 manager_->StartInjectScripts(frame, UserScript::DOCUMENT_START);
139 void ScriptInjectionManager::RVOHelper::DidFinishDocumentLoad(
140 blink::WebLocalFrame* frame) {
141 manager_->StartInjectScripts(frame, UserScript::DOCUMENT_END);
142 pending_idle_frames_.insert(frame);
143 // We try to run idle in two places: here and DidFinishLoad.
144 // DidFinishDocumentLoad() corresponds to completing the document's load,
145 // whereas DidFinishLoad corresponds to completing the document and all
146 // subresources' load. We don't want to hold up script injection for a
147 // particularly slow subresource, so we set a delayed task from here - but if
148 // we finish everything before that point (i.e., DidFinishLoad() is
149 // triggered), then there's no reason to keep waiting.
150 content::RenderThread::Get()->GetTaskRunner()->PostDelayedTask(
151 FROM_HERE,
152 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
153 weak_factory_.GetWeakPtr(),
154 frame),
155 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs));
158 void ScriptInjectionManager::RVOHelper::DidFinishLoad(
159 blink::WebLocalFrame* frame) {
160 // Ensure that we don't block any UI progress by running scripts.
161 // We *don't* add the frame to |pending_idle_frames_| here because
162 // DidFinishDocumentLoad should strictly come before DidFinishLoad, so the
163 // first posted task to RunIdle() pops it out of the set. This ensures we
164 // don't try to run idle twice.
165 content::RenderThread::Get()->GetTaskRunner()->PostTask(
166 FROM_HERE,
167 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
168 weak_factory_.GetWeakPtr(),
169 frame));
172 void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) {
173 // The frame is closing - invalidate.
174 InvalidateFrame(frame);
177 void ScriptInjectionManager::RVOHelper::OnDestruct() {
178 manager_->RemoveObserver(this);
181 void ScriptInjectionManager::RVOHelper::OnExecuteCode(
182 const ExtensionMsg_ExecuteCode_Params& params) {
183 manager_->HandleExecuteCode(params, render_view());
186 void ScriptInjectionManager::RVOHelper::OnExecuteDeclarativeScript(
187 int tab_id,
188 const ExtensionId& extension_id,
189 int script_id,
190 const GURL& url) {
191 blink::WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
192 CHECK(main_frame);
194 // TODO(markdittmer): This would be cleaner if we compared page_ids instead.
195 // Begin script injeciton workflow only if the current URL is identical to
196 // the one that matched declarative conditions in the browser.
197 if (main_frame->top()->document().url() == url) {
198 manager_->HandleExecuteDeclarativeScript(main_frame,
199 tab_id,
200 extension_id,
201 script_id,
202 url);
206 void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection(
207 int64 request_id) {
208 manager_->HandlePermitScriptInjection(request_id);
211 void ScriptInjectionManager::RVOHelper::RunIdle(blink::WebFrame* frame) {
212 // Only notify the manager if the frame hasn't either been removed or already
213 // had idle run since the task to RunIdle() was posted.
214 if (pending_idle_frames_.count(frame) > 0) {
215 manager_->StartInjectScripts(frame, UserScript::DOCUMENT_IDLE);
216 pending_idle_frames_.erase(frame);
220 void ScriptInjectionManager::RVOHelper::InvalidateFrame(
221 blink::WebFrame* frame) {
222 pending_idle_frames_.erase(frame);
223 manager_->InvalidateForFrame(frame);
226 ScriptInjectionManager::ScriptInjectionManager(
227 const ExtensionSet* extensions,
228 UserScriptSetManager* user_script_set_manager)
229 : extensions_(extensions),
230 injecting_scripts_(false),
231 user_script_set_manager_(user_script_set_manager),
232 user_script_set_manager_observer_(this) {
233 user_script_set_manager_observer_.Add(user_script_set_manager_);
236 ScriptInjectionManager::~ScriptInjectionManager() {
239 void ScriptInjectionManager::OnRenderViewCreated(
240 content::RenderView* render_view) {
241 rvo_helpers_.push_back(new RVOHelper(render_view, this));
244 void ScriptInjectionManager::OnUserScriptsUpdated(
245 const std::set<std::string>& changed_extensions,
246 const std::vector<UserScript*>& scripts) {
247 for (ScopedVector<ScriptInjection>::iterator iter =
248 pending_injections_.begin();
249 iter != pending_injections_.end();) {
250 if (changed_extensions.count((*iter)->extension_id()) > 0)
251 iter = pending_injections_.erase(iter);
252 else
253 ++iter;
256 // If we are currently injecting scripts, we need to make a note that these
257 // extensions were updated.
258 if (injecting_scripts_) {
259 invalidated_while_injecting_.insert(changed_extensions.begin(),
260 changed_extensions.end());
264 void ScriptInjectionManager::RemoveObserver(RVOHelper* helper) {
265 for (ScopedVector<RVOHelper>::iterator iter = rvo_helpers_.begin();
266 iter != rvo_helpers_.end();
267 ++iter) {
268 if (*iter == helper) {
269 rvo_helpers_.erase(iter);
270 break;
275 void ScriptInjectionManager::InvalidateForFrame(blink::WebFrame* frame) {
276 for (ScopedVector<ScriptInjection>::iterator iter =
277 pending_injections_.begin();
278 iter != pending_injections_.end();) {
279 if ((*iter)->web_frame() == frame)
280 iter = pending_injections_.erase(iter);
281 else
282 ++iter;
285 frame_statuses_.erase(frame);
288 bool ScriptInjectionManager::IsFrameValid(blink::WebFrame* frame) const {
289 return frame_statuses_.find(frame) != frame_statuses_.end();
292 void ScriptInjectionManager::StartInjectScripts(
293 blink::WebFrame* frame, UserScript::RunLocation run_location) {
294 FrameStatusMap::iterator iter = frame_statuses_.find(frame);
295 // We also don't execute if we detect that the run location is somehow out of
296 // order. This can happen if:
297 // - The first run location reported for the frame isn't DOCUMENT_START, or
298 // - The run location reported doesn't immediately follow the previous
299 // reported run location.
300 // We don't want to run because extensions may have requirements that scripts
301 // running in an earlier run location have run by the time a later script
302 // runs. Better to just not run.
303 // Note that we check run_location > NextRunLocation() in the second clause
304 // (as opposed to !=) because earlier signals (like DidCreateDocumentElement)
305 // can happen multiple times, so we can receive earlier/equal run locations.
306 if ((iter == frame_statuses_.end() &&
307 run_location != UserScript::DOCUMENT_START) ||
308 (iter != frame_statuses_.end() &&
309 run_location > NextRunLocation(iter->second))) {
310 // We also invalidate the frame, because the run order of pending injections
311 // may also be bad.
312 InvalidateForFrame(frame);
313 return;
314 } else if (iter != frame_statuses_.end() && iter->second >= run_location) {
315 // Certain run location signals (like DidCreateDocumentElement) can happen
316 // multiple times. Ignore the subsequent signals.
317 return;
320 // Otherwise, all is right in the world, and we can get on with the
321 // injections!
323 frame_statuses_[frame] = run_location;
325 // If a content script injects blocking code (such as a javascript alert()),
326 // then there is a chance that we are running in a nested message loop, and
327 // shouldn't inject scripts right now (to avoid conflicts).
328 if (!injecting_scripts_) {
329 InjectScripts(frame, run_location);
330 // As above, we might have been blocked, but that means that, in the mean
331 // time, it's possible the frame advanced. Inject any scripts for run
332 // locations that were registered, but never ran.
333 while ((iter = frame_statuses_.find(frame)) != frame_statuses_.end() &&
334 iter->second > run_location) {
335 run_location = NextRunLocation(run_location);
336 DCHECK_LE(run_location, UserScript::DOCUMENT_IDLE);
337 InjectScripts(frame, run_location);
342 void ScriptInjectionManager::InjectScripts(
343 blink::WebFrame* frame,
344 UserScript::RunLocation run_location) {
345 DCHECK(!injecting_scripts_);
346 DCHECK(invalidated_while_injecting_.empty());
347 base::AutoReset<bool>(&injecting_scripts_, true);
349 // Find any injections that want to run on the given frame.
350 // We create a separate vector for these because there is a chance that
351 // injected scripts can block, which can create a nested message loop. When
352 // this happens, other signals (like IPCs) can cause |pending_injections_| to
353 // be changed, so we don't want to risk that.
354 ScopedVector<ScriptInjection> frame_injections;
355 for (ScopedVector<ScriptInjection>::iterator iter =
356 pending_injections_.begin();
357 iter != pending_injections_.end();) {
358 if ((*iter)->web_frame() == frame) {
359 frame_injections.push_back(*iter);
360 iter = pending_injections_.weak_erase(iter);
361 } else {
362 ++iter;
366 // Add any injections for user scripts.
367 int tab_id = ExtensionHelper::Get(content::RenderView::FromWebView(
368 frame->top()->view()))->tab_id();
369 user_script_set_manager_->GetAllInjections(
370 &frame_injections, frame, tab_id, run_location);
372 ScriptsRunInfo scripts_run_info;
373 for (ScopedVector<ScriptInjection>::iterator iter = frame_injections.begin();
374 iter != frame_injections.end();) {
375 // If a blocking script was injected, there is potentially a possibility
376 // that the frame has been invalidated in the time since. Check.
377 if (!IsFrameValid(frame))
378 break;
380 // Try to inject the script if the extension is not "dirty" (invalidated by
381 // an update). If the injection does not finish (i.e., it is waiting for
382 // permission), add it to the list of pending injections.
383 if (invalidated_while_injecting_.count((*iter)->extension_id()) == 0 &&
384 !(*iter)->TryToInject(run_location,
385 extensions_->GetByID((*iter)->extension_id()),
386 &scripts_run_info)) {
387 pending_injections_.insert(pending_injections_.begin(), *iter);
388 iter = frame_injections.weak_erase(iter);
389 } else {
390 ++iter;
394 if (IsFrameValid(frame))
395 scripts_run_info.LogRun(frame, run_location);
397 invalidated_while_injecting_.clear();
400 void ScriptInjectionManager::HandleExecuteCode(
401 const ExtensionMsg_ExecuteCode_Params& params,
402 content::RenderView* render_view) {
403 // TODO(dcheng): Not sure how this can happen today. In an OOPI world, it
404 // would indicate a logic error--the browser must direct this request to the
405 // right renderer process to begin with.
406 blink::WebLocalFrame* main_frame =
407 render_view->GetWebView()->mainFrame()->toWebLocalFrame();
408 if (!main_frame) {
409 render_view->Send(
410 new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(),
411 params.request_id,
412 "No main frame",
413 GURL(std::string()),
414 base::ListValue()));
415 return;
418 scoped_ptr<ScriptInjection> injection(new ScriptInjection(
419 scoped_ptr<ScriptInjector>(
420 new ProgrammaticScriptInjector(params, main_frame)),
421 main_frame,
422 params.extension_id,
423 static_cast<UserScript::RunLocation>(params.run_at),
424 ExtensionHelper::Get(render_view)->tab_id()));
426 ScriptsRunInfo scripts_run_info;
427 FrameStatusMap::const_iterator iter = frame_statuses_.find(main_frame);
428 if (!injection->TryToInject(
429 iter == frame_statuses_.end() ? UserScript::UNDEFINED : iter->second,
430 extensions_->GetByID(injection->extension_id()),
431 &scripts_run_info)) {
432 pending_injections_.push_back(injection.release());
436 void ScriptInjectionManager::HandleExecuteDeclarativeScript(
437 blink::WebFrame* web_frame,
438 int tab_id,
439 const ExtensionId& extension_id,
440 int script_id,
441 const GURL& url) {
442 const Extension* extension = extensions_->GetByID(extension_id);
443 // TODO(dcheng): This function signature should really be a WebLocalFrame,
444 // rather than trying to coerce it here.
445 scoped_ptr<ScriptInjection> injection =
446 user_script_set_manager_->GetInjectionForDeclarativeScript(
447 script_id,
448 web_frame->toWebLocalFrame(),
449 tab_id,
450 url,
451 extension);
452 if (injection.get()) {
453 ScriptsRunInfo scripts_run_info;
454 // TODO(markdittmer): Use return value of TryToInject for error handling.
455 injection->TryToInject(UserScript::BROWSER_DRIVEN,
456 extension,
457 &scripts_run_info);
458 scripts_run_info.LogRun(web_frame, UserScript::BROWSER_DRIVEN);
462 void ScriptInjectionManager::HandlePermitScriptInjection(int64 request_id) {
463 ScopedVector<ScriptInjection>::iterator iter =
464 pending_injections_.begin();
465 for (; iter != pending_injections_.end(); ++iter) {
466 if ((*iter)->request_id() == request_id)
467 break;
469 if (iter == pending_injections_.end())
470 return;
472 // At this point, because the request is present in pending_injections_, we
473 // know that this is the same page that issued the request (otherwise,
474 // RVOHelper's DidStartProvisionalLoad callback would have caused it to be
475 // cleared out).
477 scoped_ptr<ScriptInjection> injection(*iter);
478 pending_injections_.weak_erase(iter);
480 ScriptsRunInfo scripts_run_info;
481 if (injection->OnPermissionGranted(extensions_->GetByID(
482 injection->extension_id()),
483 &scripts_run_info)) {
484 scripts_run_info.LogRun(injection->web_frame(), UserScript::RUN_DEFERRED);
488 } // namespace extensions