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"
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 "extensions/renderer/web_ui_injection_host.h"
23 #include "ipc/ipc_message_macros.h"
24 #include "third_party/WebKit/public/web/WebDocument.h"
25 #include "third_party/WebKit/public/web/WebFrame.h"
26 #include "third_party/WebKit/public/web/WebLocalFrame.h"
27 #include "third_party/WebKit/public/web/WebView.h"
30 namespace extensions
{
34 // The length of time to wait after the DOM is complete to try and run user
36 const int kScriptIdleTimeoutInMs
= 200;
38 // Returns the RunLocation that follows |run_location|.
39 UserScript::RunLocation
NextRunLocation(UserScript::RunLocation run_location
) {
40 switch (run_location
) {
41 case UserScript::DOCUMENT_START
:
42 return UserScript::DOCUMENT_END
;
43 case UserScript::DOCUMENT_END
:
44 return UserScript::DOCUMENT_IDLE
;
45 case UserScript::DOCUMENT_IDLE
:
46 return UserScript::RUN_LOCATION_LAST
;
47 case UserScript::UNDEFINED
:
48 case UserScript::RUN_DEFERRED
:
49 case UserScript::BROWSER_DRIVEN
:
50 case UserScript::RUN_LOCATION_LAST
:
54 return UserScript::RUN_LOCATION_LAST
;
59 class ScriptInjectionManager::RVOHelper
: public content::RenderViewObserver
{
61 RVOHelper(content::RenderView
* render_view
, ScriptInjectionManager
* manager
);
62 ~RVOHelper() override
;
65 // RenderViewObserver implementation.
66 bool OnMessageReceived(const IPC::Message
& message
) override
;
67 void DidCreateNewDocument(blink::WebLocalFrame
* frame
) override
;
68 void DidCreateDocumentElement(blink::WebLocalFrame
* frame
) override
;
69 void DidFinishDocumentLoad(blink::WebLocalFrame
* frame
) override
;
70 void DidFinishLoad(blink::WebLocalFrame
* frame
) override
;
71 void FrameDetached(blink::WebFrame
* frame
) override
;
72 void OnDestruct() override
;
74 virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params
& params
);
75 virtual void OnExecuteDeclarativeScript(int tab_id
,
76 const ExtensionId
& extension_id
,
79 virtual void OnPermitScriptInjection(int64 request_id
);
81 // Tells the ScriptInjectionManager to run tasks associated with
83 void RunIdle(blink::WebFrame
* frame
);
85 // Indicate that the given |frame| is no longer valid because it is starting
86 // a new load or closing.
87 void InvalidateFrame(blink::WebFrame
* frame
);
89 // The owning ScriptInjectionManager.
90 ScriptInjectionManager
* manager_
;
92 // The set of frames that we are about to notify for DOCUMENT_IDLE. We keep
93 // a set of those that are valid, so we don't notify that an invalid frame
95 std::set
<blink::WebFrame
*> pending_idle_frames_
;
97 base::WeakPtrFactory
<RVOHelper
> weak_factory_
;
100 ScriptInjectionManager::RVOHelper::RVOHelper(
101 content::RenderView
* render_view
,
102 ScriptInjectionManager
* manager
)
103 : content::RenderViewObserver(render_view
),
105 weak_factory_(this) {
108 ScriptInjectionManager::RVOHelper::~RVOHelper() {
111 bool ScriptInjectionManager::RVOHelper::OnMessageReceived(
112 const IPC::Message
& message
) {
114 IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RVOHelper
, message
)
115 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode
, OnExecuteCode
)
116 IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection
,
117 OnPermitScriptInjection
)
118 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteDeclarativeScript
,
119 OnExecuteDeclarativeScript
)
120 IPC_MESSAGE_UNHANDLED(handled
= false)
121 IPC_END_MESSAGE_MAP()
125 void ScriptInjectionManager::RVOHelper::DidCreateNewDocument(
126 blink::WebLocalFrame
* frame
) {
127 // A new document is going to be shown, so invalidate the old document state.
128 // Check that the frame's state is known before invalidating the frame,
129 // because it is possible that a script injection was scheduled before the
130 // page was loaded, e.g. by navigating to a javascript: URL before the page
132 if (manager_
->frame_statuses_
.find(frame
) != manager_
->frame_statuses_
.end())
133 InvalidateFrame(frame
);
136 void ScriptInjectionManager::RVOHelper::DidCreateDocumentElement(
137 blink::WebLocalFrame
* frame
) {
138 manager_
->StartInjectScripts(frame
, UserScript::DOCUMENT_START
);
141 void ScriptInjectionManager::RVOHelper::DidFinishDocumentLoad(
142 blink::WebLocalFrame
* frame
) {
143 manager_
->StartInjectScripts(frame
, UserScript::DOCUMENT_END
);
144 pending_idle_frames_
.insert(frame
);
145 // We try to run idle in two places: here and DidFinishLoad.
146 // DidFinishDocumentLoad() corresponds to completing the document's load,
147 // whereas DidFinishLoad corresponds to completing the document and all
148 // subresources' load. We don't want to hold up script injection for a
149 // particularly slow subresource, so we set a delayed task from here - but if
150 // we finish everything before that point (i.e., DidFinishLoad() is
151 // triggered), then there's no reason to keep waiting.
152 content::RenderThread::Get()->GetTaskRunner()->PostDelayedTask(
154 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle
,
155 weak_factory_
.GetWeakPtr(),
157 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs
));
160 void ScriptInjectionManager::RVOHelper::DidFinishLoad(
161 blink::WebLocalFrame
* frame
) {
162 // Ensure that we don't block any UI progress by running scripts.
163 // We *don't* add the frame to |pending_idle_frames_| here because
164 // DidFinishDocumentLoad should strictly come before DidFinishLoad, so the
165 // first posted task to RunIdle() pops it out of the set. This ensures we
166 // don't try to run idle twice.
167 content::RenderThread::Get()->GetTaskRunner()->PostTask(
169 base::Bind(&ScriptInjectionManager::RVOHelper::RunIdle
,
170 weak_factory_
.GetWeakPtr(),
174 void ScriptInjectionManager::RVOHelper::FrameDetached(blink::WebFrame
* frame
) {
175 // The frame is closing - invalidate.
176 InvalidateFrame(frame
);
179 void ScriptInjectionManager::RVOHelper::OnDestruct() {
180 manager_
->RemoveObserver(this);
183 void ScriptInjectionManager::RVOHelper::OnExecuteCode(
184 const ExtensionMsg_ExecuteCode_Params
& params
) {
185 manager_
->HandleExecuteCode(params
, render_view());
188 void ScriptInjectionManager::RVOHelper::OnExecuteDeclarativeScript(
190 const ExtensionId
& extension_id
,
193 blink::WebFrame
* main_frame
= render_view()->GetWebView()->mainFrame();
196 // TODO(markdittmer): This would be cleaner if we compared page_ids instead.
197 // Begin script injeciton workflow only if the current URL is identical to
198 // the one that matched declarative conditions in the browser.
199 if (main_frame
->top()->document().url() == url
) {
200 manager_
->HandleExecuteDeclarativeScript(main_frame
,
208 void ScriptInjectionManager::RVOHelper::OnPermitScriptInjection(
210 manager_
->HandlePermitScriptInjection(request_id
);
213 void ScriptInjectionManager::RVOHelper::RunIdle(blink::WebFrame
* frame
) {
214 // Only notify the manager if the frame hasn't either been removed or already
215 // had idle run since the task to RunIdle() was posted.
216 if (pending_idle_frames_
.count(frame
) > 0) {
217 manager_
->StartInjectScripts(frame
, UserScript::DOCUMENT_IDLE
);
218 pending_idle_frames_
.erase(frame
);
222 void ScriptInjectionManager::RVOHelper::InvalidateFrame(
223 blink::WebFrame
* frame
) {
224 pending_idle_frames_
.erase(frame
);
225 manager_
->InvalidateForFrame(frame
);
228 ScriptInjectionManager::ScriptInjectionManager(
229 const ExtensionSet
* extensions
,
230 UserScriptSetManager
* user_script_set_manager
)
231 : extensions_(extensions
),
232 user_script_set_manager_(user_script_set_manager
),
233 user_script_set_manager_observer_(this) {
234 user_script_set_manager_observer_
.Add(user_script_set_manager_
);
237 ScriptInjectionManager::~ScriptInjectionManager() {
240 void ScriptInjectionManager::OnRenderViewCreated(
241 content::RenderView
* render_view
) {
242 rvo_helpers_
.push_back(new RVOHelper(render_view
, this));
245 void ScriptInjectionManager::OnExtensionUnloaded(
246 const std::string
& extension_id
) {
247 for (auto iter
= pending_injections_
.begin();
248 iter
!= pending_injections_
.end();) {
249 if ((*iter
)->host_id().id() == extension_id
) {
250 (*iter
)->OnHostRemoved();
251 iter
= pending_injections_
.erase(iter
);
258 void ScriptInjectionManager::OnInjectionFinished(
259 ScriptInjection
* injection
) {
260 ScopedVector
<ScriptInjection
>::iterator iter
=
261 std::find(running_injections_
.begin(),
262 running_injections_
.end(),
264 if (iter
!= running_injections_
.end())
265 running_injections_
.erase(iter
);
268 void ScriptInjectionManager::OnUserScriptsUpdated(
269 const std::set
<HostID
>& changed_hosts
,
270 const std::vector
<UserScript
*>& scripts
) {
271 for (ScopedVector
<ScriptInjection
>::iterator iter
=
272 pending_injections_
.begin();
273 iter
!= pending_injections_
.end();) {
274 if (changed_hosts
.count((*iter
)->host_id()) > 0)
275 iter
= pending_injections_
.erase(iter
);
281 void ScriptInjectionManager::RemoveObserver(RVOHelper
* helper
) {
282 for (ScopedVector
<RVOHelper
>::iterator iter
= rvo_helpers_
.begin();
283 iter
!= rvo_helpers_
.end();
285 if (*iter
== helper
) {
286 rvo_helpers_
.erase(iter
);
292 void ScriptInjectionManager::InvalidateForFrame(blink::WebFrame
* frame
) {
293 for (ScopedVector
<ScriptInjection
>::iterator iter
=
294 pending_injections_
.begin();
295 iter
!= pending_injections_
.end();) {
296 if ((*iter
)->web_frame() == frame
)
297 iter
= pending_injections_
.erase(iter
);
302 frame_statuses_
.erase(frame
);
305 void ScriptInjectionManager::StartInjectScripts(
306 blink::WebFrame
* frame
, UserScript::RunLocation run_location
) {
307 FrameStatusMap::iterator iter
= frame_statuses_
.find(frame
);
308 // We also don't execute if we detect that the run location is somehow out of
309 // order. This can happen if:
310 // - The first run location reported for the frame isn't DOCUMENT_START, or
311 // - The run location reported doesn't immediately follow the previous
312 // reported run location.
313 // We don't want to run because extensions may have requirements that scripts
314 // running in an earlier run location have run by the time a later script
315 // runs. Better to just not run.
316 // Note that we check run_location > NextRunLocation() in the second clause
317 // (as opposed to !=) because earlier signals (like DidCreateDocumentElement)
318 // can happen multiple times, so we can receive earlier/equal run locations.
319 if ((iter
== frame_statuses_
.end() &&
320 run_location
!= UserScript::DOCUMENT_START
) ||
321 (iter
!= frame_statuses_
.end() &&
322 run_location
> NextRunLocation(iter
->second
))) {
323 // We also invalidate the frame, because the run order of pending injections
325 InvalidateForFrame(frame
);
327 } else if (iter
!= frame_statuses_
.end() && iter
->second
>= run_location
) {
328 // Certain run location signals (like DidCreateDocumentElement) can happen
329 // multiple times. Ignore the subsequent signals.
333 // Otherwise, all is right in the world, and we can get on with the
335 frame_statuses_
[frame
] = run_location
;
336 InjectScripts(frame
, run_location
);
339 void ScriptInjectionManager::InjectScripts(
340 blink::WebFrame
* frame
,
341 UserScript::RunLocation run_location
) {
342 // Find any injections that want to run on the given frame.
343 ScopedVector
<ScriptInjection
> frame_injections
;
344 for (ScopedVector
<ScriptInjection
>::iterator iter
=
345 pending_injections_
.begin();
346 iter
!= pending_injections_
.end();) {
347 if ((*iter
)->web_frame() == frame
) {
348 frame_injections
.push_back(*iter
);
349 iter
= pending_injections_
.weak_erase(iter
);
355 // Add any injections for user scripts.
356 int tab_id
= ExtensionHelper::Get(content::RenderView::FromWebView(
357 frame
->top()->view()))->tab_id();
358 user_script_set_manager_
->GetAllInjections(
359 &frame_injections
, frame
, tab_id
, run_location
);
361 ScriptsRunInfo scripts_run_info
;
362 std::vector
<ScriptInjection
*> released_injections
;
363 frame_injections
.release(&released_injections
);
364 for (ScriptInjection
* injection
: released_injections
)
365 TryToInject(make_scoped_ptr(injection
), run_location
, &scripts_run_info
);
367 scripts_run_info
.LogRun(frame
, run_location
);
370 void ScriptInjectionManager::TryToInject(
371 scoped_ptr
<ScriptInjection
> injection
,
372 UserScript::RunLocation run_location
,
373 ScriptsRunInfo
* scripts_run_info
) {
374 // Try to inject the script. If the injection is waiting (i.e., for
375 // permission), add it to the list of pending injections. If the injection
376 // has blocked, add it to the list of running injections.
377 switch (injection
->TryToInject(
381 case ScriptInjection::INJECTION_WAITING
:
382 pending_injections_
.push_back(injection
.release());
384 case ScriptInjection::INJECTION_BLOCKED
:
385 running_injections_
.push_back(injection
.release());
387 case ScriptInjection::INJECTION_FINISHED
:
392 void ScriptInjectionManager::HandleExecuteCode(
393 const ExtensionMsg_ExecuteCode_Params
& params
,
394 content::RenderView
* render_view
) {
395 // TODO(dcheng): Not sure how this can happen today. In an OOPI world, it
396 // would indicate a logic error--the browser must direct this request to the
397 // right renderer process to begin with.
398 blink::WebLocalFrame
* main_frame
=
399 render_view
->GetWebView()->mainFrame()->toWebLocalFrame();
402 new ExtensionHostMsg_ExecuteCodeFinished(render_view
->GetRoutingID(),
410 scoped_ptr
<const InjectionHost
> injection_host
;
411 if (params
.host_id
.type() == HostID::EXTENSIONS
) {
412 injection_host
= ExtensionInjectionHost::Create(params
.host_id
.id(),
416 } else if (params
.host_id
.type() == HostID::WEBUI
) {
417 injection_host
.reset(
418 new WebUIInjectionHost(params
.host_id
));
421 scoped_ptr
<ScriptInjection
> injection(new ScriptInjection(
422 scoped_ptr
<ScriptInjector
>(
423 new ProgrammaticScriptInjector(params
, main_frame
)),
425 injection_host
.Pass(),
426 static_cast<UserScript::RunLocation
>(params
.run_at
),
427 ExtensionHelper::Get(render_view
)->tab_id()));
429 ScriptsRunInfo scripts_run_info
;
430 FrameStatusMap::const_iterator iter
= frame_statuses_
.find(main_frame
);
434 iter
== frame_statuses_
.end() ? UserScript::UNDEFINED
: iter
->second
,
438 void ScriptInjectionManager::HandleExecuteDeclarativeScript(
439 blink::WebFrame
* web_frame
,
441 const ExtensionId
& extension_id
,
444 // TODO(dcheng): This function signature should really be a WebLocalFrame,
445 // rather than trying to coerce it here.
446 scoped_ptr
<ScriptInjection
> injection
=
447 user_script_set_manager_
->GetInjectionForDeclarativeScript(
449 web_frame
->toWebLocalFrame(),
453 if (injection
.get()) {
454 ScriptsRunInfo scripts_run_info
;
455 // TODO(markdittmer): Use return value of TryToInject for error handling.
456 TryToInject(injection
.Pass(),
457 UserScript::BROWSER_DRIVEN
,
460 scripts_run_info
.LogRun(web_frame
, UserScript::BROWSER_DRIVEN
);
464 void ScriptInjectionManager::HandlePermitScriptInjection(int64 request_id
) {
465 ScopedVector
<ScriptInjection
>::iterator iter
=
466 pending_injections_
.begin();
467 for (; iter
!= pending_injections_
.end(); ++iter
) {
468 if ((*iter
)->request_id() == request_id
) {
469 DCHECK((*iter
)->host_id().type() == HostID::EXTENSIONS
);
473 if (iter
== pending_injections_
.end())
476 // At this point, because the request is present in pending_injections_, we
477 // know that this is the same page that issued the request (otherwise,
478 // RVOHelper's DidStartProvisionalLoad callback would have caused it to be
481 scoped_ptr
<ScriptInjection
> injection(*iter
);
482 pending_injections_
.weak_erase(iter
);
484 ScriptsRunInfo scripts_run_info
;
485 ScriptInjection::InjectionResult res
= injection
->OnPermissionGranted(
487 if (res
== ScriptInjection::INJECTION_BLOCKED
)
488 running_injections_
.push_back(injection
.Pass());
489 scripts_run_info
.LogRun(injection
->web_frame(), UserScript::RUN_DEFERRED
);
492 } // namespace extensions