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_frame.h"
12 #include "content/public/renderer/render_frame_observer.h"
13 #include "content/public/renderer/render_thread.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_frame_helper.h"
18 #include "extensions/renderer/extension_injection_host.h"
19 #include "extensions/renderer/programmatic_script_injector.h"
20 #include "extensions/renderer/renderer_extension_registry.h"
21 #include "extensions/renderer/script_injection.h"
22 #include "extensions/renderer/scripts_run_info.h"
23 #include "extensions/renderer/web_ui_injection_host.h"
24 #include "ipc/ipc_message_macros.h"
25 #include "third_party/WebKit/public/web/WebDocument.h"
26 #include "third_party/WebKit/public/web/WebFrame.h"
27 #include "third_party/WebKit/public/web/WebLocalFrame.h"
28 #include "third_party/WebKit/public/web/WebView.h"
31 namespace extensions
{
35 // The length of time to wait after the DOM is complete to try and run user
37 const int kScriptIdleTimeoutInMs
= 200;
39 // Returns the RunLocation that follows |run_location|.
40 UserScript::RunLocation
NextRunLocation(UserScript::RunLocation run_location
) {
41 switch (run_location
) {
42 case UserScript::DOCUMENT_START
:
43 return UserScript::DOCUMENT_END
;
44 case UserScript::DOCUMENT_END
:
45 return UserScript::DOCUMENT_IDLE
;
46 case UserScript::DOCUMENT_IDLE
:
47 return UserScript::RUN_LOCATION_LAST
;
48 case UserScript::UNDEFINED
:
49 case UserScript::RUN_DEFERRED
:
50 case UserScript::BROWSER_DRIVEN
:
51 case UserScript::RUN_LOCATION_LAST
:
55 return UserScript::RUN_LOCATION_LAST
;
60 class ScriptInjectionManager::RFOHelper
: public content::RenderFrameObserver
{
62 RFOHelper(content::RenderFrame
* render_frame
,
63 ScriptInjectionManager
* manager
);
64 ~RFOHelper() override
;
67 // RenderFrameObserver implementation.
68 bool OnMessageReceived(const IPC::Message
& message
) override
;
69 void DidCreateNewDocument() override
;
70 void DidCreateDocumentElement() override
;
71 void DidFinishDocumentLoad() override
;
72 void DidFinishLoad() override
;
73 void FrameDetached() override
;
74 void OnDestruct() override
;
76 virtual void OnExecuteCode(const ExtensionMsg_ExecuteCode_Params
& params
);
77 virtual void OnExecuteDeclarativeScript(int tab_id
,
78 const ExtensionId
& extension_id
,
81 virtual void OnPermitScriptInjection(int64 request_id
);
83 // Tells the ScriptInjectionManager to run tasks associated with
87 // Indicate that the frame is no longer valid because it is starting
88 // a new load or closing.
89 void InvalidateAndResetFrame();
91 // The owning ScriptInjectionManager.
92 ScriptInjectionManager
* manager_
;
94 bool should_run_idle_
;
96 base::WeakPtrFactory
<RFOHelper
> weak_factory_
;
99 ScriptInjectionManager::RFOHelper::RFOHelper(content::RenderFrame
* render_frame
,
100 ScriptInjectionManager
* manager
)
101 : content::RenderFrameObserver(render_frame
),
103 should_run_idle_(true),
104 weak_factory_(this) {
107 ScriptInjectionManager::RFOHelper::~RFOHelper() {
110 bool ScriptInjectionManager::RFOHelper::OnMessageReceived(
111 const IPC::Message
& message
) {
113 IPC_BEGIN_MESSAGE_MAP(ScriptInjectionManager::RFOHelper
, message
)
114 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteCode
, OnExecuteCode
)
115 IPC_MESSAGE_HANDLER(ExtensionMsg_PermitScriptInjection
,
116 OnPermitScriptInjection
)
117 IPC_MESSAGE_HANDLER(ExtensionMsg_ExecuteDeclarativeScript
,
118 OnExecuteDeclarativeScript
)
119 IPC_MESSAGE_UNHANDLED(handled
= false)
120 IPC_END_MESSAGE_MAP()
124 void ScriptInjectionManager::RFOHelper::DidCreateNewDocument() {
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
130 if (manager_
->frame_statuses_
.count(render_frame()) != 0)
131 InvalidateAndResetFrame();
134 void ScriptInjectionManager::RFOHelper::DidCreateDocumentElement() {
135 manager_
->StartInjectScripts(render_frame(), UserScript::DOCUMENT_START
);
138 void ScriptInjectionManager::RFOHelper::DidFinishDocumentLoad() {
139 DCHECK(content::RenderThread::Get());
140 manager_
->StartInjectScripts(render_frame(), UserScript::DOCUMENT_END
);
141 // We try to run idle in two places: here and DidFinishLoad.
142 // DidFinishDocumentLoad() corresponds to completing the document's load,
143 // whereas DidFinishLoad corresponds to completing the document and all
144 // subresources' load. We don't want to hold up script injection for a
145 // particularly slow subresource, so we set a delayed task from here - but if
146 // we finish everything before that point (i.e., DidFinishLoad() is
147 // triggered), then there's no reason to keep waiting.
148 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
150 base::Bind(&ScriptInjectionManager::RFOHelper::RunIdle
,
151 weak_factory_
.GetWeakPtr()),
152 base::TimeDelta::FromMilliseconds(kScriptIdleTimeoutInMs
));
155 void ScriptInjectionManager::RFOHelper::DidFinishLoad() {
156 DCHECK(content::RenderThread::Get());
157 // Ensure that we don't block any UI progress by running scripts.
158 base::ThreadTaskRunnerHandle::Get()->PostTask(
160 base::Bind(&ScriptInjectionManager::RFOHelper::RunIdle
,
161 weak_factory_
.GetWeakPtr()));
164 void ScriptInjectionManager::RFOHelper::FrameDetached() {
165 // The frame is closing - invalidate.
166 InvalidateAndResetFrame();
169 void ScriptInjectionManager::RFOHelper::OnDestruct() {
170 manager_
->RemoveObserver(this);
173 void ScriptInjectionManager::RFOHelper::OnExecuteCode(
174 const ExtensionMsg_ExecuteCode_Params
& params
) {
175 manager_
->HandleExecuteCode(params
, render_frame());
178 void ScriptInjectionManager::RFOHelper::OnExecuteDeclarativeScript(
180 const ExtensionId
& extension_id
,
183 // TODO(markdittmer): URL-checking isn't the best security measure.
184 // Begin script injection workflow only if the current URL is identical to
185 // the one that matched declarative conditions in the browser.
186 if (render_frame()->GetWebFrame()->document().url() == url
) {
187 manager_
->HandleExecuteDeclarativeScript(render_frame(),
195 void ScriptInjectionManager::RFOHelper::OnPermitScriptInjection(
197 manager_
->HandlePermitScriptInjection(request_id
);
200 void ScriptInjectionManager::RFOHelper::RunIdle() {
201 // Only notify the manager if the frame hasn't either been removed or already
202 // had idle run since the task to RunIdle() was posted.
203 if (should_run_idle_
) {
204 should_run_idle_
= false;
205 manager_
->StartInjectScripts(render_frame(), UserScript::DOCUMENT_IDLE
);
209 void ScriptInjectionManager::RFOHelper::InvalidateAndResetFrame() {
210 // Invalidate any pending idle injections, and reset the frame inject on idle.
211 weak_factory_
.InvalidateWeakPtrs();
212 // We reset to inject on idle, because the frame can be reused (in the case of
214 should_run_idle_
= true;
215 manager_
->InvalidateForFrame(render_frame());
218 ScriptInjectionManager::ScriptInjectionManager(
219 UserScriptSetManager
* user_script_set_manager
)
220 : user_script_set_manager_(user_script_set_manager
),
221 user_script_set_manager_observer_(this) {
222 user_script_set_manager_observer_
.Add(user_script_set_manager_
);
225 ScriptInjectionManager::~ScriptInjectionManager() {
228 void ScriptInjectionManager::OnRenderFrameCreated(
229 content::RenderFrame
* render_frame
) {
230 rfo_helpers_
.push_back(new RFOHelper(render_frame
, this));
233 void ScriptInjectionManager::OnExtensionUnloaded(
234 const std::string
& extension_id
) {
235 for (auto iter
= pending_injections_
.begin();
236 iter
!= pending_injections_
.end();) {
237 if ((*iter
)->host_id().id() == extension_id
) {
238 (*iter
)->OnHostRemoved();
239 iter
= pending_injections_
.erase(iter
);
246 void ScriptInjectionManager::OnInjectionFinished(
247 ScriptInjection
* injection
) {
248 ScopedVector
<ScriptInjection
>::iterator iter
=
249 std::find(running_injections_
.begin(),
250 running_injections_
.end(),
252 if (iter
!= running_injections_
.end())
253 running_injections_
.erase(iter
);
256 void ScriptInjectionManager::OnUserScriptsUpdated(
257 const std::set
<HostID
>& changed_hosts
,
258 const std::vector
<UserScript
*>& scripts
) {
259 for (ScopedVector
<ScriptInjection
>::iterator iter
=
260 pending_injections_
.begin();
261 iter
!= pending_injections_
.end();) {
262 if (changed_hosts
.count((*iter
)->host_id()) > 0)
263 iter
= pending_injections_
.erase(iter
);
269 void ScriptInjectionManager::RemoveObserver(RFOHelper
* helper
) {
270 for (ScopedVector
<RFOHelper
>::iterator iter
= rfo_helpers_
.begin();
271 iter
!= rfo_helpers_
.end();
273 if (*iter
== helper
) {
274 rfo_helpers_
.erase(iter
);
280 void ScriptInjectionManager::InvalidateForFrame(content::RenderFrame
* frame
) {
281 // If the frame invalidated is the frame being injected into, we need to
283 active_injection_frames_
.erase(frame
);
285 for (ScopedVector
<ScriptInjection
>::iterator iter
=
286 pending_injections_
.begin();
287 iter
!= pending_injections_
.end();) {
288 if ((*iter
)->render_frame() == frame
)
289 iter
= pending_injections_
.erase(iter
);
294 frame_statuses_
.erase(frame
);
297 void ScriptInjectionManager::StartInjectScripts(
298 content::RenderFrame
* frame
,
299 UserScript::RunLocation run_location
) {
300 FrameStatusMap::iterator iter
= frame_statuses_
.find(frame
);
301 // We also don't execute if we detect that the run location is somehow out of
302 // order. This can happen if:
303 // - The first run location reported for the frame isn't DOCUMENT_START, or
304 // - The run location reported doesn't immediately follow the previous
305 // reported run location.
306 // We don't want to run because extensions may have requirements that scripts
307 // running in an earlier run location have run by the time a later script
308 // runs. Better to just not run.
309 // Note that we check run_location > NextRunLocation() in the second clause
310 // (as opposed to !=) because earlier signals (like DidCreateDocumentElement)
311 // can happen multiple times, so we can receive earlier/equal run locations.
312 if ((iter
== frame_statuses_
.end() &&
313 run_location
!= UserScript::DOCUMENT_START
) ||
314 (iter
!= frame_statuses_
.end() &&
315 run_location
> NextRunLocation(iter
->second
))) {
316 // We also invalidate the frame, because the run order of pending injections
318 InvalidateForFrame(frame
);
320 } else if (iter
!= frame_statuses_
.end() && iter
->second
>= run_location
) {
321 // Certain run location signals (like DidCreateDocumentElement) can happen
322 // multiple times. Ignore the subsequent signals.
326 // Otherwise, all is right in the world, and we can get on with the
328 frame_statuses_
[frame
] = run_location
;
329 InjectScripts(frame
, run_location
);
332 void ScriptInjectionManager::InjectScripts(
333 content::RenderFrame
* frame
,
334 UserScript::RunLocation run_location
) {
335 // Find any injections that want to run on the given frame.
336 ScopedVector
<ScriptInjection
> frame_injections
;
337 for (ScopedVector
<ScriptInjection
>::iterator iter
=
338 pending_injections_
.begin();
339 iter
!= pending_injections_
.end();) {
340 if ((*iter
)->render_frame() == frame
) {
341 frame_injections
.push_back(*iter
);
342 iter
= pending_injections_
.weak_erase(iter
);
348 // Add any injections for user scripts.
349 int tab_id
= ExtensionFrameHelper::Get(frame
)->tab_id();
350 user_script_set_manager_
->GetAllInjections(
351 &frame_injections
, frame
, tab_id
, run_location
);
353 // Note that we are running in |frame|.
354 active_injection_frames_
.insert(frame
);
356 ScriptsRunInfo
scripts_run_info(frame
, run_location
);
357 std::vector
<ScriptInjection
*> released_injections
;
358 frame_injections
.release(&released_injections
);
359 for (ScriptInjection
* injection
: released_injections
) {
360 // It's possible for the frame to be invalidated in the course of injection
361 // (if a script removes its own frame, for example). If this happens, abort.
362 if (!active_injection_frames_
.count(frame
))
364 TryToInject(make_scoped_ptr(injection
), run_location
, &scripts_run_info
);
367 // We are done running in the frame.
368 active_injection_frames_
.erase(frame
);
370 scripts_run_info
.LogRun();
373 void ScriptInjectionManager::TryToInject(
374 scoped_ptr
<ScriptInjection
> injection
,
375 UserScript::RunLocation run_location
,
376 ScriptsRunInfo
* scripts_run_info
) {
377 // Try to inject the script. If the injection is waiting (i.e., for
378 // permission), add it to the list of pending injections. If the injection
379 // has blocked, add it to the list of running injections.
380 // The Unretained below is safe because this object owns all the
381 // ScriptInjections, so is guaranteed to outlive them.
382 switch (injection
->TryToInject(
385 base::Bind(&ScriptInjectionManager::OnInjectionFinished
,
386 base::Unretained(this)))) {
387 case ScriptInjection::INJECTION_WAITING
:
388 pending_injections_
.push_back(injection
.Pass());
390 case ScriptInjection::INJECTION_BLOCKED
:
391 running_injections_
.push_back(injection
.Pass());
393 case ScriptInjection::INJECTION_FINISHED
:
398 void ScriptInjectionManager::HandleExecuteCode(
399 const ExtensionMsg_ExecuteCode_Params
& params
,
400 content::RenderFrame
* render_frame
) {
401 scoped_ptr
<const InjectionHost
> injection_host
;
402 if (params
.host_id
.type() == HostID::EXTENSIONS
) {
403 injection_host
= ExtensionInjectionHost::Create(params
.host_id
.id());
406 } else if (params
.host_id
.type() == HostID::WEBUI
) {
407 injection_host
.reset(
408 new WebUIInjectionHost(params
.host_id
));
411 scoped_ptr
<ScriptInjection
> injection(new ScriptInjection(
412 scoped_ptr
<ScriptInjector
>(
413 new ProgrammaticScriptInjector(params
, render_frame
)),
415 injection_host
.Pass(),
416 static_cast<UserScript::RunLocation
>(params
.run_at
)));
418 FrameStatusMap::const_iterator iter
= frame_statuses_
.find(render_frame
);
419 UserScript::RunLocation run_location
=
420 iter
== frame_statuses_
.end() ? UserScript::UNDEFINED
: iter
->second
;
422 ScriptsRunInfo
scripts_run_info(render_frame
, run_location
);
423 TryToInject(injection
.Pass(), run_location
, &scripts_run_info
);
426 void ScriptInjectionManager::HandleExecuteDeclarativeScript(
427 content::RenderFrame
* render_frame
,
429 const ExtensionId
& extension_id
,
432 scoped_ptr
<ScriptInjection
> injection
=
433 user_script_set_manager_
->GetInjectionForDeclarativeScript(
439 if (injection
.get()) {
440 ScriptsRunInfo
scripts_run_info(render_frame
, UserScript::BROWSER_DRIVEN
);
441 // TODO(markdittmer): Use return value of TryToInject for error handling.
442 TryToInject(injection
.Pass(),
443 UserScript::BROWSER_DRIVEN
,
446 scripts_run_info
.LogRun();
450 void ScriptInjectionManager::HandlePermitScriptInjection(int64 request_id
) {
451 ScopedVector
<ScriptInjection
>::iterator iter
=
452 pending_injections_
.begin();
453 for (; iter
!= pending_injections_
.end(); ++iter
) {
454 if ((*iter
)->request_id() == request_id
) {
455 DCHECK((*iter
)->host_id().type() == HostID::EXTENSIONS
);
459 if (iter
== pending_injections_
.end())
462 // At this point, because the request is present in pending_injections_, we
463 // know that this is the same page that issued the request (otherwise,
464 // RFOHelper's DidStartProvisionalLoad callback would have caused it to be
467 scoped_ptr
<ScriptInjection
> injection(*iter
);
468 pending_injections_
.weak_erase(iter
);
470 ScriptsRunInfo
scripts_run_info(injection
->render_frame(),
471 UserScript::RUN_DEFERRED
);
472 ScriptInjection::InjectionResult res
= injection
->OnPermissionGranted(
474 if (res
== ScriptInjection::INJECTION_BLOCKED
)
475 running_injections_
.push_back(injection
.Pass());
476 scripts_run_info
.LogRun();
479 } // namespace extensions