Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: menu should not be garbage-collected when popuping (8-x-y) #21224

Merged
merged 2 commits into from Nov 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 5 additions & 2 deletions native_mate/native_mate/function_template.h
Expand Up @@ -5,6 +5,8 @@
#ifndef NATIVE_MATE_NATIVE_MATE_FUNCTION_TEMPLATE_H_
#define NATIVE_MATE_NATIVE_MATE_FUNCTION_TEMPLATE_H_

#include <utility>

#include "base/callback.h"
#include "native_mate/arguments.h"
#include "native_mate/wrappable_base.h"
Expand Down Expand Up @@ -194,7 +196,8 @@ class Invoker<IndicesHolder<indices...>, ArgTypes...>
void DispatchToCallback(base::Callback<ReturnType(ArgTypes...)> callback) {
v8::MicrotasksScope script_scope(args_->isolate(),
v8::MicrotasksScope::kRunMicrotasks);
args_->Return(callback.Run(ArgumentHolder<indices, ArgTypes>::value...));
args_->Return(
callback.Run(std::move(ArgumentHolder<indices, ArgTypes>::value)...));
}

// In C++, you can declare the function foo(void), but you can't pass a void
Expand All @@ -203,7 +206,7 @@ class Invoker<IndicesHolder<indices...>, ArgTypes...>
void DispatchToCallback(base::Callback<void(ArgTypes...)> callback) {
v8::MicrotasksScope script_scope(args_->isolate(),
v8::MicrotasksScope::kRunMicrotasks);
callback.Run(ArgumentHolder<indices, ArgTypes>::value...);
callback.Run(std::move(ArgumentHolder<indices, ArgTypes>::value)...);
}

private:
Expand Down
11 changes: 11 additions & 0 deletions shell/browser/api/atom_api_menu.cc
Expand Up @@ -5,6 +5,7 @@
#include "shell/browser/api/atom_api_menu.h"

#include <map>
#include <utility>

#include "native_mate/constructor.h"
#include "shell/browser/native_window.h"
Expand Down Expand Up @@ -218,6 +219,16 @@ void Menu::OnMenuWillShow() {
Emit("menu-will-show");
}

base::OnceClosure Menu::BindSelfToClosure(base::OnceClosure callback) {
// return ((callback, ref) => { callback() }).bind(null, callback, this)
v8::Global<v8::Value> ref(isolate(), GetWrapper());
return base::BindOnce(
[](base::OnceClosure callback, v8::Global<v8::Value> ref) {
std::move(callback).Run();
},
std::move(callback), std::move(ref));
}

// static
void Menu::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {
Expand Down
8 changes: 6 additions & 2 deletions shell/browser/api/atom_api_menu.h
Expand Up @@ -13,7 +13,6 @@
#include "shell/browser/api/atom_api_top_level_window.h"
#include "shell/browser/api/trackable_object.h"
#include "shell/browser/ui/atom_menu_model.h"
#include "shell/common/api/locker.h"

namespace electron {

Expand Down Expand Up @@ -62,7 +61,7 @@ class Menu : public mate::TrackableObject<Menu>,
int x,
int y,
int positioning_item,
const base::Closure& callback) = 0;
base::OnceClosure callback) = 0;
virtual void ClosePopupAt(int32_t window_id) = 0;

std::unique_ptr<AtomMenuModel> model_;
Expand All @@ -72,6 +71,11 @@ class Menu : public mate::TrackableObject<Menu>,
void OnMenuWillClose() override;
void OnMenuWillShow() override;

protected:
// Returns a new callback which keeps references of the JS wrapper until the
// passed |callback| is called.
base::OnceClosure BindSelfToClosure(base::OnceClosure callback);

private:
void InsertItemAt(int index, int command_id, const base::string16& label);
void InsertSeparatorAt(int index);
Expand Down
6 changes: 3 additions & 3 deletions shell/browser/api/atom_api_menu_mac.h
Expand Up @@ -27,20 +27,20 @@ class MenuMac : public Menu {
int x,
int y,
int positioning_item,
const base::Closure& callback) override;
base::OnceClosure callback) override;
void PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
int32_t window_id,
int x,
int y,
int positioning_item,
base::Closure callback);
base::OnceClosure callback);
void ClosePopupAt(int32_t window_id) override;
void ClosePopupOnUI(int32_t window_id);

private:
friend class Menu;

void OnClosed(int32_t window_id, base::Closure callback);
void OnClosed(int32_t window_id, base::OnceClosure callback);

scoped_nsobject<AtomMenuController> menu_controller_;

Expand Down
24 changes: 13 additions & 11 deletions shell/browser/api/atom_api_menu_mac.mm
Expand Up @@ -38,15 +38,19 @@
int x,
int y,
int positioning_item,
const base::Closure& callback) {
base::OnceClosure callback) {
NativeWindow* native_window = window->window();
if (!native_window)
return;

// Make sure the Menu object would not be garbage-collected until the callback
// has run.
base::OnceClosure callback_with_ref = BindSelfToClosure(std::move(callback));

auto popup =
base::BindOnce(&MenuMac::PopupOnUI, weak_factory_.GetWeakPtr(),
native_window->GetWeakPtr(), window->weak_map_id(), x, y,
positioning_item, callback);
positioning_item, std::move(callback_with_ref));
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(popup));
}

Expand All @@ -55,16 +59,14 @@
int x,
int y,
int positioning_item,
base::Closure callback) {
mate::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());

base::OnceClosure callback) {
if (!native_window)
return;
NSWindow* nswindow = native_window->GetNativeWindow().GetNativeNSWindow();

auto close_callback = base::BindRepeating(
&MenuMac::OnClosed, weak_factory_.GetWeakPtr(), window_id, callback);
base::OnceClosure close_callback =
base::BindOnce(&MenuMac::OnClosed, weak_factory_.GetWeakPtr(), window_id,
std::move(callback));
popup_controllers_[window_id] = base::scoped_nsobject<AtomMenuController>(
[[AtomMenuController alloc] initWithModel:model()
useDefaultAccelerator:NO]);
Expand Down Expand Up @@ -102,7 +104,7 @@
if (rightmostMenuPoint > screenRight)
position.x = position.x - [menu size].width;

[popup_controllers_[window_id] setCloseCallback:close_callback];
[popup_controllers_[window_id] setCloseCallback:std::move(close_callback)];
// Make sure events can be pumped while the menu is up.
base::MessageLoopCurrent::ScopedNestableTaskAllower allow;

Expand Down Expand Up @@ -140,9 +142,9 @@
}
}

void MenuMac::OnClosed(int32_t window_id, base::Closure callback) {
void MenuMac::OnClosed(int32_t window_id, base::OnceClosure callback) {
popup_controllers_.erase(window_id);
callback.Run();
std::move(callback).Run();
}

// static
Expand Down
25 changes: 16 additions & 9 deletions shell/browser/api/atom_api_menu_views.cc
Expand Up @@ -5,6 +5,7 @@
#include "shell/browser/api/atom_api_menu_views.h"

#include <memory>
#include <utility>

#include "shell/browser/native_window_views.h"
#include "shell/browser/unresponsive_suppressor.h"
Expand All @@ -24,10 +25,7 @@ void MenuViews::PopupAt(TopLevelWindow* window,
int x,
int y,
int positioning_item,
const base::Closure& callback) {
mate::Locker locker(isolate());
v8::HandleScope handle_scope(isolate());

base::OnceClosure callback) {
auto* native_window = static_cast<NativeWindowViews*>(window->window());
if (!native_window)
return;
Expand All @@ -46,12 +44,21 @@ void MenuViews::PopupAt(TopLevelWindow* window,
// Don't emit unresponsive event when showing menu.
electron::UnresponsiveSuppressor suppressor;

// Make sure the Menu object would not be garbage-collected until the callback
// has run.
base::OnceClosure callback_with_ref = BindSelfToClosure(std::move(callback));

// Show the menu.
//
// Note that while views::MenuRunner accepts RepeatingCallback as close
// callback, it is fine passing OnceCallback to it because we reset the
// menu runner immediately when the menu is closed.
int32_t window_id = window->weak_map_id();
auto close_callback = base::BindRepeating(
&MenuViews::OnClosed, weak_factory_.GetWeakPtr(), window_id, callback);
auto close_callback = base::AdaptCallbackForRepeating(
base::BindOnce(&MenuViews::OnClosed, weak_factory_.GetWeakPtr(),
window_id, std::move(callback_with_ref)));
menu_runners_[window_id] =
std::make_unique<MenuRunner>(model(), flags, close_callback);
std::make_unique<MenuRunner>(model(), flags, std::move(close_callback));
menu_runners_[window_id]->RunMenuAt(
native_window->widget(), nullptr, gfx::Rect(location, gfx::Size()),
views::MenuAnchorPosition::kTopLeft, ui::MENU_SOURCE_MOUSE);
Expand All @@ -71,9 +78,9 @@ void MenuViews::ClosePopupAt(int32_t window_id) {
}
}

void MenuViews::OnClosed(int32_t window_id, base::Closure callback) {
void MenuViews::OnClosed(int32_t window_id, base::OnceClosure callback) {
menu_runners_.erase(window_id);
callback.Run();
std::move(callback).Run();
}

// static
Expand Down
4 changes: 2 additions & 2 deletions shell/browser/api/atom_api_menu_views.h
Expand Up @@ -27,11 +27,11 @@ class MenuViews : public Menu {
int x,
int y,
int positioning_item,
const base::Closure& callback) override;
base::OnceClosure callback) override;
void ClosePopupAt(int32_t window_id) override;

private:
void OnClosed(int32_t window_id, base::Closure callback);
void OnClosed(int32_t window_id, base::OnceClosure callback);

// window ID -> open context menu
std::map<int32_t, std::unique_ptr<views::MenuRunner>> menu_runners_;
Expand Down
4 changes: 2 additions & 2 deletions shell/browser/ui/cocoa/atom_menu_controller.h
Expand Up @@ -28,7 +28,7 @@ class AtomMenuModel;
base::scoped_nsobject<NSMenu> menu_;
BOOL isMenuOpen_;
BOOL useDefaultAccelerator_;
base::Callback<void()> closeCallback;
base::OnceClosure closeCallback;
}

@property(nonatomic, assign) electron::AtomMenuModel* model;
Expand All @@ -38,7 +38,7 @@ class AtomMenuModel;
- (id)initWithModel:(electron::AtomMenuModel*)model
useDefaultAccelerator:(BOOL)use;

- (void)setCloseCallback:(const base::Callback<void()>&)callback;
- (void)setCloseCallback:(base::OnceClosure)callback;

// Populate current NSMenu with |model|.
- (void)populateWithModel:(electron::AtomMenuModel*)model;
Expand Down
8 changes: 4 additions & 4 deletions shell/browser/ui/cocoa/atom_menu_controller.mm
Expand Up @@ -118,8 +118,8 @@ - (void)dealloc {
[super dealloc];
}

- (void)setCloseCallback:(const base::Callback<void()>&)callback {
closeCallback = callback;
- (void)setCloseCallback:(base::OnceClosure)callback {
closeCallback = std::move(callback);
}

- (void)populateWithModel:(electron::AtomMenuModel*)model {
Expand Down Expand Up @@ -153,7 +153,7 @@ - (void)cancel {
isMenuOpen_ = NO;
model_->MenuWillClose();
if (!closeCallback.is_null()) {
base::PostTask(FROM_HERE, {BrowserThread::UI}, closeCallback);
base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(closeCallback));
}
}
}
Expand Down Expand Up @@ -385,7 +385,7 @@ - (void)menuDidClose:(NSMenu*)menu {
// Post async task so that itemSelected runs before the close callback
// deletes the controller from the map which deallocates it
if (!closeCallback.is_null()) {
base::PostTask(FROM_HERE, {BrowserThread::UI}, closeCallback);
base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(closeCallback));
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions shell/common/gin_helper/function_template.h
Expand Up @@ -5,6 +5,8 @@
#ifndef SHELL_COMMON_GIN_HELPER_FUNCTION_TEMPLATE_H_
#define SHELL_COMMON_GIN_HELPER_FUNCTION_TEMPLATE_H_

#include <utility>

#include "base/bind.h"
#include "base/callback.h"
#include "gin/arguments.h"
Expand Down Expand Up @@ -199,7 +201,8 @@ class Invoker<IndicesHolder<indices...>, ArgTypes...>
void DispatchToCallback(base::Callback<ReturnType(ArgTypes...)> callback) {
v8::MicrotasksScope script_scope(args_->isolate(),
v8::MicrotasksScope::kRunMicrotasks);
args_->Return(callback.Run(ArgumentHolder<indices, ArgTypes>::value...));
args_->Return(
callback.Run(std::move(ArgumentHolder<indices, ArgTypes>::value)...));
}

// In C++, you can declare the function foo(void), but you can't pass a void
Expand All @@ -208,7 +211,7 @@ class Invoker<IndicesHolder<indices...>, ArgTypes...>
void DispatchToCallback(base::Callback<void(ArgTypes...)> callback) {
v8::MicrotasksScope script_scope(args_->isolate(),
v8::MicrotasksScope::kRunMicrotasks);
callback.Run(ArgumentHolder<indices, ArgTypes>::value...);
callback.Run(std::move(ArgumentHolder<indices, ArgTypes>::value)...);
}

private:
Expand Down
25 changes: 25 additions & 0 deletions spec-main/api-menu-spec.ts
Expand Up @@ -822,6 +822,31 @@ describe('Menu module', function () {
menu.closePopup()
})
})

it('prevents menu from getting garbage-collected when popuping', (done) => {
let menu = Menu.buildFromTemplate([{role: 'paste'}])
menu.popup({ window: w })

// Keep a weak reference to the menu.
const v8Util = process.electronBinding('v8_util')
const map = (v8Util as any).createIDWeakMap() as any
map.set(0, menu)

setTimeout(() => {
// Do garbage collection, since |menu| is not referenced in this closure
// it would be gone after next call.
v8Util.requestGarbageCollectionForTesting()
setTimeout(() => {
// Try to receive menu from weak reference.
if (map.has(0)) {
map.get(0).closePopup()
done()
} else {
done('Menu is garbage-collected while popuping')
}
})
})
})
})

describe('Menu.setApplicationMenu', () => {
Expand Down