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