/* 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); }