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.h"
9 #include "base/lazy_instance.h"
10 #include "base/metrics/histogram.h"
11 #include "base/timer/elapsed_timer.h"
12 #include "base/values.h"
13 #include "content/public/child/v8_value_converter.h"
14 #include "content/public/renderer/render_frame.h"
15 #include "content/public/renderer/render_view.h"
16 #include "extensions/common/extension_messages.h"
17 #include "extensions/common/host_id.h"
18 #include "extensions/common/manifest_handlers/csp_info.h"
19 #include "extensions/renderer/dom_activity_logger.h"
20 #include "extensions/renderer/extension_groups.h"
21 #include "extensions/renderer/extension_injection_host.h"
22 #include "extensions/renderer/extensions_renderer_client.h"
23 #include "extensions/renderer/script_injection_callback.h"
24 #include "extensions/renderer/scripts_run_info.h"
25 #include "third_party/WebKit/public/platform/WebString.h"
26 #include "third_party/WebKit/public/web/WebDocument.h"
27 #include "third_party/WebKit/public/web/WebLocalFrame.h"
28 #include "third_party/WebKit/public/web/WebScopedUserGesture.h"
29 #include "third_party/WebKit/public/web/WebScriptSource.h"
30 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
33 namespace extensions
{
37 using IsolatedWorldMap
= std::map
<std::string
, int>;
38 base::LazyInstance
<IsolatedWorldMap
> g_isolated_worlds
=
39 LAZY_INSTANCE_INITIALIZER
;
41 const int64 kInvalidRequestId
= -1;
43 // The id of the next pending injection.
44 int64 g_next_pending_id
= 0;
46 // Gets the isolated world ID to use for the given |injection_host|
47 // in the given |frame|. If no isolated world has been created for that
48 // |injection_host| one will be created and initialized.
49 int GetIsolatedWorldIdForInstance(const InjectionHost
* injection_host
,
50 blink::WebLocalFrame
* frame
) {
51 static int g_next_isolated_world_id
=
52 ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
54 IsolatedWorldMap
& isolated_worlds
= g_isolated_worlds
.Get();
57 const std::string
& key
= injection_host
->id().id();
58 IsolatedWorldMap::iterator iter
= isolated_worlds
.find(key
);
59 if (iter
!= isolated_worlds
.end()) {
62 id
= g_next_isolated_world_id
++;
63 // This map will tend to pile up over time, but realistically, you're never
64 // going to have enough injection hosts for it to matter.
65 isolated_worlds
[key
] = id
;
68 // We need to set the isolated world origin and CSP even if it's not a new
69 // world since these are stored per frame, and we might not have used this
70 // isolated world in this frame before.
71 frame
->setIsolatedWorldSecurityOrigin(
72 id
, blink::WebSecurityOrigin::create(injection_host
->url()));
73 frame
->setIsolatedWorldContentSecurityPolicy(
74 id
, blink::WebString::fromUTF8(
75 injection_host
->GetContentSecurityPolicy()));
76 frame
->setIsolatedWorldHumanReadableName(
77 id
, blink::WebString::fromUTF8(injection_host
->name()));
85 std::string
ScriptInjection::GetHostIdForIsolatedWorld(int isolated_world_id
) {
86 const IsolatedWorldMap
& isolated_worlds
= g_isolated_worlds
.Get();
88 for (const auto& iter
: isolated_worlds
) {
89 if (iter
.second
== isolated_world_id
)
96 void ScriptInjection::RemoveIsolatedWorld(const std::string
& host_id
) {
97 g_isolated_worlds
.Get().erase(host_id
);
100 ScriptInjection::ScriptInjection(
101 scoped_ptr
<ScriptInjector
> injector
,
102 content::RenderFrame
* render_frame
,
103 scoped_ptr
<const InjectionHost
> injection_host
,
104 UserScript::RunLocation run_location
,
106 : injector_(injector
.Pass()),
107 render_frame_(render_frame
),
108 injection_host_(injection_host
.Pass()),
109 run_location_(run_location
),
111 request_id_(kInvalidRequestId
),
113 did_inject_js_(false),
114 weak_ptr_factory_(this) {
115 CHECK(injection_host_
.get());
118 ScriptInjection::~ScriptInjection() {
120 injector_
->OnWillNotInject(ScriptInjector::WONT_INJECT
);
123 ScriptInjection::InjectionResult
ScriptInjection::TryToInject(
124 UserScript::RunLocation current_location
,
125 ScriptsRunInfo
* scripts_run_info
,
126 const CompletionCallback
& async_completion_callback
) {
127 if (current_location
< run_location_
)
128 return INJECTION_WAITING
; // Wait for the right location.
130 if (request_id_
!= kInvalidRequestId
) {
131 // We're waiting for permission right now, try again later.
132 return INJECTION_WAITING
;
135 if (!injection_host_
) {
136 NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED
);
137 return INJECTION_FINISHED
; // We're done.
140 blink::WebLocalFrame
* web_frame
= render_frame_
->GetWebFrame();
141 switch (injector_
->CanExecuteOnFrame(
142 injection_host_
.get(), web_frame
, tab_id_
)) {
143 case PermissionsData::ACCESS_DENIED
:
144 NotifyWillNotInject(ScriptInjector::NOT_ALLOWED
);
145 return INJECTION_FINISHED
; // We're done.
146 case PermissionsData::ACCESS_WITHHELD
:
147 // Note: we don't consider ACCESS_WITHHELD for child frames because there
148 // is nowhere to surface a request for a child frame.
149 // TODO(devlin): We should ask for permission somehow. crbug.com/491402.
150 if (web_frame
->parent()) {
151 NotifyWillNotInject(ScriptInjector::NOT_ALLOWED
);
152 return INJECTION_FINISHED
;
155 SendInjectionMessage(true /* request permission */);
156 return INJECTION_WAITING
; // Wait around for permission.
157 case PermissionsData::ACCESS_ALLOWED
:
158 InjectionResult result
= Inject(scripts_run_info
);
159 // If the injection is blocked, we need to set the manager so we can
160 // notify it upon completion.
161 if (result
== INJECTION_BLOCKED
)
162 async_completion_callback_
= async_completion_callback
;
167 return INJECTION_FINISHED
;
170 ScriptInjection::InjectionResult
ScriptInjection::OnPermissionGranted(
171 ScriptsRunInfo
* scripts_run_info
) {
172 if (!injection_host_
) {
173 NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED
);
174 return INJECTION_FINISHED
;
177 return Inject(scripts_run_info
);
180 void ScriptInjection::OnHostRemoved() {
181 injection_host_
.reset(nullptr);
184 void ScriptInjection::SendInjectionMessage(bool request_permission
) {
185 // TODO(devlin): This should just use the RenderFrame.
186 content::RenderView
* render_view
= render_frame_
->GetRenderView();
188 // If we are just notifying the browser of the injection, then send an
189 // invalid request (which is treated like a notification).
190 request_id_
= request_permission
? g_next_pending_id
++ : kInvalidRequestId
;
191 render_view
->Send(new ExtensionHostMsg_RequestScriptInjectionPermission(
192 render_view
->GetRoutingID(),
194 injector_
->script_type(),
198 void ScriptInjection::NotifyWillNotInject(
199 ScriptInjector::InjectFailureReason reason
) {
201 injector_
->OnWillNotInject(reason
);
204 ScriptInjection::InjectionResult
ScriptInjection::Inject(
205 ScriptsRunInfo
* scripts_run_info
) {
206 DCHECK(injection_host_
);
207 DCHECK(scripts_run_info
);
210 if (injection_host_
->ShouldNotifyBrowserOfInjection())
211 SendInjectionMessage(false /* don't request permission */);
213 bool should_inject_js
= injector_
->ShouldInjectJs(run_location_
);
214 bool should_inject_css
= injector_
->ShouldInjectCss(run_location_
);
215 DCHECK(should_inject_js
|| should_inject_css
);
217 if (should_inject_js
)
219 if (should_inject_css
)
222 complete_
= did_inject_js_
|| !should_inject_js
;
224 injector_
->GetRunInfo(scripts_run_info
, run_location_
);
227 injector_
->OnInjectionComplete(execution_result_
.Pass(), run_location_
);
229 ++scripts_run_info
->num_blocking_js
;
231 return complete_
? INJECTION_FINISHED
: INJECTION_BLOCKED
;
234 void ScriptInjection::InjectJs() {
235 DCHECK(!did_inject_js_
);
236 blink::WebLocalFrame
* web_frame
= render_frame_
->GetWebFrame();
237 std::vector
<blink::WebScriptSource
> sources
=
238 injector_
->GetJsSources(run_location_
);
239 bool in_main_world
= injector_
->ShouldExecuteInMainWorld();
240 int world_id
= in_main_world
241 ? DOMActivityLogger::kMainWorldId
242 : GetIsolatedWorldIdForInstance(injection_host_
.get(),
244 bool is_user_gesture
= injector_
->IsUserGesture();
246 scoped_ptr
<blink::WebScriptExecutionCallback
> callback(
247 new ScriptInjectionCallback(
248 base::Bind(&ScriptInjection::OnJsInjectionCompleted
,
249 weak_ptr_factory_
.GetWeakPtr())));
251 base::ElapsedTimer exec_timer
;
252 if (injection_host_
->id().type() == HostID::EXTENSIONS
)
253 DOMActivityLogger::AttachToWorld(world_id
, injection_host_
->id().id());
255 // We only inject in the main world for javascript: urls.
256 DCHECK_EQ(1u, sources
.size());
258 web_frame
->requestExecuteScriptAndReturnValue(sources
.front(),
262 web_frame
->requestExecuteScriptInIsolatedWorld(
266 EXTENSION_GROUP_CONTENT_SCRIPTS
,
271 if (injection_host_
->id().type() == HostID::EXTENSIONS
)
272 UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer
.Elapsed());
275 void ScriptInjection::OnJsInjectionCompleted(
276 const blink::WebVector
<v8::Local
<v8::Value
> >& results
) {
277 DCHECK(!did_inject_js_
);
279 bool expects_results
= injector_
->ExpectsResults();
280 if (expects_results
) {
281 if (!results
.isEmpty() && !results
[0].IsEmpty()) {
282 // Right now, we only support returning single results (per frame).
283 scoped_ptr
<content::V8ValueConverter
> v8_converter(
284 content::V8ValueConverter::create());
285 // It's safe to always use the main world context when converting
286 // here. V8ValueConverterImpl shouldn't actually care about the
287 // context scope, and it switches to v8::Object's creation context
289 v8::Local
<v8::Context
> context
=
290 render_frame_
->GetWebFrame()->mainWorldScriptContext();
291 execution_result_
.reset(v8_converter
->FromV8Value(results
[0], context
));
293 if (!execution_result_
.get())
294 execution_result_
= base::Value::CreateNullValue();
296 did_inject_js_
= true;
298 // If |async_completion_callback_| is set, it means the script finished
299 // asynchronously, and we should run it.
300 if (!async_completion_callback_
.is_null()) {
301 injector_
->OnInjectionComplete(execution_result_
.Pass(), run_location_
);
302 // Warning: this object can be destroyed after this line!
303 async_completion_callback_
.Run(this);
307 void ScriptInjection::InjectCss() {
308 std::vector
<std::string
> css_sources
=
309 injector_
->GetCssSources(run_location_
);
310 blink::WebLocalFrame
* web_frame
= render_frame_
->GetWebFrame();
311 for (const std::string
& css
: css_sources
)
312 web_frame
->document().insertStyleSheet(blink::WebString::fromUTF8(css
));
315 } // namespace extensions