fix: dangling speculative frames (#45609)
* fix: dangling speculative frames * harden lifecycle state checks * feedback * add const
This commit is contained in:
parent
ecd7eb36ac
commit
ee67bc7dcb
6 changed files with 174 additions and 53 deletions
|
@ -1792,7 +1792,6 @@ void WebContents::RenderFrameDeleted(
|
||||||
// - An <iframe> is removed from the DOM.
|
// - An <iframe> is removed from the DOM.
|
||||||
// - Cross-origin navigation creates a new RFH in a separate process which
|
// - Cross-origin navigation creates a new RFH in a separate process which
|
||||||
// is swapped by content::RenderFrameHostManager.
|
// is swapped by content::RenderFrameHostManager.
|
||||||
//
|
|
||||||
|
|
||||||
// WebFrameMain::FromRenderFrameHost(rfh) will use the RFH's FrameTreeNode ID
|
// WebFrameMain::FromRenderFrameHost(rfh) will use the RFH's FrameTreeNode ID
|
||||||
// to find an existing instance of WebFrameMain. During a cross-origin
|
// to find an existing instance of WebFrameMain. During a cross-origin
|
||||||
|
@ -1800,8 +1799,13 @@ void WebContents::RenderFrameDeleted(
|
||||||
// this special case, we need to also ensure that WebFrameMain's internal RFH
|
// this special case, we need to also ensure that WebFrameMain's internal RFH
|
||||||
// matches before marking it as disposed.
|
// matches before marking it as disposed.
|
||||||
auto* web_frame = WebFrameMain::FromRenderFrameHost(render_frame_host);
|
auto* web_frame = WebFrameMain::FromRenderFrameHost(render_frame_host);
|
||||||
if (web_frame && web_frame->render_frame_host() == render_frame_host)
|
if (web_frame) {
|
||||||
|
// Need to directly compare frame tokens as frames pending deletion can no
|
||||||
|
// longer be looked up using content::RenderFrameHost::FromFrameToken().
|
||||||
|
if (web_frame->frame_token_ == render_frame_host->GetGlobalFrameToken()) {
|
||||||
web_frame->MarkRenderFrameDisposed();
|
web_frame->MarkRenderFrameDisposed();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebContents::RenderFrameHostChanged(content::RenderFrameHost* old_host,
|
void WebContents::RenderFrameHostChanged(content::RenderFrameHost* old_host,
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "shell/common/gin_converters/blink_converter.h"
|
#include "shell/common/gin_converters/blink_converter.h"
|
||||||
#include "shell/common/gin_converters/frame_converter.h"
|
#include "shell/common/gin_converters/frame_converter.h"
|
||||||
#include "shell/common/gin_converters/gurl_converter.h"
|
#include "shell/common/gin_converters/gurl_converter.h"
|
||||||
|
#include "shell/common/gin_converters/std_converter.h"
|
||||||
#include "shell/common/gin_converters/value_converter.h"
|
#include "shell/common/gin_converters/value_converter.h"
|
||||||
#include "shell/common/gin_helper/dictionary.h"
|
#include "shell/common/gin_helper/dictionary.h"
|
||||||
#include "shell/common/gin_helper/error_thrower.h"
|
#include "shell/common/gin_helper/error_thrower.h"
|
||||||
|
@ -37,31 +38,34 @@
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
using LifecycleState = content::RenderFrameHostImpl::LifecycleStateImpl;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
[[nodiscard]] LifecycleState GetLifecycleState(
|
||||||
|
const content::RenderFrameHost* rfh) {
|
||||||
|
const auto* rfh_impl = static_cast<const content::RenderFrameHostImpl*>(rfh);
|
||||||
|
return rfh_impl->lifecycle_state();
|
||||||
|
}
|
||||||
|
|
||||||
// RenderFrameHost (RFH) exists as a child of a FrameTreeNode. When a
|
// RenderFrameHost (RFH) exists as a child of a FrameTreeNode. When a
|
||||||
// cross-origin navigation occurs, the FrameTreeNode swaps RFHs. After
|
// cross-origin navigation occurs, the FrameTreeNode swaps RFHs. After
|
||||||
// swapping, the old RFH will be marked for deletion and run any unload
|
// swapping, the old RFH will be marked for deletion and run any unload
|
||||||
// listeners. If an IPC is sent during an unload/beforeunload listener,
|
// listeners. If an IPC is sent during an unload/beforeunload listener,
|
||||||
// it's possible that it arrives after the RFH swap and has been
|
// it's possible that it arrives after the RFH swap and has been
|
||||||
// detached from the FrameTreeNode.
|
// detached from the FrameTreeNode.
|
||||||
bool IsDetachedFrameHost(content::RenderFrameHost* rfh) {
|
[[nodiscard]] bool IsDetachedFrameHost(const content::RenderFrameHost* rfh) {
|
||||||
if (!rfh)
|
if (!rfh)
|
||||||
return true;
|
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
|
// During cross-origin navigation, a RFH may be swapped out of its
|
||||||
// FrameTreeNode with a new RFH. In these cases, it's marked for
|
// FrameTreeNode with a new RFH. In these cases, it's marked for
|
||||||
// deletion. As this pending deletion RFH won't be following future
|
// deletion. As this pending deletion RFH won't be following future
|
||||||
// swaps, we need to indicate that its been pinned.
|
// swaps, we need to indicate that its been detached.
|
||||||
return (rfh_impl->lifecycle_state() !=
|
return GetLifecycleState(rfh) == LifecycleState::kRunningUnloadHandlers;
|
||||||
content::RenderFrameHostImpl::LifecycleStateImpl::kSpeculative &&
|
|
||||||
rfh->GetLifecycleState() ==
|
|
||||||
content::RenderFrameHost::LifecycleState::kPendingDeletion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -117,8 +121,6 @@ FrameTokenMap& GetFrameTokenMap() {
|
||||||
// static
|
// static
|
||||||
WebFrameMain* WebFrameMain::FromFrameTreeNodeId(
|
WebFrameMain* WebFrameMain::FromFrameTreeNodeId(
|
||||||
content::FrameTreeNodeId frame_tree_node_id) {
|
content::FrameTreeNodeId frame_tree_node_id) {
|
||||||
// Pinned frames aren't tracked across navigations so only non-pinned
|
|
||||||
// frames will be retrieved.
|
|
||||||
FrameTreeNodeIdMap& frame_map = GetFrameTreeNodeIdMap();
|
FrameTreeNodeIdMap& frame_map = GetFrameTreeNodeIdMap();
|
||||||
auto iter = frame_map.find(frame_tree_node_id);
|
auto iter = frame_map.find(frame_tree_node_id);
|
||||||
auto* web_frame = iter == frame_map.end() ? nullptr : iter->second;
|
auto* web_frame = iter == frame_map.end() ? nullptr : iter->second;
|
||||||
|
@ -141,15 +143,29 @@ WebFrameMain* WebFrameMain::FromRenderFrameHost(content::RenderFrameHost* rfh) {
|
||||||
return FromFrameToken(rfh->GetGlobalFrameToken());
|
return FromFrameToken(rfh->GetGlobalFrameToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content::RenderFrameHost* WebFrameMain::render_frame_host() const {
|
||||||
|
return render_frame_disposed_
|
||||||
|
? nullptr
|
||||||
|
: content::RenderFrameHost::FromFrameToken(frame_token_);
|
||||||
|
}
|
||||||
|
|
||||||
gin::WrapperInfo WebFrameMain::kWrapperInfo = {gin::kEmbedderNativeGin};
|
gin::WrapperInfo WebFrameMain::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||||
|
|
||||||
WebFrameMain::WebFrameMain(content::RenderFrameHost* rfh)
|
WebFrameMain::WebFrameMain(content::RenderFrameHost* rfh)
|
||||||
: frame_tree_node_id_(rfh->GetFrameTreeNodeId()),
|
: frame_tree_node_id_(rfh->GetFrameTreeNodeId()),
|
||||||
frame_token_(rfh->GetGlobalFrameToken()),
|
frame_token_(rfh->GetGlobalFrameToken()),
|
||||||
render_frame_(rfh),
|
|
||||||
render_frame_detached_(IsDetachedFrameHost(rfh)) {
|
render_frame_detached_(IsDetachedFrameHost(rfh)) {
|
||||||
|
// Detached RFH should not insert itself in FTN lookup since it has been
|
||||||
|
// swapped already.
|
||||||
|
if (!render_frame_detached_)
|
||||||
GetFrameTreeNodeIdMap().emplace(frame_tree_node_id_, this);
|
GetFrameTreeNodeIdMap().emplace(frame_tree_node_id_, this);
|
||||||
|
|
||||||
|
DCHECK(GetFrameTokenMap().find(frame_token_) == GetFrameTokenMap().end());
|
||||||
GetFrameTokenMap().emplace(frame_token_, this);
|
GetFrameTokenMap().emplace(frame_token_, this);
|
||||||
|
|
||||||
|
// WebFrameMain should only be created for active or unloading frames.
|
||||||
|
DCHECK(GetLifecycleState(rfh) == LifecycleState::kActive ||
|
||||||
|
GetLifecycleState(rfh) == LifecycleState::kRunningUnloadHandlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebFrameMain::~WebFrameMain() {
|
WebFrameMain::~WebFrameMain() {
|
||||||
|
@ -157,14 +173,19 @@ WebFrameMain::~WebFrameMain() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFrameMain::Destroyed() {
|
void WebFrameMain::Destroyed() {
|
||||||
MarkRenderFrameDisposed();
|
if (FromFrameTreeNodeId(frame_tree_node_id_) == this) {
|
||||||
|
// WebFrameMain initialized as detached doesn't support FTN lookup and
|
||||||
|
// shouldn't erase the entry.
|
||||||
|
DCHECK(!render_frame_detached_ || render_frame_disposed_);
|
||||||
GetFrameTreeNodeIdMap().erase(frame_tree_node_id_);
|
GetFrameTreeNodeIdMap().erase(frame_tree_node_id_);
|
||||||
|
}
|
||||||
|
|
||||||
GetFrameTokenMap().erase(frame_token_);
|
GetFrameTokenMap().erase(frame_token_);
|
||||||
|
MarkRenderFrameDisposed();
|
||||||
Unpin();
|
Unpin();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFrameMain::MarkRenderFrameDisposed() {
|
void WebFrameMain::MarkRenderFrameDisposed() {
|
||||||
render_frame_ = nullptr;
|
|
||||||
render_frame_detached_ = true;
|
render_frame_detached_ = true;
|
||||||
render_frame_disposed_ = true;
|
render_frame_disposed_ = true;
|
||||||
TeardownMojoConnection();
|
TeardownMojoConnection();
|
||||||
|
@ -173,11 +194,14 @@ void WebFrameMain::MarkRenderFrameDisposed() {
|
||||||
// Should only be called when swapping frames.
|
// Should only be called when swapping frames.
|
||||||
void WebFrameMain::UpdateRenderFrameHost(content::RenderFrameHost* rfh) {
|
void WebFrameMain::UpdateRenderFrameHost(content::RenderFrameHost* rfh) {
|
||||||
GetFrameTokenMap().erase(frame_token_);
|
GetFrameTokenMap().erase(frame_token_);
|
||||||
|
|
||||||
|
// Ensure that RFH being swapped in doesn't already exist as its own
|
||||||
|
// WebFrameMain instance.
|
||||||
frame_token_ = rfh->GetGlobalFrameToken();
|
frame_token_ = rfh->GetGlobalFrameToken();
|
||||||
|
DCHECK(GetFrameTokenMap().find(frame_token_) == GetFrameTokenMap().end());
|
||||||
GetFrameTokenMap().emplace(frame_token_, this);
|
GetFrameTokenMap().emplace(frame_token_, this);
|
||||||
|
|
||||||
render_frame_disposed_ = false;
|
render_frame_disposed_ = false;
|
||||||
render_frame_ = rfh;
|
|
||||||
TeardownMojoConnection();
|
TeardownMojoConnection();
|
||||||
MaybeSetupMojoConnection();
|
MaybeSetupMojoConnection();
|
||||||
}
|
}
|
||||||
|
@ -218,7 +242,7 @@ v8::Local<v8::Promise> WebFrameMain::ExecuteJavaScript(
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
static_cast<content::RenderFrameHostImpl*>(render_frame_)
|
static_cast<content::RenderFrameHostImpl*>(render_frame_host())
|
||||||
->ExecuteJavaScriptForTests(
|
->ExecuteJavaScriptForTests(
|
||||||
code, user_gesture, true /* resolve_promises */,
|
code, user_gesture, true /* resolve_promises */,
|
||||||
/*honor_js_content_settings=*/true, content::ISOLATED_WORLD_ID_GLOBAL,
|
/*honor_js_content_settings=*/true, content::ISOLATED_WORLD_ID_GLOBAL,
|
||||||
|
@ -243,7 +267,7 @@ v8::Local<v8::Promise> WebFrameMain::ExecuteJavaScript(
|
||||||
bool WebFrameMain::Reload() {
|
bool WebFrameMain::Reload() {
|
||||||
if (!CheckRenderFrame())
|
if (!CheckRenderFrame())
|
||||||
return false;
|
return false;
|
||||||
return render_frame_->Reload();
|
return render_frame_host()->Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebFrameMain::IsDestroyed() const {
|
bool WebFrameMain::IsDestroyed() const {
|
||||||
|
@ -287,13 +311,12 @@ void WebFrameMain::MaybeSetupMojoConnection() {
|
||||||
&WebFrameMain::OnRendererConnectionError, weak_factory_.GetWeakPtr()));
|
&WebFrameMain::OnRendererConnectionError, weak_factory_.GetWeakPtr()));
|
||||||
}
|
}
|
||||||
|
|
||||||
DCHECK(render_frame_);
|
content::RenderFrameHost* rfh = render_frame_host();
|
||||||
|
DCHECK(rfh);
|
||||||
|
|
||||||
// Wait for RenderFrame to be created in renderer before accessing remote.
|
// Wait for RenderFrame to be created in renderer before accessing remote.
|
||||||
if (pending_receiver_ && render_frame_ &&
|
if (pending_receiver_ && rfh && rfh->IsRenderFrameLive()) {
|
||||||
render_frame_->IsRenderFrameLive()) {
|
rfh->GetRemoteInterfaces()->GetInterface(std::move(pending_receiver_));
|
||||||
render_frame_->GetRemoteInterfaces()->GetInterface(
|
|
||||||
std::move(pending_receiver_));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,6 +329,17 @@ void WebFrameMain::OnRendererConnectionError() {
|
||||||
TeardownMojoConnection();
|
TeardownMojoConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool WebFrameMain::HasRenderFrame() const {
|
||||||
|
if (render_frame_disposed_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If RFH is a nullptr, this instance of WebFrameMain is dangling and wasn't
|
||||||
|
// properly deleted.
|
||||||
|
CHECK(render_frame_host());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void WebFrameMain::PostMessage(v8::Isolate* isolate,
|
void WebFrameMain::PostMessage(v8::Isolate* isolate,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
v8::Local<v8::Value> message_value,
|
v8::Local<v8::Value> message_value,
|
||||||
|
@ -350,57 +384,57 @@ content::FrameTreeNodeId WebFrameMain::FrameTreeNodeID() const {
|
||||||
std::string WebFrameMain::Name() const {
|
std::string WebFrameMain::Name() const {
|
||||||
if (!CheckRenderFrame())
|
if (!CheckRenderFrame())
|
||||||
return {};
|
return {};
|
||||||
return render_frame_->GetFrameName();
|
return render_frame_host()->GetFrameName();
|
||||||
}
|
}
|
||||||
|
|
||||||
base::ProcessId WebFrameMain::OSProcessID() const {
|
base::ProcessId WebFrameMain::OSProcessID() const {
|
||||||
if (!CheckRenderFrame())
|
if (!CheckRenderFrame())
|
||||||
return -1;
|
return -1;
|
||||||
base::ProcessHandle process_handle =
|
base::ProcessHandle process_handle =
|
||||||
render_frame_->GetProcess()->GetProcess().Handle();
|
render_frame_host()->GetProcess()->GetProcess().Handle();
|
||||||
return base::GetProcId(process_handle);
|
return base::GetProcId(process_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t WebFrameMain::ProcessID() const {
|
int32_t WebFrameMain::ProcessID() const {
|
||||||
if (!CheckRenderFrame())
|
if (!CheckRenderFrame())
|
||||||
return -1;
|
return -1;
|
||||||
return render_frame_->GetProcess()->GetID().GetUnsafeValue();
|
return render_frame_host()->GetProcess()->GetID().GetUnsafeValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
int WebFrameMain::RoutingID() const {
|
int WebFrameMain::RoutingID() const {
|
||||||
if (!CheckRenderFrame())
|
if (!CheckRenderFrame())
|
||||||
return -1;
|
return -1;
|
||||||
return render_frame_->GetRoutingID();
|
return render_frame_host()->GetRoutingID();
|
||||||
}
|
}
|
||||||
|
|
||||||
GURL WebFrameMain::URL() const {
|
GURL WebFrameMain::URL() const {
|
||||||
if (!CheckRenderFrame())
|
if (!CheckRenderFrame())
|
||||||
return {};
|
return {};
|
||||||
return render_frame_->GetLastCommittedURL();
|
return render_frame_host()->GetLastCommittedURL();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string WebFrameMain::Origin() const {
|
std::string WebFrameMain::Origin() const {
|
||||||
if (!CheckRenderFrame())
|
if (!CheckRenderFrame())
|
||||||
return {};
|
return {};
|
||||||
return render_frame_->GetLastCommittedOrigin().Serialize();
|
return render_frame_host()->GetLastCommittedOrigin().Serialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
blink::mojom::PageVisibilityState WebFrameMain::VisibilityState() const {
|
blink::mojom::PageVisibilityState WebFrameMain::VisibilityState() const {
|
||||||
if (!CheckRenderFrame())
|
if (!CheckRenderFrame())
|
||||||
return blink::mojom::PageVisibilityState::kHidden;
|
return blink::mojom::PageVisibilityState::kHidden;
|
||||||
return render_frame_->GetVisibilityState();
|
return render_frame_host()->GetVisibilityState();
|
||||||
}
|
}
|
||||||
|
|
||||||
content::RenderFrameHost* WebFrameMain::Top() const {
|
content::RenderFrameHost* WebFrameMain::Top() const {
|
||||||
if (!CheckRenderFrame())
|
if (!CheckRenderFrame())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return render_frame_->GetMainFrame();
|
return render_frame_host()->GetMainFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
content::RenderFrameHost* WebFrameMain::Parent() const {
|
content::RenderFrameHost* WebFrameMain::Parent() const {
|
||||||
if (!CheckRenderFrame())
|
if (!CheckRenderFrame())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return render_frame_->GetParent();
|
return render_frame_host()->GetParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<content::RenderFrameHost*> WebFrameMain::Frames() const {
|
std::vector<content::RenderFrameHost*> WebFrameMain::Frames() const {
|
||||||
|
@ -408,9 +442,9 @@ std::vector<content::RenderFrameHost*> WebFrameMain::Frames() const {
|
||||||
if (!CheckRenderFrame())
|
if (!CheckRenderFrame())
|
||||||
return frame_hosts;
|
return frame_hosts;
|
||||||
|
|
||||||
render_frame_->ForEachRenderFrameHost(
|
render_frame_host()->ForEachRenderFrameHost(
|
||||||
[&frame_hosts, this](content::RenderFrameHost* rfh) {
|
[&frame_hosts, this](content::RenderFrameHost* rfh) {
|
||||||
if (rfh->GetParent() == render_frame_)
|
if (rfh && rfh->GetParent() == render_frame_host())
|
||||||
frame_hosts.push_back(rfh);
|
frame_hosts.push_back(rfh);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -422,7 +456,7 @@ std::vector<content::RenderFrameHost*> WebFrameMain::FramesInSubtree() const {
|
||||||
if (!CheckRenderFrame())
|
if (!CheckRenderFrame())
|
||||||
return frame_hosts;
|
return frame_hosts;
|
||||||
|
|
||||||
render_frame_->ForEachRenderFrameHost(
|
render_frame_host()->ForEachRenderFrameHost(
|
||||||
[&frame_hosts](content::RenderFrameHost* rfh) {
|
[&frame_hosts](content::RenderFrameHost* rfh) {
|
||||||
frame_hosts.push_back(rfh);
|
frame_hosts.push_back(rfh);
|
||||||
});
|
});
|
||||||
|
@ -430,6 +464,13 @@ std::vector<content::RenderFrameHost*> WebFrameMain::FramesInSubtree() const {
|
||||||
return frame_hosts;
|
return frame_hosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* WebFrameMain::LifecycleStateForTesting() const {
|
||||||
|
if (!HasRenderFrame())
|
||||||
|
return {};
|
||||||
|
return content::RenderFrameHostImpl::LifecycleStateImplToString(
|
||||||
|
GetLifecycleState(render_frame_host()));
|
||||||
|
}
|
||||||
|
|
||||||
v8::Local<v8::Promise> WebFrameMain::CollectDocumentJSCallStack(
|
v8::Local<v8::Promise> WebFrameMain::CollectDocumentJSCallStack(
|
||||||
gin::Arguments* args) {
|
gin::Arguments* args) {
|
||||||
gin_helper::Promise<base::Value> promise(args->isolate());
|
gin_helper::Promise<base::Value> promise(args->isolate());
|
||||||
|
@ -449,7 +490,8 @@ v8::Local<v8::Promise> WebFrameMain::CollectDocumentJSCallStack(
|
||||||
}
|
}
|
||||||
|
|
||||||
content::RenderProcessHostImpl* rph_impl =
|
content::RenderProcessHostImpl* rph_impl =
|
||||||
static_cast<content::RenderProcessHostImpl*>(render_frame_->GetProcess());
|
static_cast<content::RenderProcessHostImpl*>(
|
||||||
|
render_frame_host()->GetProcess());
|
||||||
|
|
||||||
rph_impl->GetJavaScriptCallStackGeneratorInterface()
|
rph_impl->GetJavaScriptCallStackGeneratorInterface()
|
||||||
->CollectJavaScriptCallStack(
|
->CollectJavaScriptCallStack(
|
||||||
|
@ -469,7 +511,8 @@ void WebFrameMain::CollectedJavaScriptCallStack(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const blink::LocalFrameToken& frame_token = render_frame_->GetFrameToken();
|
const blink::LocalFrameToken& frame_token =
|
||||||
|
render_frame_host()->GetFrameToken();
|
||||||
if (remote_frame_token == frame_token) {
|
if (remote_frame_token == frame_token) {
|
||||||
base::Value base_value(untrusted_javascript_call_stack);
|
base::Value base_value(untrusted_javascript_call_stack);
|
||||||
promise.Resolve(base_value);
|
promise.Resolve(base_value);
|
||||||
|
@ -500,7 +543,31 @@ gin::Handle<WebFrameMain> WebFrameMain::From(v8::Isolate* isolate,
|
||||||
if (!rfh)
|
if (!rfh)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
auto* web_frame = FromRenderFrameHost(rfh);
|
WebFrameMain* web_frame;
|
||||||
|
switch (GetLifecycleState(rfh)) {
|
||||||
|
case LifecycleState::kSpeculative:
|
||||||
|
case LifecycleState::kPendingCommit:
|
||||||
|
// RFH is in the process of being swapped. Need to lookup by FTN to avoid
|
||||||
|
// creating dangling WebFrameMain.
|
||||||
|
web_frame = FromFrameTreeNodeId(rfh->GetFrameTreeNodeId());
|
||||||
|
break;
|
||||||
|
case LifecycleState::kPrerendering:
|
||||||
|
case LifecycleState::kActive:
|
||||||
|
case LifecycleState::kInBackForwardCache:
|
||||||
|
// RFH is already assigned to the FrameTreeNode and can safely be looked
|
||||||
|
// up directly.
|
||||||
|
web_frame = FromRenderFrameHost(rfh);
|
||||||
|
break;
|
||||||
|
case LifecycleState::kRunningUnloadHandlers:
|
||||||
|
// Event/IPC emitted for a frame running unload handlers. Return the exact
|
||||||
|
// RFH so the security origin will be accurate.
|
||||||
|
web_frame = FromRenderFrameHost(rfh);
|
||||||
|
break;
|
||||||
|
case LifecycleState::kReadyToBeDeleted:
|
||||||
|
// RFH is gone
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
if (web_frame)
|
if (web_frame)
|
||||||
return gin::CreateHandle(isolate, web_frame);
|
return gin::CreateHandle(isolate, web_frame);
|
||||||
|
|
||||||
|
@ -536,6 +603,8 @@ void WebFrameMain::FillObjectTemplate(v8::Isolate* isolate,
|
||||||
.SetProperty("parent", &WebFrameMain::Parent)
|
.SetProperty("parent", &WebFrameMain::Parent)
|
||||||
.SetProperty("frames", &WebFrameMain::Frames)
|
.SetProperty("frames", &WebFrameMain::Frames)
|
||||||
.SetProperty("framesInSubtree", &WebFrameMain::FramesInSubtree)
|
.SetProperty("framesInSubtree", &WebFrameMain::FramesInSubtree)
|
||||||
|
.SetProperty("_lifecycleStateForTesting",
|
||||||
|
&WebFrameMain::LifecycleStateForTesting)
|
||||||
.Build();
|
.Build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "base/memory/raw_ptr.h"
|
|
||||||
#include "base/memory/weak_ptr.h"
|
#include "base/memory/weak_ptr.h"
|
||||||
#include "base/process/process.h"
|
#include "base/process/process.h"
|
||||||
#include "content/public/browser/frame_tree_node_id.h"
|
#include "content/public/browser/frame_tree_node_id.h"
|
||||||
|
@ -72,7 +71,7 @@ class WebFrameMain final : public gin::Wrappable<WebFrameMain>,
|
||||||
static gin::WrapperInfo kWrapperInfo;
|
static gin::WrapperInfo kWrapperInfo;
|
||||||
const char* GetTypeName() override;
|
const char* GetTypeName() override;
|
||||||
|
|
||||||
content::RenderFrameHost* render_frame_host() const { return render_frame_; }
|
content::RenderFrameHost* render_frame_host() const;
|
||||||
|
|
||||||
// disable copy
|
// disable copy
|
||||||
WebFrameMain(const WebFrameMain&) = delete;
|
WebFrameMain(const WebFrameMain&) = delete;
|
||||||
|
@ -101,9 +100,7 @@ class WebFrameMain final : public gin::Wrappable<WebFrameMain>,
|
||||||
void TeardownMojoConnection();
|
void TeardownMojoConnection();
|
||||||
void OnRendererConnectionError();
|
void OnRendererConnectionError();
|
||||||
|
|
||||||
[[nodiscard]] constexpr bool HasRenderFrame() const {
|
[[nodiscard]] bool HasRenderFrame() const;
|
||||||
return !render_frame_disposed_ && render_frame_ != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throws a JS error if HasRenderFrame() is false.
|
// Throws a JS error if HasRenderFrame() is false.
|
||||||
// WebFrameMain can outlive its RenderFrameHost pointer,
|
// WebFrameMain can outlive its RenderFrameHost pointer,
|
||||||
|
@ -139,6 +136,8 @@ class WebFrameMain final : public gin::Wrappable<WebFrameMain>,
|
||||||
std::vector<content::RenderFrameHost*> Frames() const;
|
std::vector<content::RenderFrameHost*> Frames() const;
|
||||||
std::vector<content::RenderFrameHost*> FramesInSubtree() const;
|
std::vector<content::RenderFrameHost*> FramesInSubtree() const;
|
||||||
|
|
||||||
|
const char* LifecycleStateForTesting() const;
|
||||||
|
|
||||||
v8::Local<v8::Promise> CollectDocumentJSCallStack(gin::Arguments* args);
|
v8::Local<v8::Promise> CollectDocumentJSCallStack(gin::Arguments* args);
|
||||||
void CollectedJavaScriptCallStack(
|
void CollectedJavaScriptCallStack(
|
||||||
gin_helper::Promise<base::Value> promise,
|
gin_helper::Promise<base::Value> promise,
|
||||||
|
@ -153,8 +152,6 @@ class WebFrameMain final : public gin::Wrappable<WebFrameMain>,
|
||||||
content::FrameTreeNodeId frame_tree_node_id_;
|
content::FrameTreeNodeId frame_tree_node_id_;
|
||||||
content::GlobalRenderFrameHostToken frame_token_;
|
content::GlobalRenderFrameHostToken frame_token_;
|
||||||
|
|
||||||
raw_ptr<content::RenderFrameHost> render_frame_ = nullptr;
|
|
||||||
|
|
||||||
// Whether the RenderFrameHost has been removed and that it should no longer
|
// Whether the RenderFrameHost has been removed and that it should no longer
|
||||||
// be accessed.
|
// be accessed.
|
||||||
bool render_frame_disposed_ = false;
|
bool render_frame_disposed_ = false;
|
||||||
|
|
|
@ -61,6 +61,15 @@ struct Converter<char[N]> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct Converter<const char*> {
|
||||||
|
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, const char* val) {
|
||||||
|
return v8::String::NewFromUtf8(isolate, val ? val : "",
|
||||||
|
v8::NewStringType::kNormal)
|
||||||
|
.ToLocalChecked();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct Converter<v8::Local<v8::Array>> {
|
struct Converter<v8::Local<v8::Array>> {
|
||||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||||
|
|
|
@ -428,6 +428,47 @@ describe('webFrameMain module', () => {
|
||||||
crossOriginPromise = w.webContents.loadURL(server.crossOriginUrl);
|
crossOriginPromise = w.webContents.loadURL(server.crossOriginUrl);
|
||||||
await expect(unloadPromise).to.eventually.be.fulfilled();
|
await expect(unloadPromise).to.eventually.be.fulfilled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Skip test as we don't have an offline repro yet
|
||||||
|
it.skip('should not crash due to dangling frames', async () => {
|
||||||
|
w = new BrowserWindow({
|
||||||
|
show: false,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(subframesPath, 'preload.js')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Persist frame references so WebFrameMain is initialized for each
|
||||||
|
const frames: Electron.WebFrameMain[] = [];
|
||||||
|
w.webContents.on('frame-created', (_event, details) => {
|
||||||
|
console.log('frame-created');
|
||||||
|
frames.push(details.frame!);
|
||||||
|
});
|
||||||
|
w.webContents.on('will-frame-navigate', (event) => {
|
||||||
|
console.log('will-frame-navigate', event);
|
||||||
|
frames.push(event.frame!);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load document with several speculative subframes
|
||||||
|
await w.webContents.loadURL('https://www.ezcater.com/delivery/pizza-catering');
|
||||||
|
|
||||||
|
// Test that no frame will crash due to a dangling render frame host
|
||||||
|
const crashTest = () => {
|
||||||
|
for (const frame of frames) {
|
||||||
|
expect(frame._lifecycleStateForTesting).to.not.equal('Speculative');
|
||||||
|
try {
|
||||||
|
expect(frame.url).to.be.a('string');
|
||||||
|
} catch {
|
||||||
|
// Exceptions from non-dangling frames are expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
crashTest();
|
||||||
|
w.webContents.destroy();
|
||||||
|
await setTimeout(1);
|
||||||
|
crashTest();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('webFrameMain.fromId', () => {
|
describe('webFrameMain.fromId', () => {
|
||||||
|
|
1
typings/internal-electron.d.ts
vendored
1
typings/internal-electron.d.ts
vendored
|
@ -132,6 +132,7 @@ declare namespace Electron {
|
||||||
_send(internal: boolean, channel: string, args: any): void;
|
_send(internal: boolean, channel: string, args: any): void;
|
||||||
_sendInternal(channel: string, ...args: any[]): void;
|
_sendInternal(channel: string, ...args: any[]): void;
|
||||||
_postMessage(channel: string, message: any, transfer?: any[]): void;
|
_postMessage(channel: string, message: any, transfer?: any[]): void;
|
||||||
|
_lifecycleStateForTesting: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WebFrame {
|
interface WebFrame {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue