1 // Copyright 2017 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/runtime_hooks_delegate.h"
6
7 #include "base/containers/span.h"
8 #include "base/stl_util.h"
9 #include "base/strings/string_piece.h"
10 #include "base/strings/stringprintf.h"
11 #include "content/public/renderer/render_frame.h"
12 #include "content/public/renderer/v8_value_converter.h"
13 #include "extensions/common/api/messaging/message.h"
14 #include "extensions/common/constants.h"
15 #include "extensions/common/extension.h"
16 #include "extensions/common/manifest.h"
17 #include "extensions/renderer/bindings/api_signature.h"
18 #include "extensions/renderer/bindings/js_runner.h"
19 #include "extensions/renderer/extension_frame_helper.h"
20 #include "extensions/renderer/get_script_context.h"
21 #include "extensions/renderer/message_target.h"
22 #include "extensions/renderer/messaging_util.h"
23 #include "extensions/renderer/native_renderer_messaging_service.h"
24 #include "extensions/renderer/script_context.h"
25 #include "gin/converter.h"
26 #include "third_party/blink/public/web/web_local_frame.h"
27
28 namespace extensions {
29
30 namespace {
31 using RequestResult = APIBindingHooks::RequestResult;
32
33 // Handler for the extensionId property on chrome.runtime.
GetExtensionId(v8::Local<v8::Name> property_name,const v8::PropertyCallbackInfo<v8::Value> & info)34 void GetExtensionId(v8::Local<v8::Name> property_name,
35 const v8::PropertyCallbackInfo<v8::Value>& info) {
36 v8::Isolate* isolate = info.GetIsolate();
37 v8::HandleScope handle_scope(isolate);
38 v8::Local<v8::Context> context = info.Holder()->CreationContext();
39
40 ScriptContext* script_context = GetScriptContextFromV8Context(context);
41 // This could potentially be invoked after the script context is removed
42 // (unlike the handler calls, which should only be invoked for valid
43 // contexts).
44 if (script_context && script_context->extension()) {
45 info.GetReturnValue().Set(
46 gin::StringToSymbol(isolate, script_context->extension()->id()));
47 }
48 }
49
50 constexpr char kGetManifest[] = "runtime.getManifest";
51 constexpr char kGetURL[] = "runtime.getURL";
52 constexpr char kConnect[] = "runtime.connect";
53 constexpr char kConnectNative[] = "runtime.connectNative";
54 constexpr char kSendMessage[] = "runtime.sendMessage";
55 constexpr char kSendNativeMessage[] = "runtime.sendNativeMessage";
56 constexpr char kGetBackgroundPage[] = "runtime.getBackgroundPage";
57 constexpr char kGetPackageDirectoryEntry[] = "runtime.getPackageDirectoryEntry";
58
59 // The custom callback supplied to runtime.getBackgroundPage to find and return
60 // the background page to the original callback. The original callback is
61 // curried in through the Data.
GetBackgroundPageCallback(const v8::FunctionCallbackInfo<v8::Value> & info)62 void GetBackgroundPageCallback(
63 const v8::FunctionCallbackInfo<v8::Value>& info) {
64 v8::Isolate* isolate = info.GetIsolate();
65 v8::HandleScope handle_scope(isolate);
66 v8::Local<v8::Context> context = info.Holder()->CreationContext();
67
68 DCHECK(!info.Data().IsEmpty());
69 if (info.Data()->IsNull())
70 return;
71
72 // The ScriptContext should always be valid, because otherwise the
73 // getBackgroundPage() request should have been invalidated (and this should
74 // never run).
75 ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
76
77 v8::Local<v8::Value> background_page =
78 ExtensionFrameHelper::GetV8BackgroundPageMainFrame(
79 isolate, script_context->extension()->id());
80 v8::Local<v8::Value> args[] = {background_page};
81 script_context->SafeCallFunction(info.Data().As<v8::Function>(),
82 base::size(args), args);
83 }
84
85 } // namespace
86
RuntimeHooksDelegate(NativeRendererMessagingService * messaging_service)87 RuntimeHooksDelegate::RuntimeHooksDelegate(
88 NativeRendererMessagingService* messaging_service)
89 : messaging_service_(messaging_service) {}
~RuntimeHooksDelegate()90 RuntimeHooksDelegate::~RuntimeHooksDelegate() {}
91
92 // static
GetURL(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)93 RequestResult RuntimeHooksDelegate::GetURL(
94 ScriptContext* script_context,
95 const std::vector<v8::Local<v8::Value>>& arguments) {
96 DCHECK_EQ(1u, arguments.size());
97 DCHECK(arguments[0]->IsString());
98 DCHECK(script_context->extension());
99
100 v8::Isolate* isolate = script_context->isolate();
101 std::string path = gin::V8ToString(isolate, arguments[0]);
102
103 RequestResult result(RequestResult::HANDLED);
104 std::string url = base::StringPrintf(
105 "chrome-extension://%s%s%s", script_context->extension()->id().c_str(),
106 !path.empty() && path[0] == '/' ? "" : "/", path.c_str());
107 // GURL considers any possible path valid. Since the argument is only appended
108 // as part of the path, there should be no way this could conceivably fail.
109 DCHECK(GURL(url).is_valid());
110 result.return_value = gin::StringToV8(isolate, url);
111 return result;
112 }
113
HandleRequest(const std::string & method_name,const APISignature * signature,v8::Local<v8::Context> context,std::vector<v8::Local<v8::Value>> * arguments,const APITypeReferenceMap & refs)114 RequestResult RuntimeHooksDelegate::HandleRequest(
115 const std::string& method_name,
116 const APISignature* signature,
117 v8::Local<v8::Context> context,
118 std::vector<v8::Local<v8::Value>>* arguments,
119 const APITypeReferenceMap& refs) {
120 using Handler = RequestResult (RuntimeHooksDelegate::*)(
121 ScriptContext*, const std::vector<v8::Local<v8::Value>>&);
122 static const struct {
123 Handler handler;
124 base::StringPiece method;
125 } kHandlers[] = {
126 {&RuntimeHooksDelegate::HandleSendMessage, kSendMessage},
127 {&RuntimeHooksDelegate::HandleConnect, kConnect},
128 {&RuntimeHooksDelegate::HandleGetURL, kGetURL},
129 {&RuntimeHooksDelegate::HandleGetManifest, kGetManifest},
130 {&RuntimeHooksDelegate::HandleConnectNative, kConnectNative},
131 {&RuntimeHooksDelegate::HandleSendNativeMessage, kSendNativeMessage},
132 {&RuntimeHooksDelegate::HandleGetBackgroundPage, kGetBackgroundPage},
133 {&RuntimeHooksDelegate::HandleGetPackageDirectoryEntryCallback,
134 kGetPackageDirectoryEntry},
135 };
136
137 ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
138
139 Handler handler = nullptr;
140 for (const auto& handler_entry : kHandlers) {
141 if (handler_entry.method == method_name) {
142 handler = handler_entry.handler;
143 break;
144 }
145 }
146
147 if (!handler)
148 return RequestResult(RequestResult::NOT_HANDLED);
149
150 bool should_massage = false;
151 bool allow_options = false;
152 if (method_name == kSendMessage) {
153 should_massage = true;
154 allow_options = true;
155 } else if (method_name == kSendNativeMessage) {
156 should_massage = true;
157 }
158
159 if (should_massage) {
160 messaging_util::MassageSendMessageArguments(context->GetIsolate(),
161 allow_options, arguments);
162 }
163
164 APISignature::V8ParseResult parse_result =
165 signature->ParseArgumentsToV8(context, *arguments, refs);
166 if (!parse_result.succeeded()) {
167 RequestResult result(RequestResult::INVALID_INVOCATION);
168 result.error = std::move(*parse_result.error);
169 return result;
170 }
171
172 return (this->*handler)(script_context, *parse_result.arguments);
173 }
174
InitializeTemplate(v8::Isolate * isolate,v8::Local<v8::ObjectTemplate> object_template,const APITypeReferenceMap & type_refs)175 void RuntimeHooksDelegate::InitializeTemplate(
176 v8::Isolate* isolate,
177 v8::Local<v8::ObjectTemplate> object_template,
178 const APITypeReferenceMap& type_refs) {
179 object_template->SetAccessor(gin::StringToSymbol(isolate, "id"),
180 &GetExtensionId);
181 }
182
HandleGetManifest(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & parsed_arguments)183 RequestResult RuntimeHooksDelegate::HandleGetManifest(
184 ScriptContext* script_context,
185 const std::vector<v8::Local<v8::Value>>& parsed_arguments) {
186 DCHECK(script_context->extension());
187
188 RequestResult result(RequestResult::HANDLED);
189 result.return_value = content::V8ValueConverter::Create()->ToV8Value(
190 script_context->extension()->manifest()->value(),
191 script_context->v8_context());
192
193 return result;
194 }
195
HandleGetURL(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)196 RequestResult RuntimeHooksDelegate::HandleGetURL(
197 ScriptContext* script_context,
198 const std::vector<v8::Local<v8::Value>>& arguments) {
199 return GetURL(script_context, arguments);
200 }
201
HandleSendMessage(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)202 RequestResult RuntimeHooksDelegate::HandleSendMessage(
203 ScriptContext* script_context,
204 const std::vector<v8::Local<v8::Value>>& arguments) {
205 DCHECK_EQ(4u, arguments.size());
206
207 std::string target_id;
208 std::string error;
209 if (!messaging_util::GetTargetExtensionId(script_context, arguments[0],
210 "runtime.sendMessage", &target_id,
211 &error)) {
212 RequestResult result(RequestResult::INVALID_INVOCATION);
213 result.error = std::move(error);
214 return result;
215 }
216
217 v8::Local<v8::Context> v8_context = script_context->v8_context();
218
219 v8::Local<v8::Value> v8_message = arguments[1];
220 std::unique_ptr<Message> message =
221 messaging_util::MessageFromV8(v8_context, v8_message, &error);
222 if (!message) {
223 RequestResult result(RequestResult::INVALID_INVOCATION);
224 result.error = std::move(error);
225 return result;
226 }
227
228 // Note: arguments[2] is the options argument. However, the only available
229 // option for sendMessage() is includeTlsChannelId. That option has no effect
230 // since M72, but it is still part of the public spec for compatibility and is
231 // parsed into |arguments|. See crbug.com/1045232.
232
233 v8::Local<v8::Function> response_callback;
234 if (!arguments[3]->IsNull())
235 response_callback = arguments[3].As<v8::Function>();
236
237 messaging_service_->SendOneTimeMessage(
238 script_context, MessageTarget::ForExtension(target_id),
239 messaging_util::kSendMessageChannel, *message, response_callback);
240
241 return RequestResult(RequestResult::HANDLED);
242 }
243
HandleSendNativeMessage(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)244 RequestResult RuntimeHooksDelegate::HandleSendNativeMessage(
245 ScriptContext* script_context,
246 const std::vector<v8::Local<v8::Value>>& arguments) {
247 DCHECK_EQ(3u, arguments.size());
248
249 std::string application_name =
250 gin::V8ToString(script_context->isolate(), arguments[0]);
251
252 v8::Local<v8::Value> v8_message = arguments[1];
253 DCHECK(!v8_message.IsEmpty());
254 std::string error;
255 std::unique_ptr<Message> message = messaging_util::MessageFromV8(
256 script_context->v8_context(), v8_message, &error);
257 if (!message) {
258 RequestResult result(RequestResult::INVALID_INVOCATION);
259 result.error = std::move(error);
260 return result;
261 }
262
263 v8::Local<v8::Function> response_callback;
264 if (!arguments[2]->IsNull())
265 response_callback = arguments[2].As<v8::Function>();
266
267 messaging_service_->SendOneTimeMessage(
268 script_context, MessageTarget::ForNativeApp(application_name),
269 std::string(), *message, response_callback);
270
271 return RequestResult(RequestResult::HANDLED);
272 }
273
HandleConnect(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)274 RequestResult RuntimeHooksDelegate::HandleConnect(
275 ScriptContext* script_context,
276 const std::vector<v8::Local<v8::Value>>& arguments) {
277 DCHECK_EQ(2u, arguments.size());
278
279 std::string target_id;
280 std::string error;
281 if (!messaging_util::GetTargetExtensionId(script_context, arguments[0],
282 "runtime.connect", &target_id,
283 &error)) {
284 RequestResult result(RequestResult::INVALID_INVOCATION);
285 result.error = std::move(error);
286 return result;
287 }
288
289 messaging_util::MessageOptions options;
290 if (!arguments[1]->IsNull()) {
291 options = messaging_util::ParseMessageOptions(
292 script_context->v8_context(), arguments[1].As<v8::Object>(),
293 messaging_util::PARSE_CHANNEL_NAME);
294 }
295
296 gin::Handle<GinPort> port = messaging_service_->Connect(
297 script_context, MessageTarget::ForExtension(target_id),
298 options.channel_name);
299 DCHECK(!port.IsEmpty());
300
301 RequestResult result(RequestResult::HANDLED);
302 result.return_value = port.ToV8();
303 return result;
304 }
305
HandleConnectNative(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)306 RequestResult RuntimeHooksDelegate::HandleConnectNative(
307 ScriptContext* script_context,
308 const std::vector<v8::Local<v8::Value>>& arguments) {
309 DCHECK_EQ(1u, arguments.size());
310 DCHECK(arguments[0]->IsString());
311
312 std::string application_name =
313 gin::V8ToString(script_context->isolate(), arguments[0]);
314 gin::Handle<GinPort> port = messaging_service_->Connect(
315 script_context, MessageTarget::ForNativeApp(application_name),
316 std::string());
317
318 RequestResult result(RequestResult::HANDLED);
319 result.return_value = port.ToV8();
320 return result;
321 }
322
HandleGetBackgroundPage(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)323 RequestResult RuntimeHooksDelegate::HandleGetBackgroundPage(
324 ScriptContext* script_context,
325 const std::vector<v8::Local<v8::Value>>& arguments) {
326 DCHECK(script_context->extension());
327
328 RequestResult result(RequestResult::NOT_HANDLED);
329 if (!v8::Function::New(script_context->v8_context(),
330 &GetBackgroundPageCallback, arguments[0])
331 .ToLocal(&result.custom_callback)) {
332 return RequestResult(RequestResult::THROWN);
333 }
334
335 return result;
336 }
337
HandleGetPackageDirectoryEntryCallback(ScriptContext * script_context,const std::vector<v8::Local<v8::Value>> & arguments)338 RequestResult RuntimeHooksDelegate::HandleGetPackageDirectoryEntryCallback(
339 ScriptContext* script_context,
340 const std::vector<v8::Local<v8::Value>>& arguments) {
341 // TODO(devlin): This is basically just copied and translated from
342 // the JS bindings, and still relies on the custom JS bindings for
343 // getBindDirectoryEntryCallback. This entire API is a bit crazy, and needs
344 // some help.
345 v8::Isolate* isolate = script_context->isolate();
346 v8::Local<v8::Context> v8_context = script_context->v8_context();
347
348 v8::MaybeLocal<v8::Value> maybe_custom_callback;
349 { // Begin natives enabled scope (for requiring the module).
350 ModuleSystem::NativesEnabledScope enable_natives(
351 script_context->module_system());
352 content::RenderFrame* background_page =
353 ExtensionFrameHelper::GetBackgroundPageFrame(
354 script_context->extension()->id());
355
356 // The JS function will sometimes use the background page's context to do
357 // some work (see also
358 // extensions/renderer/resources/file_entry_binding_util.js). In order to
359 // allow native code to run in the background page, we'll also need a
360 // NativesEnabledScope for that context.
361 DCHECK(v8_context == isolate->GetCurrentContext());
362 base::Optional<ModuleSystem::NativesEnabledScope> background_page_natives;
363 if (background_page &&
364 background_page != script_context->GetRenderFrame() &&
365 blink::WebFrame::ScriptCanAccess(background_page->GetWebFrame())) {
366 ScriptContext* background_page_script_context =
367 GetScriptContextFromV8Context(
368 background_page->GetWebFrame()->MainWorldScriptContext());
369 if (background_page_script_context) {
370 background_page_natives.emplace(
371 background_page_script_context->module_system());
372 }
373 }
374
375 v8::Local<v8::Object> file_entry_binding_util;
376 // ModuleSystem::Require can return an empty Maybe when it fails for any
377 // number of reasons. It *shouldn't* ever throw, but it is technically
378 // possible. This makes the handling the failure result complicated. Since
379 // this shouldn't happen at all, bail and consider it handled if it fails.
380 if (!script_context->module_system()
381 ->Require("fileEntryBindingUtil")
382 .ToLocal(&file_entry_binding_util)) {
383 NOTREACHED();
384 // Abort, and consider the request handled.
385 return RequestResult(RequestResult::HANDLED);
386 }
387
388 v8::Local<v8::Value> get_bind_directory_entry_callback_value;
389 if (!file_entry_binding_util
390 ->Get(v8_context, gin::StringToSymbol(
391 isolate, "getBindDirectoryEntryCallback"))
392 .ToLocal(&get_bind_directory_entry_callback_value)) {
393 NOTREACHED();
394 return RequestResult(RequestResult::THROWN);
395 }
396
397 if (!get_bind_directory_entry_callback_value->IsFunction()) {
398 NOTREACHED();
399 // Abort, and consider the request handled.
400 return RequestResult(RequestResult::HANDLED);
401 }
402
403 v8::Local<v8::Function> get_bind_directory_entry_callback =
404 get_bind_directory_entry_callback_value.As<v8::Function>();
405
406 maybe_custom_callback =
407 JSRunner::Get(v8_context)
408 ->RunJSFunctionSync(get_bind_directory_entry_callback, v8_context,
409 0, nullptr);
410 } // End modules enabled scope.
411 v8::Local<v8::Value> callback;
412 if (!maybe_custom_callback.ToLocal(&callback)) {
413 NOTREACHED();
414 return RequestResult(RequestResult::THROWN);
415 }
416
417 if (!callback->IsFunction()) {
418 NOTREACHED();
419 // Abort, and consider the request handled.
420 return RequestResult(RequestResult::HANDLED);
421 }
422
423 RequestResult result(RequestResult::NOT_HANDLED);
424 result.custom_callback = callback.As<v8::Function>();
425 return result;
426 }
427
428 } // namespace extensions
429