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