summaryrefslogtreecommitdiff
path: root/chromium/components/js_injection
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/js_injection')
-rw-r--r--chromium/components/js_injection/DEPS6
-rw-r--r--chromium/components/js_injection/OWNERS5
-rw-r--r--chromium/components/js_injection/README.md3
-rw-r--r--chromium/components/js_injection/browser/BUILD.gn27
-rw-r--r--chromium/components/js_injection/browser/js_communication_host.cc227
-rw-r--r--chromium/components/js_injection/browser/js_communication_host.h110
-rw-r--r--chromium/components/js_injection/browser/js_to_browser_messaging.cc129
-rw-r--r--chromium/components/js_injection/browser/js_to_browser_messaging.h66
-rw-r--r--chromium/components/js_injection/browser/web_message.cc13
-rw-r--r--chromium/components/js_injection/browser/web_message.h26
-rw-r--r--chromium/components/js_injection/browser/web_message_host.h24
-rw-r--r--chromium/components/js_injection/browser/web_message_host_factory.h34
-rw-r--r--chromium/components/js_injection/browser/web_message_reply_proxy.h25
-rw-r--r--chromium/components/js_injection/common/BUILD.gn71
-rw-r--r--chromium/components/js_injection/common/OWNERS4
-rw-r--r--chromium/components/js_injection/common/interfaces.mojom65
-rw-r--r--chromium/components/js_injection/common/origin_matcher.cc125
-rw-r--r--chromium/components/js_injection/common/origin_matcher.h75
-rw-r--r--chromium/components/js_injection/common/origin_matcher.mojom22
-rw-r--r--chromium/components/js_injection/common/origin_matcher_internal.cc125
-rw-r--r--chromium/components/js_injection/common/origin_matcher_internal.h89
-rw-r--r--chromium/components/js_injection/common/origin_matcher_mojom_traits.cc85
-rw-r--r--chromium/components/js_injection/common/origin_matcher_mojom_traits.h45
-rw-r--r--chromium/components/js_injection/common/origin_matcher_unittest.cc404
-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
30 files changed, 2376 insertions, 0 deletions
diff --git a/chromium/components/js_injection/DEPS b/chromium/components/js_injection/DEPS
new file mode 100644
index 00000000000..74dd110e2d9
--- /dev/null
+++ b/chromium/components/js_injection/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+content/public",
+ "+mojo/public",
+ "+net",
+ "+third_party/blink/public",
+]
diff --git a/chromium/components/js_injection/OWNERS b/chromium/components/js_injection/OWNERS
new file mode 100644
index 00000000000..e13cff503f9
--- /dev/null
+++ b/chromium/components/js_injection/OWNERS
@@ -0,0 +1,5 @@
+ctzsm@chromium.org
+sky@chromium.org
+
+# TEAM: android-webview-dev@chromium.org
+# COMPONENT: Mobile>WebView
diff --git a/chromium/components/js_injection/README.md b/chromium/components/js_injection/README.md
new file mode 100644
index 00000000000..1c051b8d2ba
--- /dev/null
+++ b/chromium/components/js_injection/README.md
@@ -0,0 +1,3 @@
+This directory contains code used by WebView and WebLayer to inject
+javascript from the browser to the renderer, as well as a simple message
+port style API.
diff --git a/chromium/components/js_injection/browser/BUILD.gn b/chromium/components/js_injection/browser/BUILD.gn
new file mode 100644
index 00000000000..f2b8216f66a
--- /dev/null
+++ b/chromium/components/js_injection/browser/BUILD.gn
@@ -0,0 +1,27 @@
+# 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("browser") {
+ sources = [
+ "js_communication_host.cc",
+ "js_communication_host.h",
+ "js_to_browser_messaging.cc",
+ "js_to_browser_messaging.h",
+ "web_message.cc",
+ "web_message.h",
+ "web_message_host.h",
+ "web_message_host_factory.h",
+ "web_message_reply_proxy.h",
+ ]
+
+ deps = [
+ "//base",
+ "//components/js_injection/common",
+ "//components/js_injection/common:common_mojom",
+ "//content/public/browser",
+ "//mojo/public/cpp/bindings",
+ "//third_party/blink/public/common",
+ "//url",
+ ]
+}
diff --git a/chromium/components/js_injection/browser/js_communication_host.cc b/chromium/components/js_injection/browser/js_communication_host.cc
new file mode 100644
index 00000000000..f4115b3aa86
--- /dev/null
+++ b/chromium/components/js_injection/browser/js_communication_host.cc
@@ -0,0 +1,227 @@
+// 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/browser/js_communication_host.h"
+
+#include "base/bind.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/js_injection/browser/js_to_browser_messaging.h"
+#include "components/js_injection/browser/web_message_host.h"
+#include "components/js_injection/browser/web_message_host_factory.h"
+#include "components/js_injection/common/origin_matcher.h"
+#include "components/js_injection/common/origin_matcher_mojom_traits.h"
+#include "content/public/browser/web_contents.h"
+#include "mojo/public/cpp/bindings/pending_associated_remote.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+
+namespace js_injection {
+namespace {
+
+std::string ConvertToNativeAllowedOriginRulesWithSanityCheck(
+ const std::vector<std::string>& allowed_origin_rules_strings,
+ OriginMatcher& allowed_origin_rules) {
+ for (auto& rule : allowed_origin_rules_strings) {
+ if (!allowed_origin_rules.AddRuleFromString(rule))
+ return "allowedOriginRules " + rule + " is invalid";
+ }
+ return std::string();
+}
+
+} // namespace
+
+struct JsObject {
+ JsObject(const base::string16& name,
+ OriginMatcher allowed_origin_rules,
+ std::unique_ptr<WebMessageHostFactory> factory)
+ : name(std::move(name)),
+ allowed_origin_rules(std::move(allowed_origin_rules)),
+ factory(std::move(factory)) {}
+ JsObject(JsObject&& other) = delete;
+ JsObject& operator=(JsObject&& other) = delete;
+ ~JsObject() = default;
+
+ base::string16 name;
+ OriginMatcher allowed_origin_rules;
+ std::unique_ptr<WebMessageHostFactory> factory;
+};
+
+struct DocumentStartJavaScript {
+ DocumentStartJavaScript(base::string16 script,
+ OriginMatcher allowed_origin_rules,
+ int32_t script_id)
+ : script_(std::move(script)),
+ allowed_origin_rules_(allowed_origin_rules),
+ script_id_(script_id) {}
+
+ DocumentStartJavaScript(DocumentStartJavaScript&) = delete;
+ DocumentStartJavaScript& operator=(DocumentStartJavaScript&) = delete;
+ DocumentStartJavaScript(DocumentStartJavaScript&&) = default;
+ DocumentStartJavaScript& operator=(DocumentStartJavaScript&&) = default;
+
+ base::string16 script_;
+ OriginMatcher allowed_origin_rules_;
+ int32_t script_id_;
+};
+
+JsCommunicationHost::AddScriptResult::AddScriptResult() = default;
+JsCommunicationHost::AddScriptResult::AddScriptResult(
+ const JsCommunicationHost::AddScriptResult&) = default;
+JsCommunicationHost::AddScriptResult&
+JsCommunicationHost::AddScriptResult::operator=(
+ const JsCommunicationHost::AddScriptResult&) = default;
+JsCommunicationHost::AddScriptResult::~AddScriptResult() = default;
+
+JsCommunicationHost::JsCommunicationHost(content::WebContents* web_contents)
+ : content::WebContentsObserver(web_contents) {}
+
+JsCommunicationHost::~JsCommunicationHost() = default;
+
+JsCommunicationHost::AddScriptResult
+JsCommunicationHost::AddDocumentStartJavaScript(
+ const base::string16& script,
+ const std::vector<std::string>& allowed_origin_rules) {
+ OriginMatcher origin_matcher;
+ std::string error_message = ConvertToNativeAllowedOriginRulesWithSanityCheck(
+ allowed_origin_rules, origin_matcher);
+ AddScriptResult result;
+ if (!error_message.empty()) {
+ result.error_message = std::move(error_message);
+ return result;
+ }
+
+ scripts_.emplace_back(script, origin_matcher, next_script_id_++);
+
+ web_contents()->ForEachFrame(base::BindRepeating(
+ &JsCommunicationHost::NotifyFrameForAddDocumentStartJavaScript,
+ base::Unretained(this), &*scripts_.rbegin()));
+ result.script_id = scripts_.rbegin()->script_id_;
+ return result;
+}
+
+bool JsCommunicationHost::RemoveDocumentStartJavaScript(int script_id) {
+ for (auto it = scripts_.begin(); it != scripts_.end(); ++it) {
+ if (it->script_id_ == script_id) {
+ scripts_.erase(it);
+ web_contents()->ForEachFrame(base::BindRepeating(
+ &JsCommunicationHost::NotifyFrameForRemoveDocumentStartJavaScript,
+ base::Unretained(this), script_id));
+ return true;
+ }
+ }
+ return false;
+}
+
+base::string16 JsCommunicationHost::AddWebMessageHostFactory(
+ std::unique_ptr<WebMessageHostFactory> factory,
+ const base::string16& js_object_name,
+ const std::vector<std::string>& allowed_origin_rules) {
+ OriginMatcher origin_matcher;
+ std::string error_message = ConvertToNativeAllowedOriginRulesWithSanityCheck(
+ allowed_origin_rules, origin_matcher);
+ if (!error_message.empty())
+ return base::UTF8ToUTF16(error_message);
+
+ for (const auto& js_object : js_objects_) {
+ if (js_object->name == js_object_name) {
+ return base::ASCIIToUTF16("jsObjectName ") + js_object->name +
+ base::ASCIIToUTF16(" was already added.");
+ }
+ }
+
+ js_objects_.push_back(std::make_unique<JsObject>(
+ js_object_name, origin_matcher, std::move(factory)));
+
+ web_contents()->ForEachFrame(base::BindRepeating(
+ &JsCommunicationHost::NotifyFrameForWebMessageListener,
+ base::Unretained(this)));
+ return base::string16();
+}
+
+void JsCommunicationHost::RemoveWebMessageHostFactory(
+ const base::string16& js_object_name) {
+ for (auto iterator = js_objects_.begin(); iterator != js_objects_.end();
+ ++iterator) {
+ if ((*iterator)->name == js_object_name) {
+ js_objects_.erase(iterator);
+ web_contents()->ForEachFrame(base::BindRepeating(
+ &JsCommunicationHost::NotifyFrameForWebMessageListener,
+ base::Unretained(this)));
+ break;
+ }
+ }
+}
+
+std::vector<JsCommunicationHost::RegisteredFactory>
+JsCommunicationHost::GetWebMessageHostFactories() {
+ const size_t num_objects = js_objects_.size();
+ std::vector<RegisteredFactory> factories(num_objects);
+ for (size_t i = 0; i < num_objects; ++i) {
+ factories[i].js_name = js_objects_[i]->name;
+ factories[i].allowed_origin_rules = js_objects_[i]->allowed_origin_rules;
+ factories[i].factory = js_objects_[i]->factory.get();
+ }
+ return factories;
+}
+
+void JsCommunicationHost::RenderFrameCreated(
+ content::RenderFrameHost* render_frame_host) {
+ NotifyFrameForWebMessageListener(render_frame_host);
+ NotifyFrameForAllDocumentStartJavaScripts(render_frame_host);
+}
+
+void JsCommunicationHost::RenderFrameDeleted(
+ content::RenderFrameHost* render_frame_host) {
+ js_to_browser_messagings_.erase(render_frame_host);
+}
+
+void JsCommunicationHost::NotifyFrameForAllDocumentStartJavaScripts(
+ content::RenderFrameHost* render_frame_host) {
+ for (const auto& script : scripts_) {
+ NotifyFrameForAddDocumentStartJavaScript(&script, render_frame_host);
+ }
+}
+
+void JsCommunicationHost::NotifyFrameForWebMessageListener(
+ content::RenderFrameHost* render_frame_host) {
+ mojo::AssociatedRemote<mojom::JsCommunication> configurator_remote;
+ render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
+ &configurator_remote);
+ std::vector<mojom::JsObjectPtr> js_objects;
+ js_objects.reserve(js_objects_.size());
+ for (const auto& js_object : js_objects_) {
+ mojo::PendingAssociatedRemote<mojom::JsToBrowserMessaging> pending_remote;
+ js_to_browser_messagings_[render_frame_host].emplace_back(
+ std::make_unique<JsToBrowserMessaging>(
+ render_frame_host,
+ pending_remote.InitWithNewEndpointAndPassReceiver(),
+ js_object->factory.get(), js_object->allowed_origin_rules));
+ js_objects.push_back(mojom::JsObject::New(js_object->name,
+ std::move(pending_remote),
+ js_object->allowed_origin_rules));
+ }
+ configurator_remote->SetJsObjects(std::move(js_objects));
+}
+
+void JsCommunicationHost::NotifyFrameForAddDocumentStartJavaScript(
+ const DocumentStartJavaScript* script,
+ content::RenderFrameHost* render_frame_host) {
+ DCHECK(script);
+ mojo::AssociatedRemote<mojom::JsCommunication> configurator_remote;
+ render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
+ &configurator_remote);
+ configurator_remote->AddDocumentStartScript(
+ mojom::DocumentStartJavaScript::New(script->script_id_, script->script_,
+ script->allowed_origin_rules_));
+}
+
+void JsCommunicationHost::NotifyFrameForRemoveDocumentStartJavaScript(
+ int32_t script_id,
+ content::RenderFrameHost* render_frame_host) {
+ mojo::AssociatedRemote<mojom::JsCommunication> configurator_remote;
+ render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
+ &configurator_remote);
+ configurator_remote->RemoveDocumentStartScript(script_id);
+}
+
+} // namespace js_injection
diff --git a/chromium/components/js_injection/browser/js_communication_host.h b/chromium/components/js_injection/browser/js_communication_host.h
new file mode 100644
index 00000000000..3cd096d163e
--- /dev/null
+++ b/chromium/components/js_injection/browser/js_communication_host.h
@@ -0,0 +1,110 @@
+// 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_BROWSER_JS_COMMUNICATION_HOST_H_
+#define COMPONENTS_JS_INJECTION_BROWSER_JS_COMMUNICATION_HOST_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/optional.h"
+#include "base/strings/string16.h"
+#include "components/js_injection/common/interfaces.mojom.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace content {
+class RenderFrameHost;
+} // namespace content
+
+namespace js_injection {
+
+class OriginMatcher;
+struct DocumentStartJavaScript;
+struct JsObject;
+class JsToBrowserMessaging;
+class WebMessageHostFactory;
+
+// This class is 1:1 with WebContents, when AddWebMessageListener() is called,
+// it stores the information in this class and send them to renderer side
+// JsCommunication if there is any. When RenderFrameCreated() gets called, it
+// needs to configure that new RenderFrame with the information stores in this
+// class.
+class JsCommunicationHost : public content::WebContentsObserver {
+ public:
+ explicit JsCommunicationHost(content::WebContents* web_contents);
+ ~JsCommunicationHost() override;
+
+ // Captures the result of adding script. There are two possibilities when
+ // adding script: there was an error, in which case |error_message| is set,
+ // otherwise the add was successful and |script_id| is set.
+ struct AddScriptResult {
+ AddScriptResult();
+ AddScriptResult(const AddScriptResult&);
+ AddScriptResult& operator=(const AddScriptResult&);
+ ~AddScriptResult();
+
+ base::Optional<std::string> error_message;
+ base::Optional<int> script_id;
+ };
+
+ // Native side AddDocumentStartJavaScript, returns an error message if the
+ // parameters didn't pass necessary checks.
+ AddScriptResult AddDocumentStartJavaScript(
+ const base::string16& script,
+ const std::vector<std::string>& allowed_origin_rules);
+
+ bool RemoveDocumentStartJavaScript(int script_id);
+
+ // Adds a new WebMessageHostFactory. For any urls that match
+ // |allowed_origin_rules|, |js_object_name| is registered as a JS object that
+ // can be used by script on the page to send and receive messages. Returns
+ // an empty string on success. On failure, the return string gives the error
+ // message.
+ base::string16 AddWebMessageHostFactory(
+ std::unique_ptr<WebMessageHostFactory> factory,
+ const base::string16& js_object_name,
+ const std::vector<std::string>& allowed_origin_rules);
+
+ // Returns the factory previously registered under the specified name.
+ void RemoveWebMessageHostFactory(const base::string16& js_object_name);
+
+ struct RegisteredFactory {
+ base::string16 js_name;
+ OriginMatcher allowed_origin_rules;
+ WebMessageHostFactory* factory = nullptr;
+ };
+
+ // Returns the registered factories.
+ std::vector<RegisteredFactory> GetWebMessageHostFactories();
+
+ // content::WebContentsObserver implementations
+ void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
+ void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
+
+ private:
+ void NotifyFrameForWebMessageListener(
+ content::RenderFrameHost* render_frame_host);
+ void NotifyFrameForAllDocumentStartJavaScripts(
+ content::RenderFrameHost* render_frame_host);
+ void NotifyFrameForAddDocumentStartJavaScript(
+ const DocumentStartJavaScript* script,
+ content::RenderFrameHost* render_frame_host);
+
+ void NotifyFrameForRemoveDocumentStartJavaScript(
+ int32_t script_id,
+ content::RenderFrameHost* render_frame_host);
+
+ int32_t next_script_id_ = 0;
+ std::vector<DocumentStartJavaScript> scripts_;
+ std::vector<std::unique_ptr<JsObject>> js_objects_;
+ std::map<content::RenderFrameHost*,
+ std::vector<std::unique_ptr<JsToBrowserMessaging>>>
+ js_to_browser_messagings_;
+
+ DISALLOW_COPY_AND_ASSIGN(JsCommunicationHost);
+};
+
+} // namespace js_injection
+
+#endif // COMPONENTS_JS_INJECTION_BROWSER_JS_COMMUNICATION_HOST_H_
diff --git a/chromium/components/js_injection/browser/js_to_browser_messaging.cc b/chromium/components/js_injection/browser/js_to_browser_messaging.cc
new file mode 100644
index 00000000000..e7ce36d7fd0
--- /dev/null
+++ b/chromium/components/js_injection/browser/js_to_browser_messaging.cc
@@ -0,0 +1,129 @@
+// 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/browser/js_to_browser_messaging.h"
+
+#include "base/stl_util.h"
+#include "components/js_injection/browser/web_message.h"
+#include "components/js_injection/browser/web_message_host.h"
+#include "components/js_injection/browser/web_message_host_factory.h"
+#include "components/js_injection/browser/web_message_reply_proxy.h"
+#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
+#include "third_party/blink/public/common/messaging/message_port_descriptor.h"
+#include "url/origin.h"
+#include "url/url_util.h"
+
+namespace js_injection {
+namespace {
+
+// We want to pass a string "null" for local file schemes, to make it
+// consistent to the Blink side SecurityOrigin serialization. When both
+// setAllow{File,Universal}AccessFromFileURLs are false, Blink::SecurityOrigin
+// will be serialized as string "null" for local file schemes, but when
+// setAllowFileAccessFromFileURLs is true, Blink::SecurityOrigin will be
+// serialized as the scheme, which will be inconsistentt to this place. In
+// this case we want to let developer to know that local files are not safe,
+// so we still pass "null".
+std::string GetOriginString(const url::Origin& source_origin) {
+ return base::Contains(url::GetLocalSchemes(), source_origin.scheme())
+ ? "null"
+ : source_origin.Serialize();
+}
+
+} // namespace
+
+class JsToBrowserMessaging::ReplyProxyImpl : public WebMessageReplyProxy {
+ public:
+ explicit ReplyProxyImpl(
+ mojo::PendingAssociatedRemote<mojom::BrowserToJsMessaging>
+ java_to_js_messaging)
+ : java_to_js_messaging_(std::move(java_to_js_messaging)) {}
+ ReplyProxyImpl(const ReplyProxyImpl&) = delete;
+ ReplyProxyImpl& operator=(const ReplyProxyImpl&) = delete;
+ ~ReplyProxyImpl() override = default;
+
+ // WebMessageReplyProxy:
+ void PostMessage(std::unique_ptr<WebMessage> message) override {
+ java_to_js_messaging_->OnPostMessage(message->message);
+ }
+
+ private:
+ mojo::AssociatedRemote<mojom::BrowserToJsMessaging> java_to_js_messaging_;
+};
+
+JsToBrowserMessaging::JsToBrowserMessaging(
+ content::RenderFrameHost* render_frame_host,
+ mojo::PendingAssociatedReceiver<mojom::JsToBrowserMessaging> receiver,
+ WebMessageHostFactory* factory,
+ const OriginMatcher& origin_matcher)
+ : render_frame_host_(render_frame_host),
+ connection_factory_(factory),
+ origin_matcher_(origin_matcher) {
+ receiver_.Bind(std::move(receiver));
+}
+
+JsToBrowserMessaging::~JsToBrowserMessaging() = default;
+
+void JsToBrowserMessaging::PostMessage(
+ const base::string16& message,
+ std::vector<blink::MessagePortDescriptor> ports) {
+ DCHECK(render_frame_host_);
+
+ content::WebContents* web_contents =
+ content::WebContents::FromRenderFrameHost(render_frame_host_);
+
+ if (!web_contents)
+ return;
+
+ // |source_origin| has no race with this PostMessage call, because of
+ // associated mojo channel, the committed origin message and PostMessage are
+ // in sequence.
+ const url::Origin source_origin =
+ render_frame_host_->GetLastCommittedOrigin();
+
+ if (!origin_matcher_.Matches(source_origin))
+ return;
+
+ // SetBrowserToJsMessaging must be called before this.
+ DCHECK(reply_proxy_);
+
+ if (!host_) {
+ const std::string origin_string = GetOriginString(source_origin);
+ const bool is_main_frame =
+ web_contents->GetMainFrame() == render_frame_host_;
+
+ host_ = connection_factory_->CreateHost(origin_string, is_main_frame,
+ reply_proxy_.get());
+#if DCHECK_IS_ON()
+ origin_string_ = origin_string;
+ is_main_frame_ = is_main_frame;
+#endif
+ if (!host_)
+ return;
+ }
+ // The origin and whether this is the main frame should not change once
+ // PostMessage() has been received.
+#if DCHECK_IS_ON()
+ DCHECK_EQ(GetOriginString(source_origin), origin_string_);
+ DCHECK_EQ(is_main_frame_, web_contents->GetMainFrame() == render_frame_host_);
+#endif
+ std::unique_ptr<WebMessage> web_message = std::make_unique<WebMessage>();
+ web_message->message = message;
+ web_message->ports = std::move(ports);
+ host_->OnPostMessage(std::move(web_message));
+}
+
+void JsToBrowserMessaging::SetBrowserToJsMessaging(
+ mojo::PendingAssociatedRemote<mojom::BrowserToJsMessaging>
+ java_to_js_messaging) {
+ // A RenderFrame may inject JsToBrowserMessaging in the JavaScript context
+ // more than once because of reusing of RenderFrame.
+ host_.reset();
+ reply_proxy_ =
+ std::make_unique<ReplyProxyImpl>(std::move(java_to_js_messaging));
+}
+
+} // namespace js_injection
diff --git a/chromium/components/js_injection/browser/js_to_browser_messaging.h b/chromium/components/js_injection/browser/js_to_browser_messaging.h
new file mode 100644
index 00000000000..716905182f1
--- /dev/null
+++ b/chromium/components/js_injection/browser/js_to_browser_messaging.h
@@ -0,0 +1,66 @@
+// 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_BROWSER_JS_TO_BROWSER_MESSAGING_H_
+#define COMPONENTS_JS_INJECTION_BROWSER_JS_TO_BROWSER_MESSAGING_H_
+
+#include <vector>
+
+#include "base/check.h"
+#include "base/strings/string16.h"
+#include "components/js_injection/common/interfaces.mojom.h"
+#include "components/js_injection/common/origin_matcher.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_associated_remote.h"
+#include "third_party/blink/public/common/messaging/message_port_descriptor.h"
+
+namespace content {
+class RenderFrameHost;
+}
+
+namespace js_injection {
+
+class WebMessageHost;
+class WebMessageHostFactory;
+
+// Implementation of mojo::JsToBrowserMessaging interface. Receives
+// PostMessage() call from renderer JsBinding.
+class JsToBrowserMessaging : public mojom::JsToBrowserMessaging {
+ public:
+ JsToBrowserMessaging(
+ content::RenderFrameHost* rfh,
+ mojo::PendingAssociatedReceiver<mojom::JsToBrowserMessaging> receiver,
+ WebMessageHostFactory* factory,
+ const OriginMatcher& origin_matcher);
+ ~JsToBrowserMessaging() override;
+
+ // mojom::JsToBrowserMessaging implementation.
+ void PostMessage(const base::string16& message,
+ std::vector<blink::MessagePortDescriptor> ports) override;
+ void SetBrowserToJsMessaging(
+ mojo::PendingAssociatedRemote<mojom::BrowserToJsMessaging>
+ java_to_js_messaging) override;
+
+ private:
+ class ReplyProxyImpl;
+
+ content::RenderFrameHost* render_frame_host_;
+ std::unique_ptr<ReplyProxyImpl> reply_proxy_;
+ WebMessageHostFactory* connection_factory_;
+ OriginMatcher origin_matcher_;
+ mojo::AssociatedReceiver<mojom::JsToBrowserMessaging> receiver_{this};
+ std::unique_ptr<WebMessageHost> host_;
+#if DCHECK_IS_ON()
+ std::string origin_string_;
+ bool is_main_frame_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(JsToBrowserMessaging);
+};
+
+} // namespace js_injection
+
+#endif // COMPONENTS_JS_INJECTION_BROWSER_JS_TO_BROWSER_MESSAGING_H_
diff --git a/chromium/components/js_injection/browser/web_message.cc b/chromium/components/js_injection/browser/web_message.cc
new file mode 100644
index 00000000000..34e8e64e08f
--- /dev/null
+++ b/chromium/components/js_injection/browser/web_message.cc
@@ -0,0 +1,13 @@
+// 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.
+
+#include "components/js_injection/browser/web_message.h"
+
+namespace js_injection {
+
+WebMessage::WebMessage() = default;
+
+WebMessage::~WebMessage() = default;
+
+} // namespace js_injection
diff --git a/chromium/components/js_injection/browser/web_message.h b/chromium/components/js_injection/browser/web_message.h
new file mode 100644
index 00000000000..1061cc82a76
--- /dev/null
+++ b/chromium/components/js_injection/browser/web_message.h
@@ -0,0 +1,26 @@
+// 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.
+
+#ifndef COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_H_
+#define COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "third_party/blink/public/common/messaging/message_port_descriptor.h"
+
+namespace js_injection {
+
+// Represents a message to or from the page.
+struct WebMessage {
+ WebMessage();
+ ~WebMessage();
+
+ base::string16 message;
+ std::vector<blink::MessagePortDescriptor> ports;
+};
+
+} // namespace js_injection
+
+#endif // COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_H_
diff --git a/chromium/components/js_injection/browser/web_message_host.h b/chromium/components/js_injection/browser/web_message_host.h
new file mode 100644
index 00000000000..8fa339893fa
--- /dev/null
+++ b/chromium/components/js_injection/browser/web_message_host.h
@@ -0,0 +1,24 @@
+// 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.
+
+#ifndef COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_HOST_H_
+#define COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_HOST_H_
+
+#include <memory>
+
+namespace js_injection {
+
+struct WebMessage;
+
+// Represents the browser side of a WebMessage channel.
+class WebMessageHost {
+ public:
+ virtual ~WebMessageHost() = default;
+
+ virtual void OnPostMessage(std::unique_ptr<WebMessage> message) = 0;
+};
+
+} // namespace js_injection
+
+#endif // COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_HOST_H_
diff --git a/chromium/components/js_injection/browser/web_message_host_factory.h b/chromium/components/js_injection/browser/web_message_host_factory.h
new file mode 100644
index 00000000000..aeaaf772bc2
--- /dev/null
+++ b/chromium/components/js_injection/browser/web_message_host_factory.h
@@ -0,0 +1,34 @@
+// 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.
+
+#ifndef COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_HOST_FACTORY_H_
+#define COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_HOST_FACTORY_H_
+
+#include <memory>
+#include <string>
+
+namespace js_injection {
+
+class WebMessageHost;
+class WebMessageReplyProxy;
+
+// Creates a WebMessageHost in response to a page interacting with the object
+// registered by way of JsCommunicationHost::AddWebMessageHostFactory(). A
+// WebMessageHost is created for every page that matches the parameters of
+// AddWebMessageHostFactory().
+class WebMessageHostFactory {
+ public:
+ virtual ~WebMessageHostFactory() = default;
+
+ // Creates a WebMessageHost for the specified page. |proxy| is valid for
+ // the life of the host and may be used to send messages back to the page.
+ virtual std::unique_ptr<WebMessageHost> CreateHost(
+ const std::string& origin_string,
+ bool is_main_frame,
+ WebMessageReplyProxy* proxy) = 0;
+};
+
+} // namespace js_injection
+
+#endif // COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_HOST_FACTORY_H_
diff --git a/chromium/components/js_injection/browser/web_message_reply_proxy.h b/chromium/components/js_injection/browser/web_message_reply_proxy.h
new file mode 100644
index 00000000000..e94171f221f
--- /dev/null
+++ b/chromium/components/js_injection/browser/web_message_reply_proxy.h
@@ -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.
+
+#ifndef COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_REPLY_PROXY_H_
+#define COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_REPLY_PROXY_H_
+
+#include "base/strings/string16.h"
+
+namespace js_injection {
+
+struct WebMessage;
+
+// Used to send messages to the page.
+class WebMessageReplyProxy {
+ public:
+ virtual void PostMessage(std::unique_ptr<WebMessage> message) = 0;
+
+ protected:
+ virtual ~WebMessageReplyProxy() = default;
+};
+
+} // namespace js_injection
+
+#endif // COMPONENTS_JS_INJECTION_BROWSER_WEB_MESSAGE_REPLY_PROXY_H_
diff --git a/chromium/components/js_injection/common/BUILD.gn b/chromium/components/js_injection/common/BUILD.gn
new file mode 100644
index 00000000000..4c444214440
--- /dev/null
+++ b/chromium/components/js_injection/common/BUILD.gn
@@ -0,0 +1,71 @@
+# 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.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("common_mojom") {
+ sources = [
+ "interfaces.mojom",
+ "origin_matcher.mojom",
+ ]
+
+ public_deps = [
+ "//mojo/public/mojom/base",
+ "//third_party/blink/public/mojom:mojom_core",
+ ]
+
+ cpp_typemaps = [
+ {
+ types = [
+ {
+ mojom = "js_injection.mojom.OriginMatcher"
+ cpp = "::js_injection::OriginMatcher"
+ },
+ {
+ mojom = "js_injection.mojom.OriginMatcherRule"
+ cpp = "::std::unique_ptr<::js_injection::OriginMatcherRule>"
+ move_only = true
+ },
+ ]
+ traits_headers = [
+ "origin_matcher_mojom_traits.h",
+ "origin_matcher.h",
+ ]
+ traits_sources = [ "origin_matcher_mojom_traits.cc" ]
+ traits_public_deps = [ ":common" ]
+ },
+ ]
+ overridden_deps = [ "//third_party/blink/public/mojom:mojom_core" ]
+ component_deps = [ "//third_party/blink/public/common" ]
+}
+
+source_set("common") {
+ public = [ "origin_matcher.h" ]
+ sources = [
+ "origin_matcher.cc",
+ "origin_matcher_internal.cc",
+ "origin_matcher_internal.h",
+ ]
+ deps = [
+ "//base",
+ "//net",
+ "//url",
+ ]
+
+ # origin_matcher_internal is needed by mojom traits and tests.
+ friend = [ ":*" ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [ "origin_matcher_unittest.cc" ]
+ deps = [
+ ":common",
+ ":common_mojom",
+ "//base",
+ "//base/test:test_support",
+ "//mojo/public/cpp/test_support:test_utils",
+ "//url",
+ ]
+}
diff --git a/chromium/components/js_injection/common/OWNERS b/chromium/components/js_injection/common/OWNERS
new file mode 100644
index 00000000000..b36774dad1d
--- /dev/null
+++ b/chromium/components/js_injection/common/OWNERS
@@ -0,0 +1,4 @@
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/chromium/components/js_injection/common/interfaces.mojom b/chromium/components/js_injection/common/interfaces.mojom
new file mode 100644
index 00000000000..0e5b1047f91
--- /dev/null
+++ b/chromium/components/js_injection/common/interfaces.mojom
@@ -0,0 +1,65 @@
+// 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.
+
+module js_injection.mojom;
+
+import "components/js_injection/common/origin_matcher.mojom";
+import "mojo/public/mojom/base/string16.mojom";
+import "third_party/blink/public/mojom/messaging/message_port_descriptor.mojom";
+
+// JsObject struct represents a JavaScript object we will inject in the main
+// JavaScript world of a frame. |js_object_name| will be used as the name
+// of the JavaScript object. We will inject the object if the frame's origin
+// matches |origin_matcher|. |js_to_browser_messaging| will be used for that
+// JavaScript object to send message back to browser side.
+struct JsObject {
+ mojo_base.mojom.String16 js_object_name;
+ pending_associated_remote<JsToBrowserMessaging> js_to_browser_messaging;
+ js_injection.mojom.OriginMatcher origin_matcher;
+};
+
+// DocumentStartJavaScript struct contains the JavaScript snippet |script| and
+// the corresponding |origin_matcher|. We will run the script if the frame's
+// origin matches any rules in the |origin_matcher|.
+struct DocumentStartJavaScript {
+ int32 script_id;
+ mojo_base.mojom.String16 script;
+ js_injection.mojom.OriginMatcher origin_matcher;
+};
+
+// For JavaScript postMessage() API, implemented by browser.
+interface JsToBrowserMessaging {
+ // Called from renderer, browser receives |message| and possible |ports|,
+ // The |message| is an opaque type and the contents are defined by the client
+ // of this API.
+ PostMessage(mojo_base.mojom.String16 message,
+ array<blink.mojom.MessagePortDescriptor> ports);
+
+ // When there is a new BrowserToJsMessaging created in renderer, we need to
+ // send/ it to browser, so browser could send message back to Js.
+ SetBrowserToJsMessaging(
+ pending_associated_remote<BrowserToJsMessaging> browser_to_js_messaging);
+};
+
+// For the browser to reply back to injected JavaScript object. Implemented by
+// the renderer.
+interface BrowserToJsMessaging {
+ // Called from browser, to send message to page.
+ OnPostMessage(mojo_base.mojom.String16 message);
+};
+
+// For browser to configure renderer, implemented by renderer.
+interface JsCommunication {
+ // Called from browser, to tell renderer that if we need to inject
+ // JavaScript objects to the frame based on the |js_objects| array.
+ SetJsObjects(array<js_injection.mojom.JsObject> js_objects);
+
+ // Called from browser, to add a script for a frame to run at document start
+ // stage. The script will run only if the frame's origin matches any of the
+ // allowed_origin_rules.
+ AddDocumentStartScript(js_injection.mojom.DocumentStartJavaScript script);
+
+ // Called from browser, to remove the script by the given script_id.
+ RemoveDocumentStartScript(int32 script_id);
+};
diff --git a/chromium/components/js_injection/common/origin_matcher.cc b/chromium/components/js_injection/common/origin_matcher.cc
new file mode 100644
index 00000000000..bdfc9fa3eaf
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher.cc
@@ -0,0 +1,125 @@
+// 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.
+
+#include "components/js_injection/common/origin_matcher.h"
+
+#include "components/js_injection/common/origin_matcher_internal.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/parse_number.h"
+#include "net/base/url_util.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/url_constants.h"
+#include "url/url_util.h"
+
+namespace js_injection {
+
+namespace {
+
+inline int GetDefaultPortForSchemeIfNoPortInfo(const std::string& scheme,
+ int port) {
+ // The input has explicit port information, so don't modify it.
+ if (port != -1)
+ return port;
+
+ // Hard code the port for http and https.
+ if (scheme == url::kHttpScheme)
+ return 80;
+ if (scheme == url::kHttpsScheme)
+ return 443;
+
+ return port;
+}
+
+} // namespace
+
+OriginMatcher::OriginMatcher(const OriginMatcher& rhs) {
+ *this = rhs;
+}
+
+OriginMatcher& OriginMatcher::operator=(const OriginMatcher& rhs) {
+ rules_.clear();
+ for (const auto& rule : rhs.Serialize())
+ AddRuleFromString(rule);
+ return *this;
+}
+
+void OriginMatcher::SetRules(RuleList rules) {
+ rules_.swap(rules);
+}
+
+bool OriginMatcher::AddRuleFromString(const std::string& raw_untrimmed) {
+ std::string raw;
+ base::TrimWhitespaceASCII(raw_untrimmed, base::TRIM_ALL, &raw);
+
+ if (raw == "*") {
+ rules_.push_back(std::make_unique<MatchAllOriginsRule>());
+ return true;
+ }
+
+ // Extract scheme-restriction.
+ std::string::size_type scheme_pos = raw.find("://");
+ if (scheme_pos == std::string::npos)
+ return false;
+
+ const std::string scheme = raw.substr(0, scheme_pos);
+ if (!SubdomainMatchingRule::IsValidScheme(scheme))
+ return false;
+
+ std::string host_and_port = raw.substr(scheme_pos + 3);
+ if (host_and_port.empty()) {
+ if (!SubdomainMatchingRule::IsValidSchemeAndHost(scheme, std::string()))
+ return false;
+ rules_.push_back(
+ std::make_unique<SubdomainMatchingRule>(scheme, std::string(), -1));
+ return true;
+ }
+
+ std::string host;
+ int port;
+ if (!net::ParseHostAndPort(host_and_port, &host, &port) ||
+ !SubdomainMatchingRule::IsValidSchemeAndHost(scheme, host)) {
+ return false;
+ }
+
+ // Check if we have an <ip-address>[:port] input and try to canonicalize the
+ // IP literal.
+ net::IPAddress ip_address;
+ if (ip_address.AssignFromIPLiteral(host)) {
+ port = GetDefaultPortForSchemeIfNoPortInfo(scheme, port);
+ host = ip_address.ToString();
+ if (ip_address.IsIPv6())
+ host = '[' + host + ']';
+ rules_.push_back(
+ std::make_unique<SubdomainMatchingRule>(scheme, host, port));
+ return true;
+ }
+
+ port = GetDefaultPortForSchemeIfNoPortInfo(scheme, port);
+ rules_.push_back(std::make_unique<SubdomainMatchingRule>(scheme, host, port));
+ return true;
+}
+
+bool OriginMatcher::Matches(const url::Origin& origin) const {
+ GURL origin_url = origin.GetURL();
+ // Since we only do kInclude vs kNoMatch, the order doesn't actually matter.
+ for (auto it = rules_.rbegin(); it != rules_.rend(); ++it) {
+ net::SchemeHostPortMatcherResult result = (*it)->Evaluate(origin_url);
+ if (result == net::SchemeHostPortMatcherResult::kInclude)
+ return true;
+ }
+ return false;
+}
+
+std::vector<std::string> OriginMatcher::Serialize() const {
+ std::vector<std::string> result;
+ result.reserve(rules_.size());
+ for (const auto& rule : rules_) {
+ result.push_back(rule->ToString());
+ }
+ return result;
+}
+
+} // namespace js_injection
diff --git a/chromium/components/js_injection/common/origin_matcher.h b/chromium/components/js_injection/common/origin_matcher.h
new file mode 100644
index 00000000000..08f176042b6
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher.h
@@ -0,0 +1,75 @@
+// 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.
+
+#ifndef COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_H_
+#define COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace url {
+class Origin;
+} // namespace url
+
+namespace js_injection {
+
+class OriginMatcherRule;
+
+// An url origin matcher allows wildcard subdomain matching. It supports two
+// types of rules.
+//
+// (1) "*"
+// A single * (without quote) will match any origin.
+//
+// (2) SCHEME "://" [ HOSTNAME_PATTERN ][":" PORT]
+//
+// SCHEME is required. When matching custom schemes, HOSTNAME_PATTERN and PORT
+// shouldn't present. When SCHEME is "http" or "https", HOSTNAME_PATTERN is
+// required.
+//
+// HOSTNAME_PATTERN allows wildcard '*' to match subdomains, such as
+// "*.example.com". Rules such as "x.*.y.com", "*foobar.com" are not allowed.
+// Note that "*.example.com" won't match "example.com", so need another rule
+// "example.com" to match it. If the HOSTNAME_PATTERN is an IP literal, it
+// will be used for exact matching.
+//
+// PORT is optional for "http" and "https" schemes, when it is not present, for
+// "http" and "https" schemes, it will match default port number (80 and 443
+// correspondingly).
+class OriginMatcher {
+ public:
+ using RuleList = std::vector<std::unique_ptr<OriginMatcherRule>>;
+
+ OriginMatcher() = default;
+ // Allow copy and assign.
+ OriginMatcher(const OriginMatcher& rhs);
+ OriginMatcher(OriginMatcher&&) = default;
+ OriginMatcher& operator=(const OriginMatcher& rhs);
+ OriginMatcher& operator=(OriginMatcher&&) = default;
+
+ ~OriginMatcher() = default;
+
+ void SetRules(RuleList rules);
+
+ // Adds a rule given by the string |raw|. Returns true if the rule was
+ // successfully added.
+ bool AddRuleFromString(const std::string& raw);
+
+ // Returns true if the |origin| matches any rule in this matcher.
+ bool Matches(const url::Origin& origin) const;
+
+ // Returns the current list of rules.
+ const RuleList& rules() const { return rules_; }
+
+ // Returns string representation of this origin matcher.
+ std::vector<std::string> Serialize() const;
+
+ private:
+ RuleList rules_;
+};
+
+} // namespace js_injection
+
+#endif // COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_H_
diff --git a/chromium/components/js_injection/common/origin_matcher.mojom b/chromium/components/js_injection/common/origin_matcher.mojom
new file mode 100644
index 00000000000..1461812adb0
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher.mojom
@@ -0,0 +1,22 @@
+// 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.
+
+module js_injection.mojom;
+
+// Values correspond to that of SubdomainMatchingRule.
+struct SubdomainMatchingRule {
+ string scheme;
+ // An empty host matches any host.
+ string optional_host;
+ int32 optional_port;
+};
+
+struct OriginMatcherRule {
+ // If this is not set, the rule matches any url.
+ SubdomainMatchingRule? subdomain_matching_rule;
+};
+
+struct OriginMatcher {
+ array<OriginMatcherRule> rules;
+};
diff --git a/chromium/components/js_injection/common/origin_matcher_internal.cc b/chromium/components/js_injection/common/origin_matcher_internal.cc
new file mode 100644
index 00000000000..0630b5f9e72
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher_internal.cc
@@ -0,0 +1,125 @@
+// 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.
+
+#include "components/js_injection/common/origin_matcher_internal.h"
+
+#include "base/strings/pattern.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/scheme_host_port_matcher_rule.h"
+#include "net/base/url_util.h"
+#include "url/gurl.h"
+#include "url/url_constants.h"
+
+namespace js_injection {
+namespace {
+
+// Returns false if |host| has too many wildcards.
+inline bool HostWildcardSanityCheck(const std::string& host) {
+ size_t wildcard_count = std::count(host.begin(), host.end(), '*');
+ if (wildcard_count == 0)
+ return true;
+
+ // We only allow one wildcard.
+ if (wildcard_count > 1)
+ return false;
+
+ // Start with "*." for subdomain matching.
+ if (base::StartsWith(host, "*.", base::CompareCase::SENSITIVE))
+ return true;
+
+ return false;
+}
+
+} // namespace
+
+OriginMatcherRule::OriginMatcherRule(OriginMatcherRuleType type)
+ : type_(type) {}
+
+OriginMatcherRule::~OriginMatcherRule() = default;
+
+MatchAllOriginsRule::MatchAllOriginsRule()
+ : OriginMatcherRule(OriginMatcherRuleType::kAny) {}
+
+MatchAllOriginsRule::~MatchAllOriginsRule() = default;
+
+net::SchemeHostPortMatcherResult MatchAllOriginsRule::Evaluate(
+ const GURL& url) const {
+ return net::SchemeHostPortMatcherResult::kInclude;
+}
+
+std::string MatchAllOriginsRule::ToString() const {
+ return "*";
+}
+
+SubdomainMatchingRule::SubdomainMatchingRule(const std::string& scheme,
+ const std::string& optional_host,
+ int optional_port)
+ : OriginMatcherRule(OriginMatcherRuleType::kSubdomain),
+ scheme_(base::ToLowerASCII(scheme)),
+ optional_host_(base::ToLowerASCII(optional_host)),
+ optional_port_(optional_port) {
+ DCHECK(IsValidScheme(scheme));
+ DCHECK(IsValidSchemeAndHost(scheme_, optional_host_));
+}
+
+SubdomainMatchingRule::~SubdomainMatchingRule() = default;
+
+// static
+bool SubdomainMatchingRule::IsValidScheme(const std::string& scheme) {
+ // Wild cards are not allowed in the scheme.
+ return !scheme.empty() && scheme.find('*') == std::string::npos;
+}
+
+// static
+bool SubdomainMatchingRule::CanSchemeHaveHost(const std::string& scheme) {
+ return scheme == url::kHttpScheme || scheme == url::kHttpsScheme;
+}
+
+// static
+bool SubdomainMatchingRule::IsValidSchemeAndHost(const std::string& scheme,
+ const std::string& host) {
+ if (host.empty()) {
+ if (CanSchemeHaveHost(scheme))
+ return false;
+ return true;
+ }
+ if (!CanSchemeHaveHost(scheme))
+ return false;
+
+ // |scheme| is either https or http.
+
+ // URL like rule is invalid.
+ if (host.find('/') != std::string::npos)
+ return false;
+
+ return HostWildcardSanityCheck(host);
+}
+
+net::SchemeHostPortMatcherResult SubdomainMatchingRule::Evaluate(
+ const GURL& url) const {
+ if (optional_port_ != -1 && url.EffectiveIntPort() != optional_port_) {
+ // Didn't match port expectation.
+ return net::SchemeHostPortMatcherResult::kNoMatch;
+ }
+
+ if (url.scheme() != scheme_) {
+ // Didn't match scheme expectation.
+ return net::SchemeHostPortMatcherResult::kNoMatch;
+ }
+
+ return base::MatchPattern(url.host(), optional_host_)
+ ? net::SchemeHostPortMatcherResult::kInclude
+ : net::SchemeHostPortMatcherResult::kNoMatch;
+}
+
+std::string SubdomainMatchingRule::ToString() const {
+ std::string str;
+ base::StringAppendF(&str, "%s://%s", scheme_.c_str(), optional_host_.c_str());
+ if (optional_port_ != -1)
+ base::StringAppendF(&str, ":%d", optional_port_);
+ return str;
+}
+
+} // namespace js_injection
diff --git a/chromium/components/js_injection/common/origin_matcher_internal.h b/chromium/components/js_injection/common/origin_matcher_internal.h
new file mode 100644
index 00000000000..b9661bfa7e9
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher_internal.h
@@ -0,0 +1,89 @@
+// 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.
+
+#ifndef COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_INTERNAL_H_
+#define COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_INTERNAL_H_
+
+#include <string>
+
+#include "net/base/scheme_host_port_matcher.h"
+
+// NOTE: this file is an implementation detail and only used by code in
+// js_injection.
+
+namespace js_injection {
+
+enum class OriginMatcherRuleType { kAny, kSubdomain };
+
+// Common superclass that includes the type of matcher.
+class OriginMatcherRule : public net::SchemeHostPortMatcherRule {
+ public:
+ explicit OriginMatcherRule(OriginMatcherRuleType type);
+ ~OriginMatcherRule() override;
+
+ OriginMatcherRuleType type() const { return type_; }
+
+ private:
+ const OriginMatcherRuleType type_;
+};
+
+// Matches *all* urls.
+class MatchAllOriginsRule : public OriginMatcherRule {
+ public:
+ MatchAllOriginsRule();
+ MatchAllOriginsRule(const MatchAllOriginsRule&) = delete;
+ MatchAllOriginsRule& operator=(const MatchAllOriginsRule&) = delete;
+ ~MatchAllOriginsRule() override;
+
+ // OriginMatcherRule:
+ net::SchemeHostPortMatcherResult Evaluate(const GURL& url) const override;
+ std::string ToString() const override;
+};
+
+// Matches against a specific scheme, optional host (potentially with
+// wild-cards) and an optional port.
+class SubdomainMatchingRule : public OriginMatcherRule {
+ public:
+ SubdomainMatchingRule(const std::string& scheme,
+ const std::string& optional_host,
+ int optional_port);
+ // This constructor is implemented only in tests, it does no checking of
+ // args.
+ SubdomainMatchingRule(const std::string& scheme,
+ const std::string& optional_host,
+ int optional_port,
+ bool for_test);
+ SubdomainMatchingRule(const SubdomainMatchingRule&) = delete;
+ SubdomainMatchingRule& operator=(const SubdomainMatchingRule&) = delete;
+ ~SubdomainMatchingRule() override;
+
+ // Returns true if |scheme| is a valid scheme identifier.
+ static bool IsValidScheme(const std::string& scheme);
+
+ // Returns true if the |scheme| is allowed to have a host and port part.
+ static bool CanSchemeHaveHost(const std::string& scheme);
+
+ // Returns true if |scheme| and |host| are valid.
+ static bool IsValidSchemeAndHost(const std::string& scheme,
+ const std::string& host);
+
+ const std::string& scheme() const { return scheme_; }
+ const std::string& optional_host() const { return optional_host_; }
+ int optional_port() const { return optional_port_; }
+
+ // OriginMatcherRule:
+ net::SchemeHostPortMatcherResult Evaluate(const GURL& url) const override;
+ std::string ToString() const override;
+
+ private:
+ const std::string scheme_;
+ // Empty string means no host provided.
+ const std::string optional_host_;
+ // -1 means no port provided.
+ const int optional_port_;
+};
+
+} // namespace js_injection
+
+#endif // COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_INTERNAL_H_
diff --git a/chromium/components/js_injection/common/origin_matcher_mojom_traits.cc b/chromium/components/js_injection/common/origin_matcher_mojom_traits.cc
new file mode 100644
index 00000000000..3bb7bd838a0
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher_mojom_traits.cc
@@ -0,0 +1,85 @@
+// 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.
+
+#include "components/js_injection/common/origin_matcher_mojom_traits.h"
+
+#include "base/strings/pattern.h"
+#include "base/strings/stringprintf.h"
+#include "components/js_injection/common/origin_matcher_internal.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
+#include "net/base/parse_number.h"
+#include "net/base/scheme_host_port_matcher_rule.h"
+#include "net/base/url_util.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/url_constants.h"
+#include "url/url_util.h"
+
+namespace mojo {
+
+using js_injection::SubdomainMatchingRule;
+using js_injection::mojom::OriginMatcherRuleDataView;
+
+// static
+js_injection::mojom::SubdomainMatchingRulePtr
+StructTraits<OriginMatcherRuleDataView, OriginMatcherRuleUniquePtr>::
+ subdomain_matching_rule(const OriginMatcherRuleUniquePtr& rule) {
+ if (rule->type() == js_injection::OriginMatcherRuleType::kAny)
+ return nullptr;
+
+ DCHECK_EQ(js_injection::OriginMatcherRuleType::kSubdomain, rule->type());
+ const SubdomainMatchingRule* matching_rule =
+ static_cast<SubdomainMatchingRule*>(rule.get());
+ js_injection::mojom::SubdomainMatchingRulePtr matching_rule_ptr(
+ js_injection::mojom::SubdomainMatchingRule::New());
+
+ matching_rule_ptr->scheme = matching_rule->scheme();
+ matching_rule_ptr->optional_host = matching_rule->optional_host();
+ matching_rule_ptr->optional_port = matching_rule->optional_port();
+ return matching_rule_ptr;
+}
+
+// static
+bool StructTraits<OriginMatcherRuleDataView, OriginMatcherRuleUniquePtr>::Read(
+ OriginMatcherRuleDataView r,
+ OriginMatcherRuleUniquePtr* out) {
+ DCHECK(!out->get());
+
+ js_injection::mojom::SubdomainMatchingRuleDataView
+ subdomain_matching_rule_data_view;
+ r.GetSubdomainMatchingRuleDataView(&subdomain_matching_rule_data_view);
+ if (subdomain_matching_rule_data_view.is_null()) {
+ *out = std::make_unique<js_injection::MatchAllOriginsRule>();
+ return true;
+ }
+
+ js_injection::mojom::SubdomainMatchingRulePtr subdomain_matching_rule;
+ if (!r.ReadSubdomainMatchingRule(&subdomain_matching_rule))
+ return false;
+ if (!SubdomainMatchingRule::IsValidScheme(subdomain_matching_rule->scheme) ||
+ !SubdomainMatchingRule::IsValidSchemeAndHost(
+ subdomain_matching_rule->scheme,
+ subdomain_matching_rule->optional_host)) {
+ return false;
+ }
+ *out = std::make_unique<SubdomainMatchingRule>(
+ subdomain_matching_rule->scheme, subdomain_matching_rule->optional_host,
+ subdomain_matching_rule->optional_port);
+ return true;
+}
+
+// static
+bool StructTraits<js_injection::mojom::OriginMatcherDataView,
+ js_injection::OriginMatcher>::
+ Read(js_injection::mojom::OriginMatcherDataView data,
+ js_injection::OriginMatcher* out) {
+ std::vector<OriginMatcherRuleUniquePtr> rules;
+ if (!data.ReadRules(&rules))
+ return false;
+ out->SetRules(std::move(rules));
+ return true;
+}
+
+} // namespace mojo
diff --git a/chromium/components/js_injection/common/origin_matcher_mojom_traits.h b/chromium/components/js_injection/common/origin_matcher_mojom_traits.h
new file mode 100644
index 00000000000..873d2cb94e2
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher_mojom_traits.h
@@ -0,0 +1,45 @@
+// 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.
+
+#ifndef COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_MOJOM_TRAITS_H_
+#define COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_MOJOM_TRAITS_H_
+
+#include <string>
+#include <vector>
+
+#include "components/js_injection/common/origin_matcher.h"
+#include "components/js_injection/common/origin_matcher.mojom.h"
+#include "components/js_injection/common/origin_matcher_internal.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+
+namespace mojo {
+
+using OriginMatcherRuleUniquePtr =
+ std::unique_ptr<js_injection::OriginMatcherRule>;
+
+template <>
+struct StructTraits<js_injection::mojom::OriginMatcherRuleDataView,
+ OriginMatcherRuleUniquePtr> {
+ static js_injection::mojom::SubdomainMatchingRulePtr subdomain_matching_rule(
+ const OriginMatcherRuleUniquePtr& rule);
+ static bool Read(js_injection::mojom::OriginMatcherRuleDataView r,
+ OriginMatcherRuleUniquePtr* out);
+};
+
+template <>
+struct StructTraits<js_injection::mojom::OriginMatcherDataView,
+ js_injection::OriginMatcher> {
+ public:
+ static const std::vector<OriginMatcherRuleUniquePtr>& rules(
+ const js_injection::OriginMatcher& r) {
+ return r.rules();
+ }
+
+ static bool Read(js_injection::mojom::OriginMatcherDataView data,
+ js_injection::OriginMatcher* out);
+};
+
+} // namespace mojo
+
+#endif // COMPONENTS_JS_INJECTION_COMMON_ORIGIN_MATCHER_MOJOM_TRAITS_H_
diff --git a/chromium/components/js_injection/common/origin_matcher_unittest.cc b/chromium/components/js_injection/common/origin_matcher_unittest.cc
new file mode 100644
index 00000000000..ab7564ef210
--- /dev/null
+++ b/chromium/components/js_injection/common/origin_matcher_unittest.cc
@@ -0,0 +1,404 @@
+// 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.
+
+#include "components/js_injection/common/origin_matcher.h"
+
+#include "components/js_injection/common/origin_matcher.mojom.h"
+#include "components/js_injection/common/origin_matcher_internal.h"
+#include "mojo/public/cpp/test_support/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+#include "url/url_util.h"
+
+namespace js_injection {
+
+SubdomainMatchingRule::SubdomainMatchingRule(const std::string& scheme,
+ const std::string& optional_host,
+ int optional_port,
+ bool for_test)
+ : OriginMatcherRule(OriginMatcherRuleType::kSubdomain),
+ scheme_(scheme),
+ optional_host_(optional_host),
+ optional_port_(optional_port) {}
+
+class OriginMatcherTest : public testing::Test {
+ public:
+ void SetUp() override {
+ scheme_registry_ = std::make_unique<url::ScopedSchemeRegistryForTests>();
+ url::EnableNonStandardSchemesForAndroidWebView();
+ }
+
+ static url::Origin CreateOriginFromString(const std::string& url) {
+ return url::Origin::Create(GURL(url));
+ }
+
+ private:
+ std::unique_ptr<url::ScopedSchemeRegistryForTests> scheme_registry_;
+};
+
+TEST_F(OriginMatcherTest, InvalidInputs) {
+ OriginMatcher matcher;
+ // Empty string is invalid.
+ EXPECT_FALSE(matcher.AddRuleFromString(""));
+ // Scheme doesn't present.
+ EXPECT_FALSE(matcher.AddRuleFromString("example.com"));
+ EXPECT_FALSE(matcher.AddRuleFromString("://example.com"));
+ // Scheme doesn't do wildcard matching.
+ EXPECT_FALSE(matcher.AddRuleFromString("*://example.com"));
+ // URL like rule is invalid.
+ EXPECT_FALSE(matcher.AddRuleFromString("https://www.example.com/index.html"));
+ EXPECT_FALSE(matcher.AddRuleFromString("http://192.168.0.1/*"));
+ // Only accept hostname pattern starts with "*." if there is a "*" inside.
+ EXPECT_FALSE(matcher.AddRuleFromString("https://*foobar.com"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://x.*.y.com"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://*example.com"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://e*xample.com"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://example.com*"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://*"));
+ EXPECT_FALSE(matcher.AddRuleFromString("http://*"));
+ // Invalid port.
+ EXPECT_FALSE(matcher.AddRuleFromString("https://example.com:"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://example.com:*"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://example.com:**"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://example.com:-1"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://example.com:+443"));
+ // Empty hostname pattern for http/https.
+ EXPECT_FALSE(matcher.AddRuleFromString("http://"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://:80"));
+ // No IP block support.
+ EXPECT_FALSE(matcher.AddRuleFromString("https://192.168.0.0/16"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://fefe:13::abc/33"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://:1"));
+ // Invalid IP address.
+ EXPECT_FALSE(matcher.AddRuleFromString("http://[a:b:*]"));
+ EXPECT_FALSE(matcher.AddRuleFromString("http://[a:b:*"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://fefe:13::*"));
+ EXPECT_FALSE(matcher.AddRuleFromString("https://fefe:13:*/33"));
+ // Custom scheme with host and/or port are invalid. This is because in
+ // WebView, all the URI with the same custom scheme belong to one origin.
+ EXPECT_FALSE(matcher.AddRuleFromString("x-mail://hostname:80"));
+ EXPECT_FALSE(matcher.AddRuleFromString("x-mail://hostname"));
+ EXPECT_FALSE(matcher.AddRuleFromString("x-mail://*"));
+ // file scheme with "host"
+ EXPECT_FALSE(matcher.AddRuleFromString("file://host"));
+ EXPECT_FALSE(matcher.AddRuleFromString("file://*"));
+}
+
+TEST_F(OriginMatcherTest, ExactMatching) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("https://www.example.com:99"));
+ EXPECT_EQ("https://www.example.com:99", matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:99")));
+
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://music.example.com:99")));
+}
+
+TEST_F(OriginMatcherTest, SchemeDefaultPortHttp) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("http://www.example.com"));
+ EXPECT_EQ("http://www.example.com:80", matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:80")));
+
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://music.example.com:80")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://music.example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:80")));
+}
+
+TEST_F(OriginMatcherTest, SchemeDefaultPortHttps) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("https://www.example.com"));
+ EXPECT_EQ("https://www.example.com:443", matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:443")));
+
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:443")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://music.example.com:99")));
+}
+
+TEST_F(OriginMatcherTest, SubdomainMatching) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("https://*.example.com"));
+ EXPECT_EQ("https://*.example.com:443", matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:443")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://music.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://music.example.com:443")));
+ EXPECT_TRUE(matcher.Matches(
+ CreateOriginFromString("https://music.video.radio.example.com")));
+
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("ftp://www.example.com")));
+ EXPECT_FALSE(matcher.Matches(CreateOriginFromString("https://example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://music.example.com:99")));
+}
+
+TEST_F(OriginMatcherTest, SubdomainMatching2) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("http://*.www.example.com"));
+ EXPECT_EQ("http://*.www.example.com:80", matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://www.www.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://abc.www.example.com:80")));
+ EXPECT_TRUE(matcher.Matches(
+ CreateOriginFromString("http://music.video.www.example.com")));
+
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("ftp://www.example.com")));
+ EXPECT_FALSE(matcher.Matches(CreateOriginFromString("https://example.com")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com:99")));
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://music.example.com:99")));
+}
+
+TEST_F(OriginMatcherTest, PunyCode) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("http://*.xn--fsqu00a.com"));
+
+ // Chinese domain example.com
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("http://www.例子.com")));
+}
+
+TEST_F(OriginMatcherTest, IPv4AddressMatching) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("https://192.168.0.1"));
+ EXPECT_EQ("https://192.168.0.1:443", matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("https://192.168.0.1")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://192.168.0.1:443")));
+
+ EXPECT_FALSE(
+ matcher.Matches(CreateOriginFromString("https://192.168.0.1:99")));
+ EXPECT_FALSE(matcher.Matches(CreateOriginFromString("http://192.168.0.1")));
+ EXPECT_FALSE(matcher.Matches(CreateOriginFromString("http://192.168.0.2")));
+}
+
+TEST_F(OriginMatcherTest, IPv6AddressMatching) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("https://[3ffe:2a00:100:7031:0:0::1]"));
+ // Note that the IPv6 address is canonicalized.
+ EXPECT_EQ("https://[3ffe:2a00:100:7031::1]:443",
+ matcher.rules()[0]->ToString());
+
+ EXPECT_TRUE(matcher.Matches(
+ CreateOriginFromString("https://[3ffe:2a00:100:7031::1]")));
+ EXPECT_TRUE(matcher.Matches(
+ CreateOriginFromString("https://[3ffe:2a00:100:7031::1]:443")));
+
+ EXPECT_FALSE(matcher.Matches(
+ CreateOriginFromString("http://[3ffe:2a00:100:7031::1]")));
+ EXPECT_FALSE(matcher.Matches(
+ CreateOriginFromString("http://[3ffe:2a00:100:7031::1]:443")));
+ EXPECT_FALSE(matcher.Matches(
+ CreateOriginFromString("https://[3ffe:2a00:100:7031::1]:8080")));
+}
+
+TEST_F(OriginMatcherTest, WildcardMatchesEveryOrigin) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("*"));
+
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://www.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://foo.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://www.example.com:8080")));
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("http://192.168.0.1")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("http://192.168.0.1:8080")));
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("https://[a:b:c:d::]")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("https://[a:b:c:d::]:8080")));
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("ftp://example.com")));
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("about:blank")));
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString(
+ "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("file:///usr/local/a.txt")));
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString(
+ "blob:http://127.0.0.1:8080/0530b9d1-c1c2-40ff-9f9c-c57336646baa")));
+}
+
+TEST_F(OriginMatcherTest, FileOrigin) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("file://"));
+
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("file:///sdcard")));
+ EXPECT_TRUE(
+ matcher.Matches(CreateOriginFromString("file:///android_assets")));
+}
+
+TEST_F(OriginMatcherTest, CustomSchemeOrigin) {
+ OriginMatcher matcher;
+ EXPECT_TRUE(matcher.AddRuleFromString("x-mail://"));
+
+ EXPECT_TRUE(matcher.Matches(CreateOriginFromString("x-mail://hostname")));
+}
+
+namespace {
+
+void CompareMatcherRules(const OriginMatcherRule& r1,
+ const OriginMatcherRule& r2) {
+ ASSERT_EQ(r1.type(), r2.type());
+ if (r1.type() == js_injection::OriginMatcherRuleType::kAny)
+ return;
+ const SubdomainMatchingRule& s1 =
+ static_cast<const SubdomainMatchingRule&>(r1);
+ const SubdomainMatchingRule& s2 =
+ static_cast<const SubdomainMatchingRule&>(r2);
+ EXPECT_EQ(s1.scheme(), s2.scheme());
+ EXPECT_EQ(s1.optional_host(), s2.optional_host());
+ EXPECT_EQ(s1.optional_port(), s2.optional_port());
+}
+
+void CompareMatchers(const OriginMatcher& m1, const OriginMatcher& m2) {
+ ASSERT_EQ(m1.rules().size(), m2.rules().size());
+ for (size_t i = 0; i < m1.rules().size(); ++i) {
+ ASSERT_NO_FATAL_FAILURE(
+ CompareMatcherRules(*(m1.rules()[i].get()), *(m2.rules())[i].get()));
+ }
+}
+
+} // namespace
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeMatchAll) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ ASSERT_TRUE(matcher.AddRuleFromString("*"));
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+ ASSERT_NO_FATAL_FAILURE(CompareMatchers(matcher, deserialized));
+}
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeSubdomainMatcher) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ ASSERT_TRUE(matcher.AddRuleFromString("https://*.example.com"));
+ ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+ ASSERT_NO_FATAL_FAILURE(CompareMatchers(matcher, deserialized));
+}
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeInvalidSubdomain) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ {
+ OriginMatcher::RuleList rules;
+ // The subdomain is not allowed to have a '/'.
+ rules.push_back(std::make_unique<SubdomainMatchingRule>(
+ "http", "bogus/host", 100, true));
+ matcher.SetRules(std::move(rules));
+ }
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+}
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeInvalidScheme) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ {
+ OriginMatcher::RuleList rules;
+ // The scheme can not be empty.
+ rules.push_back(std::make_unique<SubdomainMatchingRule>(std::string(),
+ "host", 101, true));
+ matcher.SetRules(std::move(rules));
+ }
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+}
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeTooManyWildcards) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ {
+ OriginMatcher::RuleList rules;
+ // Only one wildcard is allowed.
+ rules.push_back(
+ std::make_unique<SubdomainMatchingRule>("http", "**", 101, true));
+ matcher.SetRules(std::move(rules));
+ }
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+}
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeInvalidWildcard) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ {
+ OriginMatcher::RuleList rules;
+ // The wild card must be at the front.
+ rules.push_back(
+ std::make_unique<SubdomainMatchingRule>("http", "ab*", 101, true));
+ matcher.SetRules(std::move(rules));
+ }
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+}
+
+TEST_F(OriginMatcherTest, SerializeAndDeserializeValidWildcard) {
+ OriginMatcher matcher;
+ OriginMatcher deserialized;
+ {
+ OriginMatcher::RuleList rules;
+ // The wild card must be at the front.
+ rules.push_back(
+ std::make_unique<SubdomainMatchingRule>("http", "*.ab", 101, true));
+ matcher.SetRules(std::move(rules));
+ }
+ EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::OriginMatcher>(
+ &matcher, &deserialized));
+ ASSERT_NO_FATAL_FAILURE(CompareMatchers(matcher, deserialized));
+}
+
+} // namespace js_injection
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_