feat: add WebFrameMain detached property (#43473)
* feat: add WebFrameMain detached property fix: throw instead of returning null senderFrame test: detached frames fix: ensure IPCs of pending deletion RFHs are dispatched fix: lookup WFM by FTN ID to dispatch IPCs feat: add frame.isDestroyed() return null fix: return undefined docs: add null to all frame properties refactor: option c, return null and emit warning refactor: add routingId & processId to navigation events test: null frame property docs: clarify warning message better wording clarify null frame fix: browserwindow spec * maybe fix 🤷 * fix: use updated util #43722 * docs: add notice for frame change of behavior * docs: clarify why frame properties may be null * lint * wip * fix: content::FrameTreeNodeId lookup and converter * refactor: avoid holey array deoptimization --------- Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
This commit is contained in:
parent
527efc01a4
commit
8b3d70a2a3
20 changed files with 410 additions and 126 deletions
|
@ -33,6 +33,37 @@
|
|||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/v8_value_serializer.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// RenderFrameHost (RFH) exists as a child of a FrameTreeNode. When a
|
||||
// cross-origin navigation occurs, the FrameTreeNode swaps RFHs. After
|
||||
// swapping, the old RFH will be marked for deletion and run any unload
|
||||
// listeners. If an IPC is sent during an unload/beforeunload listener,
|
||||
// it's possible that it arrives after the RFH swap and has been
|
||||
// detached from the FrameTreeNode.
|
||||
bool IsDetachedFrameHost(content::RenderFrameHost* rfh) {
|
||||
if (!rfh)
|
||||
return true;
|
||||
|
||||
// RenderFrameCreated is called for speculative frames which may not be
|
||||
// used in certain cross-origin navigations. Invoking
|
||||
// RenderFrameHost::GetLifecycleState currently crashes when called for
|
||||
// speculative frames so we need to filter it out for now. Check
|
||||
// https://crbug.com/1183639 for details on when this can be removed.
|
||||
auto* rfh_impl = static_cast<content::RenderFrameHostImpl*>(rfh);
|
||||
|
||||
// During cross-origin navigation, a RFH may be swapped out of its
|
||||
// FrameTreeNode with a new RFH. In these cases, it's marked for
|
||||
// deletion. As this pending deletion RFH won't be following future
|
||||
// swaps, we need to indicate that its been pinned.
|
||||
return (rfh_impl->lifecycle_state() !=
|
||||
content::RenderFrameHostImpl::LifecycleStateImpl::kSpeculative &&
|
||||
rfh->GetLifecycleState() ==
|
||||
content::RenderFrameHost::LifecycleState::kPendingDeletion);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace gin {
|
||||
|
||||
template <>
|
||||
|
@ -57,45 +88,64 @@ struct Converter<blink::mojom::PageVisibilityState> {
|
|||
|
||||
namespace electron::api {
|
||||
|
||||
// FrameTreeNodeId -> WebFrameMain*
|
||||
// Using FrameTreeNode allows us to track frame across navigations. This
|
||||
// is most similar to how <iframe> works.
|
||||
typedef std::unordered_map<content::FrameTreeNodeId,
|
||||
WebFrameMain*,
|
||||
content::FrameTreeNodeId::Hasher>
|
||||
WebFrameMainIdMap;
|
||||
FrameTreeNodeIdMap;
|
||||
|
||||
WebFrameMainIdMap& GetWebFrameMainMap() {
|
||||
static base::NoDestructor<WebFrameMainIdMap> instance;
|
||||
// Token -> WebFrameMain*
|
||||
// Maps exact RFH to a WebFrameMain instance.
|
||||
typedef std::map<content::GlobalRenderFrameHostToken, WebFrameMain*>
|
||||
FrameTokenMap;
|
||||
|
||||
FrameTreeNodeIdMap& GetFrameTreeNodeIdMap() {
|
||||
static base::NoDestructor<FrameTreeNodeIdMap> instance;
|
||||
return *instance;
|
||||
}
|
||||
FrameTokenMap& GetFrameTokenMap() {
|
||||
static base::NoDestructor<FrameTokenMap> instance;
|
||||
return *instance;
|
||||
}
|
||||
|
||||
// static
|
||||
WebFrameMain* WebFrameMain::FromFrameTreeNodeId(
|
||||
content::FrameTreeNodeId frame_tree_node_id) {
|
||||
WebFrameMainIdMap& frame_map = GetWebFrameMainMap();
|
||||
// Pinned frames aren't tracked across navigations so only non-pinned
|
||||
// frames will be retrieved.
|
||||
FrameTreeNodeIdMap& frame_map = GetFrameTreeNodeIdMap();
|
||||
auto iter = frame_map.find(frame_tree_node_id);
|
||||
auto* web_frame = iter == frame_map.end() ? nullptr : iter->second;
|
||||
return web_frame;
|
||||
}
|
||||
|
||||
// static
|
||||
WebFrameMain* WebFrameMain::FromFrameToken(
|
||||
content::GlobalRenderFrameHostToken frame_token) {
|
||||
FrameTokenMap& frame_map = GetFrameTokenMap();
|
||||
auto iter = frame_map.find(frame_token);
|
||||
auto* web_frame = iter == frame_map.end() ? nullptr : iter->second;
|
||||
return web_frame;
|
||||
}
|
||||
|
||||
// static
|
||||
WebFrameMain* WebFrameMain::FromRenderFrameHost(content::RenderFrameHost* rfh) {
|
||||
if (!rfh)
|
||||
return nullptr;
|
||||
|
||||
// TODO(codebytere): remove after refactoring away from FrameTreeNodeId as map
|
||||
// key.
|
||||
auto* ftn =
|
||||
static_cast<content::RenderFrameHostImpl*>(rfh)->frame_tree_node();
|
||||
if (!ftn)
|
||||
return nullptr;
|
||||
|
||||
return FromFrameTreeNodeId(rfh->GetFrameTreeNodeId());
|
||||
return FromFrameToken(rfh->GetGlobalFrameToken());
|
||||
}
|
||||
|
||||
gin::WrapperInfo WebFrameMain::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
|
||||
WebFrameMain::WebFrameMain(content::RenderFrameHost* rfh)
|
||||
: frame_tree_node_id_(rfh->GetFrameTreeNodeId()), render_frame_(rfh) {
|
||||
GetWebFrameMainMap().emplace(frame_tree_node_id_, this);
|
||||
: frame_tree_node_id_(rfh->GetFrameTreeNodeId()),
|
||||
frame_token_(rfh->GetGlobalFrameToken()),
|
||||
render_frame_(rfh),
|
||||
render_frame_detached_(IsDetachedFrameHost(rfh)) {
|
||||
GetFrameTreeNodeIdMap().emplace(frame_tree_node_id_, this);
|
||||
GetFrameTokenMap().emplace(frame_token_, this);
|
||||
}
|
||||
|
||||
WebFrameMain::~WebFrameMain() {
|
||||
|
@ -104,18 +154,24 @@ WebFrameMain::~WebFrameMain() {
|
|||
|
||||
void WebFrameMain::Destroyed() {
|
||||
MarkRenderFrameDisposed();
|
||||
GetWebFrameMainMap().erase(frame_tree_node_id_);
|
||||
GetFrameTreeNodeIdMap().erase(frame_tree_node_id_);
|
||||
GetFrameTokenMap().erase(frame_token_);
|
||||
Unpin();
|
||||
}
|
||||
|
||||
void WebFrameMain::MarkRenderFrameDisposed() {
|
||||
render_frame_ = nullptr;
|
||||
render_frame_detached_ = true;
|
||||
render_frame_disposed_ = true;
|
||||
TeardownMojoConnection();
|
||||
}
|
||||
|
||||
// Should only be called when swapping frames.
|
||||
void WebFrameMain::UpdateRenderFrameHost(content::RenderFrameHost* rfh) {
|
||||
// Should only be called when swapping frames.
|
||||
GetFrameTokenMap().erase(frame_token_);
|
||||
frame_token_ = rfh->GetGlobalFrameToken();
|
||||
GetFrameTokenMap().emplace(frame_token_, this);
|
||||
|
||||
render_frame_disposed_ = false;
|
||||
render_frame_ = rfh;
|
||||
TeardownMojoConnection();
|
||||
|
@ -186,6 +242,10 @@ bool WebFrameMain::Reload() {
|
|||
return render_frame_->Reload();
|
||||
}
|
||||
|
||||
bool WebFrameMain::IsDestroyed() const {
|
||||
return render_frame_disposed_;
|
||||
}
|
||||
|
||||
void WebFrameMain::Send(v8::Isolate* isolate,
|
||||
bool internal,
|
||||
const std::string& channel,
|
||||
|
@ -275,8 +335,12 @@ void WebFrameMain::PostMessage(v8::Isolate* isolate,
|
|||
std::move(transferable_message));
|
||||
}
|
||||
|
||||
int WebFrameMain::FrameTreeNodeIDAsInt() const {
|
||||
return frame_tree_node_id_.value();
|
||||
bool WebFrameMain::Detached() const {
|
||||
return render_frame_detached_;
|
||||
}
|
||||
|
||||
content::FrameTreeNodeId WebFrameMain::FrameTreeNodeID() const {
|
||||
return frame_tree_node_id_;
|
||||
}
|
||||
|
||||
std::string WebFrameMain::Name() const {
|
||||
|
@ -389,29 +453,17 @@ gin::Handle<WebFrameMain> WebFrameMain::From(v8::Isolate* isolate,
|
|||
return handle;
|
||||
}
|
||||
|
||||
// static
|
||||
gin::Handle<WebFrameMain> WebFrameMain::FromOrNull(
|
||||
v8::Isolate* isolate,
|
||||
content::RenderFrameHost* rfh) {
|
||||
if (!rfh)
|
||||
return gin::Handle<WebFrameMain>();
|
||||
|
||||
auto* web_frame = FromRenderFrameHost(rfh);
|
||||
if (!web_frame)
|
||||
return gin::Handle<WebFrameMain>();
|
||||
|
||||
return gin::CreateHandle(isolate, web_frame);
|
||||
}
|
||||
|
||||
// static
|
||||
void WebFrameMain::FillObjectTemplate(v8::Isolate* isolate,
|
||||
v8::Local<v8::ObjectTemplate> templ) {
|
||||
gin_helper::ObjectTemplateBuilder(isolate, templ)
|
||||
.SetMethod("executeJavaScript", &WebFrameMain::ExecuteJavaScript)
|
||||
.SetMethod("reload", &WebFrameMain::Reload)
|
||||
.SetMethod("isDestroyed", &WebFrameMain::IsDestroyed)
|
||||
.SetMethod("_send", &WebFrameMain::Send)
|
||||
.SetMethod("_postMessage", &WebFrameMain::PostMessage)
|
||||
.SetProperty("frameTreeNodeId", &WebFrameMain::FrameTreeNodeIDAsInt)
|
||||
.SetProperty("detached", &WebFrameMain::Detached)
|
||||
.SetProperty("frameTreeNodeId", &WebFrameMain::FrameTreeNodeID)
|
||||
.SetProperty("name", &WebFrameMain::Name)
|
||||
.SetProperty("osProcessId", &WebFrameMain::OSProcessID)
|
||||
.SetProperty("processId", &WebFrameMain::ProcessID)
|
||||
|
@ -446,22 +498,38 @@ v8::Local<v8::Value> FromID(gin_helper::ErrorThrower thrower,
|
|||
|
||||
auto* rfh =
|
||||
content::RenderFrameHost::FromID(render_process_id, render_frame_id);
|
||||
if (!rfh)
|
||||
return v8::Undefined(thrower.isolate());
|
||||
|
||||
return WebFrameMain::From(thrower.isolate(), rfh).ToV8();
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> FromIDOrNull(gin_helper::ErrorThrower thrower,
|
||||
int render_process_id,
|
||||
int render_frame_id) {
|
||||
v8::Local<v8::Value> FromIdIfExists(gin_helper::ErrorThrower thrower,
|
||||
int render_process_id,
|
||||
int render_frame_id) {
|
||||
if (!electron::Browser::Get()->is_ready()) {
|
||||
thrower.ThrowError("WebFrameMain is available only after app ready");
|
||||
return v8::Null(thrower.isolate());
|
||||
}
|
||||
|
||||
auto* rfh =
|
||||
content::RenderFrameHost* rfh =
|
||||
content::RenderFrameHost::FromID(render_process_id, render_frame_id);
|
||||
WebFrameMain* web_frame = WebFrameMain::FromRenderFrameHost(rfh);
|
||||
if (!web_frame)
|
||||
return v8::Null(thrower.isolate());
|
||||
return gin::CreateHandle(thrower.isolate(), web_frame).ToV8();
|
||||
}
|
||||
|
||||
return WebFrameMain::FromOrNull(thrower.isolate(), rfh).ToV8();
|
||||
v8::Local<v8::Value> FromFtnIdIfExists(gin_helper::ErrorThrower thrower,
|
||||
int frame_tree_node_id) {
|
||||
if (!electron::Browser::Get()->is_ready()) {
|
||||
thrower.ThrowError("WebFrameMain is available only after app ready");
|
||||
return v8::Null(thrower.isolate());
|
||||
}
|
||||
WebFrameMain* web_frame = WebFrameMain::FromFrameTreeNodeId(
|
||||
content::FrameTreeNodeId(frame_tree_node_id));
|
||||
if (!web_frame)
|
||||
return v8::Null(thrower.isolate());
|
||||
return gin::CreateHandle(thrower.isolate(), web_frame).ToV8();
|
||||
}
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
|
@ -472,7 +540,8 @@ void Initialize(v8::Local<v8::Object> exports,
|
|||
gin_helper::Dictionary dict(isolate, exports);
|
||||
dict.Set("WebFrameMain", WebFrameMain::GetConstructor(context));
|
||||
dict.SetMethod("fromId", &FromID);
|
||||
dict.SetMethod("fromIdOrNull", &FromIDOrNull);
|
||||
dict.SetMethod("_fromIdIfExists", &FromIdIfExists);
|
||||
dict.SetMethod("_fromFtnIdIfExists", &FromFtnIdIfExists);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue