[FIX] website: fix entering edit mode after canceling a beforeunload

The website now being displayed in the backend of Odoo (since [1])
within an iframe. To be able to send events inside this iframe, the
`PublicRoot` widget from within the iframe is "captured" (using the
`OdooFrameContentLoaded` event). This is used to send events to Public
Widgets such as notifying them that the edition is about to start, so
they need to reload in edit mode.

Prior to this commit, the WebsiteRootInstance would be set to
`undefined` when the page was about to be unloaded (`beforeunload`
event). This is useful as this prevents the editor to start in an
inconsistent state if a user clicks on a link or changes page and clicks
edit too soon.

Unfortunately, the beforeunload event can be canceled.

Two ways this can be noticed:

- On firefox, enter edit mode and edit the page
- Try to close the tab
- A browser dialog is displayed asking the user to confirm if they want
to leave the page
- This is done using the beforeunload event
- It is triggered in both the iframe and the top window on firefox when
trying to close a tab
- Clicking on cancel won't allow you to resume the edition as the
`websiteRootInstance` was set to `undefined`

- On any browser, enter edit mode
- Upload a document using the media dialog
- Save
- Click to download the document
- Try to enter edit mode

Clicking on the link triggers a `beforeunload` as the browser is about
to leave the page. The browser somehow detects a download and cancels
the beforeunload. But the `websiteRootInstance` was already set to
undefined.

This commit fixes that by only setting the websiteRootInstance to
undefined in some context:

- When clicking on a link that will result in navigating inside the
iframe
- When changing website
- When the `WebsitePreview` component is asked to reload the iframe
- Doing it again when a `pagehide` event is triggered.

This last one is only for safety (e.g. if a widget within the iframe
triggers navigation which leads to a pagehide). Though using this event
is often too late, as the editor has time to start but often not fully,
and destroying it so early can lead to tracebacks.

[1]: https://github.com/odoo/odoo/commit/31cc10b91dc7762e23b4bde9b945be0c4ce3fe3b

task-3046473

closes odoo/odoo#112048

X-original-commit: e47a9900e4a7842774d33a332b325e1f08c3ca45
Signed-off-by: Outagant Mehdi (mou) <mou@odoo.com>
Signed-off-by: Arthur Detroux (ard) <ard@odoo.com>
This commit is contained in:
Arthur Detroux (ard)
2023-02-07 08:41:00 +01:00
parent d2d1c01971
commit d7c7823d8c
2 changed files with 13 additions and 1 deletions
@@ -232,6 +232,7 @@ export class WebsitePreview extends Component {
reloadIframe(url) {
return new Promise((resolve, reject) => {
this.websiteService.websiteRootInstance = undefined;
this.iframe.el.addEventListener('OdooFrameContentLoaded', resolve, { once: true });
if (url) {
this.iframe.el.contentWindow.location = url;
@@ -311,6 +312,7 @@ export class WebsitePreview extends Component {
this.iframe.el.contentWindow.addEventListener('beforeunload', this._onPageUnload.bind(this));
this._replaceBrowserUrl();
this.iframe.el.contentWindow.addEventListener('hashchange', this._replaceBrowserUrl.bind(this));
this.iframe.el.contentWindow.addEventListener('pagehide', this._onPageHide.bind(this));
this.websiteService.pageDocument = this.iframe.el.contentDocument;
@@ -374,6 +376,8 @@ export class WebsitePreview extends Component {
} else if (href && target !== '_blank' && !isEditing && this._isTopWindowURL(linkEl)) {
ev.preventDefault();
this.router.redirect(href);
} else if (this.iframe.el.contentWindow.location.pathname !== new URL(href).pathname) {
this.websiteService.websiteRootInstance = undefined;
}
});
this.iframe.el.contentDocument.addEventListener('keydown', ev => {
@@ -390,7 +394,6 @@ export class WebsitePreview extends Component {
}
_onPageUnload() {
this.websiteService.websiteRootInstance = undefined;
this.iframe.el.setAttribute('is-ready', 'false');
// Before leaving the iframe, its content is replicated on an
// underlying iframe, to avoid for white flashes (visible on
@@ -404,6 +407,14 @@ export class WebsitePreview extends Component {
$().getScrollingElement(this.iframefallback.el.contentDocument)[0].scrollTop = $().getScrollingElement(this.iframe.el.contentDocument)[0].scrollTop;
}
}
_onPageHide() {
// Normally, at this point, the websiteRootInstance is already set to
// `undefined`, as we want to do that as early as possible to prevent
// the editor to be in an unstable state. But some events not managed
// by the websitePreview could trigger a `pagehide`, so for safety,
// it is set to undefined again.
this.websiteService.websiteRootInstance = undefined;
}
}
WebsitePreview.template = 'website.WebsitePreview';
WebsitePreview.components = {
@@ -190,6 +190,7 @@ export const websiteService = {
},
goToWebsite({ websiteId, path, edition, translation, lang } = {}) {
this.websiteRootInstance = undefined;
if (lang) {
invalidateSnippetCache = true;
path = `/website/lang/${lang}?r=${encodeURIComponent(path)}`;