Move base_dir to user_story_set, remove file_path.
[chromium-blink-merge.git] / extensions / renderer / script_injection_manager.cc
blob93862dd04bdfe1109bf23c223ccef4cb734ec90e
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/extension_injection_host.h"
19 #include "extensions/renderer/programmatic_script_injector.h"
20 #include "extensions/renderer/script_injection.h"
21 #include "extensions/renderer/scripts_run_info.h"
22 #include "ipc/ipc_message_macros.h"
23 #include "third_party/WebKit/public/web/WebDocument.h"
24 #include "third_party/WebKit/public/web/WebFrame.h"
25 #include "third_party/WebKit/public/web/WebLocalFrame.h"
26 #include "third_party/WebKit/public/web/WebView.h"
27 #include "url/gurl.h"
29 namespace extensions {
31 namespace {
33 // The length of time to wait after the DOM is complete to try and run user
34 // scripts.
35 const int kScriptIdleTimeoutInMs = 200;
37 // Returns the RunLocation that follows |run_location|.
38 UserScript::RunLocation NextRunLocation(UserScript::RunLocation run_location) {
39 switch (run_location) {
40 case UserScript::DOCUMENT_START:
41 return UserScript::DOCUMENT_END;
42 case UserScript::DOCUMENT_END:
43 return UserScript::DOCUMENT_IDLE;
44 case UserScript::DOCUMENT_IDLE:
45 return UserScript::RUN_LOCATION_LAST;
46 case UserScript::UNDEFINED:
47 case UserScript::RUN_DEFERRED:
48 case UserScript::BROWSER_DRIVEN:
49 case UserScript::RUN_LOCATION_LAST:
50 break;
52 NOTREACHED();
53 return UserScript::RUN_LOCATION_LAST;
57 // TODO(hanxi): let ScriptInjection own an InjectionHost to avoid constructing
58 // an ExtensionInjectionHost many times.
59 // Note: the ScriptInjection should be able to know when the backing extension
60 // is removed.
61 scoped_ptr<ExtensionInjectionHost> GetExtensionInjectionHost(
62 const std::string& extension_id, const ExtensionSet* extensions) {
63 const Extension* extension = extensions->GetByID(extension_id);
64 if (!extension)
65 return scoped_ptr<ExtensionInjectionHost>();
66 return scoped_ptr<ExtensionInjectionHost>(
67 new ExtensionInjectionHost(extension));
70 } // namespace
72 class ScriptInjectionManager::RVOHelper : public content::RenderViewObserver {
73 public:
74 RVOHelper(content::RenderView* render_view, ScriptInjectionManager* manager);
75 ~RVOHelper() override;
77 private:
78 // RenderViewObserver implementation.
79 bool OnMessageReceived(const IPC::Message& message) override;
80 void DidCreateNewDocument(blink::WebLocalFrame* frame) override;
81 void DidCreateDocumentElement(blink::WebLocalFrame* frame) override;
82 void DidFinishDocumentLoad(blink::WebLocalFrame* frame) override;
83 void DidFinishLoad(blink::WebLocalFrame* frame) override;
84 void FrameDetached(blink::WebFrame* frame) override;
85 void OnDestruct() override;
87 virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params& params);
88 virtual void OnExecuteDeclarativeScript(int tab_id,
89 const ExtensionId& extension_id,
90 int script_id,
91 const GURL& url);
92 virtual void OnPermitScriptInjection(int64 request_id);
94 // Tells the ScriptInjectionManager to run tasks associated with
95 // document_idle.
96 void RunIdle(blink::WebFrame* frame);
98 // Indicate that the given |frame| is no longer valid because it is starting
99 // a new load or closing.
100 void InvalidateFrame(blink::WebFrame* frame);
102 // The owning ScriptInjectionManager.
103 ScriptInjectionManager* manager_;
105 // The set of frames that we are about to notify for DOCUMENT_IDLE. We keep
106 // a set of those that are valid, so we don't notify that an invalid frame
107 // became idle.
108 std::set<blink::WebFrame*> pending_idle_frames_;
110 base::WeakPtrFactory<RVOHelper> weak_factory_;
113 ScriptInjectionManager::RVOHelper::RVOHelper(
114 content::RenderView* render_view,
115 ScriptInjectionManager* manager)
116 : content::RenderViewObserver(render_view),
117 manager_(manager),
118 weak_factory_(this) {
121 ScriptInjectionManager::RVOHelper::~RVOHelper() {
124 bool ScriptInjectionManager::RVOHelper::OnMessageReceived(
125 const IPC::Message& message) {
126 bool handled = true;
127 IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RVOHelper, message)
128 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode, OnExecuteCode)
129 IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection,
130 OnPermitScriptInjection)
131 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteDeclarativeScript,
132 OnExecuteDeclarativeScript)
133 IPC_MESSAGE_UNHANDLED(handled = false)
134 IPC_END_MESSAGE_MAP()
135 return handled;
138 void ScriptInjectionManager::RVOHelper::DidCreateNewDocument(
139 blink::WebLocalFrame* frame) {
140 // A new document is going to be shown, so invalidate the old document state.
141 // Check that the frame's state is known before invalidating the frame,
142 // because it is possible that a script injection was scheduled before the
143 // page was loaded, e.g. by navigating to a javascript: URL before the page
144 // has loaded.
145 if (manager_->frame_statuses_.find(frame) != manager_->frame_statuses_.end())
146 InvalidateFrame(frame);
149 void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement(
150 blink::WebLocalFrame* frame) {
151 manager_->StartInjectScripts(frame, UserScript::DOCUMENT_START);
154 void ScriptInjectionManager::RVOHelper::DidFinishDocumentLoad(
155 blink::WebLocalFrame* frame) {
156 manager_->StartInjectScripts(frame, UserScript::DOCUMENT_END);
157 pending_idle_frames_.insert(frame);
158 // We try to run idle in two places: here and DidFinishLoad.
159 // DidFinishDocumentLoad() corresponds to completing the document's load,
160 // whereas DidFinishLoad corresponds to completing the document and all
161 // subresources' load. We don't want to hold up script injection for a
162 // particularly slow subresource, so we set a delayed task from here - but if
163 // we finish everything before that point (i.e., DidFinishLoad() is
164 // triggered), then there's no reason to keep waiting.
165 content::RenderThread::Get()->GetTaskRunner()->PostDelayedTask(
166 FROM_HERE,
167 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
168 weak_factory_.GetWeakPtr(),
169 frame),
170 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs));
173 void ScriptInjectionManager::RVOHelper::DidFinishLoad(
174 blink::WebLocalFrame* frame) {
175 // Ensure that we don't block any UI progress by running scripts.
176 // We *don't* add the frame to |pending_idle_frames_| here because
177 // DidFinishDocumentLoad should strictly come before DidFinishLoad, so the
178 // first posted task to RunIdle() pops it out of the set. This ensures we
179 // don't try to run idle twice.
180 content::RenderThread::Get()->GetTaskRunner()->PostTask(
181 FROM_HERE,
182 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle,
183 weak_factory_.GetWeakPtr(),
184 frame));
187 void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame* frame) {
188 // The frame is closing - invalidate.
189 InvalidateFrame(frame);
192 void ScriptInjectionManager::RVOHelper::OnDestruct() {
193 manager_->RemoveObserver(this);
196 void ScriptInjectionManager::RVOHelper::OnExecuteCode(
197 const ExtensionMsg_ExecuteCode_Params& params) {
198 manager_->HandleExecuteCode(params, render_view());
201 void ScriptInjectionManager::RVOHelper::OnExecuteDeclarativeScript(
202 int tab_id,
203 const ExtensionId& extension_id,
204 int script_id,
205 const GURL& url) {
206 blink::WebFrame* main_frame = render_view()->GetWebView()->mainFrame();
207 CHECK(main_frame);
209 // TODO(markdittmer): This would be cleaner if we compared page_ids instead.
210 // Begin script injeciton workflow only if the current URL is identical to
211 // the one that matched declarative conditions in the browser.
212 if (main_frame->top()->document().url() == url) {
213 manager_->HandleExecuteDeclarativeScript(main_frame,
214 tab_id,
215 extension_id,
216 script_id,
217 url);
221 void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection(
222 int64 request_id) {
223 manager_->HandlePermitScriptInjection(request_id);
226 void ScriptInjectionManager::RVOHelper::RunIdle(blink::WebFrame* frame) {
227 // Only notify the manager if the frame hasn't either been removed or already
228 // had idle run since the task to RunIdle() was posted.
229 if (pending_idle_frames_.count(frame) > 0) {
230 manager_->StartInjectScripts(frame, UserScript::DOCUMENT_IDLE);
231 pending_idle_frames_.erase(frame);
235 void ScriptInjectionManager::RVOHelper::InvalidateFrame(
236 blink::WebFrame* frame) {
237 pending_idle_frames_.erase(frame);
238 manager_->InvalidateForFrame(frame);
241 ScriptInjectionManager::ScriptInjectionManager(
242 const ExtensionSet* extensions,
243 UserScriptSetManager* user_script_set_manager)
244 : extensions_(extensions),
245 injecting_scripts_(false),
246 user_script_set_manager_(user_script_set_manager),
247 user_script_set_manager_observer_(this) {
248 user_script_set_manager_observer_.Add(user_script_set_manager_);
251 ScriptInjectionManager::~ScriptInjectionManager() {
254 void ScriptInjectionManager::OnRenderViewCreated(
255 content::RenderView* render_view) {
256 rvo_helpers_.push_back(new RVOHelper(render_view, this));
259 void ScriptInjectionManager::OnUserScriptsUpdated(
260 const std::set<std::string>& changed_extensions,
261 const std::vector<UserScript*>& scripts) {
262 for (ScopedVector<ScriptInjection>::iterator iter =
263 pending_injections_.begin();
264 iter != pending_injections_.end();) {
265 if (changed_extensions.count((*iter)->host_id().id()) > 0)
266 iter = pending_injections_.erase(iter);
267 else
268 ++iter;
271 // If we are currently injecting scripts, we need to make a note that these
272 // extensions were updated.
273 if (injecting_scripts_) {
274 invalidated_while_injecting_.insert(changed_extensions.begin(),
275 changed_extensions.end());
279 void ScriptInjectionManager::RemoveObserver(RVOHelper* helper) {
280 for (ScopedVector<RVOHelper>::iterator iter = rvo_helpers_.begin();
281 iter != rvo_helpers_.end();
282 ++iter) {
283 if (*iter == helper) {
284 rvo_helpers_.erase(iter);
285 break;
290 void ScriptInjectionManager::InvalidateForFrame(blink::WebFrame* frame) {
291 for (ScopedVector<ScriptInjection>::iterator iter =
292 pending_injections_.begin();
293 iter != pending_injections_.end();) {
294 if ((*iter)->web_frame() == frame)
295 iter = pending_injections_.erase(iter);
296 else
297 ++iter;
300 frame_statuses_.erase(frame);
303 bool ScriptInjectionManager::IsFrameValid(blink::WebFrame* frame) const {
304 return frame_statuses_.find(frame) != frame_statuses_.end();
307 void ScriptInjectionManager::StartInjectScripts(
308 blink::WebFrame* frame, UserScript::RunLocation run_location) {
309 FrameStatusMap::iterator iter = frame_statuses_.find(frame);
310 // We also don't execute if we detect that the run location is somehow out of
311 // order. This can happen if:
312 // - The first run location reported for the frame isn't DOCUMENT_START, or
313 // - The run location reported doesn't immediately follow the previous
314 // reported run location.
315 // We don't want to run because extensions may have requirements that scripts
316 // running in an earlier run location have run by the time a later script
317 // runs. Better to just not run.
318 // Note that we check run_location > NextRunLocation() in the second clause
319 // (as opposed to !=) because earlier signals (like DidCreateDocumentElement)
320 // can happen multiple times, so we can receive earlier/equal run locations.
321 if ((iter == frame_statuses_.end() &&
322 run_location != UserScript::DOCUMENT_START) ||
323 (iter != frame_statuses_.end() &&
324 run_location > NextRunLocation(iter->second))) {
325 // We also invalidate the frame, because the run order of pending injections
326 // may also be bad.
327 InvalidateForFrame(frame);
328 return;
329 } else if (iter != frame_statuses_.end() && iter->second >= run_location) {
330 // Certain run location signals (like DidCreateDocumentElement) can happen
331 // multiple times. Ignore the subsequent signals.
332 return;
335 // Otherwise, all is right in the world, and we can get on with the
336 // injections!
338 frame_statuses_[frame] = run_location;
340 // If a content script injects blocking code (such as a javascript alert()),
341 // then there is a chance that we are running in a nested message loop, and
342 // shouldn't inject scripts right now (to avoid conflicts).
343 if (!injecting_scripts_) {
344 InjectScripts(frame, run_location);
345 // As above, we might have been blocked, but that means that, in the mean
346 // time, it's possible the frame advanced. Inject any scripts for run
347 // locations that were registered, but never ran.
348 while ((iter = frame_statuses_.find(frame)) != frame_statuses_.end() &&
349 iter->second > run_location) {
350 run_location = NextRunLocation(run_location);
351 DCHECK_LE(run_location, UserScript::DOCUMENT_IDLE);
352 InjectScripts(frame, run_location);
357 void ScriptInjectionManager::InjectScripts(
358 blink::WebFrame* frame,
359 UserScript::RunLocation run_location) {
360 DCHECK(!injecting_scripts_);
361 DCHECK(invalidated_while_injecting_.empty());
362 base::AutoReset<bool>(&injecting_scripts_, true);
364 // Find any injections that want to run on the given frame.
365 // We create a separate vector for these because there is a chance that
366 // injected scripts can block, which can create a nested message loop. When
367 // this happens, other signals (like IPCs) can cause |pending_injections_| to
368 // be changed, so we don't want to risk that.
369 ScopedVector<ScriptInjection> frame_injections;
370 for (ScopedVector<ScriptInjection>::iterator iter =
371 pending_injections_.begin();
372 iter != pending_injections_.end();) {
373 if ((*iter)->web_frame() == frame) {
374 frame_injections.push_back(*iter);
375 iter = pending_injections_.weak_erase(iter);
376 } else {
377 ++iter;
381 // Add any injections for user scripts.
382 int tab_id = ExtensionHelper::Get(content::RenderView::FromWebView(
383 frame->top()->view()))->tab_id();
384 user_script_set_manager_->GetAllInjections(
385 &frame_injections, frame, tab_id, run_location);
387 ScriptsRunInfo scripts_run_info;
388 for (ScopedVector<ScriptInjection>::iterator iter = frame_injections.begin();
389 iter != frame_injections.end();) {
390 // If a blocking script was injected, there is potentially a possibility
391 // that the frame has been invalidated in the time since. Check.
392 if (!IsFrameValid(frame))
393 break;
395 const std::string& extension_id = (*iter)->host_id().id();
396 scoped_ptr<ExtensionInjectionHost> extension_injection_host =
397 GetExtensionInjectionHost(extension_id, extensions_);
398 // Try to inject the script if the extension is not "dirty" (invalidated by
399 // an update). If the injection does not finish (i.e., it is waiting for
400 // permission), add it to the list of pending injections.
401 if (invalidated_while_injecting_.count(extension_id) == 0 &&
402 !(*iter)->TryToInject(run_location,
403 extension_injection_host.get(),
404 &scripts_run_info)) {
405 pending_injections_.insert(pending_injections_.begin(), *iter);
406 iter = frame_injections.weak_erase(iter);
407 } else {
408 ++iter;
412 if (IsFrameValid(frame))
413 scripts_run_info.LogRun(frame, run_location);
415 invalidated_while_injecting_.clear();
418 void ScriptInjectionManager::HandleExecuteCode(
419 const ExtensionMsg_ExecuteCode_Params& params,
420 content::RenderView* render_view) {
421 // TODO(dcheng): Not sure how this can happen today. In an OOPI world, it
422 // would indicate a logic error--the browser must direct this request to the
423 // right renderer process to begin with.
424 blink::WebLocalFrame* main_frame =
425 render_view->GetWebView()->mainFrame()->toWebLocalFrame();
426 if (!main_frame) {
427 render_view->Send(
428 new ExtensionHostMsg_ExecuteCodeFinished(render_view->GetRoutingID(),
429 params.request_id,
430 "No main frame",
431 GURL(std::string()),
432 base::ListValue()));
433 return;
436 scoped_ptr<ScriptInjection> injection(new ScriptInjection(
437 scoped_ptr<ScriptInjector>(
438 new ProgrammaticScriptInjector(params, main_frame)),
439 main_frame,
440 HostID(HostID::EXTENSIONS, params.extension_id),
441 static_cast<UserScript::RunLocation>(params.run_at),
442 ExtensionHelper::Get(render_view)->tab_id()));
444 ScriptsRunInfo scripts_run_info;
445 FrameStatusMap::const_iterator iter = frame_statuses_.find(main_frame);
447 scoped_ptr<ExtensionInjectionHost> extension_injection_host =
448 GetExtensionInjectionHost(injection->host_id().id(), extensions_);
450 if (!injection->TryToInject(
451 iter == frame_statuses_.end() ? UserScript::UNDEFINED : iter->second,
452 extension_injection_host.get(),
453 &scripts_run_info)) {
454 pending_injections_.push_back(injection.release());
458 void ScriptInjectionManager::HandleExecuteDeclarativeScript(
459 blink::WebFrame* web_frame,
460 int tab_id,
461 const ExtensionId& extension_id,
462 int script_id,
463 const GURL& url) {
464 scoped_ptr<ExtensionInjectionHost> extension_injection_host =
465 GetExtensionInjectionHost(extension_id, extensions_);
466 const Extension* extension = extensions_->GetByID(extension_id);
467 // TODO(dcheng): This function signature should really be a WebLocalFrame,
468 // rather than trying to coerce it here.
469 scoped_ptr<ScriptInjection> injection =
470 user_script_set_manager_->GetInjectionForDeclarativeScript(
471 script_id,
472 web_frame->toWebLocalFrame(),
473 tab_id,
474 url,
475 extension);
476 if (injection.get()) {
477 ScriptsRunInfo scripts_run_info;
478 // TODO(markdittmer): Use return value of TryToInject for error handling.
479 injection->TryToInject(UserScript::BROWSER_DRIVEN,
480 extension_injection_host.get(),
481 &scripts_run_info);
482 scripts_run_info.LogRun(web_frame, UserScript::BROWSER_DRIVEN);
486 void ScriptInjectionManager::HandlePermitScriptInjection(int64 request_id) {
487 ScopedVector<ScriptInjection>::iterator iter =
488 pending_injections_.begin();
489 for (; iter != pending_injections_.end(); ++iter) {
490 if ((*iter)->request_id() == request_id)
491 break;
493 if (iter == pending_injections_.end())
494 return;
496 // At this point, because the request is present in pending_injections_, we
497 // know that this is the same page that issued the request (otherwise,
498 // RVOHelper's DidStartProvisionalLoad callback would have caused it to be
499 // cleared out).
501 scoped_ptr<ScriptInjection> injection(*iter);
502 pending_injections_.weak_erase(iter);
504 ScriptsRunInfo scripts_run_info;
505 scoped_ptr<ExtensionInjectionHost> extension_injection_host =
506 GetExtensionInjectionHost(injection->host_id().id(), extensions_);
507 if (injection->OnPermissionGranted(extension_injection_host.get(),
508 &scripts_run_info)) {
509 scripts_run_info.LogRun(injection->web_frame(), UserScript::RUN_DEFERRED);
513 } // namespace extensions