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:
parent
0285592d61
commit
c5ea177b3d
11 changed files with 135 additions and 12 deletions
|
@ -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
|
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.
|
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 it’s generally best
|
||||||
|
to respect the user’s 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_
|
#### Event: 'session-end' _Windows_
|
||||||
|
|
||||||
Emitted when window session is going to end due to force shutdown or machine restart
|
Returns:
|
||||||
or session log off.
|
|
||||||
|
* `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'
|
#### 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
|
[vibrancy-docs]: https://developer.apple.com/documentation/appkit/nsvisualeffectview?preferredLanguage=objc
|
||||||
[window-levels]: https://developer.apple.com/documentation/appkit/nswindow/level
|
[window-levels]: https://developer.apple.com/documentation/appkit/nswindow/level
|
||||||
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
|
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
|
||||||
|
[window-session-end-event]:../api/structures/window-session-end-event.md
|
||||||
|
|
|
@ -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
|
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.
|
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 it’s generally best
|
||||||
|
to respect the user’s 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_
|
#### Event: 'session-end' _Windows_
|
||||||
|
|
||||||
Emitted when window session is going to end due to force shutdown or machine restart
|
Returns:
|
||||||
or session log off.
|
|
||||||
|
* `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'
|
#### 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
|
[vibrancy-docs]: https://developer.apple.com/documentation/appkit/nsvisualeffectview?preferredLanguage=objc
|
||||||
[window-levels]: https://developer.apple.com/documentation/appkit/nswindow/level
|
[window-levels]: https://developer.apple.com/documentation/appkit/nswindow/level
|
||||||
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
|
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
|
||||||
|
[window-session-end-event]:../api/structures/window-session-end-event.md
|
||||||
|
|
7
docs/api/structures/window-session-end-event.md
Normal file
7
docs/api/structures/window-session-end-event.md
Normal 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).
|
|
@ -151,6 +151,7 @@ auto_filenames = {
|
||||||
"docs/api/structures/web-request-filter.md",
|
"docs/api/structures/web-request-filter.md",
|
||||||
"docs/api/structures/web-source.md",
|
"docs/api/structures/web-source.md",
|
||||||
"docs/api/structures/window-open-handler-response.md",
|
"docs/api/structures/window-open-handler-response.md",
|
||||||
|
"docs/api/structures/window-session-end-event.md",
|
||||||
]
|
]
|
||||||
|
|
||||||
sandbox_bundle_deps = [
|
sandbox_bundle_deps = [
|
||||||
|
|
|
@ -177,8 +177,37 @@ void BaseWindow::OnWindowClosed() {
|
||||||
FROM_HERE, GetDestroyClosure());
|
FROM_HERE, GetDestroyClosure());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseWindow::OnWindowEndSession() {
|
void BaseWindow::OnWindowQueryEndSession(
|
||||||
Emit("session-end");
|
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() {
|
void BaseWindow::OnWindowBlur() {
|
||||||
|
|
|
@ -57,7 +57,9 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
|
||||||
// NativeWindowObserver:
|
// NativeWindowObserver:
|
||||||
void WillCloseWindow(bool* prevent_default) override;
|
void WillCloseWindow(bool* prevent_default) override;
|
||||||
void OnWindowClosed() 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 OnWindowBlur() override;
|
||||||
void OnWindowFocus() override;
|
void OnWindowFocus() override;
|
||||||
void OnWindowShow() override;
|
void OnWindowShow() override;
|
||||||
|
|
|
@ -532,9 +532,17 @@ void NativeWindow::NotifyWindowClosed() {
|
||||||
WindowList::RemoveWindow(this);
|
WindowList::RemoveWindow(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NativeWindow::NotifyWindowEndSession() {
|
void NativeWindow::NotifyWindowQueryEndSession(
|
||||||
|
const std::vector<std::string>& reasons,
|
||||||
|
bool* prevent_default) {
|
||||||
for (NativeWindowObserver& observer : observers_)
|
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() {
|
void NativeWindow::NotifyWindowBlur() {
|
||||||
|
|
|
@ -302,7 +302,9 @@ class NativeWindow : public base::SupportsUserData,
|
||||||
void NotifyWindowRequestPreferredWidth(int* width);
|
void NotifyWindowRequestPreferredWidth(int* width);
|
||||||
void NotifyWindowCloseButtonClicked();
|
void NotifyWindowCloseButtonClicked();
|
||||||
void NotifyWindowClosed();
|
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 NotifyWindowBlur();
|
||||||
void NotifyWindowFocus();
|
void NotifyWindowFocus();
|
||||||
void NotifyWindowShow();
|
void NotifyWindowShow();
|
||||||
|
|
|
@ -50,8 +50,12 @@ class NativeWindowObserver : public base::CheckedObserver {
|
||||||
// Called when the window is closed.
|
// Called when the window is closed.
|
||||||
virtual void OnWindowClosed() {}
|
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
|
// Called when Windows sends WM_ENDSESSION message
|
||||||
virtual void OnWindowEndSession() {}
|
virtual void OnWindowEndSession(const std::vector<std::string>& reasons) {}
|
||||||
|
|
||||||
// Called when window loses focus.
|
// Called when window loses focus.
|
||||||
virtual void OnWindowBlur() {}
|
virtual void OnWindowBlur() {}
|
||||||
|
|
|
@ -27,6 +27,24 @@ namespace electron {
|
||||||
|
|
||||||
namespace {
|
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.
|
// Convert Win32 WM_APPCOMMANDS to strings.
|
||||||
constexpr std::string_view AppCommandToString(int command_id) {
|
constexpr std::string_view AppCommandToString(int command_id) {
|
||||||
switch (command_id) {
|
switch (command_id) {
|
||||||
|
@ -389,9 +407,20 @@ bool NativeWindowViews::PreHandleMSG(UINT message,
|
||||||
}
|
}
|
||||||
return false;
|
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: {
|
case WM_ENDSESSION: {
|
||||||
|
std::vector<std::string> reasons = EndSessionToStringVec(l_param);
|
||||||
if (w_param) {
|
if (w_param) {
|
||||||
NotifyWindowEndSession();
|
NotifyWindowEndSession(reasons);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,17 @@ class EventEmitter : public gin_helper::Wrappable<T> {
|
||||||
return EmitWithEvent(name, event, std::forward<Args>(args)...);
|
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
|
// disable copy
|
||||||
EventEmitter(const EventEmitter&) = delete;
|
EventEmitter(const EventEmitter&) = delete;
|
||||||
EventEmitter& operator=(const EventEmitter&) = delete;
|
EventEmitter& operator=(const EventEmitter&) = delete;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue