From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../aboutwebauthn/content/aboutWebauthn.js | 866 +++++++++++++++++++++ 1 file changed, 866 insertions(+) create mode 100644 toolkit/components/aboutwebauthn/content/aboutWebauthn.js (limited to 'toolkit/components/aboutwebauthn/content/aboutWebauthn.js') diff --git a/toolkit/components/aboutwebauthn/content/aboutWebauthn.js b/toolkit/components/aboutwebauthn/content/aboutWebauthn.js new file mode 100644 index 0000000000..9142ed198a --- /dev/null +++ b/toolkit/components/aboutwebauthn/content/aboutWebauthn.js @@ -0,0 +1,866 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +let AboutWebauthnService = null; + +var AboutWebauthnManagerJS = { + _topic: "about-webauthn-prompt", + _initialized: false, + _l10n: null, + _bio_l10n: null, + _curr_data: null, + _current_tab: "", + _previous_tab: "", + + init() { + if (this._initialized) { + return; + } + this._l10n = new Localization(["toolkit/about/aboutWebauthn.ftl"], true); + this._bio_l10n = new Map(); + this._bio_l10n.set( + "Ctap2EnrollFeedbackFpGood", + "about-webauthn-ctap2-enroll-feedback-good" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackFpTooHigh", + "about-webauthn-ctap2-enroll-feedback-too-high" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackFpTooLow", + "about-webauthn-ctap2-enroll-feedback-too-low" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackFpTooLeft", + "about-webauthn-ctap2-enroll-feedback-too-left" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackFpTooRight", + "about-webauthn-ctap2-enroll-feedback-too-right" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackFpTooFast", + "about-webauthn-ctap2-enroll-feedback-too-fast" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackFpTooSlow", + "about-webauthn-ctap2-enroll-feedback-too-slow" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackFpPoorQuality", + "about-webauthn-ctap2-enroll-feedback-poor-quality" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackFpTooSkewed", + "about-webauthn-ctap2-enroll-feedback-too-skewed" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackFpTooShort", + "about-webauthn-ctap2-enroll-feedback-too-short" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackFpMergeFailure", + "about-webauthn-ctap2-enroll-feedback-merge-failure" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackFpExists", + "about-webauthn-ctap2-enroll-feedback-exists" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackNoUserActivity", + "about-webauthn-ctap2-enroll-feedback-no-user-activity" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackNoUserPresenceTransition", + "about-webauthn-ctap2-enroll-feedback-no-user-presence-transition" + ); + this._bio_l10n.set( + "Ctap2EnrollFeedbackOther", + "about-webauthn-ctap2-enroll-feedback-other" + ); + + Services.obs.addObserver(this, this._topic); + this._initialized = true; + reset_page(); + }, + + uninit() { + Services.obs.removeObserver(this, this._topic); + this._initialized = false; + this._l10n = null; + this._current_tab = ""; + this._previous_tab = ""; + }, + + observe(aSubject, aTopic, aData) { + let data = JSON.parse(aData); + + // We have token + if (data.type == "selected-device") { + this._curr_data = data.auth_info; + this.show_ui_based_on_authenticator_info(data); + fake_click_event_for_id("info-tab-button"); + } else if (data.type == "select-device") { + set_info_text("about-webauthn-text-select-device"); + } else if (data.type == "pin-required") { + open_pin_required_tab(); + } else if (data.type == "pin-invalid") { + let retries = data.retries ? data.retries : 0; + show_results_banner( + "error", + "about-webauthn-results-pin-invalid-error", + JSON.stringify({ retriesLeft: retries }) + ); + open_pin_required_tab(); + } else if (data.type == "bio-enrollment-update") { + if (data.result.EnrollmentList) { + show_results_banner("success", "about-webauthn-results-success"); + this.show_enrollment_list(data.result.EnrollmentList); + bio_enrollment_in_progress(false); + } else if (data.result.DeleteSuccess || data.result.AddSuccess) { + show_results_banner("success", "about-webauthn-results-success"); + clear_bio_enrollment_samples(); + // Update AuthenticatorInfo + this._curr_data = data.result.DeleteSuccess ?? data.result.AddSuccess; + fake_click_event_for_id("bio-enrollments-tab-button"); + bio_enrollment_in_progress(false); + // If we still have some enrollments to show, update the list + // otherwise, remove it. + if ( + this._curr_data.options.bioEnroll === true || + this._curr_data.options.userVerificationMgmtPreview === true + ) { + list_bio_enrollments(); + } else { + // Hide the list, because it's empty + document.getElementById( + "bio-enrollment-list-subsection" + ).hidden = true; + } + } else if (data.result.UpdateSuccess) { + fake_click_event_for_id("bio-enrollments-tab-button"); + list_bio_enrollments(); + } else if (data.result.SampleStatus) { + show_add_bio_enrollment_section(); + let up = document.getElementById("enrollment-update"); + let sample_update = document.createElement("div"); + let new_line = document.createElement("label"); + new_line.classList.add("sample"); + new_line.setAttribute( + "data-l10n-id", + this._bio_l10n.get(data.result.SampleStatus[0]) + ); + sample_update.appendChild(new_line); + let samples_needed_line = document.createElement("label"); + samples_needed_line.setAttribute( + "data-l10n-id", + "about-webauthn-samples-still-needed" + ); + samples_needed_line.setAttribute( + "data-l10n-args", + JSON.stringify({ repeatCount: data.result.SampleStatus[1] }) + ); + sample_update.classList.add("bio-enrollment-sample"); + sample_update.appendChild(samples_needed_line); + up.appendChild(sample_update); + } + } else if (data.type == "credential-management-update") { + credential_management_in_progress(false); + if (data.result.CredentialList) { + show_results_banner("success", "about-webauthn-results-success"); + this.show_credential_list(data.result.CredentialList.credential_list); + } else { + // DeleteSuccess or UpdateSuccess + show_results_banner("success", "about-webauthn-results-success"); + list_credentials(); + } + } else if (data.type == "listen-success") { + reset_page(); + // Show results + show_results_banner("success", "about-webauthn-results-success"); + this._reset_in_progress = ""; + AboutWebauthnService.listen(); + } else if (data.type == "listen-error") { + reset_page(); + + if (!data.error) { + show_results_banner("error", "about-webauthn-results-general-error"); + } else if (data.error.type == "pin-auth-blocked") { + show_results_banner( + "error", + "about-webauthn-results-pin-auth-blocked-error" + ); + } else if (data.error.type == "pin-not-set") { + show_results_banner( + "error", + "about-webauthn-results-pin-not-set-error" + ); + } else if (data.error.type == "device-blocked") { + show_results_banner( + "error", + "about-webauthn-results-pin-blocked-error" + ); + } else if (data.error.type == "pin-is-too-short") { + show_results_banner( + "error", + "about-webauthn-results-pin-too-short-error" + ); + } else if (data.error.type == "pin-is-too-long") { + show_results_banner( + "error", + "about-webauthn-results-pin-too-long-error" + ); + } else if (data.error.type == "pin-invalid") { + let retries = data.error.retries + ? JSON.stringify({ retriesLeft: data.error.retries }) + : null; + show_results_banner( + "error", + "about-webauthn-results-pin-invalid-error", + retries + ); + } else if (data.error.type == "cancel") { + show_results_banner( + "error", + "about-webauthn-results-cancelled-by-user-error" + ); + } else { + show_results_banner("error", "about-webauthn-results-general-error"); + } + AboutWebauthnService.listen(); + } + }, + + show_authenticator_options(options, element, l10n_base) { + let table = document.getElementById(element); + var empty_table = document.createElement("table"); + empty_table.id = element; + table.parentNode.replaceChild(empty_table, table); + table = document.getElementById(element); + for (let key in options) { + if (key == "options") { + continue; + } + // Create an empty element and add it to the 1st position of the table: + var row = table.insertRow(0); + + // Insert new cells ( elements) at the 1st and 2nd position of the "new" element: + var cell1 = row.insertCell(0); + var cell2 = row.insertCell(1); + + // Add some text to the new cells: + let key_text = this._l10n.formatValueSync( + l10n_base + "-" + key.toLowerCase().replace(/_/g, "-") + ); + var key_node = document.createTextNode(key_text); + cell1.appendChild(key_node); + var raw_value = JSON.stringify(options[key]); + var value = raw_value; + if (["true", "false", "null"].includes(raw_value)) { + value = this._l10n.formatValueSync(l10n_base + "-" + raw_value); + } + var value_node = document.createTextNode(value); + cell2.appendChild(value_node); + } + }, + + show_ui_based_on_authenticator_info(data) { + // Hide the "Please plug in a token"-message + document.getElementById("info-text-div").hidden = true; + // Show options, based on what the token supports + if (data.auth_info) { + document.getElementById("ctap2-token-info").style.display = "flex"; + this.show_authenticator_options( + data.auth_info.options, + "authenticator-options", + "about-webauthn-auth-option" + ); + this.show_authenticator_options( + data.auth_info, + "authenticator-info", + "about-webauthn-auth-info" + ); + // Check if token supports PINs + if (data.auth_info.options.clientPin != null) { + document.getElementById("pin-tab-button").style.display = "flex"; + if (data.auth_info.options.clientPin === true) { + // It has a Pin set + document.getElementById("change-pin-button").style.display = "block"; + document.getElementById("set-pin-button").style.display = "none"; + document.getElementById("current-pin-div").hidden = false; + } else { + // It does not have a Pin set yet + document.getElementById("change-pin-button").style.display = "none"; + document.getElementById("set-pin-button").style.display = "block"; + document.getElementById("current-pin-div").hidden = true; + } + } else { + document.getElementById("pin-tab-button").style.display = "none"; + } + + if ( + data.auth_info.options.credMgmt || + data.auth_info.options.credentialMgmtPreview + ) { + document.getElementById("credentials-tab-button").style.display = + "flex"; + } else { + document.getElementById("credentials-tab-button").style.display = + "none"; + } + + if ( + data.auth_info.options.bioEnroll != null || + data.auth_info.options.userVerificationMgmtPreview != null + ) { + document.getElementById("bio-enrollments-tab-button").style.display = + "flex"; + } else { + document.getElementById("bio-enrollments-tab-button").style.display = + "none"; + } + } else { + // Currently auth-rs doesn't send this, because it filters out ctap2-devices. + // U2F / CTAP1 tokens can't be managed + set_info_text("about-webauthn-text-non-ctap2-device"); + } + }, + + show_credential_list(credential_list) { + // We may have temporarily hidden the tab when asking the user for a PIN + // so we have to show it again. + fake_click_event_for_id("credentials-tab-button"); + document.getElementById("credential-list-subsection").hidden = false; + let table = document.getElementById("credential-list"); + var empty_table = document.createElement("table"); + empty_table.id = "credential-list"; + table.parentNode.replaceChild(empty_table, table); + if (!credential_list.length) { + document.getElementById("credential-list-empty-label").hidden = false; + return; + } + document.getElementById("credential-list-empty-label").hidden = true; + table = document.getElementById("credential-list"); + credential_list.forEach(rp => { + // Add some text to the new cells: + let key_text = rp.rp.id; + rp.credentials.forEach(cred => { + let value_text = cred.user.name; + // Create an empty element and add it to the 1st position of the table: + var row = table.insertRow(0); + var key_node = document.createTextNode(key_text); + var value_node = document.createTextNode(value_text); + row.insertCell(0).appendChild(key_node); + row.insertCell(1).appendChild(value_node); + var delete_button = document.createElement("button"); + delete_button.classList.add("delete-button"); + delete_button.classList.add("credentials-button"); + let garbage_icon = document.createElement("img"); + garbage_icon.setAttribute( + "src", + "chrome://global/skin/icons/delete.svg" + ); + garbage_icon.classList.add("delete-icon"); + delete_button.appendChild(garbage_icon); + let delete_text = document.createElement("span"); + delete_text.setAttribute( + "data-l10n-id", + "about-webauthn-delete-button" + ); + delete_button.appendChild(delete_text); + delete_button.addEventListener("click", function () { + let context = document.getElementById("confirmation-context"); + context.textContent = key_text + " - " + value_text; + credential_management_in_progress(true); + let cmd = { + CredentialManagement: { DeleteCredential: cred.credential_id }, + }; + context.setAttribute("data-ctap-command", JSON.stringify(cmd)); + open_delete_confirmation_tab(); + }); + row.insertCell(2).appendChild(delete_button); + }); + }); + }, + + show_enrollment_list(enrollment_list) { + // We may have temporarily hidden the tab when asking the user for a PIN + // so we have to show it again. + fake_click_event_for_id("bio-enrollments-tab-button"); + document.getElementById("bio-enrollment-list-subsection").hidden = false; + let table = document.getElementById("bio-enrollment-list"); + var empty_table = document.createElement("table"); + empty_table.id = "bio-enrollment-list"; + table.parentNode.replaceChild(empty_table, table); + if (!enrollment_list.length) { + document.getElementById("bio-enrollment-list-empty-label").hidden = false; + return; + } + document.getElementById("bio-enrollment-list-empty-label").hidden = true; + table = document.getElementById("bio-enrollment-list"); + enrollment_list.forEach(enrollment => { + let key_text = enrollment.template_friendly_name ?? ""; + var row = table.insertRow(0); + var key_node = document.createTextNode(key_text); + row.insertCell(0).appendChild(key_node); + var delete_button = document.createElement("button"); + delete_button.classList.add("delete-button"); + delete_button.classList.add("bio-enrollment-button"); + let garbage_icon = document.createElement("img"); + garbage_icon.setAttribute("src", "chrome://global/skin/icons/delete.svg"); + garbage_icon.classList.add("delete-icon"); + delete_button.appendChild(garbage_icon); + let delete_text = document.createElement("span"); + delete_text.setAttribute("data-l10n-id", "about-webauthn-delete-button"); + delete_button.appendChild(delete_text); + delete_button.addEventListener("click", function () { + let context = document.getElementById("confirmation-context"); + context.textContent = key_text; + bio_enrollment_in_progress(true); + let cmd = { + BioEnrollment: { DeleteEnrollment: enrollment.template_id }, + }; + context.setAttribute("data-ctap-command", JSON.stringify(cmd)); + open_delete_confirmation_tab(); + }); + row.insertCell(1).appendChild(delete_button); + }); + }, +}; + +function set_info_text(l10nId) { + document.getElementById("info-text-div").hidden = false; + let field = document.getElementById("info-text-field"); + field.setAttribute("data-l10n-id", l10nId); + document.getElementById("ctap2-token-info").style.display = "none"; +} + +function show_results_banner(result, l10n, l10n_args) { + let ctap_result = document.getElementById("ctap-listen-result"); + ctap_result.setAttribute("data-l10n-id", l10n); + ctap_result.classList.add(result); + if (l10n_args) { + ctap_result.setAttribute("data-l10n-args", l10n_args); + } +} + +function hide_results_banner() { + let res_banner = document.getElementById("ctap-listen-result"); + let res_div = document.getElementById("ctap-listen-div"); + let empty_banner = document.createElement("label"); + empty_banner.id = "ctap-listen-result"; + res_div.replaceChild(empty_banner, res_banner); +} + +function operation_in_progress(name, in_progress) { + let buttons = Array.from(document.getElementsByClassName(name)); + buttons.forEach(button => { + button.disabled = in_progress; + }); +} + +function credential_management_in_progress(in_progress) { + operation_in_progress("credentials-button", in_progress); +} + +function bio_enrollment_in_progress(in_progress) { + operation_in_progress("bio-enrollment-button", in_progress); +} + +function clear_bio_enrollment_samples() { + // Remove all previous status updates + let up = document.getElementById("enrollment-update"); + while (up.firstChild) { + up.removeChild(up.lastChild); + } + document.getElementById("enrollment-name").value = ""; +} + +function fake_click_event_for_id(id) { + // Not using document.getElementById(id).click(); + // here, because we have to add additional data, so we don't + // hide the results-div here, if there is any. 'Normal' clicking + // by the user will hide it. + const evt = new CustomEvent("click", { + detail: { skip_results_clearing: true }, + }); + document.getElementById(id).dispatchEvent(evt); +} + +function reset_page() { + // Hide everything that needs a device to know if it should be displayed + document.getElementById("ctap2-token-info").style.display = "none"; + Array.from(document.getElementsByClassName("optional-category")).forEach( + div => { + div.style.display = "none"; + } + ); + + // Only display the "please connect a device" - text + set_info_text("about-webauthn-text-connect-device"); + + // Clear results and input fields + hide_results_banner(); + var divs = Array.from(document.getElementsByTagName("input")); + divs.forEach(div => { + div.value = ""; + }); + + sidebar_set_disabled(false); + + // ListCredentials + credential_management_in_progress(false); + document.getElementById("credential-list-subsection").hidden = true; + + // BioEnrollment + clear_bio_enrollment_samples(); + document.getElementById("bio-enrollment-list-subsection").hidden = true; + bio_enrollment_in_progress(false); + + AboutWebauthnManagerJS._previous_tab = ""; + AboutWebauthnManagerJS._current_tab = ""; + + // Not using `document.getElementById("info-tab-button").click();` + // here, because if we were focused on a category-button that got removed (e.g. + // when unplugging the device), we have to reset the ARIA-related attributes + // first, before we can click on the button, otherwise the a11y-tests + // will complain. So we fake the click again. + const evt = { + detail: {}, + currentTarget: document.getElementById("info-tab-button"), + }; + open_info_tab(evt); +} + +function sidebar_set_disabled(disabled) { + var cats = Array.from(document.getElementsByClassName("category")); + cats.forEach(cat => { + if (disabled) { + cat.classList.add("disabled-category"); + } else { + cat.classList.remove("disabled-category"); + } + }); +} + +function check_pin_repeat_is_correct(button) { + let pin = document.getElementById("new-pin"); + let pin_repeat = document.getElementById("new-pin-repeat"); + let has_current_pin = !document.getElementById("current-pin-div").hidden; + let current_pin = document.getElementById("current-pin"); + let can_enable_button = + pin.value != null && pin.value != "" && pin.value == pin_repeat.value; + if (has_current_pin && !current_pin.value) { + can_enable_button = false; + } + if (!can_enable_button) { + pin.classList.add("different"); + pin_repeat.classList.add("different"); + document.getElementById("set-pin-button").disabled = true; + document.getElementById("change-pin-button").disabled = true; + return false; + } + pin.classList.remove("different"); + pin_repeat.classList.remove("different"); + document.getElementById("set-pin-button").disabled = false; + document.getElementById("change-pin-button").disabled = false; + return true; +} + +function send_pin() { + close_temporary_overlay_tab(); + let pin = document.getElementById("pin-required").value; + AboutWebauthnService.pinCallback(0, pin); +} + +function set_pin() { + let pin = document.getElementById("new-pin").value; + let cmd = { SetPIN: pin }; + AboutWebauthnService.runCommand(JSON.stringify(cmd)); +} + +function change_pin() { + let curr_pin = document.getElementById("current-pin").value; + let new_pin = document.getElementById("new-pin").value; + let cmd = { ChangePIN: [curr_pin, new_pin] }; + AboutWebauthnService.runCommand(JSON.stringify(cmd)); +} + +function list_credentials() { + credential_management_in_progress(true); + let cmd = { CredentialManagement: "GetCredentials" }; + AboutWebauthnService.runCommand(JSON.stringify(cmd)); +} + +function list_bio_enrollments() { + bio_enrollment_in_progress(true); + let cmd = { BioEnrollment: "GetEnrollments" }; + AboutWebauthnService.runCommand(JSON.stringify(cmd)); +} + +function show_add_bio_enrollment_section() { + const evt = new CustomEvent("click", { + detail: { temporary_overlay: true }, + }); + open_tab(evt, "add-bio-enrollment-section"); + document.getElementById("enrollment-name").focus(); +} + +function start_bio_enrollment() { + bio_enrollment_in_progress(true); + let name = document.getElementById("enrollment-name").value; + if (!name) { + name = null; // Empty means "Not set" + } + let cmd = { BioEnrollment: { StartNewEnrollment: name } }; + AboutWebauthnService.runCommand(JSON.stringify(cmd)); +} + +function cancel_transaction() { + credential_management_in_progress(false); + bio_enrollment_in_progress(false); + AboutWebauthnService.cancel(0); +} + +function confirm_deletion() { + let context = document.getElementById("confirmation-context"); + let cmd = context.getAttribute("data-ctap-command"); + AboutWebauthnService.runCommand(cmd); +} + +function cancel_confirmation() { + credential_management_in_progress(false); + bio_enrollment_in_progress(false); + close_temporary_overlay_tab(); +} + +async function onLoad() { + document.getElementById("set-pin-button").addEventListener("click", set_pin); + document + .getElementById("change-pin-button") + .addEventListener("click", change_pin); + document + .getElementById("list-credentials-button") + .addEventListener("click", list_credentials); + document + .getElementById("list-bio-enrollments-button") + .addEventListener("click", list_bio_enrollments); + document + .getElementById("add-bio-enrollment-button") + .addEventListener("click", show_add_bio_enrollment_section); + document + .getElementById("start-enrollment-button") + .addEventListener("click", start_bio_enrollment); + document + .getElementById("new-pin") + .addEventListener("input", check_pin_repeat_is_correct); + document + .getElementById("new-pin-repeat") + .addEventListener("input", check_pin_repeat_is_correct); + document + .getElementById("current-pin") + .addEventListener("input", check_pin_repeat_is_correct); + let info_button = document.getElementById("info-tab-button"); + info_button.addEventListener("click", open_info_tab); + info_button.addEventListener("keydown", handle_keydowns); + let pin_button = document.getElementById("pin-tab-button"); + pin_button.addEventListener("click", open_pin_tab); + pin_button.addEventListener("keydown", handle_keydowns); + let credentials_button = document.getElementById("credentials-tab-button"); + credentials_button.addEventListener("click", open_credentials_tab); + credentials_button.addEventListener("keydown", handle_keydowns); + let bio_enrollments_button = document.getElementById( + "bio-enrollments-tab-button" + ); + bio_enrollments_button.addEventListener("click", open_bio_enrollments_tab); + bio_enrollments_button.addEventListener("keydown", handle_keydowns); + document + .getElementById("send-pin-button") + .addEventListener("click", send_pin); + document + .getElementById("cancel-send-pin-button") + .addEventListener("click", cancel_transaction); + document + .getElementById("cancel-enrollment-button") + .addEventListener("click", cancel_transaction); + document + .getElementById("cancel-confirmation-button") + .addEventListener("click", cancel_confirmation); + document + .getElementById("confirm-deletion-button") + .addEventListener("click", confirm_deletion); + AboutWebauthnManagerJS.init(); + try { + AboutWebauthnService.listen(); + } catch (ex) { + set_info_text("about-webauthn-text-not-available"); + AboutWebauthnManagerJS.uninit(); + } +} + +function handle_keydowns(event) { + let index; + let event_was_handled = true; + let tabs = Array.from(document.getElementsByClassName("category")); + if (tabs.length <= 0) { + return; + } + + switch (event.key) { + case "ArrowLeft": + case "ArrowUp": + if (event.currentTarget === tabs[0]) { + event.currentTarget.focus(); + } else { + index = tabs.indexOf(event.currentTarget); + tabs[index - 1].focus(); + } + break; + + case "ArrowRight": + case "ArrowDown": + if (event.currentTarget === tabs[tabs.length - 1]) { + event.currentTarget.focus(); + } else { + index = tabs.indexOf(event.currentTarget); + tabs[index + 1].focus(); + } + break; + + case "Home": + tabs[0].focus(); + break; + + case "End": + tabs[tabs.length - 1].focus(); + break; + + case "Enter": + case " ": + event.currentTarget.click(); + break; + + default: + event_was_handled = false; + break; + } + + if (event_was_handled) { + event.stopPropagation(); + event.preventDefault(); + } +} + +function open_tab(evt, tabName) { + var tabcontent, tablinks; + // Hide all others + tabcontent = Array.from(document.getElementsByClassName("tabcontent")); + tabcontent.forEach(tab => { + tab.style.display = "none"; + }); + // Display the one we selected + document.getElementById(tabName).style.display = "block"; + + // If this is a temporary overlay, like pin-required, we don't + // touch the sidebar and which button is selected. + if (!evt.detail.temporary_overlay) { + tablinks = Array.from(document.getElementsByClassName("category")); + tablinks.forEach(tablink => { + tablink.removeAttribute("selected"); + tablink.setAttribute("aria-selected", "false"); + tablink.setAttribute("tabindex", "-1"); + tablink.disabled = false; + }); + evt.currentTarget.setAttribute("selected", "true"); + evt.currentTarget.setAttribute("tabindex", "0"); + evt.currentTarget.setAttribute("aria-selected", "true"); + } + + if (!evt.detail.skip_results_clearing) { + hide_results_banner(); + } + sidebar_set_disabled(false); + AboutWebauthnManagerJS._previous_tab = AboutWebauthnManagerJS._current_tab; + AboutWebauthnManagerJS._current_tab = tabName; +} + +function open_info_tab(evt) { + open_tab(evt, "token-info-section"); +} +function open_pin_tab(evt) { + open_tab(evt, "set-change-pin-section"); +} +function open_credentials_tab(evt) { + open_tab(evt, "credential-management-section"); +} +function open_bio_enrollments_tab(evt) { + // We can only list, if there are any registered already + if ( + AboutWebauthnManagerJS._curr_data.options.bioEnroll === true || + AboutWebauthnManagerJS._curr_data.options.userVerificationMgmtPreview === + true + ) { + document.getElementById("list-bio-enrollments-button").style.display = + "inline-block"; + } else { + document.getElementById("list-bio-enrollments-button").style.display = + "none"; + } + open_tab(evt, "bio-enrollment-section"); +} +function open_reset_tab(evt) { + open_tab(evt, "reset-token-section"); +} +function open_pin_required_tab() { + // Remove any old value we might have had + document.getElementById("pin-required").value = ""; + const evt = new CustomEvent("click", { + detail: { + temporary_overlay: true, + skip_results_clearing: true, // We might be called multiple times, if PIN was invalid + }, + }); + open_tab(evt, "pin-required-section"); + document.getElementById("pin-required").focus(); + // This is a temporary overlay, so we don't want the + // user to click away from it, unless via the Cancel-button. + sidebar_set_disabled(true); +} +function close_temporary_overlay_tab() { + const evt = new CustomEvent("click", { + detail: { temporary_overlay: true }, + }); + open_tab(evt, AboutWebauthnManagerJS._previous_tab); + sidebar_set_disabled(false); +} +function open_delete_confirmation_tab() { + const evt = new CustomEvent("click", { + detail: { + temporary_overlay: true, + }, + }); + open_tab(evt, "confirm-deletion-section"); + // This is a temporary overlay, so we don't want the + // user to click away from it, unless via the Cancel-button. + sidebar_set_disabled(true); +} + +try { + AboutWebauthnService = Cc["@mozilla.org/webauthn/service;1"].getService( + Ci.nsIWebAuthnService + ); + document.addEventListener("DOMContentLoaded", onLoad); + window.addEventListener("beforeunload", event => { + AboutWebauthnManagerJS.uninit(); + if (AboutWebauthnService) { + AboutWebauthnService.cancel(0); + } + }); +} catch (ex) { + // Do nothing if we fail to create a singleton instance, + // showing the default no-module message. + console.error(ex); +} -- cgit v1.2.3