From 26f68076bcd8e3d5893cffcd9c1c01ff949318a9 Mon Sep 17 00:00:00 2001 From: Chris Mills Date: Thu, 12 Oct 2023 18:28:48 +0100 Subject: [PATCH] Modernize beforeunload event information (#29595) * Modernize beforeunload event information * Make fixes for wbamberg and dizhang168 comments * Make sure reasons for the code example structure are clearly stated * other small fixes --- .../en-us/web/api/beforeunloadevent/index.md | 84 ++++++------ .../beforeunloadevent/returnvalue/index.md | 36 +++++ .../api/window/beforeunload_event/index.md | 124 ++++++++---------- 3 files changed, 129 insertions(+), 115 deletions(-) create mode 100644 files/en-us/web/api/beforeunloadevent/returnvalue/index.md diff --git a/files/en-us/web/api/beforeunloadevent/index.md b/files/en-us/web/api/beforeunloadevent/index.md index cba1ff1d76c88bf..e82aa9c6c02a77f 100644 --- a/files/en-us/web/api/beforeunloadevent/index.md +++ b/files/en-us/web/api/beforeunloadevent/index.md @@ -7,64 +7,61 @@ browser-compat: api.BeforeUnloadEvent {{APIRef}} -**`BeforeUnloadEvent`** is an interface for the {{domxref("Window/beforeunload_event", "beforeunload")}} event. +The **`BeforeUnloadEvent`** interface represents the event object for the {{domxref("Window/beforeunload_event", "beforeunload")}} event, which is fired when the current window, contained document, and associated resources are about to be unloaded. -The `beforeunload` event is fired when the window, the document and its resources are about to be unloaded. +See the {{domxref("Window/beforeunload_event", "beforeunload")}} event reference for detailed guidance on using this event. -When a non-empty string is assigned to the `returnValue` Event property, a dialog box appears, asking the users for confirmation to leave the page (see example below). When no value is provided, the event is processed silently. Some implementations only show the dialog box if the frame or any embedded frame receives a user gesture or user interaction. See [Browser compatibility](#browser_compatibility) for more information. +{{InheritanceDiagram}} -> **Note:** For security reasons, only a generic string not under the control of the webpage is shown instead of the returned string. +## Instance properties -{{InheritanceDiagram}} +_Inherits properties from its parent, {{DOMxRef("Event")}}._ + +- {{domxref("BeforeUnloadEvent.returnValue", "returnValue")}} {{Deprecated_Inline}} + - : When set to a [truthy](/en-US/docs/Glossary/Truthy) value, triggers a browser-controlled confirmation dialog asking users to confirm if they want to leave the page when they try to close or reload it. This is a legacy feature, and best practice is to trigger the dialog by invoking `event.preventDefault()` instead, as shown in the example. + +## Instance methods - - - - - - - - - - - - - - - - - - - -
BubblesNo
CancelableYes
Target objectsdefaultView
Interface{{domxref("Event")}}
+_Inherits methods from its parent, {{DOMxRef("Event")}}._ ## Examples -```js -window.addEventListener("beforeunload", (event) => { - event.returnValue = "\\o/"; -}); +In the following example we have an HTML text {{htmlelement("input")}} to represent some data that could be changed and require saving: -// is equivalent to -window.addEventListener("beforeunload", (event) => { - event.preventDefault(); -}); +```html +
+ +
``` -WebKit-derived browsers don't follow the spec for the dialog box. An almost-cross-browser working example would be close to the below example. +Our JavaScript attaches an {{domxref("HTMLElement.input_event", "input")}} event listener to the `` element that listens for changes in the inputted value. When the value is updated to a non-empty value, a {{domxref("Window.beforeunload_event", "beforeunload")}} event listener is attached to the {{domxref("Window")}} object. + +If the value becomes an empty string again (i.e. the value is deleted), the `beforeunload` event listener is removed again. It would be more efficient to just add the `beforeunload` listener once, and conditionally call `preventDefault()` based on the value of the target input. Best practice however is to add the listener when needed (i.e. when there is unsaved data that could be lost) and remove it again when it is not needed, for reasons outlined in the [`beforeunload` event usage notes](/en-US/docs/Web/API/Window/beforeunload_event#usage_notes). + +The `beforeunload` event handler function invokes `preventDefault()` to trigger the warning dialog when the user closes or navigates the tab. ```js -window.addEventListener("beforeunload", (e) => { - const confirmationMessage = "\\o/"; +const beforeUnloadHandler = (event) => { + event.preventDefault(); +}; - // Gecko + IE - (e || window.event).returnValue = confirmationMessage; +const nameInput = document.querySelector("#name"); - // Safari, Chrome, and other WebKit-derived browsers - return confirmationMessage; +nameInput.addEventListener("input", (event) => { + if (event.target.value !== "") { + window.addEventListener("beforeunload", beforeUnloadHandler); + } else { + window.removeEventListener("beforeunload", beforeUnloadHandler); + } }); ``` +When the `` value is non-empty, if you try to close, navigate, or reload the page the browser displays the warning dialog. Try it out: + +{{EmbedLiveSample("Examples", "100%", 50)}} + +> **Note:** The browser-generated confirmation dialog can also be triggered by setting the {{domxref("BeforeUnloadEvent.returnValue", "event.returnValue")}} property to a truthy value on the `beforeunload` event object, or by returning a truthy value from the event handler function. However, these are legacy mechanisms, and best practice is to trigger the dialog by invoking `event.preventDefault()` as shown in the example above. + ## Specifications {{Specifications}} @@ -75,9 +72,4 @@ window.addEventListener("beforeunload", (e) => { ## See also -- {{domxref("Document/DOMContentLoaded_event", "DOMContentLoaded")}} -- {{domxref("Document/readystatechange_event", "readystatechange")}} -- {{domxref("Window/load_event", "load")}} -- {{domxref("Window/beforeunload_event", "beforeunload")}} -- {{domxref("Window/unload_event", "unload")}} -- [Unloading Documents — Prompt to unload a document](https://html.spec.whatwg.org/multipage/browsing-the-web.html#prompt-to-unload-a-document) +- {{domxref("Window/beforeunload_event", "beforeunload")}} event diff --git a/files/en-us/web/api/beforeunloadevent/returnvalue/index.md b/files/en-us/web/api/beforeunloadevent/returnvalue/index.md new file mode 100644 index 000000000000000..20356df58ab8dba --- /dev/null +++ b/files/en-us/web/api/beforeunloadevent/returnvalue/index.md @@ -0,0 +1,36 @@ +--- +title: "BeforeUnloadEvent: returnValue property" +short-title: returnValue +slug: Web/API/BeforeUnloadEvent/returnValue +page-type: web-api-instance-property +status: + - deprecated +browser-compat: api.BeforeUnloadEvent.returnValue +--- + +{{APIRef("HTML DOM")}} + +The **`returnValue`** property of the +{{domxref("BeforeUnloadEvent")}} interface, when set to a truthy value, triggers a browser-generated confirmation dialog asking users to confirm if they _really_ want to leave the page when they try to close or reload it, or navigate somewhere else. This is intended to help prevent loss of unsaved data. + +> **Note:** `returnValue` is a legacy feature, and best practice is to trigger the dialog by invoking {{domxref("Event.preventDefault()")}} on the `BeforeUnloadEvent` object instead. See the {{domxref("Window/beforeunload_event", "beforeunload")}} event reference for detailed up-to-date guidance. + +## Value + +`returnValue` is initialized to an empty string (`""`) value. + +Setting it to just about any [truthy](/en-US/docs/Glossary/Truthy) value will cause the dialog to be triggered on page close/reload, however note that it also requires [sticky activation](/en-US/docs/Glossary/Sticky_activation). In other words, the browser will only show the dialog if the frame or any embedded frame receives a user gesture or user interaction. If the user has never interacted with the page, then there is no user data to save, so no legitimate use case for the dialog. + +> **Note:** A generic browser-specified string is displayed in the dialog. This cannot be controlled by the webpage code. + +## Examples + +See the {{domxref("Window/beforeunload_event", "beforeunload")}} event reference page for a best practice example. + +## Specifications + +{{Specifications}} + +## Browser compatibility + +{{Compat}} diff --git a/files/en-us/web/api/window/beforeunload_event/index.md b/files/en-us/web/api/window/beforeunload_event/index.md index 44a7858c9022ab3..f3c55c4036a1021 100644 --- a/files/en-us/web/api/window/beforeunload_event/index.md +++ b/files/en-us/web/api/window/beforeunload_event/index.md @@ -8,13 +8,17 @@ browser-compat: api.Window.beforeunload_event {{APIRef}} -The **`beforeunload`** event is fired when the window, the document and its resources are about to be unloaded. The document is still visible and the event is still cancelable at this point. +The **`beforeunload`** event is fired when the current window, contained document, and associated resources are about to be unloaded. The document is still visible and the event is still cancelable at this point. -This event enables a web page to trigger a confirmation dialog asking the user if they really want to leave the page. If the user confirms, the browser navigates to the new page, otherwise it cancels the navigation. +The main use case for this event is to trigger a browser-generated confirmation dialog that asks users to confirm if they _really_ want to leave the page when they try to close or reload it, or navigate somewhere else. This is intended to help prevent loss of unsaved data. -According to the specification, to show the confirmation dialog an event handler should call {{domxref("Event.preventDefault()", "preventDefault()")}} on the event. +The dialog can be triggered in the following ways: -The HTML specification states that calls to {{domxref("window.alert()")}}, {{domxref("window.confirm()")}}, and {{domxref("window.prompt()")}} methods may be ignored during this event. See the [HTML specification](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#user-prompts) for more details. +- Calling the event object's {{domxref("Event.preventDefault()", "preventDefault()")}} method. +- Setting the event object's {{domxref("BeforeUnloadEvent.returnValue", "returnValue")}} property to a non-empty string value or any other [truthy](/en-US/docs/Glossary/Truthy) value. +- Returning any truthy value from the event handler function, e.g. `return "string"`. Note that this only works when the function is attached via the `onbeforeunload` property, not the {{domxref("EventTarget.addEventListener", "addEventListener()")}} method. This behavior is consistent across modern versions of Firefox, Safari, and Chrome. + +The last two machanisms are legacy features; best practice is to trigger the dialog by invoking `preventDefault()` on the event object. ## Syntax @@ -29,60 +33,74 @@ onbeforeunload = (event) => {}; A {{domxref("BeforeUnloadEvent")}}. Inherits from {{domxref("Event")}}. -## Event handler aliases +## Usage notes -In addition to the `Window` interface, the event handler property `onbeforeunload` is also available on the following targets: +To trigger the dialog being shown when the user closes or navigates the tab, a `beforeunload` event handler function should call {{domxref("Event.preventDefault()", "preventDefault()")}} on the event object. You should note that modern implementations: -- {{domxref("HTMLBodyElement")}} -- {{domxref("HTMLFrameSetElement")}} -- {{domxref("SVGSVGElement")}} - -## Security +- Require [sticky activation](/en-US/docs/Glossary/Sticky_activation) for the dialog to be displayed. In other words, the browser will only show the dialog box if the frame or any embedded frame receives a user gesture or user interaction. If the user has never interacted with the page, then there is no user data to save, so no legitimate use case for the dialog. +- Only show a generic browser-specified string in the displayed dialog. This cannot be controlled by the webpage code. -[Sticky activation](/en-US/docs/Glossary/Sticky_activation) is required. -The user has to have interacted with the page in order for this feature to work. +The `beforeunload` event suffers from some problems: -## Usage notes +- It is not reliably fired, especially on mobile platforms. For example, the `beforeunload` event is not fired at all in the following scenario: -The `beforeunload` event suffers from the same problems as the [`unload`](/en-US/docs/Web/API/Window/unload_event) event. + 1. A mobile user visits your page. + 2. The user then switches to a different app. + 3. Later, the user closes the browser from the app manager. -Especially on mobile, the `beforeunload` event is not reliably fired. For example, the `beforeunload` event is not fired at all in the following scenario: + > **Note:** It is recommended to use the {{domxref("Document.visibilitychange_event", "visibilitychange")}} event as a more reliable signal for automatic app state saving that gets around problems like the above. See [Don't lose user and app state, use Page Visibility](https://www.igvita.com/2015/11/20/dont-lose-user-and-app-state-use-page-visibility/) for more details. -1. A mobile user visits your page. -2. The user then switches to a different app. -3. Later, the user closes the browser from the app manager. +- In Firefox, `beforeunload` is not compatible with the [back/forward cache](https://web.dev/bfcache/) (bfcache): that is, Firefox will not place pages in the bfcache if they have `beforeunload` listeners, and this is bad for performance. -Additionally, on Firefox, the `beforeunload` event is not compatible with the [back/forward cache](https://web.dev/bfcache/) (bfcache): that is, Firefox will not place pages in the bfcache if they have `beforeunload` listeners, and this is bad for performance. +It is therefore recommended that developers listen for `beforeunload` only when users have unsaved changes so that the dialog mentioned above can be used to warn them about impending data loss, and remove the listener again when it is not needed. Listening for `beforeunload` sparingly can minimize the effect on performance. -However, unlike the `unload` event, there is a legitimate use case for the `beforeunload` event: the scenario where the user has entered unsaved data that will be lost if the page is unloaded. +## Event handler aliases -It is recommended that developers listen for `beforeunload` only in this scenario, and only when they actually have unsaved changes, so as to minimize the effect on performance. See the Examples section below for an example of this. +In addition to the `Window` interface, the event handler property `onbeforeunload` is also available on the following targets: -See the [bfcache guide](https://web.dev/bfcache/#only-add-beforeunload-listeners-conditionally) on web.dev for more information about the problems associated with the `beforeunload` event. +- {{domxref("HTMLBodyElement")}} +- {{domxref("HTMLFrameSetElement")}} +- {{domxref("SVGSVGElement")}} ## Examples -In this example a page listens for changes to a [text `input`](/en-US/docs/Web/HTML/Element/input/text). If the element contains a value, it adds a listener for `beforeunload`. If the element is empty, it removes the listener: +In the following example we have an HTML text {{htmlelement("input")}} to represent some data that could be changed and require saving: + +```html +
+ +
+``` + +Our JavaScript attaches an {{domxref("HTMLElement.input_event", "input")}} event listener to the `` element that listens for changes in the inputted value. When the value is updated to a non-empty value, a {{domxref("Window.beforeunload_event", "beforeunload")}} event listener is attached to the {{domxref("Window")}} object. + +If the value becomes an empty string again (i.e. the value is deleted), the `beforeunload` event listener is removed again — as mentioned above in the [Usage notes](#usage_notes), the listener should be removed when there is no unsaved data to warn about. + +The `beforeunload` event handler function invokes `preventDefault()` to trigger the warning dialog when the user closes or navigates the tab. ```js -const beforeUnloadListener = (event) => { +const beforeUnloadHandler = (event) => { event.preventDefault(); - return (event.returnValue = ""); + // Equivalent to the following legacy mechanisms + // event.returnValue = "string"; + // return "string"; (only works with onbeforeunload) }; const nameInput = document.querySelector("#name"); nameInput.addEventListener("input", (event) => { if (event.target.value !== "") { - addEventListener("beforeunload", beforeUnloadListener, { capture: true }); + window.addEventListener("beforeunload", beforeUnloadHandler); } else { - removeEventListener("beforeunload", beforeUnloadListener, { - capture: true, - }); + window.removeEventListener("beforeunload", beforeUnloadHandler); } }); ``` +When the `` value is non-empty, if you try to close, navigate, or reload the page the browser displays the warning dialog. Try it out: + +{{EmbedLiveSample("Examples", "100%", 50)}} + ## Specifications {{Specifications}} @@ -91,44 +109,12 @@ nameInput.addEventListener("input", (event) => { {{Compat}} -## Compatibility notes - -The HTML specification states that authors should use the -{{domxref("Event.preventDefault()")}} method instead of using -{{domxref("Event.returnValue")}} to prompt the user. However, this is not yet supported -by all browsers. - -When this event returns (or sets the `returnValue` property to) a value -other than `null` or `undefined`, the user will be prompted to -confirm the page unload. In older browsers, the return value of the event is displayed -in this dialog. Since Firefox 44, Chrome 51, Opera 38, and Safari 9.1, a generic -string not under the control of the webpage is shown instead of the returned -string. For example: - -- Firefox displays the string, "This page is asking you to confirm that you want to - leave - data you have entered may not be saved." (see [Firefox bug 588292](https://bugzil.la/588292)). -- Chrome displays the string, "Do you want to leave the site? Changes you made may not be saved." (see [Chrome Platform Status](https://chromestatus.com/feature/5349061406228480)). - -In some browsers, calls to {{domxref("window.alert()")}}, -{{domxref("window.confirm()")}}, and {{domxref("window.prompt()")}} may be ignored -during this event. See the [HTML specification](https://html.spec.whatwg.org/multipage/webappapis.html#user-prompts) -for more details. - -Note also, that various browsers ignore the result of the event and do not ask the user -for confirmation at all. In such cases, the document will always be unloaded -automatically. Firefox has a switch named `dom.disable_beforeunload` in -_about:config_ to enable this behavior. As of Chrome 60, the confirmation [will be skipped](https://chromestatus.com/feature/5082396709879808) if -the user has not performed a gesture in the frame or page since it was loaded. Pressing -F5 in the page seems to count as user interaction, whereas mouse-clicking the refresh -arrow or pressing F5 with Chrome DevTools focused does not count as user interaction (as -of Chrome 81). - ## See also -- Related events: {{domxref("Document/DOMContentLoaded_event", "DOMContentLoaded")}}, {{domxref("Document/readystatechange_event", "readystatechange")}}, {{domxref("Window/load_event", "load")}}, {{domxref("Window/unload_event", "unload")}} -- [Unloading Documents — Prompt to unload a document](https://html.spec.whatwg.org/multipage/browsing-the-web.html#prompt-to-unload-a-document) -- [Remove Custom Messages in onbeforeload Dialogs after Chrome 51](https://developer.chrome.com/blog/chrome-51-deprecations/#remove-custom-messages-in-onbeforeunload-dialogs) -- [Don't lose user and app state, use Page Visibility](https://www.igvita.com/2015/11/20/dont-lose-user-and-app-state-use-page-visibility/) explains in detail why you should use `visibilitychange`, not `beforeunload`/`unload`. -- [Page Lifecycle API](https://developer.chrome.com/blog/page-lifecycle-api/#developer-recommendations-for-each-state) gives best-practices guidance on handling page lifecycle behavior in your web applications. -- [PageLifecycle.js](https://github.com/GoogleChromeLabs/page-lifecycle): a JavaScript library that deals with cross-browser inconsistencies in page lifecycle behavior. -- [Back/forward cache](https://web.dev/bfcache/) explains what the back/forward cache is, and its implications for various page lifecycle events. +- {{domxref("BeforeUnloadEvent")}} interface +- Related events: + - {{domxref("Document/DOMContentLoaded_event", "DOMContentLoaded")}} + - {{domxref("Document/readystatechange_event", "readystatechange")}} + - {{domxref("Window/load_event", "load")}} + - {{domxref("Window/unload_event", "unload")}} +- [Page Lifecycle API](https://developer.chrome.com/blog/page-lifecycle-api/#developer-recommendations-for-each-state) provides more useful guidance on handling page lifecycle behavior in your web apps.