/* 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 https://mozilla.org/MPL/2.0/. */ import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; import { html, classMap, styleMap, } from "chrome://global/content/vendor/lit.all.mjs"; import { connectionTimer, defaultTimeValue, } from "chrome://browser/content/ipprotection/ipprotection-timer.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://global/content/elements/moz-toggle.mjs"; // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/ipprotection/ipprotection-site-settings-control.mjs"; /** * Custom element that implements a status card for IP protection. */ export default class IPProtectionStatusCard extends MozLitElement { TOGGLE_ON_EVENT = "ipprotection-status-card:user-toggled-on"; TOGGLE_OFF_EVENT = "ipprotection-status-card:user-toggled-off"; static queries = { statusGroupEl: "#status-card", connectionToggleEl: "#connection-toggle", locationEl: "#location-wrapper", siteSettingsEl: "ipprotection-site-settings-control", }; static shadowRootOptions = { ...MozLitElement.shadowRootOptions, delegatesFocus: true, }; static properties = { protectionEnabled: { type: Boolean }, canShowTime: { type: Boolean }, enabledSince: { type: Object }, location: { type: Object }, siteData: { type: Object }, // Track toggle state separately so that we can tell when the toggle // is enabled because of the existing protection state or because of user action. _toggleEnabled: { type: Boolean, state: true }, }; constructor() { super(); this.keyListener = this.#keyListener.bind(this); } connectedCallback() { super.connectedCallback(); this.dispatchEvent(new CustomEvent("IPProtection:Init", { bubbles: true })); this.addEventListener("keydown", this.keyListener, { capture: true }); } disconnectedCallback() { super.disconnectedCallback(); this.removeEventListener("keydown", this.keyListener, { capture: true }); } handleToggleConnect(event) { let isEnabled = event.target.pressed; if (isEnabled) { this.dispatchEvent( new CustomEvent(this.TOGGLE_ON_EVENT, { bubbles: true, composed: true, }) ); } else { this.dispatchEvent( new CustomEvent(this.TOGGLE_OFF_EVENT, { bubbles: true, composed: true, }) ); } this._toggleEnabled = isEnabled; } focus() { this.connectionToggleEl?.focus(); } #keyListener(event) { let keyCode = event.code; switch (keyCode) { case "ArrowUp": // Intentional fall-through case "ArrowDown": { event.stopPropagation(); event.preventDefault(); let direction = keyCode == "ArrowDown" ? Services.focus.MOVEFOCUS_FORWARD : Services.focus.MOVEFOCUS_BACKWARD; Services.focus.moveFocus( window, null, direction, Services.focus.FLAG_BYKEY ); break; } } } updated(changedProperties) { super.updated(changedProperties); // If the toggle state isn't set, do so now and let it // match the protection state. if (!changedProperties.has("_toggleEnabled")) { this._toggleEnabled = this.protectionEnabled; } if (!this.protectionEnabled && this._toggleEnabled) { // After pressing the toggle, if somehow protection was turned off // (eg. error thrown), unset the toggle. this._toggleEnabled = false; } } cardContentTemplate() { const statusCardL10nId = this.protectionEnabled ? "ipprotection-connection-status-on" : "ipprotection-connection-status-off"; const toggleL10nId = this.protectionEnabled ? "ipprotection-toggle-active" : "ipprotection-toggle-inactive"; const siteSettingsTemplate = this.protectionEnabled ? this.siteSettingsTemplate() : null; return html` ${siteSettingsTemplate} `; } siteSettingsTemplate() { // TODO: Once we're able to detect the current site and its exception status, show // ipprotection-site-settings-control (Bug 1997412). if (!this.siteData?.siteName) { return null; } return html` `; } cardDescriptionTemplate() { // The template consists of location name and connection time. let time = this.canShowTime ? connectionTimer(this.enabledSince) : defaultTimeValue; // To work around mox-box-item description elements being hard to reach because of the shadowDOM, // let's use a lit stylemap to apply style changes directly. let labelStyles = styleMap({ display: "flex", gap: "var(--space-small)", }); let imgStyles = styleMap({ "-moz-context-properties": "fill", fill: "currentColor", }); return this.location ? html`
${this.location.name}
` : null; } render() { let content = this.cardContentTemplate(); return html`${content}`; } } customElements.define("ipprotection-status-card", IPProtectionStatusCard);