summaryrefslogtreecommitdiff
path: root/chromium/components/js_injection/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/js_injection/renderer')
-rw-r--r--chromium/components/js_injection/renderer/BUILD.gn25
-rw-r--r--chromium/components/js_injection/renderer/DEPS4
-rw-r--r--chromium/components/js_injection/renderer/js_binding.cc246
-rw-r--r--chromium/components/js_injection/renderer/js_binding.h87
-rw-r--r--chromium/components/js_injection/renderer/js_communication.cc133
-rw-r--r--chromium/components/js_injection/renderer/js_communication.h76
6 files changed, 571 insertions, 0 deletions
diff --git a/chromium/components/js_injection/renderer/BUILD.gn b/chromium/components/js_injection/renderer/BUILD.gn
new file mode 100644
index 00000000000..3cdd5294923
--- /dev/null
+++ b/chromium/components/js_injection/renderer/BUILD.gn
@@ -0,0 +1,25 @@
+# Copyright 2020 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.
+
+source_set("renderer") {
+ sources = [
+ "js_binding.cc",
+ "js_binding.h",
+ "js_communication.cc",
+ "js_communication.h",
+ ]
+
+ deps = [
+ "//base",
+ "//components/js_injection/common",
+ "//components/js_injection/common:common_mojom",
+ "//content/public/common",
+ "//content/public/renderer",
+ "//gin",
+ "//mojo/public/cpp/bindings",
+ "//third_party/blink/public:blink",
+ "//url",
+ "//v8",
+ ]
+}
diff --git a/chromium/components/js_injection/renderer/DEPS b/chromium/components/js_injection/renderer/DEPS
new file mode 100644
index 00000000000..33ec23100fe
--- /dev/null
+++ b/chromium/components/js_injection/renderer/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+gin",
+ "+v8/include/v8.h",
+]
diff --git a/chromium/components/js_injection/renderer/js_binding.cc b/chromium/components/js_injection/renderer/js_binding.cc
new file mode 100644
index 00000000000..f4c5798990c
--- /dev/null
+++ b/chromium/components/js_injection/renderer/js_binding.cc
@@ -0,0 +1,246 @@
+// 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/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 base::string16& 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 base::string16& 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 base::string16& 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) {
+ base::string16 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) {
+ base::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
diff --git a/chromium/components/js_injection/renderer/js_binding.h b/chromium/components/js_injection/renderer/js_binding.h
new file mode 100644
index 00000000000..1fca33f9110
--- /dev/null
+++ b/chromium/components/js_injection/renderer/js_binding.h
@@ -0,0 +1,87 @@
+// 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.
+
+#ifndef COMPONENTS_JS_INJECTION_RENDERER_JS_BINDING_H_
+#define COMPONENTS_JS_INJECTION_RENDERER_JS_BINDING_H_
+
+#include <string>
+
+#include "base/auto_reset.h"
+#include "base/strings/string16.h"
+#include "components/js_injection/common/interfaces.mojom.h"
+#include "gin/arguments.h"
+#include "gin/wrappable.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+
+namespace v8 {
+template <typename T>
+class Global;
+class Function;
+} // namespace v8
+
+namespace content {
+class RenderFrame;
+} // namespace content
+
+namespace js_injection {
+class JsCommunication;
+// A gin::Wrappable class used for providing JavaScript API. We will inject the
+// object of this class to JavaScript world in JsCommunication.
+// JsCommunication will own at most one instance of this class. When the
+// RenderFrame gone or another DidClearWindowObject comes, the instance will be
+// destroyed.
+class JsBinding : public gin::Wrappable<JsBinding>,
+ public mojom::BrowserToJsMessaging {
+ public:
+ static gin::WrapperInfo kWrapperInfo;
+
+ static std::unique_ptr<JsBinding> Install(
+ content::RenderFrame* render_frame,
+ const base::string16& js_object_name,
+ JsCommunication* js_java_configurator);
+
+ // mojom::BrowserToJsMessaging implementation.
+ void OnPostMessage(const base::string16& message) override;
+
+ void ReleaseV8GlobalObjects();
+
+ ~JsBinding() final;
+
+ private:
+ explicit JsBinding(content::RenderFrame* render_frame,
+ const base::string16& js_object_name,
+ JsCommunication* js_java_configurator);
+
+ // gin::Wrappable implementation
+ gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
+ v8::Isolate* isolate) final;
+
+ // For jsObject.postMessage(message[, ports]) JavaScript API.
+ void PostMessage(gin::Arguments* args);
+ // For jsObject.addEventListener("message", listener) JavaScript API.
+ void AddEventListener(gin::Arguments* args);
+ // For jsObject.removeEventListener("message", listener) JavaScript API.
+ void RemoveEventListener(gin::Arguments* args);
+ // For get jsObject.onmessage.
+ v8::Local<v8::Function> GetOnMessage(v8::Isolate* isolate);
+ // For set jsObject.onmessage.
+ void SetOnMessage(v8::Isolate* isolate, v8::Local<v8::Value> value);
+
+ content::RenderFrame* render_frame_;
+ base::string16 js_object_name_;
+ v8::Global<v8::Function> on_message_;
+ std::vector<v8::Global<v8::Function>> listeners_;
+ // |js_java_configurator| owns JsBinding objects, so it will out live
+ // JsBinding's life cycle, it is safe to access it.
+ JsCommunication* js_java_configurator_;
+
+ mojo::AssociatedReceiver<mojom::BrowserToJsMessaging> receiver_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(JsBinding);
+};
+
+} // namespace js_injection
+
+#endif // COMPONENTS_JS_INJECTION_RENDERER_JS_BINDING_H_
diff --git a/chromium/components/js_injection/renderer/js_communication.cc b/chromium/components/js_injection/renderer/js_communication.cc
new file mode 100644
index 00000000000..dd648297f20
--- /dev/null
+++ b/chromium/components/js_injection/renderer/js_communication.cc
@@ -0,0 +1,133 @@
+// 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_communication.h"
+
+#include "components/js_injection/common/origin_matcher.h"
+#include "components/js_injection/renderer/js_binding.h"
+#include "content/public/common/isolated_world_ids.h"
+#include "content/public/renderer/render_frame.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
+#include "third_party/blink/public/platform/task_type.h"
+#include "third_party/blink/public/web/web_local_frame.h"
+#include "third_party/blink/public/web/web_script_source.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace js_injection {
+
+struct JsCommunication::JsObjectInfo {
+ OriginMatcher origin_matcher;
+ mojo::AssociatedRemote<mojom::JsToBrowserMessaging> js_to_java_messaging;
+};
+
+struct JsCommunication::DocumentStartJavaScript {
+ OriginMatcher origin_matcher;
+ blink::WebString script;
+ int32_t script_id;
+};
+
+JsCommunication::JsCommunication(content::RenderFrame* render_frame)
+ : RenderFrameObserver(render_frame),
+ RenderFrameObserverTracker<JsCommunication>(render_frame) {
+ render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
+ base::BindRepeating(&JsCommunication::BindPendingReceiver,
+ base::Unretained(this)));
+}
+
+JsCommunication::~JsCommunication() = default;
+
+void JsCommunication::SetJsObjects(
+ std::vector<mojom::JsObjectPtr> js_object_ptrs) {
+ JsObjectMap js_objects;
+ for (const auto& js_object : js_object_ptrs) {
+ const auto& js_object_info_pair = js_objects.insert(
+ {js_object->js_object_name, std::make_unique<JsObjectInfo>()});
+ JsObjectInfo* js_object_info = js_object_info_pair.first->second.get();
+ js_object_info->origin_matcher = js_object->origin_matcher;
+ js_object_info->js_to_java_messaging =
+ mojo::AssociatedRemote<mojom::JsToBrowserMessaging>(
+ std::move(js_object->js_to_browser_messaging));
+ }
+ js_objects_.swap(js_objects);
+}
+
+void JsCommunication::AddDocumentStartScript(
+ mojom::DocumentStartJavaScriptPtr script_ptr) {
+ DocumentStartJavaScript* script = new DocumentStartJavaScript{
+ script_ptr->origin_matcher,
+ blink::WebString::FromUTF16(script_ptr->script), script_ptr->script_id};
+ scripts_.push_back(std::unique_ptr<DocumentStartJavaScript>(script));
+}
+
+void JsCommunication::RemoveDocumentStartScript(int32_t script_id) {
+ for (auto it = scripts_.begin(); it != scripts_.end(); ++it) {
+ if ((*it)->script_id == script_id) {
+ scripts_.erase(it);
+ break;
+ }
+ }
+}
+
+void JsCommunication::DidClearWindowObject() {
+ if (inside_did_clear_window_object_)
+ return;
+
+ base::AutoReset<bool> flag_entry(&inside_did_clear_window_object_, true);
+
+ url::Origin frame_origin =
+ url::Origin(render_frame()->GetWebFrame()->GetSecurityOrigin());
+ std::vector<std::unique_ptr<JsBinding>> js_bindings;
+ js_bindings.reserve(js_objects_.size());
+ for (const auto& js_object : js_objects_) {
+ if (!js_object.second->origin_matcher.Matches(frame_origin))
+ continue;
+ js_bindings.push_back(
+ JsBinding::Install(render_frame(), js_object.first, this));
+ }
+ js_bindings_.swap(js_bindings);
+}
+
+void JsCommunication::WillReleaseScriptContext(v8::Local<v8::Context> context,
+ int32_t world_id) {
+ // We created v8 global objects only in the main world, should clear them only
+ // when this is for main world.
+ if (world_id != content::ISOLATED_WORLD_ID_GLOBAL)
+ return;
+
+ for (const auto& js_binding : js_bindings_)
+ js_binding->ReleaseV8GlobalObjects();
+}
+
+void JsCommunication::OnDestruct() {
+ delete this;
+}
+
+void JsCommunication::RunScriptsAtDocumentStart() {
+ url::Origin frame_origin =
+ url::Origin(render_frame()->GetWebFrame()->GetSecurityOrigin());
+ for (const auto& script : scripts_) {
+ if (!script->origin_matcher.Matches(frame_origin))
+ continue;
+ render_frame()->GetWebFrame()->ExecuteScript(
+ blink::WebScriptSource(script->script));
+ }
+}
+
+void JsCommunication::BindPendingReceiver(
+ mojo::PendingAssociatedReceiver<mojom::JsCommunication> pending_receiver) {
+ receiver_.Bind(std::move(pending_receiver),
+ render_frame()->GetTaskRunner(
+ blink::TaskType::kInternalNavigationAssociated));
+}
+
+mojom::JsToBrowserMessaging* JsCommunication::GetJsToJavaMessage(
+ const base::string16& js_object_name) {
+ auto iterator = js_objects_.find(js_object_name);
+ if (iterator == js_objects_.end())
+ return nullptr;
+ return iterator->second->js_to_java_messaging.get();
+}
+
+} // namespace js_injection
diff --git a/chromium/components/js_injection/renderer/js_communication.h b/chromium/components/js_injection/renderer/js_communication.h
new file mode 100644
index 00000000000..8986e784c65
--- /dev/null
+++ b/chromium/components/js_injection/renderer/js_communication.h
@@ -0,0 +1,76 @@
+// 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.
+
+#ifndef COMPONENTS_JS_INJECTION_RENDERER_JS_COMMUNICATION_H_
+#define COMPONENTS_JS_INJECTION_RENDERER_JS_COMMUNICATION_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "components/js_injection/common/interfaces.mojom.h"
+#include "content/public/renderer/render_frame_observer.h"
+#include "content/public/renderer/render_frame_observer_tracker.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "third_party/blink/public/platform/web_string.h"
+
+namespace content {
+class RenderFrame;
+}
+
+namespace js_injection {
+
+class JsBinding;
+
+class JsCommunication
+ : public mojom::JsCommunication,
+ public content::RenderFrameObserver,
+ public content::RenderFrameObserverTracker<JsCommunication> {
+ public:
+ explicit JsCommunication(content::RenderFrame* render_frame);
+ ~JsCommunication() override;
+
+ // mojom::JsCommunication implementation
+ void SetJsObjects(std::vector<mojom::JsObjectPtr> js_object_ptrs) override;
+ void AddDocumentStartScript(
+ mojom::DocumentStartJavaScriptPtr script_ptr) override;
+ void RemoveDocumentStartScript(int32_t script_id) override;
+
+ // RenderFrameObserver implementation
+ void DidClearWindowObject() override;
+ void WillReleaseScriptContext(v8::Local<v8::Context> context,
+ int32_t world_id) override;
+ void OnDestruct() override;
+
+ void RunScriptsAtDocumentStart();
+
+ mojom::JsToBrowserMessaging* GetJsToJavaMessage(
+ const base::string16& js_object_name);
+
+ private:
+ struct JsObjectInfo;
+ struct DocumentStartJavaScript;
+
+ void BindPendingReceiver(
+ mojo::PendingAssociatedReceiver<mojom::JsCommunication> pending_receiver);
+
+ using JsObjectMap = std::map<base::string16, std::unique_ptr<JsObjectInfo>>;
+ JsObjectMap js_objects_;
+
+ // In some cases DidClearWindowObject will be called twice in a row, we need
+ // to prevent doing multiple injection in that case.
+ bool inside_did_clear_window_object_ = false;
+
+ std::vector<std::unique_ptr<DocumentStartJavaScript>> scripts_;
+ std::vector<std::unique_ptr<JsBinding>> js_bindings_;
+
+ // Associated with legacy IPC channel.
+ mojo::AssociatedReceiver<mojom::JsCommunication> receiver_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(JsCommunication);
+};
+
+} // namespace js_injection
+
+#endif // COMPONENTS_JS_INJECTION_RENDERER_JS_COMMUNICATION_H_