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_context_set.h"
7 #include "base/message_loop/message_loop.h"
8 #include "content/public/common/url_constants.h"
9 #include "content/public/renderer/render_frame.h"
10 #include "extensions/common/extension.h"
11 #include "extensions/renderer/extension_groups.h"
12 #include "extensions/renderer/script_context.h"
13 #include "extensions/renderer/script_injection.h"
14 #include "third_party/WebKit/public/web/WebDocument.h"
15 #include "third_party/WebKit/public/web/WebLocalFrame.h"
16 #include "v8/include/v8.h"
18 namespace extensions
{
21 // There is only ever one instance of the ScriptContextSet.
22 ScriptContextSet
* g_context_set
= nullptr;
25 ScriptContextSet::ScriptContextSet(ExtensionSet
* extensions
,
26 ExtensionIdSet
* active_extension_ids
)
27 : extensions_(extensions
), active_extension_ids_(active_extension_ids
) {
28 DCHECK(!g_context_set
);
32 ScriptContextSet::~ScriptContextSet() {
33 g_context_set
= nullptr;
36 ScriptContext
* ScriptContextSet::Register(
37 blink::WebLocalFrame
* frame
,
38 const v8::Local
<v8::Context
>& v8_context
,
41 const Extension
* extension
=
42 GetExtensionFromFrameAndWorld(frame
, world_id
, false);
43 const Extension
* effective_extension
=
44 GetExtensionFromFrameAndWorld(frame
, world_id
, true);
46 GURL frame_url
= ScriptContext::GetDataSourceURLForFrame(frame
);
47 Feature::Context context_type
=
48 ClassifyJavaScriptContext(extension
, extension_group
, frame_url
,
49 frame
->document().securityOrigin());
50 Feature::Context effective_context_type
= ClassifyJavaScriptContext(
51 effective_extension
, extension_group
,
52 ScriptContext::GetEffectiveDocumentURL(frame
, frame_url
, true),
53 frame
->document().securityOrigin());
55 ScriptContext
* context
=
56 new ScriptContext(v8_context
, frame
, extension
, context_type
,
57 effective_extension
, effective_context_type
);
58 contexts_
.insert(context
); // takes ownership
62 void ScriptContextSet::Remove(ScriptContext
* context
) {
63 if (contexts_
.erase(context
)) {
64 context
->Invalidate();
65 base::MessageLoop::current()->DeleteSoon(FROM_HERE
, context
);
69 ScriptContext
* ScriptContextSet::GetCurrent() const {
70 v8::Isolate
* isolate
= v8::Isolate::GetCurrent();
71 return isolate
->InContext() ? GetByV8Context(isolate
->GetCurrentContext())
75 ScriptContext
* ScriptContextSet::GetCalling() const {
76 v8::Isolate
* isolate
= v8::Isolate::GetCurrent();
77 v8::Local
<v8::Context
> calling
= isolate
->GetCallingContext();
78 return calling
.IsEmpty() ? nullptr : GetByV8Context(calling
);
81 ScriptContext
* ScriptContextSet::GetByV8Context(
82 const v8::Local
<v8::Context
>& v8_context
) const {
83 for (ScriptContext
* script_context
: contexts_
) {
84 if (script_context
->v8_context() == v8_context
)
85 return script_context
;
90 ScriptContext
* ScriptContextSet::GetContextByV8Context(
91 const v8::Local
<v8::Context
>& v8_context
) {
92 // g_context_set can be null in unittests.
93 return g_context_set
? g_context_set
->GetByV8Context(v8_context
) : nullptr;
96 void ScriptContextSet::ForEach(
97 const std::string
& extension_id
,
98 content::RenderFrame
* render_frame
,
99 const base::Callback
<void(ScriptContext
*)>& callback
) const {
100 // We copy the context list, because calling into javascript may modify it
101 // out from under us.
102 std::set
<ScriptContext
*> contexts_copy
= contexts_
;
104 for (ScriptContext
* context
: contexts_copy
) {
105 // For the same reason as above, contexts may become invalid while we run.
106 if (!context
->is_valid())
109 if (!extension_id
.empty()) {
110 const Extension
* extension
= context
->extension();
111 if (!extension
|| (extension_id
!= extension
->id()))
115 content::RenderFrame
* context_render_frame
= context
->GetRenderFrame();
116 if (!context_render_frame
)
119 if (render_frame
&& render_frame
!= context_render_frame
)
122 callback
.Run(context
);
126 std::set
<ScriptContext
*> ScriptContextSet::OnExtensionUnloaded(
127 const std::string
& extension_id
) {
128 std::set
<ScriptContext
*> removed
;
129 ForEach(extension_id
,
130 base::Bind(&ScriptContextSet::DispatchOnUnloadEventAndRemove
,
131 base::Unretained(this), &removed
));
135 const Extension
* ScriptContextSet::GetExtensionFromFrameAndWorld(
136 const blink::WebLocalFrame
* frame
,
138 bool use_effective_url
) {
139 std::string extension_id
;
141 // Isolated worlds (content script).
142 extension_id
= ScriptInjection::GetHostIdForIsolatedWorld(world_id
);
143 } else if (!frame
->document().securityOrigin().isUnique()) {
144 // TODO(kalman): Delete the above check.
145 // Extension pages (chrome-extension:// URLs).
146 GURL frame_url
= ScriptContext::GetDataSourceURLForFrame(frame
);
147 frame_url
= ScriptContext::GetEffectiveDocumentURL(frame
, frame_url
,
149 extension_id
= extensions_
->GetExtensionOrAppIDByURL(frame_url
);
152 // There are conditions where despite a context being associated with an
153 // extension, no extension actually gets found. Ignore "invalid" because CSP
154 // blocks extension page loading by switching the extension ID to "invalid".
155 const Extension
* extension
= extensions_
->GetByID(extension_id
);
156 if (!extension
&& !extension_id
.empty() && extension_id
!= "invalid") {
157 // TODO(kalman): Do something here?
162 Feature::Context
ScriptContextSet::ClassifyJavaScriptContext(
163 const Extension
* extension
,
166 const blink::WebSecurityOrigin
& origin
) {
167 // WARNING: This logic must match ProcessMap::GetContextType, as much as
170 DCHECK_GE(extension_group
, 0);
171 if (extension_group
== EXTENSION_GROUP_CONTENT_SCRIPTS
) {
172 return extension
? // TODO(kalman): when does this happen?
173 Feature::CONTENT_SCRIPT_CONTEXT
174 : Feature::UNSPECIFIED_CONTEXT
;
177 // We have an explicit check for sandboxed pages before checking whether the
178 // extension is active in this process because:
179 // 1. Sandboxed pages run in the same process as regular extension pages, so
180 // the extension is considered active.
181 // 2. ScriptContext creation (which triggers bindings injection) happens
182 // before the SecurityContext is updated with the sandbox flags (after
183 // reading the CSP header), so the caller can't check if the context's
184 // security origin is unique yet.
185 if (ScriptContext::IsSandboxedPage(*extensions_
, url
))
186 return Feature::WEB_PAGE_CONTEXT
;
188 if (extension
&& active_extension_ids_
->count(extension
->id()) > 0) {
189 // |extension| is active in this process, but it could be either a true
190 // extension process or within the extent of a hosted app. In the latter
191 // case this would usually be considered a (blessed) web page context,
192 // unless the extension in question is a component extension, in which case
193 // we cheat and call it blessed.
194 return (extension
->is_hosted_app() &&
195 extension
->location() != Manifest::COMPONENT
)
196 ? Feature::BLESSED_WEB_PAGE_CONTEXT
197 : Feature::BLESSED_EXTENSION_CONTEXT
;
200 // TODO(kalman): This isUnique() check is wrong, it should be performed as
201 // part of ScriptContext::IsSandboxedPage().
202 if (!origin
.isUnique() && extensions_
->ExtensionBindingsAllowed(url
)) {
203 if (!extension
) // TODO(kalman): when does this happen?
204 return Feature::UNSPECIFIED_CONTEXT
;
205 return extension
->is_hosted_app() ? Feature::BLESSED_WEB_PAGE_CONTEXT
206 : Feature::UNBLESSED_EXTENSION_CONTEXT
;
210 return Feature::UNSPECIFIED_CONTEXT
;
212 if (url
.SchemeIs(content::kChromeUIScheme
))
213 return Feature::WEBUI_CONTEXT
;
215 return Feature::WEB_PAGE_CONTEXT
;
218 void ScriptContextSet::DispatchOnUnloadEventAndRemove(
219 std::set
<ScriptContext
*>* out
,
220 ScriptContext
* context
) {
221 context
->DispatchOnUnloadEvent();
222 Remove(context
); // deleted asynchronously
223 out
->insert(context
);
226 } // namespace extensions