fix: remove cyclic references of BrowserWindow (#22006)

* fix: remove cyclic references in BrowserWindow

* fix: prevent TopLevelWindow from garbage collection

* test: garbage collection of BrowserWindow

* chore: createIDWeakMap is used in tests
This commit is contained in:
Cheng Zhao 2020-02-11 09:37:46 +09:00 committed by GitHub
parent 9942149f3c
commit 9ad6f06831
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 76 additions and 32 deletions

View file

@ -25,26 +25,6 @@ BrowserWindow.prototype._init = function () {
nativeSetBounds.call(this, bounds, ...opts)
}
// window.resizeTo(...)
// window.moveTo(...)
this.webContents.on('move', (event, size) => {
this.setBounds(size)
})
// Hide the auto-hide menu when webContents is focused.
this.webContents.on('activate', () => {
if (process.platform !== 'darwin' && this.autoHideMenuBar && this.isMenuBarVisible()) {
this.setMenuBarVisibility(false)
}
})
// Change window title to page title.
this.webContents.on('page-title-updated', (event, title, ...args) => {
// Route the event to BrowserWindow.
this.emit('page-title-updated', event, title, ...args)
if (!this.isDestroyed() && !event.defaultPrevented) this.setTitle(title)
})
// Sometimes the webContents doesn't get focus when window is shown, so we
// have to force focusing on webContents in this case. The safest way is to
// focus it when we first start to load URL, if we do it earlier it won't

View file

@ -222,6 +222,31 @@ void BrowserWindow::OnDraggableRegionsUpdated(
UpdateDraggableRegions(regions);
}
void BrowserWindow::OnSetContentBounds(const gfx::Rect& rect) {
// window.resizeTo(...)
// window.moveTo(...)
window()->SetBounds(rect, false);
}
void BrowserWindow::OnActivateContents() {
// Hide the auto-hide menu when webContents is focused.
#if !defined(OS_MACOSX)
if (IsMenuBarAutoHide() && IsMenuBarVisible())
window()->SetMenuBarVisibility(false);
#endif
}
void BrowserWindow::OnPageTitleUpdated(const base::string16& title,
bool explicit_set) {
// Change window title to page title.
auto self = GetWeakPtr();
if (!Emit("page-title-updated", title, explicit_set)) {
// |this| might be deleted, or marked as destroyed by close().
if (self && !IsDestroyed())
SetTitle(base::UTF16ToUTF8(title));
}
}
void BrowserWindow::RequestPreferredWidth(int* width) {
*width = web_contents()->GetPreferredSize().width();
}
@ -251,6 +276,8 @@ void BrowserWindow::OnCloseButtonClicked(bool* prevent_default) {
void BrowserWindow::OnWindowClosed() {
Cleanup();
// See TopLevelWindow::OnWindowClosed on why calling InvalidateWeakPtrs.
weak_factory_.InvalidateWeakPtrs();
TopLevelWindow::OnWindowClosed();
}

View file

@ -57,6 +57,10 @@ class BrowserWindow : public TopLevelWindow,
void OnRendererResponsive() override;
void OnDraggableRegionsUpdated(
const std::vector<mojom::DraggableRegionPtr>& regions) override;
void OnSetContentBounds(const gfx::Rect& rect) override;
void OnActivateContents() override;
void OnPageTitleUpdated(const base::string16& title,
bool explicit_set) override;
// NativeWindowObserver:
void RequestPreferredWidth(int* width) override;

View file

@ -120,6 +120,9 @@ TopLevelWindow::~TopLevelWindow() {
// Destroy the native window in next tick because the native code might be
// iterating all windows.
base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, window_.release());
// Remove global reference so the JS object can be garbage collected.
self_ref_.Reset();
}
void TopLevelWindow::InitWith(v8::Isolate* isolate,
@ -135,6 +138,9 @@ void TopLevelWindow::InitWith(v8::Isolate* isolate,
DCHECK(!parent.IsEmpty());
parent->child_windows_.Set(isolate, weak_map_id(), wrapper);
}
// Reference this object in case it got garbage collected.
self_ref_.Reset(isolate, wrapper);
}
void TopLevelWindow::WillCloseWindow(bool* prevent_default) {

View file

@ -259,6 +259,9 @@ class TopLevelWindow : public gin_helper::TrackableObject<TopLevelWindow>,
std::unique_ptr<NativeWindow> window_;
// Reference to JS wrapper to prevent garbage collection.
v8::Global<v8::Value> self_ref_;
base::WeakPtrFactory<TopLevelWindow> weak_factory_;
};

View file

@ -705,8 +705,9 @@ void WebContents::BeforeUnloadFired(content::WebContents* tab,
}
void WebContents::SetContentsBounds(content::WebContents* source,
const gfx::Rect& pos) {
Emit("move", pos);
const gfx::Rect& rect) {
for (ExtendedWebContentsObserver& observer : observers_)
observer.OnSetContentBounds(rect);
}
void WebContents::CloseContents(content::WebContents* source) {
@ -725,7 +726,8 @@ void WebContents::CloseContents(content::WebContents* source) {
}
void WebContents::ActivateContents(content::WebContents* source) {
Emit("activate");
for (ExtendedWebContentsObserver& observer : observers_)
observer.OnActivateContents();
}
void WebContents::UpdateTargetURL(content::WebContents* source,
@ -1228,6 +1230,8 @@ void WebContents::TitleWasSet(content::NavigationEntry* entry) {
final_title = title;
}
}
for (ExtendedWebContentsObserver& observer : observers_)
observer.OnPageTitleUpdated(final_title, explicit_set);
Emit("page-title-updated", final_title, explicit_set);
}

View file

@ -81,6 +81,10 @@ class ExtendedWebContentsObserver : public base::CheckedObserver {
virtual void OnRendererResponsive() {}
virtual void OnDraggableRegionsUpdated(
const std::vector<mojom::DraggableRegionPtr>& regions) {}
virtual void OnSetContentBounds(const gfx::Rect& rect) {}
virtual void OnActivateContents() {}
virtual void OnPageTitleUpdated(const base::string16& title,
bool explicit_set) {}
protected:
~ExtendedWebContentsObserver() override {}

View file

@ -126,12 +126,12 @@ void Initialize(v8::Local<v8::Object> exports,
&electron::RemoteCallbackFreer::BindTo);
dict.SetMethod("setRemoteObjectFreer", &electron::RemoteObjectFreer::BindTo);
dict.SetMethod("addRemoteObjectRef", &electron::RemoteObjectFreer::AddRef);
dict.SetMethod("createIDWeakMap",
&electron::api::KeyWeakMap<int32_t>::Create);
dict.SetMethod(
"createDoubleIDWeakMap",
&electron::api::KeyWeakMap<std::pair<std::string, int32_t>>::Create);
#endif
dict.SetMethod("createIDWeakMap",
&electron::api::KeyWeakMap<int32_t>::Create);
dict.SetMethod("requestGarbageCollectionForTesting",
&RequestGarbageCollectionForTesting);
dict.SetMethod("isSameOrigin", &IsSameOrigin);

View file

@ -9,7 +9,7 @@ import { app, BrowserWindow, BrowserView, ipcMain, OnBeforeSendHeadersListenerDe
import { emittedOnce } from './events-helpers'
import { ifit, ifdescribe } from './spec-helpers'
import { closeWindow } from './window-helpers'
import { closeWindow, closeAllWindows } from './window-helpers'
const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures')
@ -37,12 +37,6 @@ const expectBoundsEqual = (actual: any, expected: any) => {
}
}
const closeAllWindows = async () => {
for (const w of BrowserWindow.getAllWindows()) {
await closeWindow(w, { assertNotWindows: false })
}
}
describe('BrowserWindow module', () => {
describe('BrowserWindow constructor', () => {
it('allows passing void 0 as the webContents', async () => {
@ -58,6 +52,28 @@ describe('BrowserWindow module', () => {
})
})
describe('garbage collection', () => {
const v8Util = process.electronBinding('v8_util')
afterEach(closeAllWindows)
it('window does not get garbage collected when opened', (done) => {
const w = new BrowserWindow({ show: false })
// Keep a weak reference to the window.
const map = v8Util.createIDWeakMap<Electron.BrowserWindow>()
map.set(0, w)
setTimeout(() => {
// Do garbage collection, since |w| is not referenced in this closure
// it would be gone after next call if there is no other reference.
v8Util.requestGarbageCollectionForTesting()
setTimeout(() => {
expect(map.has(0)).to.equal(true)
done()
})
})
})
})
describe('BrowserWindow.close()', () => {
let w = null as unknown as BrowserWindow
beforeEach(() => {