js_binding.cc 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // Copyright 2019 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 "components/js_injection/renderer/js_binding.h"
  5. #include <vector>
  6. #include "base/containers/contains.h"
  7. #include "base/strings/string_util.h"
  8. #include "components/js_injection/renderer/js_communication.h"
  9. #include "content/public/renderer/render_frame.h"
  10. #include "gin/data_object_builder.h"
  11. #include "gin/handle.h"
  12. #include "gin/object_template_builder.h"
  13. #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
  14. #include "third_party/blink/public/common/messaging/message_port_channel.h"
  15. #include "third_party/blink/public/platform/web_security_origin.h"
  16. #include "third_party/blink/public/web/blink.h"
  17. #include "third_party/blink/public/web/web_frame.h"
  18. #include "third_party/blink/public/web/web_local_frame.h"
  19. #include "third_party/blink/public/web/web_message_port_converter.h"
  20. #include "v8/include/v8.h"
  21. namespace {
  22. constexpr char kPostMessage[] = "postMessage";
  23. constexpr char kOnMessage[] = "onmessage";
  24. constexpr char kAddEventListener[] = "addEventListener";
  25. constexpr char kRemoveEventListener[] = "removeEventListener";
  26. } // anonymous namespace
  27. namespace js_injection {
  28. gin::WrapperInfo JsBinding::kWrapperInfo = {gin::kEmbedderNativeGin};
  29. // static
  30. base::WeakPtr<JsBinding> JsBinding::Install(
  31. content::RenderFrame* render_frame,
  32. const std::u16string& js_object_name,
  33. base::WeakPtr<JsCommunication> js_communication) {
  34. CHECK(!js_object_name.empty())
  35. << "JavaScript wrapper name shouldn't be empty";
  36. v8::Isolate* isolate = blink::MainThreadIsolate();
  37. v8::HandleScope handle_scope(isolate);
  38. v8::Local<v8::Context> context =
  39. render_frame->GetWebFrame()->MainWorldScriptContext();
  40. if (context.IsEmpty())
  41. return nullptr;
  42. v8::Context::Scope context_scope(context);
  43. // The call to CreateHandle() takes ownership of `js_binding` (but only on
  44. // success).
  45. JsBinding* js_binding =
  46. new JsBinding(render_frame, js_object_name, js_communication);
  47. gin::Handle<JsBinding> bindings = gin::CreateHandle(isolate, js_binding);
  48. if (bindings.IsEmpty()) {
  49. delete js_binding;
  50. return nullptr;
  51. }
  52. v8::Local<v8::Object> global = context->Global();
  53. global
  54. ->CreateDataProperty(context,
  55. gin::StringToSymbol(isolate, js_object_name),
  56. bindings.ToV8())
  57. .Check();
  58. return js_binding->weak_ptr_factory_.GetWeakPtr();
  59. }
  60. JsBinding::JsBinding(content::RenderFrame* render_frame,
  61. const std::u16string& js_object_name,
  62. base::WeakPtr<JsCommunication> js_communication)
  63. : render_frame_(render_frame),
  64. js_object_name_(js_object_name),
  65. js_communication_(js_communication) {
  66. mojom::JsToBrowserMessaging* js_to_java_messaging =
  67. js_communication_->GetJsToJavaMessage(js_object_name_);
  68. if (js_to_java_messaging) {
  69. js_to_java_messaging->SetBrowserToJsMessaging(
  70. receiver_.BindNewEndpointAndPassRemote());
  71. }
  72. }
  73. JsBinding::~JsBinding() = default;
  74. void JsBinding::OnPostMessage(const std::u16string& message) {
  75. // If `js_communication_` is null, this object will soon be destroyed.
  76. if (!js_communication_)
  77. return;
  78. v8::Isolate* isolate = blink::MainThreadIsolate();
  79. v8::HandleScope handle_scope(isolate);
  80. blink::WebLocalFrame* web_frame = render_frame_->GetWebFrame();
  81. if (!web_frame)
  82. return;
  83. v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
  84. if (context.IsEmpty())
  85. return;
  86. v8::Context::Scope context_scope(context);
  87. // Setting verbose makes the exception get reported to the default
  88. // uncaught-exception handlers, rather than just being silently swallowed.
  89. v8::TryCatch try_catch(isolate);
  90. try_catch.SetVerbose(true);
  91. // Simulate MessageEvent's data property. See
  92. // https://html.spec.whatwg.org/multipage/comms.html#messageevent
  93. v8::Local<v8::Object> event =
  94. gin::DataObjectBuilder(isolate).Set("data", message).Build();
  95. v8::Local<v8::Value> argv[] = {event};
  96. v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked();
  97. v8::Local<v8::Function> on_message = GetOnMessage(isolate);
  98. if (!on_message.IsEmpty()) {
  99. web_frame->RequestExecuteV8Function(context, on_message, self, 1, argv, {});
  100. }
  101. // Copy the listeners so that if the listener modifies the list in some way
  102. // there isn't a UAF.
  103. std::vector<v8::Local<v8::Function>> listeners_copy;
  104. listeners_copy.reserve(listeners_.size());
  105. for (const auto& listener : listeners_) {
  106. listeners_copy.push_back(listener.Get(isolate));
  107. }
  108. for (const auto& listener : listeners_copy) {
  109. // Ensure the listener is still registered.
  110. if (base::Contains(listeners_, listener)) {
  111. web_frame->RequestExecuteV8Function(context, listener, self, 1, argv, {});
  112. }
  113. }
  114. }
  115. void JsBinding::ReleaseV8GlobalObjects() {
  116. listeners_.clear();
  117. on_message_.Reset();
  118. }
  119. gin::ObjectTemplateBuilder JsBinding::GetObjectTemplateBuilder(
  120. v8::Isolate* isolate) {
  121. return gin::Wrappable<JsBinding>::GetObjectTemplateBuilder(isolate)
  122. .SetMethod(kPostMessage, &JsBinding::PostMessage)
  123. .SetMethod(kAddEventListener, &JsBinding::AddEventListener)
  124. .SetMethod(kRemoveEventListener, &JsBinding::RemoveEventListener)
  125. .SetProperty(kOnMessage, &JsBinding::GetOnMessage,
  126. &JsBinding::SetOnMessage);
  127. }
  128. void JsBinding::PostMessage(gin::Arguments* args) {
  129. std::u16string message;
  130. if (!args->GetNext(&message)) {
  131. args->ThrowError();
  132. return;
  133. }
  134. std::vector<blink::MessagePortChannel> ports;
  135. std::vector<v8::Local<v8::Object>> objs;
  136. // If we get more than two arguments and the second argument is not an array
  137. // of ports, we can't process.
  138. if (args->Length() >= 2 && !args->GetNext(&objs)) {
  139. args->ThrowError();
  140. return;
  141. }
  142. for (auto& obj : objs) {
  143. absl::optional<blink::MessagePortChannel> port =
  144. blink::WebMessagePortConverter::DisentangleAndExtractMessagePortChannel(
  145. args->isolate(), obj);
  146. // If the port is null we should throw an exception.
  147. if (!port.has_value()) {
  148. args->ThrowError();
  149. return;
  150. }
  151. ports.emplace_back(port.value());
  152. }
  153. mojom::JsToBrowserMessaging* js_to_java_messaging =
  154. js_communication_ ? js_communication_->GetJsToJavaMessage(js_object_name_)
  155. : nullptr;
  156. if (js_to_java_messaging) {
  157. js_to_java_messaging->PostMessage(
  158. message, blink::MessagePortChannel::ReleaseHandles(ports));
  159. }
  160. }
  161. // AddEventListener() needs to match EventTarget's AddEventListener() in blink.
  162. // It takes |type|, |listener| parameters, we ignore the |options| parameter.
  163. // See https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener
  164. void JsBinding::AddEventListener(gin::Arguments* args) {
  165. std::string type;
  166. if (!args->GetNext(&type)) {
  167. args->ThrowError();
  168. return;
  169. }
  170. // We only support message event.
  171. if (type != "message")
  172. return;
  173. v8::Local<v8::Function> listener;
  174. if (!args->GetNext(&listener))
  175. return;
  176. // Should be at most 3 parameters.
  177. if (args->Length() > 3) {
  178. args->ThrowError();
  179. return;
  180. }
  181. if (base::Contains(listeners_, listener))
  182. return;
  183. v8::Local<v8::Context> context = args->GetHolderCreationContext();
  184. listeners_.push_back(
  185. v8::Global<v8::Function>(context->GetIsolate(), listener));
  186. }
  187. // RemoveEventListener() needs to match EventTarget's RemoveEventListener() in
  188. // blink. It takes |type|, |listener| parameters, we ignore |options| parameter.
  189. // See https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener
  190. void JsBinding::RemoveEventListener(gin::Arguments* args) {
  191. std::string type;
  192. if (!args->GetNext(&type)) {
  193. args->ThrowError();
  194. return;
  195. }
  196. // We only support message event.
  197. if (type != "message")
  198. return;
  199. v8::Local<v8::Function> listener;
  200. if (!args->GetNext(&listener))
  201. return;
  202. // Should be at most 3 parameters.
  203. if (args->Length() > 3) {
  204. args->ThrowError();
  205. return;
  206. }
  207. auto iter = std::find(listeners_.begin(), listeners_.end(), listener);
  208. if (iter == listeners_.end())
  209. return;
  210. listeners_.erase(iter);
  211. }
  212. v8::Local<v8::Function> JsBinding::GetOnMessage(v8::Isolate* isolate) {
  213. return on_message_.Get(isolate);
  214. }
  215. void JsBinding::SetOnMessage(v8::Isolate* isolate, v8::Local<v8::Value> value) {
  216. if (value->IsFunction())
  217. on_message_.Reset(isolate, value.As<v8::Function>());
  218. else
  219. on_message_.Reset();
  220. }
  221. } // namespace js_injection