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_frame_helper.h"
19 #include "extensions/renderer/extension_groups.h"
20 #include "extensions/renderer/extensions_renderer_client.h"
21 #include "extensions/renderer/script_injection_callback.h"
22 #include "extensions/renderer/scripts_run_info.h"
23 #include "third_party/WebKit/public/platform/WebString.h"
24 #include "third_party/WebKit/public/web/WebDocument.h"
25 #include "third_party/WebKit/public/web/WebLocalFrame.h"
26 #include "third_party/WebKit/public/web/WebScriptSource.h"
27 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
30 namespace extensions
{
34 using IsolatedWorldMap
= std::map
<std::string
, int>;
35 base::LazyInstance
<IsolatedWorldMap
> g_isolated_worlds
=
36 LAZY_INSTANCE_INITIALIZER
;
38 const int64 kInvalidRequestId
= -1;
40 // The id of the next pending injection.
41 int64 g_next_pending_id
= 0;
43 // Gets the isolated world ID to use for the given |injection_host|
44 // in the given |frame|. If no isolated world has been created for that
45 // |injection_host| one will be created and initialized.
46 int GetIsolatedWorldIdForInstance(const InjectionHost
* injection_host
,
47 blink::WebLocalFrame
* frame
) {
48 static int g_next_isolated_world_id
=
49 ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
51 IsolatedWorldMap
& isolated_worlds
= g_isolated_worlds
.Get();
54 const std::string
& key
= injection_host
->id().id();
55 IsolatedWorldMap::iterator iter
= isolated_worlds
.find(key
);
56 if (iter
!= isolated_worlds
.end()) {
59 id
= g_next_isolated_world_id
++;
60 // This map will tend to pile up over time, but realistically, you're never
61 // going to have enough injection hosts for it to matter.
62 isolated_worlds
[key
] = id
;
65 // We need to set the isolated world origin and CSP even if it's not a new
66 // world since these are stored per frame, and we might not have used this
67 // isolated world in this frame before.
68 frame
->setIsolatedWorldSecurityOrigin(
69 id
, blink::WebSecurityOrigin::create(injection_host
->url()));
70 frame
->setIsolatedWorldContentSecurityPolicy(
71 id
, blink::WebString::fromUTF8(
72 injection_host
->GetContentSecurityPolicy()));
73 frame
->setIsolatedWorldHumanReadableName(
74 id
, blink::WebString::fromUTF8(injection_host
->name()));
82 std::string
ScriptInjection::GetHostIdForIsolatedWorld(int isolated_world_id
) {
83 const IsolatedWorldMap
& isolated_worlds
= g_isolated_worlds
.Get();
85 for (const auto& iter
: isolated_worlds
) {
86 if (iter
.second
== isolated_world_id
)
93 void ScriptInjection::RemoveIsolatedWorld(const std::string
& host_id
) {
94 g_isolated_worlds
.Get().erase(host_id
);
97 ScriptInjection::ScriptInjection(
98 scoped_ptr
<ScriptInjector
> injector
,
99 content::RenderFrame
* render_frame
,
100 scoped_ptr
<const InjectionHost
> injection_host
,
101 UserScript::RunLocation run_location
)
102 : injector_(injector
.Pass()),
103 render_frame_(render_frame
),
104 injection_host_(injection_host
.Pass()),
105 run_location_(run_location
),
106 request_id_(kInvalidRequestId
),
108 did_inject_js_(false),
109 weak_ptr_factory_(this) {
110 CHECK(injection_host_
.get());
113 ScriptInjection::~ScriptInjection() {
115 injector_
->OnWillNotInject(ScriptInjector::WONT_INJECT
);
118 ScriptInjection::InjectionResult
ScriptInjection::TryToInject(
119 UserScript::RunLocation current_location
,
120 ScriptsRunInfo
* scripts_run_info
,
121 const CompletionCallback
& async_completion_callback
) {
122 if (current_location
< run_location_
)
123 return INJECTION_WAITING
; // Wait for the right location.
125 if (request_id_
!= kInvalidRequestId
) {
126 // We're waiting for permission right now, try again later.
127 return INJECTION_WAITING
;
130 if (!injection_host_
) {
131 NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED
);
132 return INJECTION_FINISHED
; // We're done.
135 blink::WebLocalFrame
* web_frame
= render_frame_
->GetWebFrame();
136 switch (injector_
->CanExecuteOnFrame(
137 injection_host_
.get(), web_frame
,
138 ExtensionFrameHelper::Get(render_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 SendInjectionMessage(true /* request permission */);
144 return INJECTION_WAITING
; // Wait around for permission.
145 case PermissionsData::ACCESS_ALLOWED
:
146 InjectionResult result
= Inject(scripts_run_info
);
147 // If the injection is blocked, we need to set the manager so we can
148 // notify it upon completion.
149 if (result
== INJECTION_BLOCKED
)
150 async_completion_callback_
= async_completion_callback
;
155 return INJECTION_FINISHED
;
158 ScriptInjection::InjectionResult
ScriptInjection::OnPermissionGranted(
159 ScriptsRunInfo
* scripts_run_info
) {
160 if (!injection_host_
) {
161 NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED
);
162 return INJECTION_FINISHED
;
165 return Inject(scripts_run_info
);
168 void ScriptInjection::OnHostRemoved() {
169 injection_host_
.reset(nullptr);
172 void ScriptInjection::SendInjectionMessage(bool request_permission
) {
173 // If we are just notifying the browser of the injection, then send an
174 // invalid request (which is treated like a notification).
175 request_id_
= request_permission
? g_next_pending_id
++ : kInvalidRequestId
;
176 render_frame_
->Send(new ExtensionHostMsg_RequestScriptInjectionPermission(
177 render_frame_
->GetRoutingID(),
179 injector_
->script_type(),
183 void ScriptInjection::NotifyWillNotInject(
184 ScriptInjector::InjectFailureReason reason
) {
186 injector_
->OnWillNotInject(reason
);
189 ScriptInjection::InjectionResult
ScriptInjection::Inject(
190 ScriptsRunInfo
* scripts_run_info
) {
191 DCHECK(injection_host_
);
192 DCHECK(scripts_run_info
);
195 if (injection_host_
->ShouldNotifyBrowserOfInjection())
196 SendInjectionMessage(false /* don't request permission */);
198 bool should_inject_js
= injector_
->ShouldInjectJs(run_location_
);
199 bool should_inject_css
= injector_
->ShouldInjectCss(run_location_
);
200 DCHECK(should_inject_js
|| should_inject_css
);
202 if (should_inject_js
)
204 if (should_inject_css
)
207 complete_
= did_inject_js_
|| !should_inject_js
;
209 injector_
->GetRunInfo(scripts_run_info
, run_location_
);
212 injector_
->OnInjectionComplete(execution_result_
.Pass(), run_location_
);
214 ++scripts_run_info
->num_blocking_js
;
216 return complete_
? INJECTION_FINISHED
: INJECTION_BLOCKED
;
219 void ScriptInjection::InjectJs() {
220 DCHECK(!did_inject_js_
);
221 blink::WebLocalFrame
* web_frame
= render_frame_
->GetWebFrame();
222 std::vector
<blink::WebScriptSource
> sources
=
223 injector_
->GetJsSources(run_location_
);
224 bool in_main_world
= injector_
->ShouldExecuteInMainWorld();
225 int world_id
= in_main_world
226 ? DOMActivityLogger::kMainWorldId
227 : GetIsolatedWorldIdForInstance(injection_host_
.get(),
229 bool is_user_gesture
= injector_
->IsUserGesture();
231 scoped_ptr
<blink::WebScriptExecutionCallback
> callback(
232 new ScriptInjectionCallback(
233 base::Bind(&ScriptInjection::OnJsInjectionCompleted
,
234 weak_ptr_factory_
.GetWeakPtr())));
236 base::ElapsedTimer exec_timer
;
237 if (injection_host_
->id().type() == HostID::EXTENSIONS
)
238 DOMActivityLogger::AttachToWorld(world_id
, injection_host_
->id().id());
240 // We only inject in the main world for javascript: urls.
241 DCHECK_EQ(1u, sources
.size());
243 web_frame
->requestExecuteScriptAndReturnValue(sources
.front(),
247 web_frame
->requestExecuteScriptInIsolatedWorld(
251 EXTENSION_GROUP_CONTENT_SCRIPTS
,
256 if (injection_host_
->id().type() == HostID::EXTENSIONS
)
257 UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer
.Elapsed());
260 void ScriptInjection::OnJsInjectionCompleted(
261 const blink::WebVector
<v8::Local
<v8::Value
> >& results
) {
262 DCHECK(!did_inject_js_
);
264 bool expects_results
= injector_
->ExpectsResults();
265 if (expects_results
) {
266 if (!results
.isEmpty() && !results
[0].IsEmpty()) {
267 // Right now, we only support returning single results (per frame).
268 scoped_ptr
<content::V8ValueConverter
> v8_converter(
269 content::V8ValueConverter::create());
270 // It's safe to always use the main world context when converting
271 // here. V8ValueConverterImpl shouldn't actually care about the
272 // context scope, and it switches to v8::Object's creation context
274 v8::Local
<v8::Context
> context
=
275 render_frame_
->GetWebFrame()->mainWorldScriptContext();
276 execution_result_
.reset(v8_converter
->FromV8Value(results
[0], context
));
278 if (!execution_result_
.get())
279 execution_result_
= base::Value::CreateNullValue();
281 did_inject_js_
= true;
283 // If |async_completion_callback_| is set, it means the script finished
284 // asynchronously, and we should run it.
285 if (!async_completion_callback_
.is_null()) {
286 injector_
->OnInjectionComplete(execution_result_
.Pass(), run_location_
);
287 // Warning: this object can be destroyed after this line!
288 async_completion_callback_
.Run(this);
292 void ScriptInjection::InjectCss() {
293 std::vector
<std::string
> css_sources
=
294 injector_
->GetCssSources(run_location_
);
295 blink::WebLocalFrame
* web_frame
= render_frame_
->GetWebFrame();
296 for (const std::string
& css
: css_sources
)
297 web_frame
->document().insertStyleSheet(blink::WebString::fromUTF8(css
));
300 } // namespace extensions