| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/js_injection/renderer/js_binding.h" |
| |
| #include <vector> |
| |
| #include "base/containers/contains.h" |
| #include "base/strings/string_util.h" |
| #include "components/js_injection/renderer/js_communication.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "gin/data_object_builder.h" |
| #include "gin/handle.h" |
| #include "gin/object_template_builder.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" |
| #include "third_party/blink/public/common/messaging/message_port_channel.h" |
| #include "third_party/blink/public/platform/web_security_origin.h" |
| #include "third_party/blink/public/web/blink.h" |
| #include "third_party/blink/public/web/web_frame.h" |
| #include "third_party/blink/public/web/web_local_frame.h" |
| #include "third_party/blink/public/web/web_message_port_converter.h" |
| #include "v8/include/v8.h" |
| |
| namespace { |
| constexpr char kPostMessage[] = "postMessage"; |
| constexpr char kOnMessage[] = "onmessage"; |
| constexpr char kAddEventListener[] = "addEventListener"; |
| constexpr char kRemoveEventListener[] = "removeEventListener"; |
| } // anonymous namespace |
| |
| namespace js_injection { |
| |
| gin::WrapperInfo JsBinding::kWrapperInfo = {gin::kEmbedderNativeGin}; |
| |
| // static |
| std::unique_ptr<JsBinding> JsBinding::Install( |
| content::RenderFrame* render_frame, |
| const std::u16string& js_object_name, |
| JsCommunication* js_java_configurator) { |
| CHECK(!js_object_name.empty()) |
| << "JavaScript wrapper name shouldn't be empty"; |
| |
| v8::Isolate* isolate = blink::MainThreadIsolate(); |
| v8::HandleScope handle_scope(isolate); |
| v8::Local<v8::Context> context = |
| render_frame->GetWebFrame()->MainWorldScriptContext(); |
| if (context.IsEmpty()) |
| return nullptr; |
| |
| v8::Context::Scope context_scope(context); |
| std::unique_ptr<JsBinding> js_binding( |
| new JsBinding(render_frame, js_object_name, js_java_configurator)); |
| gin::Handle<JsBinding> bindings = |
| gin::CreateHandle(isolate, js_binding.get()); |
| if (bindings.IsEmpty()) |
| return nullptr; |
| |
| v8::Local<v8::Object> global = context->Global(); |
| global |
| ->CreateDataProperty(context, |
| gin::StringToSymbol(isolate, js_object_name), |
| bindings.ToV8()) |
| .Check(); |
| |
| return js_binding; |
| } |
| |
| JsBinding::JsBinding(content::RenderFrame* render_frame, |
| const std::u16string& js_object_name, |
| JsCommunication* js_java_configurator) |
| : render_frame_(render_frame), |
| js_object_name_(js_object_name), |
| js_java_configurator_(js_java_configurator) { |
| mojom::JsToBrowserMessaging* js_to_java_messaging = |
| js_java_configurator_->GetJsToJavaMessage(js_object_name_); |
| if (js_to_java_messaging) { |
| js_to_java_messaging->SetBrowserToJsMessaging( |
| receiver_.BindNewEndpointAndPassRemote()); |
| } |
| } |
| |
| JsBinding::~JsBinding() = default; |
| |
| void JsBinding::OnPostMessage(const std::u16string& message) { |
| v8::Isolate* isolate = blink::MainThreadIsolate(); |
| v8::HandleScope handle_scope(isolate); |
| |
| blink::WebLocalFrame* web_frame = render_frame_->GetWebFrame(); |
| if (!web_frame) |
| return; |
| |
| v8::Local<v8::Context> context = web_frame->MainWorldScriptContext(); |
| if (context.IsEmpty()) |
| return; |
| |
| v8::Context::Scope context_scope(context); |
| // Setting verbose makes the exception get reported to the default |
| // uncaught-exception handlers, rather than just being silently swallowed. |
| v8::TryCatch try_catch(isolate); |
| try_catch.SetVerbose(true); |
| |
| // Simulate MessageEvent's data property. See |
| // https://html.spec.whatwg.org/multipage/comms.html#messageevent |
| v8::Local<v8::Object> event = |
| gin::DataObjectBuilder(isolate).Set("data", message).Build(); |
| v8::Local<v8::Value> argv[] = {event}; |
| |
| v8::Local<v8::Object> self = GetWrapper(isolate).ToLocalChecked(); |
| v8::Local<v8::Function> on_message = GetOnMessage(isolate); |
| if (!on_message.IsEmpty()) { |
| web_frame->RequestExecuteV8Function(context, on_message, self, 1, argv, |
| nullptr); |
| } |
| |
| for (const auto& listener : listeners_) { |
| web_frame->RequestExecuteV8Function(context, listener.Get(isolate), self, 1, |
| argv, nullptr); |
| } |
| } |
| |
| void JsBinding::ReleaseV8GlobalObjects() { |
| listeners_.clear(); |
| on_message_.Reset(); |
| } |
| |
| gin::ObjectTemplateBuilder JsBinding::GetObjectTemplateBuilder( |
| v8::Isolate* isolate) { |
| return gin::Wrappable<JsBinding>::GetObjectTemplateBuilder(isolate) |
| .SetMethod(kPostMessage, &JsBinding::PostMessage) |
| .SetMethod(kAddEventListener, &JsBinding::AddEventListener) |
| .SetMethod(kRemoveEventListener, &JsBinding::RemoveEventListener) |
| .SetProperty(kOnMessage, &JsBinding::GetOnMessage, |
| &JsBinding::SetOnMessage); |
| } |
| |
| void JsBinding::PostMessage(gin::Arguments* args) { |
| std::u16string message; |
| if (!args->GetNext(&message)) { |
| args->ThrowError(); |
| return; |
| } |
| |
| std::vector<blink::MessagePortChannel> ports; |
| std::vector<v8::Local<v8::Object>> objs; |
| // If we get more than two arguments and the second argument is not an array |
| // of ports, we can't process. |
| if (args->Length() >= 2 && !args->GetNext(&objs)) { |
| args->ThrowError(); |
| return; |
| } |
| |
| for (auto& obj : objs) { |
| absl::optional<blink::MessagePortChannel> port = |
| blink::WebMessagePortConverter::DisentangleAndExtractMessagePortChannel( |
| args->isolate(), obj); |
| // If the port is null we should throw an exception. |
| if (!port.has_value()) { |
| args->ThrowError(); |
| return; |
| } |
| ports.emplace_back(port.value()); |
| } |
| |
| mojom::JsToBrowserMessaging* js_to_java_messaging = |
| js_java_configurator_->GetJsToJavaMessage(js_object_name_); |
| if (js_to_java_messaging) { |
| js_to_java_messaging->PostMessage( |
| message, blink::MessagePortChannel::ReleaseHandles(ports)); |
| } |
| } |
| |
| // AddEventListener() needs to match EventTarget's AddEventListener() in blink. |
| // It takes |type|, |listener| parameters, we ignore the |options| parameter. |
| // See https://dom.spec.whatwg.org/#dom-eventtarget-addeventlistener |
| void JsBinding::AddEventListener(gin::Arguments* args) { |
| std::string type; |
| if (!args->GetNext(&type)) { |
| args->ThrowError(); |
| return; |
| } |
| |
| // We only support message event. |
| if (type != "message") |
| return; |
| |
| v8::Local<v8::Function> listener; |
| if (!args->GetNext(&listener)) |
| return; |
| |
| // Should be at most 3 parameters. |
| if (args->Length() > 3) { |
| args->ThrowError(); |
| return; |
| } |
| |
| if (base::Contains(listeners_, listener)) |
| return; |
| |
| v8::Local<v8::Context> context = args->GetHolderCreationContext(); |
| listeners_.push_back( |
| v8::Global<v8::Function>(context->GetIsolate(), listener)); |
| } |
| |
| // RemoveEventListener() needs to match EventTarget's RemoveEventListener() in |
| // blink. It takes |type|, |listener| parameters, we ignore |options| parameter. |
| // See https://dom.spec.whatwg.org/#dom-eventtarget-removeeventlistener |
| void JsBinding::RemoveEventListener(gin::Arguments* args) { |
| std::string type; |
| if (!args->GetNext(&type)) { |
| args->ThrowError(); |
| return; |
| } |
| |
| // We only support message event. |
| if (type != "message") |
| return; |
| |
| v8::Local<v8::Function> listener; |
| if (!args->GetNext(&listener)) |
| return; |
| |
| // Should be at most 3 parameters. |
| if (args->Length() > 3) { |
| args->ThrowError(); |
| return; |
| } |
| |
| auto iter = std::find(listeners_.begin(), listeners_.end(), listener); |
| if (iter == listeners_.end()) |
| return; |
| |
| listeners_.erase(iter); |
| } |
| |
| v8::Local<v8::Function> JsBinding::GetOnMessage(v8::Isolate* isolate) { |
| return on_message_.Get(isolate); |
| } |
| |
| void JsBinding::SetOnMessage(v8::Isolate* isolate, v8::Local<v8::Value> value) { |
| if (value->IsFunction()) |
| on_message_.Reset(isolate, value.As<v8::Function>()); |
| else |
| on_message_.Reset(); |
| } |
| |
| } // namespace js_injection |