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_view.h"
15 #include "extensions/common/extension_messages.h"
16 #include "extensions/common/host_id.h"
17 #include "extensions/common/manifest_handlers/csp_info.h"
18 #include "extensions/renderer/dom_activity_logger.h"
19 #include "extensions/renderer/extension_groups.h"
20 #include "extensions/renderer/extension_injection_host.h"
21 #include "extensions/renderer/extensions_renderer_client.h"
22 #include "extensions/renderer/script_injection_callback.h"
23 #include "extensions/renderer/script_injection_manager.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 // Append all the child frames of |parent_frame| to |frames_vector|.
47 void AppendAllChildFrames(blink::WebFrame
* parent_frame
,
48 std::vector
<blink::WebFrame
*>* frames_vector
) {
50 for (blink::WebFrame
* child_frame
= parent_frame
->firstChild(); child_frame
;
51 child_frame
= child_frame
->nextSibling()) {
52 frames_vector
->push_back(child_frame
);
53 AppendAllChildFrames(child_frame
, frames_vector
);
57 // Gets the isolated world ID to use for the given |injection_host|
58 // in the given |frame|. If no isolated world has been created for that
59 // |injection_host| one will be created and initialized.
60 int GetIsolatedWorldIdForInstance(const InjectionHost
* injection_host
,
61 blink::WebLocalFrame
* frame
) {
62 static int g_next_isolated_world_id
=
63 ExtensionsRendererClient::Get()->GetLowestIsolatedWorldId();
65 IsolatedWorldMap
& isolated_worlds
= g_isolated_worlds
.Get();
68 const std::string
& key
= injection_host
->id().id();
69 IsolatedWorldMap::iterator iter
= isolated_worlds
.find(key
);
70 if (iter
!= isolated_worlds
.end()) {
73 id
= g_next_isolated_world_id
++;
74 // This map will tend to pile up over time, but realistically, you're never
75 // going to have enough injection hosts for it to matter.
76 isolated_worlds
[key
] = id
;
79 // We need to set the isolated world origin and CSP even if it's not a new
80 // world since these are stored per frame, and we might not have used this
81 // isolated world in this frame before.
82 frame
->setIsolatedWorldSecurityOrigin(
83 id
, blink::WebSecurityOrigin::create(injection_host
->url()));
84 frame
->setIsolatedWorldContentSecurityPolicy(
85 id
, blink::WebString::fromUTF8(
86 injection_host
->GetContentSecurityPolicy()));
87 frame
->setIsolatedWorldHumanReadableName(
88 id
, blink::WebString::fromUTF8(injection_host
->name()));
96 std::string
ScriptInjection::GetHostIdForIsolatedWorld(int isolated_world_id
) {
97 const IsolatedWorldMap
& isolated_worlds
= g_isolated_worlds
.Get();
99 for (const auto& iter
: isolated_worlds
) {
100 if (iter
.second
== isolated_world_id
)
103 return std::string();
107 void ScriptInjection::RemoveIsolatedWorld(const std::string
& host_id
) {
108 g_isolated_worlds
.Get().erase(host_id
);
111 ScriptInjection::ScriptInjection(
112 scoped_ptr
<ScriptInjector
> injector
,
113 blink::WebLocalFrame
* web_frame
,
114 scoped_ptr
<const InjectionHost
> injection_host
,
115 UserScript::RunLocation run_location
,
117 : injector_(injector
.Pass()),
118 web_frame_(web_frame
),
119 injection_host_(injection_host
.Pass()),
120 run_location_(run_location
),
122 request_id_(kInvalidRequestId
),
125 execution_results_(new base::ListValue()),
126 all_injections_started_(false),
127 script_injection_manager_(nullptr) {
128 CHECK(injection_host_
.get());
131 ScriptInjection::~ScriptInjection() {
133 injector_
->OnWillNotInject(ScriptInjector::WONT_INJECT
);
136 ScriptInjection::InjectionResult
ScriptInjection::TryToInject(
137 UserScript::RunLocation current_location
,
138 ScriptsRunInfo
* scripts_run_info
,
139 ScriptInjectionManager
* manager
) {
140 if (current_location
< run_location_
)
141 return INJECTION_WAITING
; // Wait for the right location.
143 if (request_id_
!= kInvalidRequestId
) {
144 // We're waiting for permission right now, try again later.
145 return INJECTION_WAITING
;
148 if (!injection_host_
) {
149 NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED
);
150 return INJECTION_FINISHED
; // We're done.
153 switch (injector_
->CanExecuteOnFrame(
154 injection_host_
.get(), web_frame_
, tab_id_
,
155 web_frame_
->top()->document().url())) {
156 case PermissionsData::ACCESS_DENIED
:
157 NotifyWillNotInject(ScriptInjector::NOT_ALLOWED
);
158 return INJECTION_FINISHED
; // We're done.
159 case PermissionsData::ACCESS_WITHHELD
:
160 SendInjectionMessage(true /* request permission */);
161 return INJECTION_WAITING
; // Wait around for permission.
162 case PermissionsData::ACCESS_ALLOWED
:
163 InjectionResult result
= Inject(scripts_run_info
);
164 // If the injection is blocked, we need to set the manager so we can
165 // notify it upon completion.
166 if (result
== INJECTION_BLOCKED
)
167 script_injection_manager_
= manager
;
172 return INJECTION_FINISHED
;
175 ScriptInjection::InjectionResult
ScriptInjection::OnPermissionGranted(
176 ScriptsRunInfo
* scripts_run_info
) {
177 if (!injection_host_
) {
178 NotifyWillNotInject(ScriptInjector::EXTENSION_REMOVED
);
179 return INJECTION_FINISHED
;
182 return Inject(scripts_run_info
);
185 void ScriptInjection::OnHostRemoved() {
186 injection_host_
.reset(nullptr);
189 void ScriptInjection::SendInjectionMessage(bool request_permission
) {
190 content::RenderView
* render_view
=
191 content::RenderView::FromWebView(web_frame()->top()->view());
193 // If we are just notifying the browser of the injection, then send an
194 // invalid request (which is treated like a notification).
195 request_id_
= request_permission
? g_next_pending_id
++ : kInvalidRequestId
;
196 render_view
->Send(new ExtensionHostMsg_RequestScriptInjectionPermission(
197 render_view
->GetRoutingID(),
199 injector_
->script_type(),
203 void ScriptInjection::NotifyWillNotInject(
204 ScriptInjector::InjectFailureReason reason
) {
206 injector_
->OnWillNotInject(reason
);
209 ScriptInjection::InjectionResult
ScriptInjection::Inject(
210 ScriptsRunInfo
* scripts_run_info
) {
211 DCHECK(injection_host_
);
212 DCHECK(scripts_run_info
);
215 if (injection_host_
->ShouldNotifyBrowserOfInjection())
216 SendInjectionMessage(false /* don't request permission */);
218 std::vector
<blink::WebFrame
*> frame_vector
;
219 frame_vector
.push_back(web_frame_
);
220 if (injector_
->ShouldExecuteInChildFrames())
221 AppendAllChildFrames(web_frame_
, &frame_vector
);
223 bool inject_js
= injector_
->ShouldInjectJs(run_location_
);
224 bool inject_css
= injector_
->ShouldInjectCss(run_location_
);
225 DCHECK(inject_js
|| inject_css
);
227 GURL top_url
= web_frame_
->top()->document().url();
228 for (std::vector
<blink::WebFrame
*>::iterator iter
= frame_vector
.begin();
229 iter
!= frame_vector
.end();
231 // TODO(dcheng): Unfortunately, the code as written won't work in an OOPI
232 // world. This is just a temporary hack to make things compile.
233 blink::WebLocalFrame
* frame
= (*iter
)->toWebLocalFrame();
235 // We recheck access here in the renderer for extra safety against races
236 // with navigation, but different frames can have different URLs, and the
237 // injection host might only have access to a subset of them.
238 // For child frames, we just skip ones the injection host doesn't have
239 // access to and carry on.
240 // Note: we don't consider ACCESS_WITHHELD because there is nowhere to
241 // surface a request for a child frame.
242 // TODO(rdevlin.cronin): We should ask for permission somehow.
243 if (injector_
->CanExecuteOnFrame(
244 injection_host_
.get(), frame
, tab_id_
, top_url
) ==
245 PermissionsData::ACCESS_DENIED
) {
246 DCHECK(frame
->parent());
255 all_injections_started_
= true;
256 injector_
->GetRunInfo(scripts_run_info
, run_location_
);
257 scripts_run_info
->num_blocking_js
= running_frames_
;
259 return complete_
? INJECTION_FINISHED
: INJECTION_BLOCKED
;
262 void ScriptInjection::InjectJs(blink::WebLocalFrame
* frame
) {
264 std::vector
<blink::WebScriptSource
> sources
=
265 injector_
->GetJsSources(run_location_
);
266 bool in_main_world
= injector_
->ShouldExecuteInMainWorld();
267 int world_id
= in_main_world
268 ? DOMActivityLogger::kMainWorldId
269 : GetIsolatedWorldIdForInstance(injection_host_
.get(),
271 bool is_user_gesture
= injector_
->IsUserGesture();
273 scoped_ptr
<blink::WebScriptExecutionCallback
> callback(
274 new ScriptInjectionCallback(this, frame
));
276 base::ElapsedTimer exec_timer
;
277 if (injection_host_
->id().type() == HostID::EXTENSIONS
)
278 DOMActivityLogger::AttachToWorld(world_id
, injection_host_
->id().id());
280 // We only inject in the main world for javascript: urls.
281 DCHECK_EQ(1u, sources
.size());
283 frame
->requestExecuteScriptAndReturnValue(sources
.front(),
287 frame
->requestExecuteScriptInIsolatedWorld(world_id
,
290 EXTENSION_GROUP_CONTENT_SCRIPTS
,
295 if (injection_host_
->id().type() == HostID::EXTENSIONS
)
296 UMA_HISTOGRAM_TIMES("Extensions.InjectScriptTime", exec_timer
.Elapsed());
299 void ScriptInjection::OnJsInjectionCompleted(
300 blink::WebLocalFrame
* frame
,
301 const blink::WebVector
<v8::Local
<v8::Value
> >& results
) {
302 DCHECK(running_frames_
> 0);
305 bool expects_results
= injector_
->ExpectsResults();
306 if (expects_results
) {
307 scoped_ptr
<base::Value
> result
;
308 if (!results
.isEmpty() && !results
[0].IsEmpty()) {
309 // Right now, we only support returning single results (per frame).
310 scoped_ptr
<content::V8ValueConverter
> v8_converter(
311 content::V8ValueConverter::create());
312 // It's safe to always use the main world context when converting
313 // here. V8ValueConverterImpl shouldn't actually care about the
314 // context scope, and it switches to v8::Object's creation context
316 v8::Local
<v8::Context
> context
= frame
->mainWorldScriptContext();
317 result
.reset(v8_converter
->FromV8Value(results
[0], context
));
320 result
.reset(base::Value::CreateNullValue());
321 // We guarantee that the main frame's result is at the first index, but
322 // any sub frames results do not have guaranteed order.
323 execution_results_
->Insert(
324 frame
== web_frame_
? 0 : execution_results_
->GetSize(),
330 void ScriptInjection::TryToFinish() {
331 if (all_injections_started_
&& running_frames_
== 0) {
333 injector_
->OnInjectionComplete(execution_results_
.Pass(),
336 // This object can be destroyed after next line.
337 if (script_injection_manager_
)
338 script_injection_manager_
->OnInjectionFinished(this);
342 void ScriptInjection::InjectCss(blink::WebLocalFrame
* frame
) {
343 std::vector
<std::string
> css_sources
=
344 injector_
->GetCssSources(run_location_
);
345 for (std::vector
<std::string
>::const_iterator iter
= css_sources
.begin();
346 iter
!= css_sources
.end();
348 frame
->document().insertStyleSheet(blink::WebString::fromUTF8(*iter
));
352 } // namespace extensions