runtime_hooks_delegate.cc 20 KB

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