blob: 6f23f0774bd321fe0b9dc085f2d7d9ff8dc1ef1b [file] [log] [blame]
Devlin Cronindbbb47c2017-10-13 00:50:161// 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"
Avi Drissman197295ae2018-12-25 20:28:348#include "base/stl_util.h"
Devlin Cronindbbb47c2017-10-13 00:50:169#include "base/strings/string_piece.h"
10#include "base/strings/stringprintf.h"
Devlin Croninb15f7f02018-01-31 19:37:3211#include "content/public/renderer/render_frame.h"
John Abd-El-Malek312a30bb2017-10-23 19:51:5212#include "content/public/renderer/v8_value_converter.h"
Devlin Cronindbbb47c2017-10-13 00:50:1613#include "extensions/common/api/messaging/message.h"
Devlin Croninb15f7f02018-01-31 19:37:3214#include "extensions/common/constants.h"
Devlin Cronindbbb47c2017-10-13 00:50:1615#include "extensions/common/extension.h"
16#include "extensions/common/manifest.h"
17#include "extensions/renderer/bindings/api_signature.h"
Devlin Croninb15f7f02018-01-31 19:37:3218#include "extensions/renderer/bindings/js_runner.h"
19#include "extensions/renderer/extension_frame_helper.h"
Devlin Cronin9ff5b9c52017-12-06 23:18:2120#include "extensions/renderer/get_script_context.h"
Devlin Cronindbbb47c2017-10-13 00:50:1621#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"
Devlin Cronindbbb47c2017-10-13 00:50:1625#include "gin/converter.h"
Blink Reformata30d4232018-04-07 15:31:0626#include "third_party/blink/public/web/web_local_frame.h"
Devlin Cronindbbb47c2017-10-13 00:50:1627
28namespace extensions {
29
30namespace {
31using RequestResult = APIBindingHooks::RequestResult;
32
Devlin Cronindbbb47c2017-10-13 00:50:1633// Handler for the extensionId property on chrome.runtime.
34void 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
Devlin Cronin9ff5b9c52017-12-06 23:18:2140 ScriptContext* script_context = GetScriptContextFromV8Context(context);
Devlin Cronindbbb47c2017-10-13 00:50:1641 // 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
50constexpr char kGetManifest[] = "runtime.getManifest";
51constexpr char kGetURL[] = "runtime.getURL";
52constexpr char kConnect[] = "runtime.connect";
Devlin Cronin63aa35e2017-10-21 01:27:3453constexpr char kConnectNative[] = "runtime.connectNative";
Devlin Cronindbbb47c2017-10-13 00:50:1654constexpr char kSendMessage[] = "runtime.sendMessage";
Devlin Cronin63aa35e2017-10-21 01:27:3455constexpr char kSendNativeMessage[] = "runtime.sendNativeMessage";
Devlin Croninb15f7f02018-01-31 19:37:3256constexpr char kGetBackgroundPage[] = "runtime.getBackgroundPage";
57constexpr 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.
62void 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>(),
Avi Drissman197295ae2018-12-25 20:28:3482 base::size(args), args);
Devlin Croninb15f7f02018-01-31 19:37:3283}
Devlin Cronindbbb47c2017-10-13 00:50:1684
Devlin Cronindbbb47c2017-10-13 00:50:1685} // namespace
86
87RuntimeHooksDelegate::RuntimeHooksDelegate(
88 NativeRendererMessagingService* messaging_service)
89 : messaging_service_(messaging_service) {}
90RuntimeHooksDelegate::~RuntimeHooksDelegate() {}
91
Tim Judkinsd63d4a92019-07-23 17:32:4792// static
93RequestResult 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());
Tim Judkins4eab6672019-08-06 22:24:01107 // 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());
Tim Judkinsd63d4a92019-07-23 17:32:47110 result.return_value = gin::StringToV8(isolate, url);
Tim Judkinsd63d4a92019-07-23 17:32:47111 return result;
112}
113
Devlin Cronindbbb47c2017-10-13 00:50:16114RequestResult 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},
Devlin Cronin63aa35e2017-10-21 01:27:34130 {&RuntimeHooksDelegate::HandleConnectNative, kConnectNative},
131 {&RuntimeHooksDelegate::HandleSendNativeMessage, kSendNativeMessage},
Devlin Croninb15f7f02018-01-31 19:37:32132 {&RuntimeHooksDelegate::HandleGetBackgroundPage, kGetBackgroundPage},
133 {&RuntimeHooksDelegate::HandleGetPackageDirectoryEntryCallback,
134 kGetPackageDirectoryEntry},
Devlin Cronindbbb47c2017-10-13 00:50:16135 };
136
Devlin Cronin9ff5b9c52017-12-06 23:18:21137 ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
Devlin Cronindbbb47c2017-10-13 00:50:16138
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
Devlin Croninb15f7f02018-01-31 19:37:32150 bool should_massage = false;
151 bool allow_options = false;
Devlin Croninc4b07fb2017-11-14 20:26:34152 if (method_name == kSendMessage) {
Devlin Croninb15f7f02018-01-31 19:37:32153 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);
Devlin Croninc4b07fb2017-11-14 20:26:34162 }
Devlin Cronindbbb47c2017-10-13 00:50:16163
Devlin Cronin4ed01612019-07-18 17:41:55164 APISignature::V8ParseResult parse_result =
165 signature->ParseArgumentsToV8(context, *arguments, refs);
166 if (!parse_result.succeeded()) {
Devlin Cronindbbb47c2017-10-13 00:50:16167 RequestResult result(RequestResult::INVALID_INVOCATION);
Devlin Cronin4ed01612019-07-18 17:41:55168 result.error = std::move(*parse_result.error);
Devlin Cronindbbb47c2017-10-13 00:50:16169 return result;
170 }
171
Devlin Cronin4ed01612019-07-18 17:41:55172 return (this->*handler)(script_context, *parse_result.arguments);
Devlin Cronindbbb47c2017-10-13 00:50:16173}
174
175void 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
183RequestResult 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
196RequestResult RuntimeHooksDelegate::HandleGetURL(
197 ScriptContext* script_context,
198 const std::vector<v8::Local<v8::Value>>& arguments) {
Tim Judkinsd63d4a92019-07-23 17:32:47199 return GetURL(script_context, arguments);
Devlin Cronindbbb47c2017-10-13 00:50:16200}
201
202RequestResult 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;
Devlin Croninc4b07fb2017-11-14 20:26:34208 std::string error;
209 if (!messaging_util::GetTargetExtensionId(script_context, arguments[0],
210 "runtime.sendMessage", &target_id,
211 &error)) {
Devlin Cronindbbb47c2017-10-13 00:50:16212 RequestResult result(RequestResult::INVALID_INVOCATION);
Devlin Croninc4b07fb2017-11-14 20:26:34213 result.error = std::move(error);
Devlin Cronindbbb47c2017-10-13 00:50:16214 return result;
215 }
216
217 v8::Local<v8::Context> v8_context = script_context->v8_context();
Devlin Cronindbbb47c2017-10-13 00:50:16218
219 v8::Local<v8::Value> v8_message = arguments[1];
Devlin Cronindbbb47c2017-10-13 00:50:16220 std::unique_ptr<Message> message =
Devlin Croninfe7aae62017-11-16 03:49:55221 messaging_util::MessageFromV8(v8_context, v8_message, &error);
Devlin Cronindbbb47c2017-10-13 00:50:16222 if (!message) {
223 RequestResult result(RequestResult::INVALID_INVOCATION);
Devlin Croninfe7aae62017-11-16 03:49:55224 result.error = std::move(error);
Devlin Cronindbbb47c2017-10-13 00:50:16225 return result;
226 }
227
Nick Harper41374d52020-01-30 22:36:47228 // 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
Devlin Cronindbbb47c2017-10-13 00:50:16233 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),
Nick Harper41374d52020-01-30 22:36:47239 messaging_util::kSendMessageChannel, *message, response_callback);
Devlin Cronindbbb47c2017-10-13 00:50:16240
241 return RequestResult(RequestResult::HANDLED);
242}
243
Devlin Cronin63aa35e2017-10-21 01:27:34244RequestResult RuntimeHooksDelegate::HandleSendNativeMessage(
245 ScriptContext* script_context,
246 const std::vector<v8::Local<v8::Value>>& arguments) {
247 DCHECK_EQ(3u, arguments.size());
248
Dan Elphick38a508052018-07-23 22:19:53249 std::string application_name =
250 gin::V8ToString(script_context->isolate(), arguments[0]);
Devlin Cronin63aa35e2017-10-21 01:27:34251
252 v8::Local<v8::Value> v8_message = arguments[1];
253 DCHECK(!v8_message.IsEmpty());
Devlin Croninfe7aae62017-11-16 03:49:55254 std::string error;
255 std::unique_ptr<Message> message = messaging_util::MessageFromV8(
256 script_context->v8_context(), v8_message, &error);
Devlin Cronin63aa35e2017-10-21 01:27:34257 if (!message) {
258 RequestResult result(RequestResult::INVALID_INVOCATION);
Devlin Croninfe7aae62017-11-16 03:49:55259 result.error = std::move(error);
Devlin Cronin63aa35e2017-10-21 01:27:34260 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),
Nick Harper41374d52020-01-30 22:36:47269 std::string(), *message, response_callback);
Devlin Cronin63aa35e2017-10-21 01:27:34270
271 return RequestResult(RequestResult::HANDLED);
272}
273
Devlin Cronindbbb47c2017-10-13 00:50:16274RequestResult 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;
Devlin Croninc4b07fb2017-11-14 20:26:34280 std::string error;
281 if (!messaging_util::GetTargetExtensionId(script_context, arguments[0],
282 "runtime.connect", &target_id,
283 &error)) {
Devlin Cronindbbb47c2017-10-13 00:50:16284 RequestResult result(RequestResult::INVALID_INVOCATION);
Devlin Croninc4b07fb2017-11-14 20:26:34285 result.error = std::move(error);
Devlin Cronindbbb47c2017-10-13 00:50:16286 return result;
287 }
288
Devlin Cronin37198932017-11-07 20:15:23289 messaging_util::MessageOptions options;
Devlin Cronindbbb47c2017-10-13 00:50:16290 if (!arguments[1]->IsNull()) {
Devlin Cronin85efd622017-12-05 19:31:57291 options = messaging_util::ParseMessageOptions(
292 script_context->v8_context(), arguments[1].As<v8::Object>(),
Devlin Cronin85efd622017-12-05 19:31:57293 messaging_util::PARSE_CHANNEL_NAME);
Devlin Cronindbbb47c2017-10-13 00:50:16294 }
295
296 gin::Handle<GinPort> port = messaging_service_->Connect(
297 script_context, MessageTarget::ForExtension(target_id),
Nick Harper41374d52020-01-30 22:36:47298 options.channel_name);
Devlin Cronindbbb47c2017-10-13 00:50:16299 DCHECK(!port.IsEmpty());
300
301 RequestResult result(RequestResult::HANDLED);
302 result.return_value = port.ToV8();
303 return result;
304}
305
Devlin Cronin63aa35e2017-10-21 01:27:34306RequestResult 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
Dan Elphick38a508052018-07-23 22:19:53312 std::string application_name =
313 gin::V8ToString(script_context->isolate(), arguments[0]);
Devlin Cronin63aa35e2017-10-21 01:27:34314 gin::Handle<GinPort> port = messaging_service_->Connect(
315 script_context, MessageTarget::ForNativeApp(application_name),
Nick Harper41374d52020-01-30 22:36:47316 std::string());
Devlin Cronin63aa35e2017-10-21 01:27:34317
318 RequestResult result(RequestResult::HANDLED);
319 result.return_value = port.ToV8();
320 return result;
321}
322
Devlin Croninb15f7f02018-01-31 19:37:32323RequestResult 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
338RequestResult 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
Devlin Croninbdc39892018-02-02 15:50:50348 v8::MaybeLocal<v8::Value> maybe_custom_callback;
Devlin Croninb15f7f02018-01-31 19:37:32349 { // 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
Devlin Croninbdc39892018-02-02 15:50:50403 v8::Local<v8::Function> get_bind_directory_entry_callback =
Devlin Croninb15f7f02018-01-31 19:37:32404 get_bind_directory_entry_callback_value.As<v8::Function>();
Devlin Croninb15f7f02018-01-31 19:37:32405
Devlin Croninbdc39892018-02-02 15:50:50406 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.
Devlin Croninb15f7f02018-01-31 19:37:32411 v8::Local<v8::Value> callback;
Devlin Croninbdc39892018-02-02 15:50:50412 if (!maybe_custom_callback.ToLocal(&callback)) {
Devlin Croninb15f7f02018-01-31 19:37:32413 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
Devlin Cronindbbb47c2017-10-13 00:50:16428} // namespace extensions