blob: e2cc9cf415c16cf3e1a88a1e9f92b72b238fdeae [file] [log] [blame]
// 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/containers/contains.h"
#include "base/memory/raw_ptr.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/disallow_activation_reason.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:
ReplyProxyImpl(content::RenderFrameHost* render_frame_host,
mojo::PendingAssociatedRemote<mojom::BrowserToJsMessaging>
java_to_js_messaging)
: render_frame_host_(render_frame_host),
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);
}
bool IsInBackForwardCache() override {
return render_frame_host_->GetLifecycleState() ==
content::RenderFrameHost::LifecycleState::kInBackForwardCache;
}
private:
raw_ptr<content::RenderFrameHost> render_frame_host_;
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::OnBackForwardCacheStateChanged() {
if (host_)
host_->OnBackForwardCacheStateChanged();
}
void JsToBrowserMessaging::PostMessage(
const std::u16string& message,
std::vector<blink::MessagePortDescriptor> ports) {
DCHECK(render_frame_host_);
if (render_frame_host_->IsInactiveAndDisallowActivation(
content::DisallowActivationReasonId::kJsInjectionPostMessage)) {
return;
}
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) {
// TODO(https://crbug.com/1183557): this should really call
// IsInactiveAndDisallowReactivation().
// 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>(
render_frame_host_, std::move(java_to_js_messaging));
}
} // namespace js_injection