diff --git a/web/e2e-tests/lib/common.ts b/web/e2e-tests/lib/common.ts index 9c57e27b5c..4cf0dadfec 100644 --- a/web/e2e-tests/lib/common.ts +++ b/web/e2e-tests/lib/common.ts @@ -137,7 +137,7 @@ export async function fill_form( params: Record, ): Promise { async function is_dropdown(page: Page, name: string): Promise { - return (await page.$(`select[name="${name}"]`)) !== null; + return (await page.$(`select[name="${CSS.escape(name)}"]`)) !== null; } for (const name of Object.keys(params)) { const name_selector = `${form_selector} [name="${name}"]`; diff --git a/web/src/compose_banner.ts b/web/src/compose_banner.ts index 254e9b357f..fa4d1251bf 100644 --- a/web/src/compose_banner.ts +++ b/web/src/compose_banner.ts @@ -111,7 +111,12 @@ export function clear_uploads(): void { } export function clear_unmute_topic_notifications(): void { - $(`#compose_banners .${CLASSNAMES.unmute_topic_notification.replaceAll(" ", ".")}`).remove(); + $( + `#compose_banners .${CLASSNAMES.unmute_topic_notification + .split(" ") + .map((classname) => CSS.escape(classname)) + .join(".")}`, + ).remove(); } export function clear_all(): void { diff --git a/web/src/echo.js b/web/src/echo.js index 52691ff29c..8e4ee6fe97 100644 --- a/web/src/echo.js +++ b/web/src/echo.js @@ -465,7 +465,7 @@ function abort_message(message) { } export function display_slow_send_loading_spinner(message) { - const $row = $(`div[zid="${message.id}"]`); + const $row = $(`div[zid="${CSS.escape(message.id)}"]`); if (message.locally_echoed && !message.failed_request) { $row.find(".slow-send-spinner").removeClass("hidden"); // We don't need to do anything special to ensure this gets diff --git a/web/src/message_lists.js b/web/src/message_lists.js index bb904ac076..605e297151 100644 --- a/web/src/message_lists.js +++ b/web/src/message_lists.js @@ -21,7 +21,7 @@ export function all_rendered_message_lists() { } export function all_current_message_rows() { - return $(`#${current.table_name}.message-list .message_row`); + return $(`#${CSS.escape(current.table_name)}.message-list .message_row`); } export function update_recipient_bar_background_color() { diff --git a/web/src/messages_overlay_ui.ts b/web/src/messages_overlay_ui.ts index a82803c2f9..f5f735948d 100644 --- a/web/src/messages_overlay_ui.ts +++ b/web/src/messages_overlay_ui.ts @@ -189,5 +189,5 @@ function scroll_to_element($element: JQuery, context: Context): void { } function get_element_by_id(id: number | string, context: Context): JQuery { - return $(`[${context.id_attribute_name}='${id}']`); + return $(`[${CSS.escape(context.id_attribute_name)}='${CSS.escape(id.toString())}']`); } diff --git a/web/tests/lib/compose_banner.js b/web/tests/lib/compose_banner.js index cffd84650c..d3c5bdda64 100644 --- a/web/tests/lib/compose_banner.js +++ b/web/tests/lib/compose_banner.js @@ -8,7 +8,12 @@ exports.mock_banners = () => { // zjquery doesn't support `remove`, which is used when clearing the compose box. // TODO: improve how we test this so that we don't have to mock things like this. for (const classname of Object.values(compose_banner.CLASSNAMES)) { - $(`#compose_banners .${classname.replaceAll(" ", ".")}`).remove = () => {}; + $( + `#compose_banners .${classname + .split(" ") + .map((classname) => CSS.escape(classname)) + .join(".")}`, + ).remove = () => {}; } $("#compose_banners .warning").remove = () => {}; $("#compose_banners .error").remove = () => {};