feat: add query-session-end and improve session-end events on Windows (#44598)

* feat: add query-session-end event for Windows

* fix: remove debug line

* feat: notify with reason on session-end

* docs: add comments and return params

* docs: add same docs to the BrowserWindow

* fix: add shutdown reason if lParam == 0

* docs: remove 'force' word

* docs: revert multithreading.md change

* docs: add reasons documentation, reason variable renamed to reasons

* docs: improve 'shutdown' reason wording

* docs: reword with 'can be'

* fix: pass reasons by reference

* fix: use newer approach which expose reasons value directly on Event object

* docs: add escaping

* style: linter fixes

* fix: project now should compile

* fix: EmitWithoutEvent method added, EmitWithEvent moved to private again

* docs: typo fix

Co-authored-by: Sam Maddock <samuel.maddock@gmail.com>

* docs: dedicated WindowSessionEndEvent type created

* docs: better wording for session-end event description

Co-authored-by: Will Anderson <will@itsananderson.com>

---------

Co-authored-by: Sam Maddock <samuel.maddock@gmail.com>
Co-authored-by: Will Anderson <will@itsananderson.com>
This commit is contained in:
Savely Krasovsky 2024-11-22 20:47:36 +01:00 committed by GitHub
parent 0285592d61
commit c5ea177b3d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 135 additions and 12 deletions

View file

@ -144,10 +144,24 @@ _**Note**: There is a subtle difference between the behaviors of `window.onbefor
Emitted when the window is closed. After you have received this event you should
remove the reference to the window and avoid using it any more.
#### Event: 'query-session-end' _Windows_
Returns:
* `event` [WindowSessionEndEvent][window-session-end-event]
Emitted when a session is about to end due to a shutdown, machine restart, or user log-off.
Calling `event.preventDefault()` can delay the system shutdown, though its generally best
to respect the users choice to end the session. However, you may choose to use it if
ending the session puts the user at risk of losing data.
#### Event: 'session-end' _Windows_
Emitted when window session is going to end due to force shutdown or machine restart
or session log off.
Returns:
* `event` [WindowSessionEndEvent][window-session-end-event]
Emitted when a session is about to end due to a shutdown, machine restart, or user log-off. Once this event fires, there is no way to prevent the session from ending.
#### Event: 'blur'
@ -1429,3 +1443,4 @@ On Linux, the `symbolColor` is automatically calculated to have minimum accessib
[vibrancy-docs]: https://developer.apple.com/documentation/appkit/nsvisualeffectview?preferredLanguage=objc
[window-levels]: https://developer.apple.com/documentation/appkit/nswindow/level
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
[window-session-end-event]:../api/structures/window-session-end-event.md

View file

@ -207,10 +207,24 @@ _**Note**: There is a subtle difference between the behaviors of `window.onbefor
Emitted when the window is closed. After you have received this event you should
remove the reference to the window and avoid using it any more.
#### Event: 'query-session-end' _Windows_
Returns:
* `event` [WindowSessionEndEvent][window-session-end-event]
Emitted when a session is about to end due to a shutdown, machine restart, or user log-off.
Calling `event.preventDefault()` can delay the system shutdown, though its generally best
to respect the users choice to end the session. However, you may choose to use it if
ending the session puts the user at risk of losing data.
#### Event: 'session-end' _Windows_
Emitted when window session is going to end due to force shutdown or machine restart
or session log off.
Returns:
* `event` [WindowSessionEndEvent][window-session-end-event]
Emitted when a session is about to end due to a shutdown, machine restart, or user log-off. Once this event fires, there is no way to prevent the session from ending.
#### Event: 'unresponsive'
@ -1672,3 +1686,4 @@ On Linux, the `symbolColor` is automatically calculated to have minimum accessib
[vibrancy-docs]: https://developer.apple.com/documentation/appkit/nsvisualeffectview?preferredLanguage=objc
[window-levels]: https://developer.apple.com/documentation/appkit/nswindow/level
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
[window-session-end-event]:../api/structures/window-session-end-event.md

View file

@ -0,0 +1,7 @@
# WindowSessionEndEvent Object extends `Event`
* `reasons` string[] - List of reasons for shutdown. Can be 'shutdown', 'close-app', 'critical', or 'logoff'.
Unfortunately, Windows does not offer a way to differentiate between a shutdown and a reboot, meaning the 'shutdown'
reason is triggered in both scenarios. For more details on the `WM_ENDSESSION` message and its associated reasons,
refer to the [MSDN documentation](https://learn.microsoft.com/en-us/windows/win32/shutdown/wm-endsession).

View file

@ -151,6 +151,7 @@ auto_filenames = {
"docs/api/structures/web-request-filter.md",
"docs/api/structures/web-source.md",
"docs/api/structures/window-open-handler-response.md",
"docs/api/structures/window-session-end-event.md",
]
sandbox_bundle_deps = [

View file

@ -177,8 +177,37 @@ void BaseWindow::OnWindowClosed() {
FROM_HERE, GetDestroyClosure());
}
void BaseWindow::OnWindowEndSession() {
Emit("session-end");
void BaseWindow::OnWindowQueryEndSession(
const std::vector<std::string>& reasons,
bool* prevent_default) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
gin::Handle<gin_helper::internal::Event> event =
gin_helper::internal::Event::New(isolate);
v8::Local<v8::Object> event_object = event.ToV8().As<v8::Object>();
gin::Dictionary dict(isolate, event_object);
dict.Set("reasons", reasons);
EmitWithoutEvent("query-session-end", event);
if (event->GetDefaultPrevented()) {
*prevent_default = true;
}
}
void BaseWindow::OnWindowEndSession(const std::vector<std::string>& reasons) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
gin::Handle<gin_helper::internal::Event> event =
gin_helper::internal::Event::New(isolate);
v8::Local<v8::Object> event_object = event.ToV8().As<v8::Object>();
gin::Dictionary dict(isolate, event_object);
dict.Set("reasons", reasons);
EmitWithoutEvent("session-end", event);
}
void BaseWindow::OnWindowBlur() {

View file

@ -57,7 +57,9 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
// NativeWindowObserver:
void WillCloseWindow(bool* prevent_default) override;
void OnWindowClosed() override;
void OnWindowEndSession() override;
void OnWindowQueryEndSession(const std::vector<std::string>& reasons,
bool* prevent_default) override;
void OnWindowEndSession(const std::vector<std::string>& reasons) override;
void OnWindowBlur() override;
void OnWindowFocus() override;
void OnWindowShow() override;

View file

@ -532,9 +532,17 @@ void NativeWindow::NotifyWindowClosed() {
WindowList::RemoveWindow(this);
}
void NativeWindow::NotifyWindowEndSession() {
void NativeWindow::NotifyWindowQueryEndSession(
const std::vector<std::string>& reasons,
bool* prevent_default) {
for (NativeWindowObserver& observer : observers_)
observer.OnWindowEndSession();
observer.OnWindowQueryEndSession(reasons, prevent_default);
}
void NativeWindow::NotifyWindowEndSession(
const std::vector<std::string>& reasons) {
for (NativeWindowObserver& observer : observers_)
observer.OnWindowEndSession(reasons);
}
void NativeWindow::NotifyWindowBlur() {

View file

@ -302,7 +302,9 @@ class NativeWindow : public base::SupportsUserData,
void NotifyWindowRequestPreferredWidth(int* width);
void NotifyWindowCloseButtonClicked();
void NotifyWindowClosed();
void NotifyWindowEndSession();
void NotifyWindowQueryEndSession(const std::vector<std::string>& reasons,
bool* prevent_default);
void NotifyWindowEndSession(const std::vector<std::string>& reasons);
void NotifyWindowBlur();
void NotifyWindowFocus();
void NotifyWindowShow();

View file

@ -50,8 +50,12 @@ class NativeWindowObserver : public base::CheckedObserver {
// Called when the window is closed.
virtual void OnWindowClosed() {}
// Called when Windows sends WM_QUERYENDSESSION message.
virtual void OnWindowQueryEndSession(const std::vector<std::string>& reasons,
bool* prevent_default) {}
// Called when Windows sends WM_ENDSESSION message
virtual void OnWindowEndSession() {}
virtual void OnWindowEndSession(const std::vector<std::string>& reasons) {}
// Called when window loses focus.
virtual void OnWindowBlur() {}

View file

@ -27,6 +27,24 @@ namespace electron {
namespace {
// Convert Win32 WM_QUERYENDSESSIONS to strings.
const std::vector<std::string> EndSessionToStringVec(LPARAM end_session_id) {
std::vector<std::string> params;
if (end_session_id == 0) {
params.push_back("shutdown");
return params;
}
if (end_session_id & ENDSESSION_CLOSEAPP)
params.push_back("close-app");
if (end_session_id & ENDSESSION_CRITICAL)
params.push_back("critical");
if (end_session_id & ENDSESSION_LOGOFF)
params.push_back("logoff");
return params;
}
// Convert Win32 WM_APPCOMMANDS to strings.
constexpr std::string_view AppCommandToString(int command_id) {
switch (command_id) {
@ -389,9 +407,20 @@ bool NativeWindowViews::PreHandleMSG(UINT message,
}
return false;
}
case WM_QUERYENDSESSION: {
bool prevent_default = false;
std::vector<std::string> reasons = EndSessionToStringVec(l_param);
NotifyWindowQueryEndSession(reasons, &prevent_default);
// Result should be TRUE by default, otherwise WM_ENDSESSION will not be
// fired in some cases: More:
// https://learn.microsoft.com/en-us/windows/win32/rstmgr/guidelines-for-applications
*result = !prevent_default;
return prevent_default;
}
case WM_ENDSESSION: {
std::vector<std::string> reasons = EndSessionToStringVec(l_param);
if (w_param) {
NotifyWindowEndSession();
NotifyWindowEndSession(reasons);
}
return false;
}

View file

@ -45,6 +45,17 @@ class EventEmitter : public gin_helper::Wrappable<T> {
return EmitWithEvent(name, event, std::forward<Args>(args)...);
}
// this.emit(name, args...);
template <typename... Args>
void EmitWithoutEvent(const std::string_view name, Args&&... args) {
v8::HandleScope handle_scope(isolate());
v8::Local<v8::Object> wrapper = GetWrapper();
if (wrapper.IsEmpty())
return;
gin_helper::EmitEvent(isolate(), GetWrapper(), name,
std::forward<Args>(args)...);
}
// disable copy
EventEmitter(const EventEmitter&) = delete;
EventEmitter& operator=(const EventEmitter&) = delete;