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