fix: cross-origin navigation disposing WebFrameMain instances (#30076)
This commit is contained in:
parent
90b5ba3bed
commit
dd16d68e96
5 changed files with 150 additions and 96 deletions
|
@ -1384,7 +1384,9 @@ void WebContents::HandleNewRenderFrame(
|
||||||
if (rwh_impl)
|
if (rwh_impl)
|
||||||
rwh_impl->disable_hidden_ = !background_throttling_;
|
rwh_impl->disable_hidden_ = !background_throttling_;
|
||||||
|
|
||||||
WebFrameMain::RenderFrameCreated(render_frame_host);
|
auto* web_frame = WebFrameMain::FromRenderFrameHost(render_frame_host);
|
||||||
|
if (web_frame)
|
||||||
|
web_frame->Connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebContents::RenderFrameCreated(
|
void WebContents::RenderFrameCreated(
|
||||||
|
@ -1392,6 +1394,46 @@ void WebContents::RenderFrameCreated(
|
||||||
HandleNewRenderFrame(render_frame_host);
|
HandleNewRenderFrame(render_frame_host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebContents::RenderFrameDeleted(
|
||||||
|
content::RenderFrameHost* render_frame_host) {
|
||||||
|
// A RenderFrameHost can be deleted when:
|
||||||
|
// - A WebContents is removed and its containing frames are disposed.
|
||||||
|
// - An <iframe> is removed from the DOM.
|
||||||
|
// - Cross-origin navigation creates a new RFH in a separate process which
|
||||||
|
// is swapped by content::RenderFrameHostManager.
|
||||||
|
//
|
||||||
|
// WebFrameMain::FromRenderFrameHost(rfh) will use the RFH's FrameTreeNode ID
|
||||||
|
// to find an existing instance of WebFrameMain. During a cross-origin
|
||||||
|
// navigation, the deleted RFH will be the old host which was swapped out. In
|
||||||
|
// this special case, we need to also ensure that WebFrameMain's internal RFH
|
||||||
|
// matches before marking it as disposed.
|
||||||
|
auto* web_frame = WebFrameMain::FromRenderFrameHost(render_frame_host);
|
||||||
|
if (web_frame && web_frame->render_frame_host() == render_frame_host)
|
||||||
|
web_frame->MarkRenderFrameDisposed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebContents::RenderFrameHostChanged(content::RenderFrameHost* old_host,
|
||||||
|
content::RenderFrameHost* new_host) {
|
||||||
|
// During cross-origin navigation, a FrameTreeNode will swap out its RFH.
|
||||||
|
// If an instance of WebFrameMain exists, it will need to have its RFH
|
||||||
|
// swapped as well.
|
||||||
|
//
|
||||||
|
// |old_host| can be a nullptr in so we use |new_host| for looking up the
|
||||||
|
// WebFrameMain instance.
|
||||||
|
auto* web_frame =
|
||||||
|
WebFrameMain::FromFrameTreeNodeId(new_host->GetFrameTreeNodeId());
|
||||||
|
if (web_frame) {
|
||||||
|
CHECK_EQ(web_frame->render_frame_host(), old_host);
|
||||||
|
web_frame->UpdateRenderFrameHost(new_host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebContents::FrameDeleted(int frame_tree_node_id) {
|
||||||
|
auto* web_frame = WebFrameMain::FromFrameTreeNodeId(frame_tree_node_id);
|
||||||
|
if (web_frame)
|
||||||
|
web_frame->Destroyed();
|
||||||
|
}
|
||||||
|
|
||||||
void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) {
|
void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) {
|
||||||
// This event is necessary for tracking any states with respect to
|
// This event is necessary for tracking any states with respect to
|
||||||
// intermediate render view hosts aka speculative render view hosts. Currently
|
// intermediate render view hosts aka speculative render view hosts. Currently
|
||||||
|
@ -1631,13 +1673,6 @@ void WebContents::UpdateDraggableRegions(
|
||||||
observer.OnDraggableRegionsUpdated(regions);
|
observer.OnDraggableRegionsUpdated(regions);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebContents::RenderFrameDeleted(
|
|
||||||
content::RenderFrameHost* render_frame_host) {
|
|
||||||
// A WebFrameMain can outlive its RenderFrameHost so we need to mark it as
|
|
||||||
// disposed to prevent access to it.
|
|
||||||
WebFrameMain::RenderFrameDeleted(render_frame_host);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebContents::DidStartNavigation(
|
void WebContents::DidStartNavigation(
|
||||||
content::NavigationHandle* navigation_handle) {
|
content::NavigationHandle* navigation_handle) {
|
||||||
EmitNavigationEvent("did-start-navigation", navigation_handle);
|
EmitNavigationEvent("did-start-navigation", navigation_handle);
|
||||||
|
|
|
@ -543,9 +543,12 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||||
void BeforeUnloadFired(bool proceed,
|
void BeforeUnloadFired(bool proceed,
|
||||||
const base::TimeTicks& proceed_time) override;
|
const base::TimeTicks& proceed_time) override;
|
||||||
void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
|
void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
|
||||||
|
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
|
||||||
|
void RenderFrameHostChanged(content::RenderFrameHost* old_host,
|
||||||
|
content::RenderFrameHost* new_host) override;
|
||||||
|
void FrameDeleted(int frame_tree_node_id) override;
|
||||||
void RenderViewDeleted(content::RenderViewHost*) override;
|
void RenderViewDeleted(content::RenderViewHost*) override;
|
||||||
void RenderProcessGone(base::TerminationStatus status) override;
|
void RenderProcessGone(base::TerminationStatus status) override;
|
||||||
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
|
|
||||||
void DOMContentLoaded(content::RenderFrameHost* render_frame_host) override;
|
void DOMContentLoaded(content::RenderFrameHost* render_frame_host) override;
|
||||||
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
|
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
|
||||||
const GURL& validated_url) override;
|
const GURL& validated_url) override;
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "base/lazy_instance.h"
|
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
|
#include "base/no_destructor.h"
|
||||||
#include "content/browser/renderer_host/frame_tree_node.h" // nogncheck
|
#include "content/browser/renderer_host/frame_tree_node.h" // nogncheck
|
||||||
#include "content/public/browser/render_frame_host.h"
|
#include "content/public/browser/render_frame_host.h"
|
||||||
#include "electron/shell/common/api/api.mojom.h"
|
#include "electron/shell/common/api/api.mojom.h"
|
||||||
|
@ -56,36 +56,54 @@ namespace electron {
|
||||||
|
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
typedef std::unordered_map<content::RenderFrameHost*, WebFrameMain*>
|
typedef std::unordered_map<int, WebFrameMain*> WebFrameMainIdMap;
|
||||||
RenderFrameMap;
|
|
||||||
base::LazyInstance<RenderFrameMap>::DestructorAtExit g_render_frame_map =
|
|
||||||
LAZY_INSTANCE_INITIALIZER;
|
|
||||||
|
|
||||||
WebFrameMain* FromRenderFrameHost(content::RenderFrameHost* rfh) {
|
WebFrameMainIdMap& GetWebFrameMainMap() {
|
||||||
auto frame_map = g_render_frame_map.Get();
|
static base::NoDestructor<WebFrameMainIdMap> instance;
|
||||||
auto iter = frame_map.find(rfh);
|
return *instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
WebFrameMain* WebFrameMain::FromFrameTreeNodeId(int frame_tree_node_id) {
|
||||||
|
WebFrameMainIdMap& frame_map = GetWebFrameMainMap();
|
||||||
|
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;
|
||||||
return web_frame;
|
return web_frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
WebFrameMain* WebFrameMain::FromRenderFrameHost(content::RenderFrameHost* rfh) {
|
||||||
|
return rfh ? FromFrameTreeNodeId(rfh->GetFrameTreeNodeId()) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
gin::WrapperInfo WebFrameMain::kWrapperInfo = {gin::kEmbedderNativeGin};
|
gin::WrapperInfo WebFrameMain::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||||
|
|
||||||
WebFrameMain::WebFrameMain(content::RenderFrameHost* rfh) : render_frame_(rfh) {
|
WebFrameMain::WebFrameMain(content::RenderFrameHost* rfh)
|
||||||
g_render_frame_map.Get().emplace(rfh, this);
|
: frame_tree_node_id_(rfh->GetFrameTreeNodeId()), render_frame_(rfh) {
|
||||||
|
GetWebFrameMainMap().emplace(frame_tree_node_id_, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebFrameMain::~WebFrameMain() {
|
WebFrameMain::~WebFrameMain() {
|
||||||
|
Destroyed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebFrameMain::Destroyed() {
|
||||||
MarkRenderFrameDisposed();
|
MarkRenderFrameDisposed();
|
||||||
|
GetWebFrameMainMap().erase(frame_tree_node_id_);
|
||||||
|
Unpin();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebFrameMain::MarkRenderFrameDisposed() {
|
void WebFrameMain::MarkRenderFrameDisposed() {
|
||||||
if (render_frame_disposed_)
|
render_frame_ = nullptr;
|
||||||
return;
|
|
||||||
Unpin();
|
|
||||||
g_render_frame_map.Get().erase(render_frame_);
|
|
||||||
render_frame_disposed_ = true;
|
render_frame_disposed_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebFrameMain::UpdateRenderFrameHost(content::RenderFrameHost* rfh) {
|
||||||
|
// Should only be called when swapping frames.
|
||||||
|
DCHECK(render_frame_);
|
||||||
|
render_frame_ = rfh;
|
||||||
|
}
|
||||||
|
|
||||||
bool WebFrameMain::CheckRenderFrame() const {
|
bool WebFrameMain::CheckRenderFrame() const {
|
||||||
if (render_frame_disposed_) {
|
if (render_frame_disposed_) {
|
||||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||||
|
@ -213,9 +231,7 @@ void WebFrameMain::PostMessage(v8::Isolate* isolate,
|
||||||
}
|
}
|
||||||
|
|
||||||
int WebFrameMain::FrameTreeNodeID() const {
|
int WebFrameMain::FrameTreeNodeID() const {
|
||||||
if (!CheckRenderFrame())
|
return frame_tree_node_id_;
|
||||||
return -1;
|
|
||||||
return render_frame_->GetFrameTreeNodeId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string WebFrameMain::Name() const {
|
std::string WebFrameMain::Name() const {
|
||||||
|
@ -293,6 +309,13 @@ std::vector<content::RenderFrameHost*> WebFrameMain::FramesInSubtree() const {
|
||||||
return frame_hosts;
|
return frame_hosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WebFrameMain::Connect() {
|
||||||
|
if (pending_receiver_) {
|
||||||
|
render_frame_->GetRemoteInterfaces()->GetInterface(
|
||||||
|
std::move(pending_receiver_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
gin::Handle<WebFrameMain> WebFrameMain::New(v8::Isolate* isolate) {
|
gin::Handle<WebFrameMain> WebFrameMain::New(v8::Isolate* isolate) {
|
||||||
return gin::Handle<WebFrameMain>();
|
return gin::Handle<WebFrameMain>();
|
||||||
|
@ -315,35 +338,6 @@ gin::Handle<WebFrameMain> WebFrameMain::From(v8::Isolate* isolate,
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
|
||||||
gin::Handle<WebFrameMain> WebFrameMain::FromID(v8::Isolate* isolate,
|
|
||||||
int render_process_id,
|
|
||||||
int render_frame_id) {
|
|
||||||
auto* rfh =
|
|
||||||
content::RenderFrameHost::FromID(render_process_id, render_frame_id);
|
|
||||||
return From(isolate, rfh);
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
void WebFrameMain::RenderFrameDeleted(content::RenderFrameHost* rfh) {
|
|
||||||
auto* web_frame = FromRenderFrameHost(rfh);
|
|
||||||
if (web_frame)
|
|
||||||
web_frame->MarkRenderFrameDisposed();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebFrameMain::RenderFrameCreated(content::RenderFrameHost* rfh) {
|
|
||||||
auto* web_frame = FromRenderFrameHost(rfh);
|
|
||||||
if (web_frame)
|
|
||||||
web_frame->Connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebFrameMain::Connect() {
|
|
||||||
if (pending_receiver_) {
|
|
||||||
render_frame_->GetRemoteInterfaces()->GetInterface(
|
|
||||||
std::move(pending_receiver_));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
// static
|
||||||
v8::Local<v8::ObjectTemplate> WebFrameMain::FillObjectTemplate(
|
v8::Local<v8::ObjectTemplate> WebFrameMain::FillObjectTemplate(
|
||||||
v8::Isolate* isolate,
|
v8::Isolate* isolate,
|
||||||
|
@ -387,9 +381,10 @@ v8::Local<v8::Value> FromID(gin_helper::ErrorThrower thrower,
|
||||||
return v8::Null(thrower.isolate());
|
return v8::Null(thrower.isolate());
|
||||||
}
|
}
|
||||||
|
|
||||||
return WebFrameMain::FromID(thrower.isolate(), render_process_id,
|
auto* rfh =
|
||||||
render_frame_id)
|
content::RenderFrameHost::FromID(render_process_id, render_frame_id);
|
||||||
.ToV8();
|
|
||||||
|
return WebFrameMain::From(thrower.isolate(), rfh).ToV8();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Initialize(v8::Local<v8::Object> exports,
|
void Initialize(v8::Local<v8::Object> exports,
|
||||||
|
|
|
@ -31,6 +31,8 @@ namespace electron {
|
||||||
|
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
|
class WebContents;
|
||||||
|
|
||||||
// Bindings for accessing frames from the main process.
|
// Bindings for accessing frames from the main process.
|
||||||
class WebFrameMain : public gin::Wrappable<WebFrameMain>,
|
class WebFrameMain : public gin::Wrappable<WebFrameMain>,
|
||||||
public gin_helper::Pinnable<WebFrameMain>,
|
public gin_helper::Pinnable<WebFrameMain>,
|
||||||
|
@ -39,23 +41,12 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain>,
|
||||||
// Create a new WebFrameMain and return the V8 wrapper of it.
|
// Create a new WebFrameMain and return the V8 wrapper of it.
|
||||||
static gin::Handle<WebFrameMain> New(v8::Isolate* isolate);
|
static gin::Handle<WebFrameMain> New(v8::Isolate* isolate);
|
||||||
|
|
||||||
static gin::Handle<WebFrameMain> FromID(v8::Isolate* isolate,
|
|
||||||
int render_process_id,
|
|
||||||
int render_frame_id);
|
|
||||||
static gin::Handle<WebFrameMain> From(
|
static gin::Handle<WebFrameMain> From(
|
||||||
v8::Isolate* isolate,
|
v8::Isolate* isolate,
|
||||||
content::RenderFrameHost* render_frame_host);
|
content::RenderFrameHost* render_frame_host);
|
||||||
|
static WebFrameMain* FromFrameTreeNodeId(int frame_tree_node_id);
|
||||||
// Called to mark any RenderFrameHost as disposed by any WebFrameMain that
|
static WebFrameMain* FromRenderFrameHost(
|
||||||
// may be holding a weak reference.
|
content::RenderFrameHost* render_frame_host);
|
||||||
static void RenderFrameDeleted(content::RenderFrameHost* rfh);
|
|
||||||
static void RenderFrameCreated(content::RenderFrameHost* rfh);
|
|
||||||
|
|
||||||
// Mark RenderFrameHost as disposed and to no longer access it. This can
|
|
||||||
// occur upon frame navigation.
|
|
||||||
void MarkRenderFrameDisposed();
|
|
||||||
|
|
||||||
const mojo::Remote<mojom::ElectronRenderer>& GetRendererApi();
|
|
||||||
|
|
||||||
// gin::Wrappable
|
// gin::Wrappable
|
||||||
static gin::WrapperInfo kWrapperInfo;
|
static gin::WrapperInfo kWrapperInfo;
|
||||||
|
@ -64,11 +55,28 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain>,
|
||||||
v8::Local<v8::ObjectTemplate>);
|
v8::Local<v8::ObjectTemplate>);
|
||||||
const char* GetTypeName() override;
|
const char* GetTypeName() override;
|
||||||
|
|
||||||
|
content::RenderFrameHost* render_frame_host() const { return render_frame_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit WebFrameMain(content::RenderFrameHost* render_frame);
|
explicit WebFrameMain(content::RenderFrameHost* render_frame);
|
||||||
~WebFrameMain() override;
|
~WebFrameMain() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend class WebContents;
|
||||||
|
|
||||||
|
// Called when FrameTreeNode is deleted.
|
||||||
|
void Destroyed();
|
||||||
|
|
||||||
|
// Mark RenderFrameHost as disposed and to no longer access it. This can
|
||||||
|
// happen when the WebFrameMain v8 handle is GC'd or when a FrameTreeNode
|
||||||
|
// is removed.
|
||||||
|
void MarkRenderFrameDisposed();
|
||||||
|
|
||||||
|
// Swap out the internal RFH when cross-origin navigation occurs.
|
||||||
|
void UpdateRenderFrameHost(content::RenderFrameHost* rfh);
|
||||||
|
|
||||||
|
const mojo::Remote<mojom::ElectronRenderer>& GetRendererApi();
|
||||||
|
|
||||||
// WebFrameMain can outlive its RenderFrameHost pointer so we need to check
|
// WebFrameMain can outlive its RenderFrameHost pointer so we need to check
|
||||||
// whether its been disposed of prior to accessing it.
|
// whether its been disposed of prior to accessing it.
|
||||||
bool CheckRenderFrame() const;
|
bool CheckRenderFrame() const;
|
||||||
|
@ -104,6 +112,8 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain>,
|
||||||
mojo::Remote<mojom::ElectronRenderer> renderer_api_;
|
mojo::Remote<mojom::ElectronRenderer> renderer_api_;
|
||||||
mojo::PendingReceiver<mojom::ElectronRenderer> pending_receiver_;
|
mojo::PendingReceiver<mojom::ElectronRenderer> pending_receiver_;
|
||||||
|
|
||||||
|
int frame_tree_node_id_;
|
||||||
|
|
||||||
content::RenderFrameHost* render_frame_ = nullptr;
|
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
|
||||||
|
|
|
@ -14,6 +14,24 @@ describe('webFrameMain module', () => {
|
||||||
|
|
||||||
const fileUrl = (filename: string) => url.pathToFileURL(path.join(subframesPath, filename)).href;
|
const fileUrl = (filename: string) => url.pathToFileURL(path.join(subframesPath, filename)).href;
|
||||||
|
|
||||||
|
type Server = { server: http.Server, url: string }
|
||||||
|
|
||||||
|
/** Creates an HTTP server whose handler embeds the given iframe src. */
|
||||||
|
const createServer = () => new Promise<Server>(resolve => {
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
const params = new URLSearchParams(url.parse(req.url || '').search || '');
|
||||||
|
if (params.has('frameSrc')) {
|
||||||
|
res.end(`<iframe src="${params.get('frameSrc')}"></iframe>`);
|
||||||
|
} else {
|
||||||
|
res.end('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.listen(0, '127.0.0.1', () => {
|
||||||
|
const url = `http://127.0.0.1:${(server.address() as AddressInfo).port}/`;
|
||||||
|
resolve({ server, url });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(closeAllWindows);
|
afterEach(closeAllWindows);
|
||||||
|
|
||||||
describe('WebFrame traversal APIs', () => {
|
describe('WebFrame traversal APIs', () => {
|
||||||
|
@ -70,24 +88,6 @@ describe('webFrameMain module', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('cross-origin', () => {
|
describe('cross-origin', () => {
|
||||||
type Server = { server: http.Server, url: string }
|
|
||||||
|
|
||||||
/** Creates an HTTP server whose handler embeds the given iframe src. */
|
|
||||||
const createServer = () => new Promise<Server>(resolve => {
|
|
||||||
const server = http.createServer((req, res) => {
|
|
||||||
const params = new URLSearchParams(url.parse(req.url || '').search || '');
|
|
||||||
if (params.has('frameSrc')) {
|
|
||||||
res.end(`<iframe src="${params.get('frameSrc')}"></iframe>`);
|
|
||||||
} else {
|
|
||||||
res.end('');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
server.listen(0, '127.0.0.1', () => {
|
|
||||||
const url = `http://127.0.0.1:${(server.address() as AddressInfo).port}/`;
|
|
||||||
resolve({ server, url });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
let serverA = null as unknown as Server;
|
let serverA = null as unknown as Server;
|
||||||
let serverB = null as unknown as Server;
|
let serverB = null as unknown as Server;
|
||||||
|
|
||||||
|
@ -194,21 +194,32 @@ describe('webFrameMain module', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('disposed WebFrames', () => {
|
describe('RenderFrame lifespan', () => {
|
||||||
let w: BrowserWindow;
|
let w: BrowserWindow;
|
||||||
let webFrame: WebFrameMain;
|
|
||||||
|
|
||||||
before(async () => {
|
beforeEach(async () => {
|
||||||
w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } });
|
w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws upon accessing properties when disposed', async () => {
|
||||||
await w.loadFile(path.join(subframesPath, 'frame-with-frame-container.html'));
|
await w.loadFile(path.join(subframesPath, 'frame-with-frame-container.html'));
|
||||||
webFrame = w.webContents.mainFrame;
|
const { mainFrame } = w.webContents;
|
||||||
w.destroy();
|
w.destroy();
|
||||||
// Wait for WebContents, and thus RenderFrameHost, to be destroyed.
|
// Wait for WebContents, and thus RenderFrameHost, to be destroyed.
|
||||||
await new Promise(resolve => setTimeout(resolve, 0));
|
await new Promise(resolve => setTimeout(resolve, 0));
|
||||||
|
expect(() => mainFrame.url).to.throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws upon accessing properties', () => {
|
it('persists through cross-origin navigation', async () => {
|
||||||
expect(() => webFrame.url).to.throw();
|
const server = await createServer();
|
||||||
|
// 'localhost' is treated as a separate origin.
|
||||||
|
const crossOriginUrl = server.url.replace('127.0.0.1', 'localhost');
|
||||||
|
await w.loadURL(server.url);
|
||||||
|
const { mainFrame } = w.webContents;
|
||||||
|
expect(mainFrame.url).to.equal(server.url);
|
||||||
|
await w.loadURL(crossOriginUrl);
|
||||||
|
expect(w.webContents.mainFrame).to.equal(mainFrame);
|
||||||
|
expect(mainFrame.url).to.equal(crossOriginUrl);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue