diff --git a/addons/web/static/src/legacy/js/core/dom.js b/addons/web/static/src/legacy/js/core/dom.js index bfadd57bdd4..d1936859653 100644 --- a/addons/web/static/src/legacy/js/core/dom.js +++ b/addons/web/static/src/legacy/js/core/dom.js @@ -561,7 +561,9 @@ var dom = { offsetTop = $el.offset().top; el.classList.add('d-none'); } - let elPosition = $scrollable[0].scrollTop + (offsetTop - $scrollable.offset().top); + const isDocScrollingEl = $scrollable.is(el.ownerDocument.scrollingElement); + let elPosition = offsetTop + - ($scrollable.offset().top - (isDocScrollingEl ? 0 : $scrollable[0].scrollTop)); if (!isInOneDocument && $iframe.length) { elPosition += $iframe.offset().top; } diff --git a/addons/web/static/src/legacy/js/libs/jquery.js b/addons/web/static/src/legacy/js/libs/jquery.js index 93404f98994..79ff64f298b 100644 --- a/addons/web/static/src/legacy/js/libs/jquery.js +++ b/addons/web/static/src/legacy/js/libs/jquery.js @@ -123,13 +123,21 @@ $.fn.extend({ }); }, /** + * @todo Should really be converted to no jQuery and probably even removed + * from jQuery utilities in master * @return {jQuery} */ closestScrollable() { + const document = this.length ? this[0].ownerDocument : window.document; + let $el = this; while ($el[0] !== document.scrollingElement) { - // Avoid infinite loop. - if (!$el.length) { + if (!$el.length || $el[0] instanceof Document) { + // Ensure that $().closestScrollable() -> $() and handle the + // case of elements not attached to the DOM. + // Also, .parent() used to loop through ancestors can + // theoretically reach the document if nothing up to the HTML + // included is not scrollable. return $(); } if ($el.isScrollable()) { @@ -173,9 +181,13 @@ $.fn.extend({ * @returns {boolean} */ isScrollable() { + if (!this.length) { + return false; + } const overflow = this.css('overflow-y'); + const el = this[0]; return overflow === 'auto' || overflow === 'scroll' - || (overflow === 'visible' && this === document.scrollingElement); + || (overflow === 'visible' && el === el.ownerDocument.scrollingElement); }, }); diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js index b755bc9b014..8ffb992f94e 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -1693,6 +1693,9 @@ var SnippetsMenu = Widget.extend({ if (!this.$scrollingElement[0]) { this.$scrollingElement = $(this.ownerDocument).find('.o_editable'); } + this.$scrollingTarget = this.$scrollingElement.is(this.ownerDocument.scrollingElement) + ? $(this.ownerDocument.defaultView) + : this.$scrollingElement; this._onScrollingElementScroll = _.throttle(() => { for (const editor of this.snippetEditors) { editor.toggleOverlayVisibility(false); @@ -1710,7 +1713,7 @@ var SnippetsMenu = Widget.extend({ // Setting capture to true allows to take advantage of event bubbling // for events that otherwise don’t support it. (e.g. useful when // scrolling a modal) - this.$scrollingElement[0].addEventListener('scroll', this._onScrollingElementScroll, {capture: true}); + this.$scrollingTarget[0].addEventListener('scroll', this._onScrollingElementScroll, {capture: true}); // Auto-selects text elements with a specific class and remove this // on text changes @@ -1805,8 +1808,9 @@ var SnippetsMenu = Widget.extend({ } this.$window.off('.snippets_menu'); this.$document.off('.snippets_menu'); - if (this.$scrollingElement) { - this.$scrollingElement[0].removeEventListener('scroll', this._onScrollingElementScroll, {capture: true}); + + if (this.$scrollingTarget) { + this.$scrollingTarget[0].removeEventListener('scroll', this._onScrollingElementScroll, {capture: true}); } } core.bus.off('deactivate_snippet', this, this._onDeactivateSnippet);