blob: e6cd1b2be8303628493c8a389dd39d6ac1379be6 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/web/content/js_messaging/content_web_frames_manager.h"
#import <set>
#import "base/ios/device_util.h"
#import "components/js_injection/browser/js_communication_host.h"
#import "content/public/browser/navigation_handle.h"
#import "content/public/browser/page.h"
#import "content/public/browser/web_contents.h"
#import "ios/web/content/js_messaging/content_java_script_feature_manager.h"
#import "ios/web/content/js_messaging/content_web_frame.h"
#import "ios/web/content/js_messaging/ios_web_message_host_factory.h"
#import "ios/web/content/web_state/content_web_state.h"
#import "ios/web/public/js_messaging/java_script_feature_util.h"
#import "ios/web/public/js_messaging/script_message.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace web {
namespace {
// Names of JSON dictionary properties that JavaScript populates when sending
// messages to the browser.
const char kHandlerNamePropertyName[] = "handler_name";
const char kMessagePropertyName[] = "message";
} // namespace
ContentWebFramesManager::ContentWebFramesManager(
ContentWebState* content_web_state)
: content::WebContentsObserver(content_web_state->GetWebContents()),
content_web_state_(content_web_state),
js_communication_host_(
std::make_unique<js_injection::JsCommunicationHost>(
content_web_state->GetWebContents())) {
auto web_message_callback =
base::BindRepeating(&ContentWebFramesManager::ScriptMessageReceived,
weak_factory_.GetWeakPtr());
auto message_host_factory =
std::make_unique<IOSWebMessageHostFactory>(web_message_callback);
js_communication_host_->AddWebMessageHostFactory(
std::move(message_host_factory), u"webkitMessageHandler", {"*"});
std::vector<JavaScriptFeature*> java_script_features;
java_script_features.push_back(
java_script_features::GetBaseJavaScriptFeature());
// TODO(crbug.com/1423527): Insert another feature that overrides the
// definition of sendWebKitMessage from common.js, to use
// webkitMessageHandler.postMessage.
java_script_features.push_back(
java_script_features::GetCommonJavaScriptFeature());
java_script_features.push_back(
java_script_features::GetMessageJavaScriptFeature());
// TODO(crbug.com/1423527): Insert other JavaScriptFeatures.
js_feature_manager_ = std::make_unique<ContentJavaScriptFeatureManager>(
std::move(java_script_features));
js_feature_manager_->AddDocumentStartScripts(js_communication_host_.get());
}
ContentWebFramesManager::~ContentWebFramesManager() = default;
void ContentWebFramesManager::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void ContentWebFramesManager::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
std::set<WebFrame*> ContentWebFramesManager::GetAllWebFrames() {
std::set<WebFrame*> frames;
for (const auto& it : available_frame_hosts_) {
frames.insert(WebFrameForContentId(it));
}
return frames;
}
WebFrame* ContentWebFramesManager::GetMainWebFrame() {
auto web_id_it = content_to_web_id_map_.find(main_frame_content_id_);
if (web_id_it == content_to_web_id_map_.end()) {
return nullptr;
}
return GetFrameWithId(web_id_it->second);
}
WebFrame* ContentWebFramesManager::GetFrameWithId(const std::string& frame_id) {
if (frame_id.empty()) {
return nullptr;
}
auto web_frames_it = web_frames_.find(frame_id);
return web_frames_it == web_frames_.end() ? nullptr
: web_frames_it->second.get();
}
void ContentWebFramesManager::RenderFrameCreated(
content::RenderFrameHost* render_frame_host) {
// TODO(crbug.com/1423527): Ensure that the random id chosen here is either
// injected into the frame or directly attached to JavaScript messages
// received from the frame, since features expect this.
std::string web_frame_id = ios::device_util::GetRandomId();
auto web_frame = std::make_unique<ContentWebFrame>(
web_frame_id, render_frame_host, content_web_state_);
web_frames_[web_frame_id] = std::move(web_frame);
content_to_web_id_map_[render_frame_host->GetGlobalId()] = web_frame_id;
}
void ContentWebFramesManager::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
content::GlobalRenderFrameHostId content_id =
render_frame_host->GetGlobalId();
auto web_id_it = content_to_web_id_map_.find(content_id);
DCHECK(web_id_it != content_to_web_id_map_.end());
if (available_frame_hosts_.count(content_id)) {
for (auto& observer : observers_) {
observer.WebFrameBecameUnavailable(this, web_id_it->second);
}
available_frame_hosts_.erase(content_id);
}
if (main_frame_content_id_ == content_id) {
main_frame_content_id_ = content::GlobalRenderFrameHostId();
}
web_frames_.erase(web_id_it->second);
content_to_web_id_map_.erase(web_id_it);
}
void ContentWebFramesManager::DOMContentLoaded(
content::RenderFrameHost* render_frame_host) {
js_feature_manager_->InjectDocumentEndScripts(render_frame_host);
}
void ContentWebFramesManager::PrimaryPageChanged(content::Page& page) {
main_frame_content_id_ = page.GetMainDocument().GetGlobalId();
}
void ContentWebFramesManager::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
content::RenderFrameHost* render_frame_host =
navigation_handle->GetRenderFrameHost();
// Some navigations (e.g., downloads, 204 responses) do not have a
// RenderFrameHost.
if (!render_frame_host) {
return;
}
// TODO(crbug.com/1423527): Inject JavaScript to override `getFrameId` to
// return the WebFrame id chosen in `RenderFrameCreated`.
content::GlobalRenderFrameHostId content_id =
render_frame_host->GetGlobalId();
if (available_frame_hosts_.count(content_id)) {
return;
}
available_frame_hosts_.insert(content_id);
// Notify observers here rather than in `RenderFrameCreated`, to
// ensure that frames are no longer in a speculative lifecycle
// phase where JavaScript injection is not yet allowed. crbug.com/1183639
// tracks delaying `RenderFrameCreated` until frames are past the
// speculative state, which is not intended to be exposed to embedders.
WebFrame* web_frame = WebFrameForContentId(content_id);
for (auto& observer : observers_) {
observer.WebFrameBecameAvailable(this, web_frame);
}
}
WebFrame* ContentWebFramesManager::WebFrameForContentId(
content::GlobalRenderFrameHostId content_id) {
auto web_id_it = content_to_web_id_map_.find(content_id);
DCHECK(web_id_it != content_to_web_id_map_.end());
auto web_frame_it = web_frames_.find(web_id_it->second);
DCHECK(web_frame_it != web_frames_.end());
return web_frame_it->second.get();
}
void ContentWebFramesManager::ScriptMessageReceived(
const ScriptMessage& script_message) {
// In ios/web/content, only a single script message handler is exposed to
// JavaScript. To simulate having multiple handlers (as in WKWebView-based
// ios/web), messages sent to this handler have the form of a dictionary with
// two fields, specifying the destination handler name along with the actual
// message for that handler. Once these two parts are extracted from
// `script_message`, a new ScriptMessage is constructed with only the actual
// message intended for the handler.
base::Value::Dict* dict = script_message.body()->GetIfDict();
if (!dict) {
return;
}
const std::string* handler_name = dict->FindString(kHandlerNamePropertyName);
base::Value* message_content = dict->Find(kMessagePropertyName);
if (!handler_name || !message_content) {
return;
}
ScriptMessage message_for_handler(
std::make_unique<base::Value>(std::move(*message_content)),
script_message.is_user_interacting(), script_message.is_main_frame(),
script_message.request_url());
js_feature_manager_->ScriptMessageReceived(message_for_handler, *handler_name,
content_web_state_);
}
} // namespace web