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 "extensions/common/extension_messages.h"
16 #include "extensions/common/host_id.h"
17 #include "extensions/renderer/dom_activity_logger.h"
18 #include "extensions/renderer/extension_groups.h"
19 #include "extensions/renderer/extensions_renderer_client.h"
20 #include "extensions/renderer/script_injection_callback.h"
21 #include "extensions/renderer/scripts_run_info.h"
22 #include "third_party/WebKit/public/platform/WebString.h"
23 #include "third_party/WebKit/public/web/WebDocument.h"
24 #include "third_party/WebKit/public/web/WebLocalFrame.h"
25 #include "third_party/WebKit/public/web/WebScriptSource.h"
26 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
29 namespace extensions
{
33 using IsolatedWorldMap
= std::map
<std::string
, int>;
34 base::LazyInstance
<IsolatedWorldMap
> g_isolated_worlds
=
35 LAZY_INSTANCE_INITIALIZER
;
37 const int64 kInvalidRequestId
= -1;
39 // The id of the next pending injection.
40 int64 g_next_pending_id
= 0;
42 // Gets the isolated world ID to use for the given |injection_host|
43 // in the given |frame|. If no isolated world has been created for that
44 // |injection_host| one will be created and initialized.
45 int GetIsolatedWorldIdForInstance(const InjectionHost
* injection_host
,
46 blink::WebLocalFrame
* frame
) {
47 static int g_next_isolated_world_id
=
48 ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
50 IsolatedWorldMap
& isolated_worlds
= g_isolated_worlds
.Get();
53 const std::string
& key
= injection_host
->id().id();
54 IsolatedWorldMap::iterator iter
= isolated_worlds
.find(key
);
55 if (iter
!= isolated_worlds
.end()) {
58 id
= g_next_isolated_world_id
++;
59 // This map will tend to pile up over time, but realistically, you're never
60 // going to have enough injection hosts for it to matter.
61 isolated_worlds
[key
] = id
;
64 // We need to set the isolated world origin and CSP even if it's not a new
65 // world since these are stored per frame, and we might not have used this
66 // isolated world in this frame before.
67 frame
->setIsolatedWorldSecurityOrigin(
68 id
, blink::WebSecurityOrigin::create(injection_host
->url()));
69 frame
->setIsolatedWorldContentSecurityPolicy(
70 id
, blink::WebString::fromUTF8(
71 injection_host
->GetContentSecurityPolicy()));
72 frame
->setIsolatedWorldHumanReadableName(
73 id
, blink::WebString::fromUTF8(injection_host
->name()));
81 std::string
ScriptInjection::GetHostIdForIsolatedWorld(int isolated_world_id
) {
82 const IsolatedWorldMap
& isolated_worlds
= g_isolated_worlds
.Get();
84 for (const auto& iter
: isolated_worlds
) {
85 if (iter
.second
== isolated_world_id
)
92 void ScriptInjection::RemoveIsolatedWorld(const std::string
& host_id
) {
93 g_isolated_worlds
.Get().erase(host_id
);
96 ScriptInjection::ScriptInjection(
97 scoped_ptr
<ScriptInjector
> injector
,
98 content::RenderFrame
* render_frame
,
99 scoped_ptr
<const InjectionHost
> injection_host
,
100 UserScript::RunLocation run_location
,
102 : injector_(injector
.Pass()),
103 render_frame_(render_frame
),
104 injection_host_(injection_host
.Pass()),
105 run_location_(run_location
),
107 request_id_(kInvalidRequestId
),
109 did_inject_js_(false),
110 weak_ptr_factory_(this) {
111 CHECK(injection_host_
.get());
114 ScriptInjection::~ScriptInjection() {
116 injector_
->OnWillNotInject(ScriptInjector::WONT_INJECT
);
119 ScriptInjection::InjectionResult
ScriptInjection::TryToInject(
120 UserScript::RunLocation current_location
,
121 ScriptsRunInfo
* scripts_run_info
,
122 const CompletionCallback
& async_completion_callback
) {
123 if (current_location
< run_location_
)
124 return INJECTION_WAITING
; // Wait for the right location.
126 if (request_id_
!= kInvalidRequestId
) {
127 // We're waiting for permission right now, try again later.
128 return INJECTION_WAITING
;
131 if (!injection_host_
) {
132 NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED
);
133 return INJECTION_FINISHED
; // We're done.
136 blink::WebLocalFrame
* web_frame
= render_frame_
->GetWebFrame();
137 switch (injector_
->CanExecuteOnFrame(
138 injection_host_
.get(), web_frame
, tab_id_
)) {
139 case PermissionsData::ACCESS_DENIED
:
140 NotifyWillNotInject(ScriptInjector::NOT_ALLOWED
);
141 return INJECTION_FINISHED
; // We're done.
142 case PermissionsData::ACCESS_WITHHELD
:
143 // Note: we don't consider ACCESS_WITHHELD for child frames because there
144 // is nowhere to surface a request for a child frame.
145 // TODO(devlin): We should ask for permission somehow. crbug.com/491402.
146 if (web_frame
->parent()) {
147 NotifyWillNotInject(ScriptInjector::NOT_ALLOWED
);
148 return INJECTION_FINISHED
;
151 SendInjectionMessage(true /* request permission */);
152 return INJECTION_WAITING
; // Wait around for permission.
153 case PermissionsData::ACCESS_ALLOWED
:
154 InjectionResult result
= Inject(scripts_run_info
);
155 // If the injection is blocked, we need to set the manager so we can
156 // notify it upon completion.
157 if (result
== INJECTION_BLOCKED
)
158 async_completion_callback_
= async_completion_callback
;
163 return INJECTION_FINISHED
;
166 ScriptInjection::InjectionResult
ScriptInjection::OnPermissionGranted(
167 ScriptsRunInfo
* scripts_run_info
) {
168 if (!injection_host_
) {
169 NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED
);
170 return INJECTION_FINISHED
;
173 return Inject(scripts_run_info
);
176 void ScriptInjection::OnHostRemoved() {
177 injection_host_
.reset(nullptr);
180 void ScriptInjection::SendInjectionMessage(bool request_permission
) {
181 // If we are just notifying the browser of the injection, then send an
182 // invalid request (which is treated like a notification).
183 request_id_
= request_permission
? g_next_pending_id
++ : kInvalidRequestId
;
184 render_frame_
->Send(new ExtensionHostMsg_RequestScriptInjectionPermission(
185 render_frame_
->GetRoutingID(),
187 injector_
->script_type(),
191 void ScriptInjection::NotifyWillNotInject(
192 ScriptInjector::InjectFailureReason reason
) {
194 injector_
->OnWillNotInject(reason
);
197 ScriptInjection::InjectionResult
ScriptInjection::Inject(
198 ScriptsRunInfo
* scripts_run_info
) {
199 DCHECK(injection_host_
);
200 DCHECK(scripts_run_info
);
203 if (injection_host_
->ShouldNotifyBrowserOfInjection())
204 SendInjectionMessage(false /* don't request permission */);
206 bool should_inject_js
= injector_
->ShouldInjectJs(run_location_
);
207 bool should_inject_css
= injector_
->ShouldInjectCss(run_location_
);
208 DCHECK(should_inject_js
|| should_inject_css
);
210 if (should_inject_js
)
212 if (should_inject_css
)
215 complete_
= did_inject_js_
|| !should_inject_js
;
217 injector_
->GetRunInfo(scripts_run_info
, run_location_
);
220 injector_
->OnInjectionComplete(execution_result_
.Pass(), run_location_
);
222 ++scripts_run_info
->num_blocking_js
;
224 return complete_
? INJECTION_FINISHED
: INJECTION_BLOCKED
;
227 void ScriptInjection::InjectJs() {
228 DCHECK(!did_inject_js_
);
229 blink::WebLocalFrame
* web_frame
= render_frame_
->GetWebFrame();
230 std::vector
<blink::WebScriptSource
> sources
=
231 injector_
->GetJsSources(run_location_
);
232 bool in_main_world
= injector_
->ShouldExecuteInMainWorld();
233 int world_id
= in_main_world
234 ? DOMActivityLogger::kMainWorldId
235 : GetIsolatedWorldIdForInstance(injection_host_
.get(),
237 bool is_user_gesture
= injector_
->IsUserGesture();
239 scoped_ptr
<blink::WebScriptExecutionCallback
> callback(
240 new ScriptInjectionCallback(
241 base::Bind(&ScriptInjection::OnJsInjectionCompleted
,
242 weak_ptr_factory_
.GetWeakPtr())));
244 base::ElapsedTimer exec_timer
;
245 if (injection_host_
->id().type() == HostID::EXTENSIONS
)
246 DOMActivityLogger::AttachToWorld(world_id
, injection_host_
->id().id());
248 // We only inject in the main world for javascript: urls.
249 DCHECK_EQ(1u, sources
.size());
251 web_frame
->requestExecuteScriptAndReturnValue(sources
.front(),
255 web_frame
->requestExecuteScriptInIsolatedWorld(
259 EXTENSION_GROUP_CONTENT_SCRIPTS
,
264 if (injection_host_
->id().type() == HostID::EXTENSIONS
)
265 UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer
.Elapsed());
268 void ScriptInjection::OnJsInjectionCompleted(
269 const blink::WebVector
<v8::Local
<v8::Value
> >& results
) {
270 DCHECK(!did_inject_js_
);
272 bool expects_results
= injector_
->ExpectsResults();
273 if (expects_results
) {
274 if (!results
.isEmpty() && !results
[0].IsEmpty()) {
275 // Right now, we only support returning single results (per frame).
276 scoped_ptr
<content::V8ValueConverter
> v8_converter(
277 content::V8ValueConverter::create());
278 // It's safe to always use the main world context when converting
279 // here. V8ValueConverterImpl shouldn't actually care about the
280 // context scope, and it switches to v8::Object's creation context
282 v8::Local
<v8::Context
> context
=
283 render_frame_
->GetWebFrame()->mainWorldScriptContext();
284 execution_result_
.reset(v8_converter
->FromV8Value(results
[0], context
));
286 if (!execution_result_
.get())
287 execution_result_
= base::Value::CreateNullValue();
289 did_inject_js_
= true;
291 // If |async_completion_callback_| is set, it means the script finished
292 // asynchronously, and we should run it.
293 if (!async_completion_callback_
.is_null()) {
294 injector_
->OnInjectionComplete(execution_result_
.Pass(), run_location_
);
295 // Warning: this object can be destroyed after this line!
296 async_completion_callback_
.Run(this);
300 void ScriptInjection::InjectCss() {
301 std::vector
<std::string
> css_sources
=
302 injector_
->GetCssSources(run_location_
);
303 blink::WebLocalFrame
* web_frame
= render_frame_
->GetWebFrame();
304 for (const std::string
& css
: css_sources
)
305 web_frame
->document().insertStyleSheet(blink::WebString::fromUTF8(css
));
308 } // namespace extensions