From dfc947a97dd37d5ec58e1c971a9a2285429dd98d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Tue, 20 Aug 2024 03:11:34 +0100 Subject: [PATCH] PERF: Defer button actions to improve interaction-next-paint (INP) (#28019) This is a variation on bc3e8a9963cf9a64d114ec751c875025af169690, which was reverted due to issues on iOS. Safari's "in response to user action" check cannot follow the `runAfterFramePaint` chain of interaction -> requestAnimationFrame -> messageChannel, and so some sensitive browser APIs (e.g. clipboard, upload, etc.) were blocked. This commit is similar, but uses `next()` instead of `runAfterFramePaint()`. The result seems the same, but doesn't have the same issue on iOS. The chat-emoji-picker change was required to resolve a test failure. The emoji picker has never closed-on-scroll on desktop, so there is no user-facing change in behavior. --- .../discourse/app/components/d-button.gjs | 23 +++++++++++-------- .../app/mixins/card-contents-base.js | 11 +++++---- .../chat-channel-message-emoji-picker.gjs | 8 +++---- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/d-button.gjs b/app/assets/javascripts/discourse/app/components/d-button.gjs index d0b0147010a..666c6791e93 100644 --- a/app/assets/javascripts/discourse/app/components/d-button.gjs +++ b/app/assets/javascripts/discourse/app/components/d-button.gjs @@ -1,6 +1,7 @@ import { on } from "@ember/modifier"; import { action } from "@ember/object"; import { empty, equal, notEmpty } from "@ember/object/computed"; +import { next } from "@ember/runloop"; import { service } from "@ember/service"; import { htmlSafe } from "@ember/template"; import { or } from "truth-helpers"; @@ -118,17 +119,19 @@ export default class DButton extends GlimmerComponentWithDeprecatedParentView { ); } } else if (typeof actionVal === "object" && actionVal.value) { - if (forwardEvent) { - actionVal.value(actionParam, event); - } else { - actionVal.value(actionParam); - } + // Using `next()` to optimise INP + next(() => + forwardEvent + ? actionVal.value(actionParam, event) + : actionVal.value(actionParam) + ); } else if (typeof actionVal === "function") { - if (forwardEvent) { - actionVal(actionParam, event); - } else { - actionVal(actionParam); - } + // Using `next()` to optimise INP + next(() => + forwardEvent + ? actionVal(actionParam, event) + : actionVal(actionParam) + ); } } else if (route) { this.router.transitionTo(route); diff --git a/app/assets/javascripts/discourse/app/mixins/card-contents-base.js b/app/assets/javascripts/discourse/app/mixins/card-contents-base.js index 652605151a3..c7a4d9cf673 100644 --- a/app/assets/javascripts/discourse/app/mixins/card-contents-base.js +++ b/app/assets/javascripts/discourse/app/mixins/card-contents-base.js @@ -1,6 +1,6 @@ import { alias, match } from "@ember/object/computed"; import Mixin from "@ember/object/mixin"; -import { schedule, throttle } from "@ember/runloop"; +import { next, schedule, throttle } from "@ember/runloop"; import { service } from "@ember/service"; import { wantsNewWindow } from "discourse/lib/intercept-click"; import { headerOffset } from "discourse/lib/offset-calculator"; @@ -86,9 +86,12 @@ export default Mixin.create({ document.querySelector(".card-cloak")?.classList.remove("hidden"); this.appEvents.trigger("user-card:show", { username }); - this._positionCard(target, event); - this._showCallback(username).then((user) => { - this.appEvents.trigger("user-card:after-show", { user }); + // Using `next()` to optimise INP + next(() => { + this._positionCard(target, event); + this._showCallback(username).then((user) => { + this.appEvents.trigger("user-card:after-show", { user }); + }); }); // We bind scrolling on mobile after cards are shown to hide them if user scrolls diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-message-emoji-picker.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-channel-message-emoji-picker.gjs index 7a8316176f9..7f440438f5c 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-message-emoji-picker.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel-message-emoji-picker.gjs @@ -13,6 +13,10 @@ export default class ChatChannelMessageEmojiPicker extends Component { context = "chat-channel-message"; listenToBodyScroll = modifier(() => { + if (!this.site.mobileView) { + return; + } + const handler = () => { this.chatEmojiPickerManager.close(); }; @@ -43,10 +47,6 @@ export default class ChatChannelMessageEmojiPicker extends Component { { placement: "top", modifiers: [ - { - name: "eventListeners", - options: { scroll: false, resize: false }, - }, { name: "flip", options: { padding: { top: headerOffset() } },