popover_menus: Fix event handler trying to hide a hidden popover.

When user is trying to open a modal after clicking on a button
in a popover, we call popovers.hide_all() before opening the modal
which hides the popover but since the event handler call isn't
finished running yet, we call instance.hide() again resulting in
tippy throwing errors that this could be a memory leak.

We introduce a wrapper function for `instance.hide` which if
the popover/tooltip is visible before hiding it to fix it.
This commit is contained in:
Aman Agrawal 2024-04-02 07:29:56 +00:00 committed by Tim Abbott
parent ac5161f439
commit 0a90a13bec
12 changed files with 62 additions and 51 deletions

View File

@ -25,6 +25,7 @@ import * as narrow_state from "./narrow_state";
import * as navigate from "./navigate";
import {page_params} from "./page_params";
import * as pm_list from "./pm_list";
import * as popover_menus from "./popover_menus";
import * as reactions from "./reactions";
import * as recent_view_ui from "./recent_view_ui";
import * as rows from "./rows";
@ -542,7 +543,7 @@ export function initialize() {
for (const mutation of mutationsList) {
// Hide instance if reference is in the removed node list.
if (check_reference_removed(mutation, instance)) {
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
}
}
};

View File

@ -41,12 +41,12 @@ export function initialize() {
$popper.one("click", ".compose_mobile_stream_button", (e) => {
compose_actions.start("stream", {trigger: "clear topic button"});
e.stopPropagation();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".compose_mobile_direct_message_button", (e) => {
compose_actions.start("private");
e.stopPropagation();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
},
onHidden(instance) {

View File

@ -12,7 +12,7 @@ import * as blueslip from "./blueslip";
import * as ListWidget from "./list_widget";
import type {ListWidget as ListWidgetType} from "./list_widget";
import {page_params} from "./page_params";
import {default_popover_props} from "./popover_menus";
import * as popover_menus from "./popover_menus";
import type {StreamSubscription} from "./sub_store";
import {parse_html} from "./ui_util";
@ -180,7 +180,7 @@ export class DropdownWidget {
return;
}
this.instance = tippy.delegate(delegate_container, {
...default_popover_props,
...popover_menus.default_popover_props,
target: this.widget_selector,
// Custom theme defined in popovers.css
theme: "dropdown-widget",
@ -286,7 +286,7 @@ export class DropdownWidget {
break;
case "Escape":
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
this.on_exit_with_escape_callback();
e.stopPropagation();
e.preventDefault();

View File

@ -145,13 +145,13 @@ export function initialize() {
});
},
);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
e.preventDefault();
e.stopPropagation();
});
$popper.on("click", ".change-language-spectator", (e) => {
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
e.preventDefault();
e.stopPropagation();
settings_preferences.launch_default_language_setting_modal();
@ -162,7 +162,7 @@ export function initialize() {
// Also, since these buttons are only visible for spectators which doesn't have events,
// if theme is changed in a different tab, the theme of this tab remains the same.
$popper.on("click", "#gear-menu-dropdown .gear-menu-select-dark-theme", (e) => {
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
e.preventDefault();
e.stopPropagation();
requestAnimationFrame(() => {
@ -172,7 +172,7 @@ export function initialize() {
});
$popper.on("click", "#gear-menu-dropdown .gear-menu-select-light-theme", (e) => {
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
e.preventDefault();
e.stopPropagation();
requestAnimationFrame(() => {

View File

@ -38,7 +38,7 @@ function common_click_handlers() {
function register_mark_all_read_handler(event) {
const {instance} = event.data;
unread_ops.confirm_mark_all_as_read();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
}
export function initialize() {
@ -52,7 +52,7 @@ export function initialize() {
$popper.one("click", "#unstar_all_messages", () => {
starred_messages_ui.confirm_unstar_all_messages();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", "#toggle_display_starred_msg_count", () => {
const data = {};
@ -63,7 +63,7 @@ export function initialize() {
url: "/json/settings",
data,
});
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
},
onShow(instance) {
@ -97,7 +97,7 @@ export function initialize() {
$popper.one("click", "#delete_all_drafts_sidebar", () => {
drafts.confirm_delete_all_drafts();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
},
onShow(instance) {

View File

@ -123,7 +123,7 @@ export function initialize() {
});
e.preventDefault();
e.stopPropagation();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".popover_edit_message, .popover_view_source", (e) => {
@ -133,7 +133,7 @@ export function initialize() {
message_edit.start($row);
e.preventDefault();
e.stopPropagation();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".popover_move_message", (e) => {
@ -149,7 +149,7 @@ export function initialize() {
);
e.preventDefault();
e.stopPropagation();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".mark_as_unread", (e) => {
@ -157,7 +157,7 @@ export function initialize() {
unread_ops.mark_as_unread_from_here(message_id);
e.preventDefault();
e.stopPropagation();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".popover_toggle_collapse", (e) => {
@ -172,7 +172,7 @@ export function initialize() {
}
e.preventDefault();
e.stopPropagation();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".rehide_muted_user_message", (e) => {
@ -188,7 +188,7 @@ export function initialize() {
}
e.preventDefault();
e.stopPropagation();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".view_read_receipts", (e) => {
@ -196,7 +196,7 @@ export function initialize() {
read_receipts.show_user_list(message_id);
e.preventDefault();
e.stopPropagation();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".delete_message", (e) => {
@ -204,7 +204,7 @@ export function initialize() {
message_edit.delete_message(message_id);
e.preventDefault();
e.stopPropagation();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".reaction_button", (e) => {
@ -216,7 +216,7 @@ export function initialize() {
emoji_picker.toggle_emoji_popover(instance.reference.parentElement, message_id, {
placement: "bottom",
});
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
new ClipboardJS($popper.find(".copy_link")[0]).on("success", () => {
@ -226,7 +226,7 @@ export function initialize() {
// We unfocus this so keyboard shortcuts, etc., will work again.
$(":focus").trigger("blur");
}, 0);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
},
onHidden(instance) {

View File

@ -10,6 +10,7 @@ import render_narrow_tooltip from "../templates/narrow_tooltip.hbs";
import {$t} from "./i18n";
import * as message_lists from "./message_lists";
import * as popover_menus from "./popover_menus";
import * as reactions from "./reactions";
import * as rows from "./rows";
import {realm} from "./state_data";
@ -83,7 +84,7 @@ function hide_tooltip_if_reference_removed(
// We have to be smart about hiding the instance, so we hide it as soon
// as it is displayed.
setTimeout(() => {
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
}, 0);
return;
}
@ -92,11 +93,11 @@ function hide_tooltip_if_reference_removed(
for (const node of nodes_to_check_for_removal) {
// Hide instance if reference's class changes.
if (mutation.type === "attributes" && mutation.attributeName === "class") {
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
}
// Hide instance if reference is in the removed node list.
if (Array.prototype.includes.call(mutation.removedNodes, node)) {
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
}
}
}

View File

@ -71,7 +71,7 @@ export function initialize() {
emoji_name: "",
emoji_code: "",
success() {
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
},
});
});

View File

@ -149,6 +149,15 @@ function get_popover_items_for_instance(instance: PopoverInstance): JQuery | und
return $current_elem.find("a, [tabindex='0']").filter(":visible");
}
export function hide_current_popover_if_visible(instance: PopoverInstance | null): void {
// Call this function instead of `instance.hide` to avoid tippy
// logging about the possibility of already hidden instances,
// which can occur when a click handler does a hide_all().
if (instance?.state.isVisible) {
instance.hide();
}
}
export const default_popover_props: Partial<PopoverProps> = {
delay: 0,
appendTo: () => document.body,
@ -191,7 +200,7 @@ export const default_popover_props: Partial<PopoverProps> = {
"referenceHidden",
);
if (is_reference_outside_window) {
instance.hide();
hide_current_popover_if_visible(instance);
return;
}
@ -250,7 +259,7 @@ export const default_popover_props: Partial<PopoverProps> = {
element.classList.contains("sticky_header"),
)
) {
instance.hide();
hide_current_popover_if_visible(instance);
}
},
},
@ -283,7 +292,7 @@ export function on_show_prep(instance: PopoverInstance): void {
$(instance.popper).one("click", ".navigate_and_close_popover", (e) => {
// Handler for links inside popover which don't need a special click handler.
e.stopPropagation();
instance.hide();
hide_current_popover_if_visible(instance);
});
}
@ -361,7 +370,7 @@ export function toggle_popover_menu(
): PopoverInstance {
const instance = target._tippy;
if (instance) {
instance.hide();
hide_current_popover_if_visible(instance);
return instance;
}
@ -415,7 +424,7 @@ export function register_popover_menu(target: string, popover_props: Partial<Pop
const instance = toggle_popover_menu(e.currentTarget, popover_props);
const $popper = $(instance.popper);
$popper.on("click", "a[href]", () => {
instance.hide();
hide_current_popover_if_visible(instance);
});
});
}

View File

@ -173,7 +173,7 @@ export function initialize() {
$popper.one("click", ".send_later_selected_send_later_time", () => {
const send_at_timestamp = scheduled_messages.get_selected_send_later_timestamp();
do_schedule_message(send_at_timestamp);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
// Handle clicks on Enter-to-send settings
$popper.one("click", ".enter_sends_choice", (e) => {
@ -195,13 +195,13 @@ export function initialize() {
});
e.stopPropagation();
setTimeout(() => {
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
}, ENTER_SENDS_SELECTION_DELAY);
});
// Handle Send later clicks
$popper.one("click", ".open_send_later_modal", () => {
open_send_later_menu();
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
},
onHidden(instance) {

View File

@ -58,7 +58,7 @@ export function initialize() {
const {stream_id, topic_name} = instance.context;
if (!stream_id) {
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
return;
}
@ -80,7 +80,7 @@ export function initialize() {
topic_name,
user_topics.all_visibility_policies.UNMUTED,
);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".sidebar-popover-remove-unmute", () => {
@ -89,7 +89,7 @@ export function initialize() {
topic_name,
user_topics.all_visibility_policies.INHERIT,
);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".sidebar-popover-mute-topic", () => {
@ -98,7 +98,7 @@ export function initialize() {
topic_name,
user_topics.all_visibility_policies.MUTED,
);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".sidebar-popover-remove-mute", () => {
@ -107,7 +107,7 @@ export function initialize() {
topic_name,
user_topics.all_visibility_policies.INHERIT,
);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".sidebar-popover-unstar-all-in-topic", () => {
@ -115,17 +115,17 @@ export function initialize() {
Number.parseInt(stream_id, 10),
topic_name,
);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".sidebar-popover-mark-topic-read", () => {
unread_ops.mark_topic_as_read(stream_id, topic_name);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".sidebar-popover-mark-topic-unread", () => {
unread_ops.mark_topic_as_unread(stream_id, topic_name);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".sidebar-popover-delete-topic-messages", () => {
@ -140,7 +140,7 @@ export function initialize() {
},
});
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".sidebar-popover-toggle-resolved", () => {
@ -148,23 +148,23 @@ export function initialize() {
message_edit.toggle_resolve_topic(message_id, topic_name, true);
});
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".sidebar-popover-move-topic-messages", () => {
stream_popover.build_move_topic_to_stream_popover(stream_id, topic_name, false);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
$popper.one("click", ".sidebar-popover-rename-topic-messages", () => {
stream_popover.build_move_topic_to_stream_popover(stream_id, topic_name, true);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
new ClipboardJS($popper.find(".sidebar-popover-copy-link-to-topic")[0]).on(
"success",
() => {
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
},
);
},

View File

@ -43,7 +43,7 @@ export function initialize() {
const {stream_id, topic_name} = instance.context;
if (!stream_id) {
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
return;
}
@ -58,7 +58,7 @@ export function initialize() {
topic_name,
visibility_policy,
);
instance.hide();
popover_menus.hide_current_popover_if_visible(instance);
});
},
onHidden(instance) {