tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ipprotection-content.mjs (9542B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      4 
      5 import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
      6 import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
      7 import {
      8  LINKS,
      9  ERRORS,
     10 } from "chrome://browser/content/ipprotection/ipprotection-constants.mjs";
     11 
     12 // eslint-disable-next-line import/no-unassigned-import
     13 import "chrome://browser/content/ipprotection/ipprotection-message-bar.mjs";
     14 // eslint-disable-next-line import/no-unassigned-import
     15 import "chrome://browser/content/ipprotection/ipprotection-signedout.mjs";
     16 // eslint-disable-next-line import/no-unassigned-import
     17 import "chrome://browser/content/ipprotection/ipprotection-status-card.mjs";
     18 // eslint-disable-next-line import/no-unassigned-import
     19 import "chrome://global/content/elements/moz-toggle.mjs";
     20 
     21 /**
     22 * Custom element that implements a message bar and status card for IP protection.
     23 */
     24 export default class IPProtectionContentElement extends MozLitElement {
     25  static queries = {
     26    signedOutEl: "ipprotection-signedout",
     27    messagebarEl: "ipprotection-message-bar",
     28    statusCardEl: "ipprotection-status-card",
     29    upgradeEl: "#upgrade-vpn-content",
     30    activeSubscriptionEl: "#active-subscription-vpn-content",
     31    supportLinkEl: "#vpn-support-link",
     32  };
     33 
     34  static properties = {
     35    state: { type: Object, attribute: false },
     36    _showMessageBar: { type: Boolean, state: true },
     37    _messageDismissed: { type: Boolean, state: true },
     38  };
     39 
     40  constructor() {
     41    super();
     42 
     43    this.state = {};
     44 
     45    this.keyListener = this.#keyListener.bind(this);
     46    this.messageBarListener = this.#messageBarListener.bind(this);
     47    this.statusCardListener = this.#statusCardListener.bind(this);
     48    this._showMessageBar = false;
     49    this._messageDismissed = false;
     50  }
     51 
     52  connectedCallback() {
     53    super.connectedCallback();
     54    this.dispatchEvent(new CustomEvent("IPProtection:Init", { bubbles: true }));
     55    this.addEventListener("keydown", this.keyListener, { capture: true });
     56    this.addEventListener(
     57      "ipprotection-status-card:user-toggled-on",
     58      this.#statusCardListener
     59    );
     60    this.addEventListener(
     61      "ipprotection-status-card:user-toggled-off",
     62      this.#statusCardListener
     63    );
     64    this.addEventListener(
     65      "ipprotection-site-settings-control:click",
     66      this.#statusCardListener
     67    );
     68    this.addEventListener(
     69      "ipprotection-message-bar:user-dismissed",
     70      this.#messageBarListener
     71    );
     72  }
     73 
     74  disconnectedCallback() {
     75    super.disconnectedCallback();
     76 
     77    this.removeEventListener("keydown", this.keyListener, { capture: true });
     78    this.removeEventListener(
     79      "ipprotection-status-card:user-toggled-on",
     80      this.#statusCardListener
     81    );
     82    this.removeEventListener(
     83      "ipprotection-status-card:user-toggled-off",
     84      this.#statusCardListener
     85    );
     86    this.removeEventListener(
     87      "ipprotection-site-settings-control:click",
     88      this.#statusCardListener
     89    );
     90    this.removeEventListener(
     91      "ipprotection-message-bar:user-dismissed",
     92      this.#messageBarListener
     93    );
     94  }
     95 
     96  get canEnableConnection() {
     97    return this.state && this.state.isProtectionEnabled && !this.state.error;
     98  }
     99 
    100  get #hasErrors() {
    101    return !this.state || this.state.error !== "";
    102  }
    103 
    104  handleClickSupportLink(event) {
    105    const win = event.target.ownerGlobal;
    106 
    107    if (event.target === this.supportLinkEl) {
    108      event.preventDefault();
    109      win.openWebLinkIn(LINKS.PRODUCT_URL, "tab");
    110      this.dispatchEvent(
    111        new CustomEvent("IPProtection:Close", { bubbles: true })
    112      );
    113    }
    114  }
    115 
    116  handleUpgrade(event) {
    117    const win = event.target.ownerGlobal;
    118    win.openWebLinkIn(LINKS.PRODUCT_URL + "#pricing", "tab");
    119    // Close the panel
    120    this.dispatchEvent(
    121      new CustomEvent("IPProtection:ClickUpgrade", { bubbles: true })
    122    );
    123 
    124    Glean.ipprotection.clickUpgradeButton.record();
    125  }
    126 
    127  focus() {
    128    if (this.state.isSignedOut) {
    129      this.signedOutEl?.focus();
    130    } else {
    131      this.statusCardEl?.focus();
    132    }
    133  }
    134 
    135  #keyListener(event) {
    136    let keyCode = event.code;
    137    switch (keyCode) {
    138      case "Tab":
    139      case "ArrowUp":
    140      // Intentional fall-through
    141      case "ArrowDown": {
    142        event.stopPropagation();
    143        event.preventDefault();
    144 
    145        let isForward =
    146          (keyCode == "Tab" && !event.shiftKey) || keyCode == "ArrowDown";
    147        let direction = isForward
    148          ? Services.focus.MOVEFOCUS_FORWARD
    149          : Services.focus.MOVEFOCUS_BACKWARD;
    150        Services.focus.moveFocus(
    151          window,
    152          null,
    153          direction,
    154          Services.focus.FLAG_BYKEY
    155        );
    156        break;
    157      }
    158    }
    159  }
    160 
    161  #statusCardListener(event) {
    162    if (event.type === "ipprotection-status-card:user-toggled-on") {
    163      this.dispatchEvent(
    164        new CustomEvent("IPProtection:UserEnable", { bubbles: true })
    165      );
    166    } else if (event.type === "ipprotection-status-card:user-toggled-off") {
    167      this.dispatchEvent(
    168        new CustomEvent("IPProtection:UserDisable", { bubbles: true })
    169      );
    170    } else if (event.type === "ipprotection-site-settings-control:click") {
    171      this.dispatchEvent(
    172        new CustomEvent("IPProtection:UserShowSiteSettings", { bubbles: true })
    173      );
    174    }
    175  }
    176 
    177  #messageBarListener(event) {
    178    if (event.type === "ipprotection-message-bar:user-dismissed") {
    179      this._showMessageBar = false;
    180      this._messageDismissed = true;
    181      this.state.error = "";
    182      this.state.bandwidthWarning = false;
    183    }
    184  }
    185 
    186  updated(changedProperties) {
    187    super.updated(changedProperties);
    188 
    189    // Clear messages when there is an error.
    190    if (this.state.error) {
    191      this._messageDismissed = false;
    192    }
    193  }
    194 
    195  messageBarTemplate() {
    196    let messageId;
    197    let messageLink;
    198    let messageLinkl10nId;
    199    let messageType = "info";
    200    // If there are errors, the error message should take precedence
    201    if (this.#hasErrors) {
    202      messageId = "ipprotection-message-generic-error";
    203      messageType = ERRORS.GENERIC;
    204    } else if (this.state.bandwidthWarning) {
    205      messageId = "ipprotection-message-bandwidth-warning";
    206      messageType = "warning";
    207    } else if (this.state.onboardingMessage) {
    208      messageId = this.state.onboardingMessage;
    209      messageType = "info";
    210 
    211      switch (this.state.onboardingMessage) {
    212        case "ipprotection-message-continuous-onboarding-intro":
    213          break;
    214        case "ipprotection-message-continuous-onboarding-autostart":
    215          messageLink = "about:settings#privacy";
    216          messageLinkl10nId = "setting-link";
    217          break;
    218        case "ipprotection-message-continuous-onboarding-site-settings":
    219          messageLink = "about:settings#privacy";
    220          messageLinkl10nId = "setting-link";
    221          break;
    222      }
    223    }
    224 
    225    return html`
    226      <ipprotection-message-bar
    227        class="vpn-top-content"
    228        type=${messageType}
    229        .messageId=${ifDefined(messageId)}
    230        .messageLink=${ifDefined(messageLink)}
    231        .messageLinkl10nId=${ifDefined(messageLinkl10nId)}
    232      ></ipprotection-message-bar>
    233    `;
    234  }
    235 
    236  statusCardTemplate() {
    237    // TODO: Pass site information to status-card to conditionally
    238    // render the site settings control. (Bug 1997412)
    239    return html`
    240      <ipprotection-status-card
    241        .protectionEnabled=${this.canEnableConnection}
    242        .location=${this.state.location}
    243        .siteData=${ifDefined(this.state.siteData)}
    244      ></ipprotection-status-card>
    245    `;
    246  }
    247 
    248  pausedTemplate() {
    249    return html`
    250      <div id="upgrade-vpn-content" class="vpn-bottom-content">
    251        <h2
    252          id="upgrade-vpn-title"
    253          data-l10n-id="upgrade-vpn-title"
    254          class="vpn-subtitle"
    255        ></h2>
    256        <p
    257          id="upgrade-vpn-paragraph"
    258          data-l10n-id="upgrade-vpn-paragraph"
    259          @click=${this.handleClickSupportLink}
    260        >
    261          <a
    262            id="vpn-support-link"
    263            href=${LINKS.PRODUCT_URL}
    264            data-l10n-name="learn-more-vpn"
    265          ></a>
    266        </p>
    267        <moz-button
    268          id="upgrade-vpn-button"
    269          class="vpn-button"
    270          @click=${this.handleUpgrade}
    271          type="secondary"
    272          data-l10n-id="upgrade-vpn-button"
    273        ></moz-button>
    274      </div>
    275    `;
    276  }
    277 
    278  mainContentTemplate() {
    279    // TODO: Update support-page with new SUMO link for Mozilla VPN - Bug 1975474
    280    if (this.state.isSignedOut) {
    281      return html` <ipprotection-signedout></ipprotection-signedout> `;
    282    }
    283 
    284    if (this.state.paused) {
    285      return html` ${this.pausedTemplate()} `;
    286    }
    287 
    288    return html` ${this.statusCardTemplate()} `;
    289  }
    290 
    291  render() {
    292    if (
    293      (this.#hasErrors ||
    294        this.state.onboardingMessage ||
    295        this.state.bandwidthWarning) &&
    296      !this._messageDismissed
    297    ) {
    298      this._showMessageBar = true;
    299    }
    300 
    301    const messageBar = this._showMessageBar ? this.messageBarTemplate() : null;
    302 
    303    let content = html`${messageBar}${this.mainContentTemplate()}`;
    304 
    305    // TODO: Conditionally render post-upgrade subview within #ipprotection-content-wrapper - Bug 1973813
    306    return html`
    307      <link
    308        rel="stylesheet"
    309        href="chrome://browser/content/ipprotection/ipprotection-content.css"
    310      />
    311      <div id="ipprotection-content-wrapper">${content}</div>
    312    `;
    313  }
    314 }
    315 
    316 customElements.define("ipprotection-content", IPProtectionContentElement);