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.
4
5 #include "extensions/renderer/script_context.h"
6
7 #include "base/logging.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/strings/string_split.h"
10 #include "base/strings/string_util.h"
11 #include "base/values.h"
12 #include "content/public/common/url_constants.h"
13 #include "content/public/renderer/render_frame.h"
14 #include "content/public/renderer/render_view.h"
15 #include "content/public/renderer/v8_value_converter.h"
16 #include "extensions/common/extension.h"
17 #include "extensions/common/extension_api.h"
18 #include "extensions/common/extension_urls.h"
19 #include "extensions/common/features/base_feature_provider.h"
20 #include "gin/per_context_data.h"
21 #include "third_party/WebKit/public/web/WebDataSource.h"
22 #include "third_party/WebKit/public/web/WebDocument.h"
23 #include "third_party/WebKit/public/web/WebFrame.h"
24 #include "third_party/WebKit/public/web/WebScopedMicrotaskSuppression.h"
25 #include "third_party/WebKit/public/web/WebSecurityOrigin.h"
26 #include "third_party/WebKit/public/web/WebView.h"
27 #include "v8/include/v8.h"
28
29 using content::V8ValueConverter;
30
31 namespace extensions {
32
33 namespace {
34
GetContextTypeDescriptionString(Feature::Context context_type)35 std::string GetContextTypeDescriptionString(Feature::Context context_type) {
36 switch (context_type) {
37 case Feature::UNSPECIFIED_CONTEXT:
38 return "UNSPECIFIED";
39 case Feature::BLESSED_EXTENSION_CONTEXT:
40 return "BLESSED_EXTENSION";
41 case Feature::UNBLESSED_EXTENSION_CONTEXT:
42 return "UNBLESSED_EXTENSION";
43 case Feature::CONTENT_SCRIPT_CONTEXT:
44 return "CONTENT_SCRIPT";
45 case Feature::WEB_PAGE_CONTEXT:
46 return "WEB_PAGE";
47 case Feature::BLESSED_WEB_PAGE_CONTEXT:
48 return "BLESSED_WEB_PAGE";
49 case Feature::WEBUI_CONTEXT:
50 return "WEBUI";
51 }
52 NOTREACHED();
53 return std::string();
54 }
55
56 } // namespace
57
ScriptContext(const v8::Handle<v8::Context> & v8_context,blink::WebFrame * web_frame,const Extension * extension,Feature::Context context_type,const Extension * effective_extension,Feature::Context effective_context_type)58 ScriptContext::ScriptContext(const v8::Handle<v8::Context>& v8_context,
59 blink::WebFrame* web_frame,
60 const Extension* extension,
61 Feature::Context context_type,
62 const Extension* effective_extension,
63 Feature::Context effective_context_type)
64 : v8_context_(v8_context),
65 web_frame_(web_frame),
66 extension_(extension),
67 context_type_(context_type),
68 effective_extension_(effective_extension),
69 effective_context_type_(effective_context_type),
70 safe_builtins_(this),
71 isolate_(v8_context->GetIsolate()) {
72 VLOG(1) << "Created context:\n"
73 << " extension id: " << GetExtensionID() << "\n"
74 << " frame: " << web_frame_ << "\n"
75 << " URL: " << GetURL() << "\n"
76 << " context type: " << GetContextTypeDescription() << "\n"
77 << " effective extension id: "
78 << (effective_extension_.get() ? effective_extension_->id() : "")
79 << " effective context type: "
80 << GetEffectiveContextTypeDescription();
81 gin::PerContextData::From(v8_context)->set_runner(this);
82 }
83
~ScriptContext()84 ScriptContext::~ScriptContext() {
85 VLOG(1) << "Destroyed context for extension\n"
86 << " extension id: " << GetExtensionID() << "\n"
87 << " effective extension id: "
88 << (effective_extension_.get() ? effective_extension_->id() : "");
89 Invalidate();
90 }
91
Invalidate()92 void ScriptContext::Invalidate() {
93 if (!is_valid())
94 return;
95 if (module_system_)
96 module_system_->Invalidate();
97 web_frame_ = NULL;
98 v8_context_.reset();
99 }
100
GetExtensionID() const101 const std::string& ScriptContext::GetExtensionID() const {
102 return extension_.get() ? extension_->id() : base::EmptyString();
103 }
104
GetRenderView() const105 content::RenderView* ScriptContext::GetRenderView() const {
106 if (web_frame_ && web_frame_->view())
107 return content::RenderView::FromWebView(web_frame_->view());
108 return NULL;
109 }
110
GetRenderFrame() const111 content::RenderFrame* ScriptContext::GetRenderFrame() const {
112 if (web_frame_)
113 return content::RenderFrame::FromWebFrame(web_frame_);
114 return NULL;
115 }
116
CallFunction(v8::Handle<v8::Function> function,int argc,v8::Handle<v8::Value> argv[]) const117 v8::Local<v8::Value> ScriptContext::CallFunction(
118 v8::Handle<v8::Function> function,
119 int argc,
120 v8::Handle<v8::Value> argv[]) const {
121 v8::EscapableHandleScope handle_scope(isolate());
122 v8::Context::Scope scope(v8_context());
123
124 blink::WebScopedMicrotaskSuppression suppression;
125 if (!is_valid()) {
126 return handle_scope.Escape(
127 v8::Local<v8::Primitive>(v8::Undefined(isolate())));
128 }
129
130 v8::Handle<v8::Object> global = v8_context()->Global();
131 if (!web_frame_)
132 return handle_scope.Escape(function->Call(global, argc, argv));
133 return handle_scope.Escape(
134 v8::Local<v8::Value>(web_frame_->callFunctionEvenIfScriptDisabled(
135 function, global, argc, argv)));
136 }
137
GetAvailability(const std::string & api_name)138 Feature::Availability ScriptContext::GetAvailability(
139 const std::string& api_name) {
140 // Hack: Hosted apps should have the availability of messaging APIs based on
141 // the URL of the page (which might have access depending on some extension
142 // with externally_connectable), not whether the app has access to messaging
143 // (which it won't).
144 const Extension* extension = extension_.get();
145 if (extension && extension->is_hosted_app() &&
146 (api_name == "runtime.connect" || api_name == "runtime.sendMessage")) {
147 extension = NULL;
148 }
149 return ExtensionAPI::GetSharedInstance()->IsAvailable(
150 api_name, extension, context_type_, GetURL());
151 }
152
DispatchEvent(const char * event_name,v8::Handle<v8::Array> args) const153 void ScriptContext::DispatchEvent(const char* event_name,
154 v8::Handle<v8::Array> args) const {
155 v8::HandleScope handle_scope(isolate());
156 v8::Context::Scope context_scope(v8_context());
157
158 v8::Handle<v8::Value> argv[] = {
159 v8::String::NewFromUtf8(isolate(), event_name), args};
160 module_system_->CallModuleMethod(
161 kEventBindings, "dispatchEvent", arraysize(argv), argv);
162 }
163
DispatchOnUnloadEvent()164 void ScriptContext::DispatchOnUnloadEvent() {
165 v8::HandleScope handle_scope(isolate());
166 v8::Context::Scope context_scope(v8_context());
167 module_system_->CallModuleMethod("unload_event", "dispatch");
168 }
169
GetContextTypeDescription()170 std::string ScriptContext::GetContextTypeDescription() {
171 return GetContextTypeDescriptionString(context_type_);
172 }
173
GetEffectiveContextTypeDescription()174 std::string ScriptContext::GetEffectiveContextTypeDescription() {
175 return GetContextTypeDescriptionString(effective_context_type_);
176 }
177
GetURL() const178 GURL ScriptContext::GetURL() const {
179 return web_frame() ? GetDataSourceURLForFrame(web_frame()) : GURL();
180 }
181
IsAnyFeatureAvailableToContext(const Feature & api)182 bool ScriptContext::IsAnyFeatureAvailableToContext(const Feature& api) {
183 return ExtensionAPI::GetSharedInstance()->IsAnyFeatureAvailableToContext(
184 api, extension(), context_type(), GetDataSourceURLForFrame(web_frame()));
185 }
186
187 // static
GetDataSourceURLForFrame(const blink::WebFrame * frame)188 GURL ScriptContext::GetDataSourceURLForFrame(const blink::WebFrame* frame) {
189 // Normally we would use frame->document().url() to determine the document's
190 // URL, but to decide whether to inject a content script, we use the URL from
191 // the data source. This "quirk" helps prevents content scripts from
192 // inadvertently adding DOM elements to the compose iframe in Gmail because
193 // the compose iframe's dataSource URL is about:blank, but the document URL
194 // changes to match the parent document after Gmail document.writes into
195 // it to create the editor.
196 // http://code.google.com/p/chromium/issues/detail?id=86742
197 blink::WebDataSource* data_source = frame->provisionalDataSource()
198 ? frame->provisionalDataSource()
199 : frame->dataSource();
200 return data_source ? GURL(data_source->request().url()) : GURL();
201 }
202
203 // static
GetEffectiveDocumentURL(const blink::WebFrame * frame,const GURL & document_url,bool match_about_blank)204 GURL ScriptContext::GetEffectiveDocumentURL(const blink::WebFrame* frame,
205 const GURL& document_url,
206 bool match_about_blank) {
207 // Common scenario. If |match_about_blank| is false (as is the case in most
208 // extensions), or if the frame is not an about:-page, just return
209 // |document_url| (supposedly the URL of the frame).
210 if (!match_about_blank || !document_url.SchemeIs(url::kAboutScheme))
211 return document_url;
212
213 // Non-sandboxed about:blank and about:srcdoc pages inherit their security
214 // origin from their parent frame/window. So, traverse the frame/window
215 // hierarchy to find the closest non-about:-page and return its URL.
216 const blink::WebFrame* parent = frame;
217 do {
218 parent = parent->parent() ? parent->parent() : parent->opener();
219 } while (parent != NULL && !parent->document().isNull() &&
220 GURL(parent->document().url()).SchemeIs(url::kAboutScheme));
221
222 if (parent && !parent->document().isNull()) {
223 // Only return the parent URL if the frame can access it.
224 const blink::WebDocument& parent_document = parent->document();
225 if (frame->document().securityOrigin().canAccess(
226 parent_document.securityOrigin()))
227 return parent_document.url();
228 }
229 return document_url;
230 }
231
GetContext()232 ScriptContext* ScriptContext::GetContext() { return this; }
233
OnResponseReceived(const std::string & name,int request_id,bool success,const base::ListValue & response,const std::string & error)234 void ScriptContext::OnResponseReceived(const std::string& name,
235 int request_id,
236 bool success,
237 const base::ListValue& response,
238 const std::string& error) {
239 v8::HandleScope handle_scope(isolate());
240
241 scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
242 v8::Handle<v8::Value> argv[] = {
243 v8::Integer::New(isolate(), request_id),
244 v8::String::NewFromUtf8(isolate(), name.c_str()),
245 v8::Boolean::New(isolate(), success),
246 converter->ToV8Value(&response, v8_context_.NewHandle(isolate())),
247 v8::String::NewFromUtf8(isolate(), error.c_str())};
248
249 v8::Handle<v8::Value> retval = module_system()->CallModuleMethod(
250 "sendRequest", "handleResponse", arraysize(argv), argv);
251
252 // In debug, the js will validate the callback parameters and return a
253 // string if a validation error has occured.
254 DCHECK(retval.IsEmpty() || retval->IsUndefined())
255 << *v8::String::Utf8Value(retval);
256 }
257
Run(const std::string & source,const std::string & resource_name)258 void ScriptContext::Run(const std::string& source,
259 const std::string& resource_name) {
260 module_system_->RunString(source, resource_name);
261 }
262
Call(v8::Handle<v8::Function> function,v8::Handle<v8::Value> receiver,int argc,v8::Handle<v8::Value> argv[])263 v8::Handle<v8::Value> ScriptContext::Call(v8::Handle<v8::Function> function,
264 v8::Handle<v8::Value> receiver,
265 int argc,
266 v8::Handle<v8::Value> argv[]) {
267 return CallFunction(function, argc, argv);
268 }
269
GetContextHolder()270 gin::ContextHolder* ScriptContext::GetContextHolder() {
271 v8::HandleScope handle_scope(isolate());
272 return gin::PerContextData::From(v8_context())->context_holder();
273 }
274
275 } // namespace extensions
276