ma1 pushed to branch mullvad-browser-140.0a1-15.0-1 at The Tor Project / Applications / Mullvad Browser

Commits:

17 changed files:

Changes:

  • browser/components/BrowserGlue.sys.mjs
    ... ... @@ -63,6 +63,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
    63 63
       ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
    
    64 64
       SearchSERPTelemetry:
    
    65 65
         "moz-src:///browser/components/search/SearchSERPTelemetry.sys.mjs",
    
    66
    +  SecurityLevelRestartNotification:
    
    67
    +    "resource:///modules/SecurityLevelRestartNotification.sys.mjs",
    
    66 68
       SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
    
    67 69
       SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
    
    68 70
       ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
    
    ... ... @@ -997,6 +999,8 @@ BrowserGlue.prototype = {
    997 999
           lazy.WeaveService.init();
    
    998 1000
         }
    
    999 1001
     
    
    1002
    +    lazy.SecurityLevelRestartNotification.ready();
    
    1003
    +
    
    1000 1004
         ClipboardPrivacy.startup();
    
    1001 1005
     
    
    1002 1006
         lazy.BrowserUtils.callModulesFromCategory(
    

  • browser/components/securitylevel/SecurityLevelUIUtils.sys.mjs
    1
    +/**
    
    2
    + * Common methods for the desktop security level components.
    
    3
    + */
    
    4
    +export const SecurityLevelUIUtils = {
    
    5
    +  /**
    
    6
    +   * Create an element that gives a description of the security level. To be
    
    7
    +   * used in the settings.
    
    8
    +   *
    
    9
    +   * @param {string} level - The security level to describe.
    
    10
    +   * @param {Document} doc - The document where the element will be inserted.
    
    11
    +   *
    
    12
    +   * @returns {Element} - The newly created element.
    
    13
    +   */
    
    14
    +  createDescriptionElement(level, doc) {
    
    15
    +    const el = doc.createElement("div");
    
    16
    +    el.classList.add("security-level-description");
    
    17
    +
    
    18
    +    let l10nIdSummary;
    
    19
    +    let bullets;
    
    20
    +    switch (level) {
    
    21
    +      case "standard":
    
    22
    +        l10nIdSummary = "security-level-summary-standard";
    
    23
    +        break;
    
    24
    +      case "safer":
    
    25
    +        l10nIdSummary = "security-level-summary-safer";
    
    26
    +        bullets = [
    
    27
    +          "security-level-preferences-bullet-https-only-javascript",
    
    28
    +          "security-level-preferences-bullet-limit-font-and-symbols",
    
    29
    +          "security-level-preferences-bullet-limit-media",
    
    30
    +        ];
    
    31
    +        break;
    
    32
    +      case "safest":
    
    33
    +        l10nIdSummary = "security-level-summary-safest";
    
    34
    +        bullets = [
    
    35
    +          "security-level-preferences-bullet-disabled-javascript",
    
    36
    +          "security-level-preferences-bullet-limit-font-and-symbols-and-images",
    
    37
    +          "security-level-preferences-bullet-limit-media",
    
    38
    +        ];
    
    39
    +        break;
    
    40
    +      case "custom":
    
    41
    +        l10nIdSummary = "security-level-summary-custom";
    
    42
    +        break;
    
    43
    +      default:
    
    44
    +        throw Error(`Unhandled level: ${level}`);
    
    45
    +    }
    
    46
    +
    
    47
    +    const summaryEl = doc.createElement("div");
    
    48
    +    summaryEl.classList.add("security-level-summary");
    
    49
    +    doc.l10n.setAttributes(summaryEl, l10nIdSummary);
    
    50
    +
    
    51
    +    el.append(summaryEl);
    
    52
    +
    
    53
    +    if (!bullets) {
    
    54
    +      return el;
    
    55
    +    }
    
    56
    +
    
    57
    +    const listEl = doc.createElement("ul");
    
    58
    +    listEl.classList.add("security-level-description-extra");
    
    59
    +    // Add a mozilla styling class as well:
    
    60
    +    listEl.classList.add("privacy-extra-information");
    
    61
    +    for (const l10nId of bullets) {
    
    62
    +      const bulletEl = doc.createElement("li");
    
    63
    +      bulletEl.classList.add("security-level-description-bullet");
    
    64
    +
    
    65
    +      doc.l10n.setAttributes(bulletEl, l10nId);
    
    66
    +
    
    67
    +      listEl.append(bulletEl);
    
    68
    +    }
    
    69
    +
    
    70
    +    el.append(listEl);
    
    71
    +    return el;
    
    72
    +  },
    
    73
    +};

  • browser/components/securitylevel/content/securityLevel.js
    1 1
     "use strict";
    
    2 2
     
    
    3
    -/* global AppConstants, Services, openPreferences, XPCOMUtils */
    
    3
    +/* global AppConstants, Services, openPreferences, XPCOMUtils, gSubDialog */
    
    4 4
     
    
    5 5
     ChromeUtils.defineESModuleGetters(this, {
    
    6 6
       SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
    
    7
    +  SecurityLevelUIUtils: "resource:///modules/SecurityLevelUIUtils.sys.mjs",
    
    7 8
     });
    
    8 9
     
    
    9 10
     /*
    
    ... ... @@ -35,12 +36,8 @@ var SecurityLevelButton = {
    35 36
       _anchorButton: null,
    
    36 37
     
    
    37 38
       _configUIFromPrefs() {
    
    38
    -    const level = SecurityLevelPrefs.securityLevel;
    
    39
    -    if (!level) {
    
    40
    -      return;
    
    41
    -    }
    
    42
    -    const custom = SecurityLevelPrefs.securityCustom;
    
    43
    -    this._button.setAttribute("level", custom ? `${level}_custom` : level);
    
    39
    +    const level = SecurityLevelPrefs.securityLevelSummary;
    
    40
    +    this._button.setAttribute("level", level);
    
    44 41
     
    
    45 42
         let l10nIdLevel;
    
    46 43
         switch (level) {
    
    ... ... @@ -53,15 +50,12 @@ var SecurityLevelButton = {
    53 50
           case "safest":
    
    54 51
             l10nIdLevel = "security-level-toolbar-button-safest";
    
    55 52
             break;
    
    53
    +      case "custom":
    
    54
    +        l10nIdLevel = "security-level-toolbar-button-custom";
    
    55
    +        break;
    
    56 56
           default:
    
    57 57
             throw Error(`Unhandled level: ${level}`);
    
    58 58
         }
    
    59
    -    if (custom) {
    
    60
    -      // Don't distinguish between the different levels when in the custom
    
    61
    -      // state. We just want to emphasise that it is custom rather than any
    
    62
    -      // specific level.
    
    63
    -      l10nIdLevel = "security-level-toolbar-button-custom";
    
    64
    -    }
    
    65 59
         document.l10n.setAttributes(this._button, l10nIdLevel);
    
    66 60
       },
    
    67 61
     
    
    ... ... @@ -164,12 +158,7 @@ var SecurityLevelPanel = {
    164 158
           panel: document.getElementById("securityLevel-panel"),
    
    165 159
           background: document.getElementById("securityLevel-background"),
    
    166 160
           levelName: document.getElementById("securityLevel-level"),
    
    167
    -      customName: document.getElementById("securityLevel-custom"),
    
    168 161
           summary: document.getElementById("securityLevel-summary"),
    
    169
    -      restoreDefaultsButton: document.getElementById(
    
    170
    -        "securityLevel-restoreDefaults"
    
    171
    -      ),
    
    172
    -      settingsButton: document.getElementById("securityLevel-settings"),
    
    173 162
         };
    
    174 163
     
    
    175 164
         const learnMoreEl = document.getElementById("securityLevel-learnMore");
    
    ... ... @@ -177,12 +166,11 @@ var SecurityLevelPanel = {
    177 166
           this.hide();
    
    178 167
         });
    
    179 168
     
    
    180
    -    this._elements.restoreDefaultsButton.addEventListener("command", () => {
    
    181
    -      this.restoreDefaults();
    
    182
    -    });
    
    183
    -    this._elements.settingsButton.addEventListener("command", () => {
    
    184
    -      this.openSecuritySettings();
    
    185
    -    });
    
    169
    +    document
    
    170
    +      .getElementById("securityLevel-settings")
    
    171
    +      .addEventListener("command", () => {
    
    172
    +        this.openSecuritySettings();
    
    173
    +      });
    
    186 174
     
    
    187 175
         this._elements.panel.addEventListener("popupshown", () => {
    
    188 176
           // Bring focus into the panel by focusing the default button.
    
    ... ... @@ -199,19 +187,7 @@ var SecurityLevelPanel = {
    199 187
         }
    
    200 188
     
    
    201 189
         // get security prefs
    
    202
    -    const level = SecurityLevelPrefs.securityLevel;
    
    203
    -    const custom = SecurityLevelPrefs.securityCustom;
    
    204
    -
    
    205
    -    // only visible when user is using custom settings
    
    206
    -    this._elements.customName.hidden = !custom;
    
    207
    -    this._elements.restoreDefaultsButton.hidden = !custom;
    
    208
    -    if (custom) {
    
    209
    -      this._elements.settingsButton.removeAttribute("default");
    
    210
    -      this._elements.restoreDefaultsButton.setAttribute("default", "true");
    
    211
    -    } else {
    
    212
    -      this._elements.settingsButton.setAttribute("default", "true");
    
    213
    -      this._elements.restoreDefaultsButton.removeAttribute("default");
    
    214
    -    }
    
    190
    +    const level = SecurityLevelPrefs.securityLevelSummary;
    
    215 191
     
    
    216 192
         // Descriptions change based on security level
    
    217 193
         this._elements.background.setAttribute("level", level);
    
    ... ... @@ -230,12 +206,13 @@ var SecurityLevelPanel = {
    230 206
             l10nIdLevel = "security-level-panel-level-safest";
    
    231 207
             l10nIdSummary = "security-level-summary-safest";
    
    232 208
             break;
    
    209
    +      case "custom":
    
    210
    +        l10nIdLevel = "security-level-panel-level-custom";
    
    211
    +        l10nIdSummary = "security-level-summary-custom";
    
    212
    +        break;
    
    233 213
           default:
    
    234 214
             throw Error(`Unhandled level: ${level}`);
    
    235 215
         }
    
    236
    -    if (custom) {
    
    237
    -      l10nIdSummary = "security-level-summary-custom";
    
    238
    -    }
    
    239 216
     
    
    240 217
         document.l10n.setAttributes(this._elements.levelName, l10nIdLevel);
    
    241 218
         document.l10n.setAttributes(this._elements.summary, l10nIdSummary);
    
    ... ... @@ -269,13 +246,6 @@ var SecurityLevelPanel = {
    269 246
         this._elements.panel.hidePopup();
    
    270 247
       },
    
    271 248
     
    
    272
    -  restoreDefaults() {
    
    273
    -    SecurityLevelPrefs.securityCustom = false;
    
    274
    -    // Move focus to the settings button since restore defaults button will
    
    275
    -    // become hidden.
    
    276
    -    this._elements.settingsButton.focus();
    
    277
    -  },
    
    278
    -
    
    279 249
       openSecuritySettings() {
    
    280 250
         openPreferences("privacy-securitylevel");
    
    281 251
         this.hide();
    
    ... ... @@ -301,59 +271,64 @@ var SecurityLevelPanel = {
    301 271
     
    
    302 272
     var SecurityLevelPreferences = {
    
    303 273
       _securityPrefsBranch: null,
    
    274
    +
    
    304 275
       /**
    
    305
    -   * The notification box shown when the user has a custom security setting.
    
    306
    -   *
    
    307
    -   * @type {Element}
    
    308
    -   */
    
    309
    -  _customNotification: null,
    
    310
    -  /**
    
    311
    -   * The radiogroup for this preference.
    
    312
    -   *
    
    313
    -   * @type {Element}
    
    314
    -   */
    
    315
    -  _radiogroup: null,
    
    316
    -  /**
    
    317
    -   * A list of radio options and their containers.
    
    276
    +   * The element that shows the current security level.
    
    318 277
        *
    
    319
    -   * @type {Array<object>}
    
    278
    +   * @type {?Element}
    
    320 279
        */
    
    321
    -  _radioOptions: null,
    
    280
    +  _currentEl: null,
    
    322 281
     
    
    323 282
       _populateXUL() {
    
    324
    -    this._customNotification = document.getElementById(
    
    325
    -      "securityLevel-customNotification"
    
    283
    +    this._currentEl = document.getElementById("security-level-current");
    
    284
    +    const changeButton = document.getElementById("security-level-change");
    
    285
    +    const badgeEl = this._currentEl.querySelector(
    
    286
    +      ".security-level-current-badge"
    
    326 287
         );
    
    327
    -    document
    
    328
    -      .getElementById("securityLevel-restoreDefaults")
    
    329
    -      .addEventListener("command", () => {
    
    330
    -        SecurityLevelPrefs.securityCustom = false;
    
    331
    -      });
    
    332
    -
    
    333
    -    this._radiogroup = document.getElementById("securityLevel-radiogroup");
    
    334 288
     
    
    335
    -    this._radioOptions = Array.from(
    
    336
    -      this._radiogroup.querySelectorAll(".securityLevel-radio-option"),
    
    337
    -      container => {
    
    338
    -        return { container, radio: container.querySelector("radio") };
    
    339
    -      }
    
    340
    -    );
    
    289
    +    for (const { level, nameId } of [
    
    290
    +      { level: "standard", nameId: "security-level-panel-level-standard" },
    
    291
    +      { level: "safer", nameId: "security-level-panel-level-safer" },
    
    292
    +      { level: "safest", nameId: "security-level-panel-level-safest" },
    
    293
    +      { level: "custom", nameId: "security-level-panel-level-custom" },
    
    294
    +    ]) {
    
    295
    +      // Classes that control visibility:
    
    296
    +      // security-level-current-standard
    
    297
    +      // security-level-current-safer
    
    298
    +      // security-level-current-safest
    
    299
    +      // security-level-current-custom
    
    300
    +      const visibilityClass = `security-level-current-${level}`;
    
    301
    +      const nameEl = document.createElement("div");
    
    302
    +      nameEl.classList.add("security-level-name", visibilityClass);
    
    303
    +      document.l10n.setAttributes(nameEl, nameId);
    
    304
    +
    
    305
    +      const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
    
    306
    +        level,
    
    307
    +        document
    
    308
    +      );
    
    309
    +      descriptionEl.classList.add(visibilityClass);
    
    310
    +
    
    311
    +      this._currentEl.insertBefore(nameEl, badgeEl);
    
    312
    +      this._currentEl.insertBefore(descriptionEl, changeButton);
    
    313
    +    }
    
    341 314
     
    
    342
    -    this._radiogroup.addEventListener("select", () => {
    
    343
    -      SecurityLevelPrefs.securityLevel = this._radiogroup.value;
    
    315
    +    changeButton.addEventListener("click", () => {
    
    316
    +      this._openDialog();
    
    344 317
         });
    
    345 318
       },
    
    346 319
     
    
    320
    +  _openDialog() {
    
    321
    +    gSubDialog.open(
    
    322
    +      "chrome://browser/content/securitylevel/securityLevelDialog.xhtml",
    
    323
    +      { features: "resizable=yes" }
    
    324
    +    );
    
    325
    +  },
    
    326
    +
    
    347 327
       _configUIFromPrefs() {
    
    348
    -    this._radiogroup.value = SecurityLevelPrefs.securityLevel;
    
    349
    -    const isCustom = SecurityLevelPrefs.securityCustom;
    
    350
    -    this._radiogroup.disabled = isCustom;
    
    351
    -    this._customNotification.hidden = !isCustom;
    
    352
    -    // Have the container's selection CSS class match the selection state of the
    
    353
    -    // radio elements.
    
    354
    -    for (const { container, radio } of this._radioOptions) {
    
    355
    -      container.classList.toggle("selected", radio.selected);
    
    356
    -    }
    
    328
    +    // Set a data-current-level attribute for showing the current security
    
    329
    +    // level, and hiding the rest.
    
    330
    +    this._currentEl.dataset.currentLevel =
    
    331
    +      SecurityLevelPrefs.securityLevelSummary;
    
    357 332
       },
    
    358 333
     
    
    359 334
       init() {
    

  • browser/components/securitylevel/content/securityLevelButton.css
    ... ... @@ -7,12 +7,6 @@ toolbarbutton#security-level-button[level="safer"] {
    7 7
     toolbarbutton#security-level-button[level="safest"] {
    
    8 8
       list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest");
    
    9 9
     }
    
    10
    -toolbarbutton#security-level-button[level="standard_custom"] {
    
    10
    +toolbarbutton#security-level-button[level="custom"] {
    
    11 11
       list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard_custom");
    
    12 12
     }
    13
    -toolbarbutton#security-level-button[level="safer_custom"] {
    
    14
    -  list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer_custom");
    
    15
    -}
    
    16
    -toolbarbutton#security-level-button[level="safest_custom"] {
    
    17
    -  list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest_custom");
    
    18
    -}

  • browser/components/securitylevel/content/securityLevelDialog.js
    1
    +"use strict";
    
    2
    +
    
    3
    +const { SecurityLevelPrefs } = ChromeUtils.importESModule(
    
    4
    +  "resource://gre/modules/SecurityLevel.sys.mjs"
    
    5
    +);
    
    6
    +const { SecurityLevelUIUtils } = ChromeUtils.importESModule(
    
    7
    +  "resource:///modules/SecurityLevelUIUtils.sys.mjs"
    
    8
    +);
    
    9
    +
    
    10
    +const gSecurityLevelDialog = {
    
    11
    +  /**
    
    12
    +   * The security level when this dialog was opened.
    
    13
    +   *
    
    14
    +   * @type {string}
    
    15
    +   */
    
    16
    +  _prevLevel: SecurityLevelPrefs.securityLevelSummary,
    
    17
    +  /**
    
    18
    +   * The security level currently selected.
    
    19
    +   *
    
    20
    +   * @type {string}
    
    21
    +   */
    
    22
    +  _selectedLevel: "",
    
    23
    +  /**
    
    24
    +   * The radiogroup for this preference.
    
    25
    +   *
    
    26
    +   * @type {?Element}
    
    27
    +   */
    
    28
    +  _radiogroup: null,
    
    29
    +  /**
    
    30
    +   * A list of radio options and their containers.
    
    31
    +   *
    
    32
    +   * @type {?Array<{ container: Element, radio: Element }>}
    
    33
    +   */
    
    34
    +  _radioOptions: null,
    
    35
    +
    
    36
    +  /**
    
    37
    +   * Initialise the dialog.
    
    38
    +   */
    
    39
    +  async init() {
    
    40
    +    const dialog = document.getElementById("security-level-dialog");
    
    41
    +    dialog.addEventListener("dialogaccept", event => {
    
    42
    +      if (this._acceptButton.disabled) {
    
    43
    +        event.preventDefault();
    
    44
    +        return;
    
    45
    +      }
    
    46
    +      this._commitChange();
    
    47
    +    });
    
    48
    +
    
    49
    +    this._acceptButton = dialog.getButton("accept");
    
    50
    +
    
    51
    +    document.l10n.setAttributes(
    
    52
    +      this._acceptButton,
    
    53
    +      "security-level-dialog-save-restart"
    
    54
    +    );
    
    55
    +
    
    56
    +    this._radiogroup = document.getElementById("security-level-radiogroup");
    
    57
    +
    
    58
    +    this._radioOptions = Array.from(
    
    59
    +      this._radiogroup.querySelectorAll(".security-level-radio-container"),
    
    60
    +      container => {
    
    61
    +        return {
    
    62
    +          container,
    
    63
    +          radio: container.querySelector(".security-level-radio"),
    
    64
    +        };
    
    65
    +      }
    
    66
    +    );
    
    67
    +
    
    68
    +    for (const { container, radio } of this._radioOptions) {
    
    69
    +      const level = radio.value;
    
    70
    +      radio.id = `security-level-radio-${level}`;
    
    71
    +      const currentEl = container.querySelector(
    
    72
    +        ".security-level-current-badge"
    
    73
    +      );
    
    74
    +      currentEl.id = `security-level-current-badge-${level}`;
    
    75
    +      const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
    
    76
    +        level,
    
    77
    +        document
    
    78
    +      );
    
    79
    +      descriptionEl.classList.add("indent");
    
    80
    +      descriptionEl.id = `security-level-description-${level}`;
    
    81
    +
    
    82
    +      // Wait for the full translation of the element before adding it to the
    
    83
    +      // DOM. In particular, we want to make sure the elements have text before
    
    84
    +      // we measure the maxHeight below.
    
    85
    +      await document.l10n.translateFragment(descriptionEl);
    
    86
    +      document.l10n.pauseObserving();
    
    87
    +      container.append(descriptionEl);
    
    88
    +      document.l10n.resumeObserving();
    
    89
    +
    
    90
    +      if (level === this._prevLevel) {
    
    91
    +        currentEl.hidden = false;
    
    92
    +        // When the currentEl is visible, include it in the accessible name for
    
    93
    +        // the radio option.
    
    94
    +        // NOTE: The currentEl has an accessible name which includes punctuation
    
    95
    +        // to help separate it's content from the security level name.
    
    96
    +        // E.g. "Standard (Current level)".
    
    97
    +        radio.setAttribute("aria-labelledby", `${radio.id} ${currentEl.id}`);
    
    98
    +      } else {
    
    99
    +        currentEl.hidden = true;
    
    100
    +      }
    
    101
    +      // We point the accessible description to the wrapping
    
    102
    +      // .security-level-description element, rather than its children
    
    103
    +      // that define the actual text content. This means that when the
    
    104
    +      // privacy-extra-information is shown or hidden, its text content is
    
    105
    +      // included or excluded from the accessible description, respectively.
    
    106
    +      radio.setAttribute("aria-describedby", descriptionEl.id);
    
    107
    +    }
    
    108
    +
    
    109
    +    // We want to reserve the maximum height of the radiogroup so that the
    
    110
    +    // dialog has enough height when the user switches options. So we cycle
    
    111
    +    // through the options and measure the height when they are selected to set
    
    112
    +    // a minimum height that fits all of them.
    
    113
    +    // NOTE: At the time of implementation, at this point the dialog may not
    
    114
    +    // yet have the "subdialog" attribute, which means it is missing the
    
    115
    +    // common.css stylesheet from its shadow root, which effects the size of the
    
    116
    +    // .radio-check element and the font. Therefore, we have duplicated the
    
    117
    +    // import of common.css in SecurityLevelDialog.xhtml to ensure it is applied
    
    118
    +    // at this earlier stage.
    
    119
    +    let maxHeight = 0;
    
    120
    +    for (const { container } of this._radioOptions) {
    
    121
    +      container.classList.add("selected");
    
    122
    +      maxHeight = Math.max(
    
    123
    +        maxHeight,
    
    124
    +        this._radiogroup.getBoundingClientRect().height
    
    125
    +      );
    
    126
    +      container.classList.remove("selected");
    
    127
    +    }
    
    128
    +    this._radiogroup.style.minHeight = `${maxHeight}px`;
    
    129
    +
    
    130
    +    if (this._prevLevel !== "custom") {
    
    131
    +      this._selectedLevel = this._prevLevel;
    
    132
    +      this._radiogroup.value = this._prevLevel;
    
    133
    +    } else {
    
    134
    +      this._radiogroup.selectedItem = null;
    
    135
    +    }
    
    136
    +
    
    137
    +    this._radiogroup.addEventListener("select", () => {
    
    138
    +      this._selectedLevel = this._radiogroup.value;
    
    139
    +      this._updateSelected();
    
    140
    +    });
    
    141
    +
    
    142
    +    this._updateSelected();
    
    143
    +  },
    
    144
    +
    
    145
    +  /**
    
    146
    +   * Update the UI in response to a change in selection.
    
    147
    +   */
    
    148
    +  _updateSelected() {
    
    149
    +    this._acceptButton.disabled =
    
    150
    +      !this._selectedLevel || this._selectedLevel === this._prevLevel;
    
    151
    +    // Have the container's `selected` CSS class match the selection state of
    
    152
    +    // the radio elements.
    
    153
    +    for (const { container, radio } of this._radioOptions) {
    
    154
    +      container.classList.toggle("selected", radio.selected);
    
    155
    +    }
    
    156
    +  },
    
    157
    +
    
    158
    +  /**
    
    159
    +   * Commit the change in security level and restart the browser.
    
    160
    +   */
    
    161
    +  _commitChange() {
    
    162
    +    SecurityLevelPrefs.setSecurityLevelBeforeRestart(this._selectedLevel);
    
    163
    +    Services.startup.quit(
    
    164
    +      Services.startup.eAttemptQuit | Services.startup.eRestart
    
    165
    +    );
    
    166
    +  },
    
    167
    +};
    
    168
    +
    
    169
    +// Initial focus is not visible, even if opened with a keyboard. We avoid the
    
    170
    +// default handler and manage the focus ourselves, which will paint the focus
    
    171
    +// ring by default.
    
    172
    +// NOTE: A side effect is that the focus ring will show even if the user opened
    
    173
    +// with a mouse event.
    
    174
    +// TODO: Remove this once bugzilla bug 1708261 is resolved.
    
    175
    +document.subDialogSetDefaultFocus = () => {
    
    176
    +  document.getElementById("security-level-radiogroup").focus();
    
    177
    +};
    
    178
    +
    
    179
    +// Delay showing and sizing the subdialog until it is fully initialised.
    
    180
    +document.mozSubdialogReady = new Promise(resolve => {
    
    181
    +  window.addEventListener(
    
    182
    +    "DOMContentLoaded",
    
    183
    +    () => {
    
    184
    +      gSecurityLevelDialog.init().finally(resolve);
    
    185
    +    },
    
    186
    +    { once: true }
    
    187
    +  );
    
    188
    +});

  • browser/components/securitylevel/content/securityLevelDialog.xhtml
    1
    +<?xml version="1.0"?>
    
    2
    +
    
    3
    +<window
    
    4
    +  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    
    5
    +  xmlns:html="http://www.w3.org/1999/xhtml"
    
    6
    +  data-l10n-id="security-level-dialog-window"
    
    7
    +>
    
    8
    +  <dialog id="security-level-dialog" buttons="accept,cancel">
    
    9
    +    <linkset>
    
    10
    +      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
    
    11
    +      <!-- NOTE: We include common.css explicitly, rather than relying on
    
    12
    +         - the dialog's shadowroot importing it, which is late loaded in
    
    13
    +         - response to the dialog's "subdialog" attribute, which is set
    
    14
    +         - in response to DOMFrameContentLoaded.
    
    15
    +         - In particular, we need the .radio-check rule and font rules from
    
    16
    +         - common-shared.css to be in place when gSecurityLevelDialog.init is
    
    17
    +         - called, which will help ensure that the radio element has the correct
    
    18
    +         - size when we measure its bounding box. -->
    
    19
    +      <html:link
    
    20
    +        rel="stylesheet"
    
    21
    +        href="chrome://global/skin/in-content/common.css"
    
    22
    +      />
    
    23
    +      <html:link
    
    24
    +        rel="stylesheet"
    
    25
    +        href="chrome://browser/skin/preferences/preferences.css"
    
    26
    +      />
    
    27
    +      <html:link
    
    28
    +        rel="stylesheet"
    
    29
    +        href="chrome://browser/skin/preferences/privacy.css"
    
    30
    +      />
    
    31
    +      <html:link
    
    32
    +        rel="stylesheet"
    
    33
    +        href="chrome://browser/content/securitylevel/securityLevelPreferences.css"
    
    34
    +      />
    
    35
    +
    
    36
    +      <html:link rel="localization" href="branding/brand.ftl" />
    
    37
    +      <html:link rel="localization" href="toolkit/global/base-browser.ftl" />
    
    38
    +    </linkset>
    
    39
    +
    
    40
    +    <script src="chrome://browser/content/securitylevel/securityLevelDialog.js" />
    
    41
    +
    
    42
    +    <description data-l10n-id="security-level-dialog-restart-description" />
    
    43
    +
    
    44
    +    <radiogroup id="security-level-radiogroup" class="highlighting-group">
    
    45
    +      <html:div
    
    46
    +        class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
    
    47
    +      >
    
    48
    +        <radio
    
    49
    +          class="security-level-radio security-level-name"
    
    50
    +          value="standard"
    
    51
    +          data-l10n-id="security-level-preferences-level-standard"
    
    52
    +        />
    
    53
    +        <html:div
    
    54
    +          class="security-level-current-badge"
    
    55
    +          data-l10n-id="security-level-preferences-current-badge"
    
    56
    +        ></html:div>
    
    57
    +      </html:div>
    
    58
    +      <html:div
    
    59
    +        class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
    
    60
    +      >
    
    61
    +        <radio
    
    62
    +          class="security-level-radio security-level-name"
    
    63
    +          value="safer"
    
    64
    +          data-l10n-id="security-level-preferences-level-safer"
    
    65
    +        />
    
    66
    +        <html:div
    
    67
    +          class="security-level-current-badge"
    
    68
    +          data-l10n-id="security-level-preferences-current-badge"
    
    69
    +        ></html:div>
    
    70
    +      </html:div>
    
    71
    +      <html:div
    
    72
    +        class="security-level-radio-container security-level-grid privacy-detailedoption info-box-container"
    
    73
    +      >
    
    74
    +        <radio
    
    75
    +          class="security-level-radio security-level-name"
    
    76
    +          value="safest"
    
    77
    +          data-l10n-id="security-level-preferences-level-safest"
    
    78
    +        />
    
    79
    +        <html:div
    
    80
    +          class="security-level-current-badge"
    
    81
    +          data-l10n-id="security-level-preferences-current-badge"
    
    82
    +        ></html:div>
    
    83
    +      </html:div>
    
    84
    +    </radiogroup>
    
    85
    +  </dialog>
    
    86
    +</window>

  • browser/components/securitylevel/content/securityLevelPanel.css
    ... ... @@ -23,7 +23,7 @@
    23 23
       background-position-x: right var(--background-inline-offset);
    
    24 24
     }
    
    25 25
     
    
    26
    -#securityLevel-background[level="standard"] {
    
    26
    +#securityLevel-background:is([level="standard"], [level="custom"]) {
    
    27 27
       background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
    
    28 28
     }
    
    29 29
     
    
    ... ... @@ -49,14 +49,6 @@
    49 49
       font-weight: 600;
    
    50 50
     }
    
    51 51
     
    
    52
    -#securityLevel-custom {
    
    53
    -  border-radius: 4px;
    
    54
    -  background-color: var(--warning-icon-bgcolor);
    
    55
    -  color: black;
    
    56
    -  padding: 0.4em 0.5em;
    
    57
    -  margin-inline-start: 1em;
    
    58
    -}
    
    59
    -
    
    60 52
     #securityLevel-summary {
    
    61 53
       padding-inline-end: 5em;
    
    62 54
       max-width: 20em;
    

  • browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
    ... ... @@ -15,10 +15,6 @@
    15 15
       <vbox id="securityLevel-background" class="panel-subview-body">
    
    16 16
         <html:p id="securityLevel-subheading">
    
    17 17
           <html:span id="securityLevel-level"></html:span>
    
    18
    -      <html:span
    
    19
    -        id="securityLevel-custom"
    
    20
    -        data-l10n-id="security-level-panel-custom-badge"
    
    21
    -      ></html:span>
    
    22 18
         </html:p>
    
    23 19
         <html:p id="securityLevel-summary"></html:p>
    
    24 20
         <html:a
    
    ... ... @@ -32,12 +28,8 @@
    32 28
         <button
    
    33 29
           id="securityLevel-settings"
    
    34 30
           class="footer-button"
    
    31
    +      default="true"
    
    35 32
           data-l10n-id="security-level-panel-open-settings-button"
    
    36 33
         />
    
    37
    -    <button
    
    38
    -      id="securityLevel-restoreDefaults"
    
    39
    -      class="footer-button"
    
    40
    -      data-l10n-id="security-level-restore-defaults-button"
    
    41
    -    />
    
    42 34
       </html:moz-button-group>
    
    43 35
     </panel>

  • browser/components/securitylevel/content/securityLevelPreferences.css
    1
    -#securityLevel-groupbox {
    
    2
    -  --section-highlight-background-color: color-mix(in srgb, var(--in-content-accent-color) 20%, transparent);
    
    1
    +.security-level-grid {
    
    2
    +  display: grid;
    
    3
    +  grid-template:
    
    4
    +    "icon name badge button" min-content
    
    5
    +    "icon summary summary button" auto
    
    6
    +    "icon extra extra ." auto
    
    7
    +    / max-content max-content 1fr max-content;
    
    3 8
     }
    
    4 9
     
    
    5
    -#securityLevel-customNotification {
    
    6
    -  /* Spacing similar to #fpiIncompatibilityWarning. */
    
    7
    -  margin-block: 16px;
    
    10
    +.security-level-icon {
    
    11
    +  grid-area: icon;
    
    12
    +  align-self: start;
    
    13
    +  width: 24px;
    
    14
    +  height: 24px;
    
    15
    +  -moz-context-properties: fill;
    
    16
    +  fill: var(--in-content-icon-color);
    
    17
    +  margin-block-start: var(--space-xsmall);
    
    18
    +  margin-inline-end: var(--space-large);
    
    8 19
     }
    
    9 20
     
    
    10
    -.info-icon.securityLevel-custom-warning-icon {
    
    11
    -  list-style-image: url("chrome://global/skin/icons/warning.svg");
    
    21
    +.security-level-current-badge {
    
    22
    +  grid-area: badge;
    
    23
    +  align-self: center;
    
    24
    +  justify-self: start;
    
    25
    +  white-space: nowrap;
    
    26
    +  background: var(--background-color-information);
    
    27
    +  color: inherit;
    
    28
    +  font-size: var(--font-size-small);
    
    29
    +  border-radius: var(--border-radius-circle);
    
    30
    +  margin-inline-start: var(--space-small);
    
    31
    +  padding-block: var(--space-xsmall);
    
    32
    +  padding-inline: var(--space-small);
    
    12 33
     }
    
    13 34
     
    
    14
    -#securityLevel-customHeading {
    
    35
    +.security-level-current-badge span {
    
    36
    +  /* Still accessible to screen reader, but not visual.
    
    37
    +   * Keep inline, but with no layout width. */
    
    38
    +  display: inline-block;
    
    39
    +  width: 1px;
    
    40
    +  margin-inline-end: -1px;
    
    41
    +  clip-path: inset(50%);
    
    42
    +}
    
    43
    +
    
    44
    +@media (prefers-contrast) and (not (forced-colors)) {
    
    45
    +  .security-level-current-badge {
    
    46
    +    /* Match the checkbox/radio colors. */
    
    47
    +    background: var(--color-accent-primary);
    
    48
    +    color: var(--button-text-color-primary);
    
    49
    +  }
    
    50
    +}
    
    51
    +
    
    52
    +@media (forced-colors) {
    
    53
    +  .security-level-current-badge {
    
    54
    +    /* Match the checkbox/radio/selected colors. */
    
    55
    +    background: SelectedItem;
    
    56
    +    color: SelectedItemText;
    
    57
    +  }
    
    58
    +}
    
    59
    +
    
    60
    +.security-level-name {
    
    61
    +  grid-area: name;
    
    15 62
       font-weight: bold;
    
    63
    +  align-self: center;
    
    64
    +  white-space: nowrap;
    
    65
    +}
    
    66
    +
    
    67
    +.security-level-description {
    
    68
    +  display: grid;
    
    69
    +  grid-column: summary-start / extra-end;
    
    70
    +  grid-row: summary-start / extra-end;
    
    71
    +  grid-template-rows: subgrid;
    
    72
    +  grid-template-columns: subgrid;
    
    73
    +  margin-block-start: var(--space-small);
    
    74
    +}
    
    75
    +
    
    76
    +.security-level-summary {
    
    77
    +  grid-area: summary;
    
    78
    +}
    
    79
    +
    
    80
    +.security-level-description-extra {
    
    81
    +  grid-area: extra;
    
    82
    +  margin-block: var(--space-medium) 0;
    
    83
    +  margin-inline: var(--space-large) 0;
    
    84
    +  padding: 0;
    
    85
    +}
    
    86
    +
    
    87
    +.security-level-description-bullet:not(:last-child) {
    
    88
    +  margin-block-end: var(--space-medium);
    
    89
    +}
    
    90
    +
    
    91
    +/* Tweak current security level display. */
    
    92
    +
    
    93
    +#security-level-current {
    
    94
    +  margin-block-start: var(--space-large);
    
    95
    +  background: var(--in-content-box-background);
    
    96
    +  border: 1px solid var(--in-content-box-border-color);
    
    97
    +  border-radius: var(--border-radius-small);
    
    98
    +  padding: var(--space-medium);
    
    99
    +}
    
    100
    +
    
    101
    +#security-level-change {
    
    102
    +  grid-area: button;
    
    103
    +  align-self: center;
    
    104
    +  margin: 0;
    
    105
    +  margin-inline-start: var(--space-large);
    
    106
    +}
    
    107
    +
    
    108
    +/* Adjust which content is visible depending on the current security level. */
    
    109
    +
    
    110
    +#security-level-current:not([data-current-level="standard"]) .security-level-current-standard {
    
    111
    +  display: none;
    
    112
    +}
    
    113
    +
    
    114
    +#security-level-current:not([data-current-level="safer"]) .security-level-current-safer {
    
    115
    +  display: none;
    
    116
    +}
    
    117
    +
    
    118
    +#security-level-current:not([data-current-level="safest"]) .security-level-current-safest {
    
    119
    +  display: none;
    
    120
    +}
    
    121
    +
    
    122
    +#security-level-current:not([data-current-level="custom"]) .security-level-current-custom {
    
    123
    +  display: none;
    
    124
    +}
    
    125
    +
    
    126
    +#security-level-current[data-current-level="standard"] .security-level-icon {
    
    127
    +  content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
    
    128
    +}
    
    129
    +
    
    130
    +#security-level-current[data-current-level="safer"] .security-level-icon {
    
    131
    +  content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer");
    
    132
    +}
    
    133
    +
    
    134
    +#security-level-current[data-current-level="safest"] .security-level-icon {
    
    135
    +  content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest");
    
    136
    +}
    
    137
    +
    
    138
    +#security-level-current[data-current-level="custom"] .security-level-icon {
    
    139
    +  content: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard_custom");
    
    140
    +}
    
    141
    +
    
    142
    +/* Tweak security level dialog. */
    
    143
    +
    
    144
    +#security-level-radiogroup {
    
    145
    +  margin-block: var(--space-large) var(--space-xlarge);
    
    146
    +}
    
    147
    +
    
    148
    +.security-level-radio-container {
    
    149
    +  padding-block: var(--space-large);
    
    150
    +}
    
    151
    +
    
    152
    +#security-level-radiogroup .security-level-radio {
    
    153
    +  margin: 0;
    
    16 154
     }
    
    17 155
     
    
    18
    -#securityLevel-radiogroup[disabled] {
    
    19
    -  opacity: 0.5;
    
    156
    +#security-level-radiogroup .radio-label-box {
    
    157
    +  /* .security-level-current-badge already has a margin. */
    
    158
    +  margin: 0;
    
    20 159
     }
    
    21 160
     
    
    22
    -/* Overwrite the rule in common-shared.css so we don't get 0.25 opacity overall
    
    23
    - * on the radio text. */
    
    24
    -#securityLevel-radiogroup[disabled] radio[disabled] .radio-label-box {
    
    25
    -  opacity: 1.0;
    
    161
    +#security-level-radiogroup .privacy-detailedoption.security-level-radio-container:not(.selected) .security-level-description-extra {
    
    162
    +  /* .privacy-detailedoption uses visibility: hidden, which does not work with
    
    163
    +   * our grid display (the margin is still reserved) so we use display: none
    
    164
    +   * instead. */
    
    165
    +  display: none;
    
    26 166
     }

  • browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
    ... ... @@ -17,112 +17,19 @@
    17 17
             data-l10n-id="security-level-preferences-learn-more-link"
    
    18 18
           ></html:a>
    
    19 19
         </description>
    
    20
    -    <hbox
    
    21
    -      id="securityLevel-customNotification"
    
    22
    -      class="info-box-container"
    
    23
    -      flex="1"
    
    24
    -    >
    
    25
    -      <hbox class="info-icon-container">
    
    26
    -        <image class="info-icon securityLevel-custom-warning-icon"/>
    
    27
    -      </hbox>
    
    28
    -      <vbox flex="1">
    
    29
    -        <label
    
    30
    -          id="securityLevel-customHeading"
    
    31
    -          data-l10n-id="security-level-preferences-custom-heading"
    
    32
    -        />
    
    33
    -        <description
    
    34
    -          id="securityLevel-customDescription"
    
    35
    -          data-l10n-id="security-level-summary-custom"
    
    36
    -          flex="1"
    
    37
    -        />
    
    38
    -      </vbox>
    
    39
    -      <hbox align="center">
    
    40
    -        <button
    
    41
    -          id="securityLevel-restoreDefaults"
    
    42
    -          data-l10n-id="security-level-restore-defaults-button"
    
    43
    -        />
    
    44
    -      </hbox>
    
    45
    -    </hbox>
    
    46
    -    <radiogroup id="securityLevel-radiogroup">
    
    47
    -      <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
    
    48
    -        <radio
    
    49
    -          value="standard"
    
    50
    -          data-l10n-id="security-level-preferences-level-standard"
    
    51
    -          aria-describedby="securityLevelSummary-standard"
    
    52
    -        />
    
    53
    -        <vbox id="securityLevelSummary-standard" class="indent">
    
    54
    -          <label data-l10n-id="security-level-summary-standard" />
    
    55
    -        </vbox>
    
    56
    -      </vbox>
    
    57
    -      <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
    
    58
    -        <!-- NOTE: We point the accessible description to the wrapping vbox
    
    59
    -          - rather than its first description element. This means that when the
    
    60
    -          - privacy-extra-information is shown or hidden, its text content is
    
    61
    -          - included or excluded from the accessible description, respectively.
    
    62
    -          -->
    
    63
    -        <radio
    
    64
    -          value="safer"
    
    65
    -          data-l10n-id="security-level-preferences-level-safer"
    
    66
    -          aria-describedby="securityLevelSummary-safer"
    
    67
    -        />
    
    68
    -        <vbox id="securityLevelSummary-safer" class="indent">
    
    69
    -          <label data-l10n-id="security-level-summary-safer" />
    
    70
    -          <vbox class="privacy-extra-information">
    
    71
    -            <vbox class="indent">
    
    72
    -              <hbox class="extra-information-label">
    
    73
    -                <label
    
    74
    -                  class="content-blocking-label"
    
    75
    -                  data-l10n-id="security-level-preferences-bullet-https-only-javascript"
    
    76
    -                />
    
    77
    -              </hbox>
    
    78
    -              <hbox class="extra-information-label">
    
    79
    -                <label
    
    80
    -                  class="content-blocking-label"
    
    81
    -                  data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols"
    
    82
    -                />
    
    83
    -              </hbox>
    
    84
    -              <hbox class="extra-information-label">
    
    85
    -                <label
    
    86
    -                  class="content-blocking-label"
    
    87
    -                  data-l10n-id="security-level-preferences-bullet-limit-media"
    
    88
    -                />
    
    89
    -              </hbox>
    
    90
    -            </vbox>
    
    91
    -          </vbox>
    
    92
    -        </vbox>
    
    93
    -      </vbox>
    
    94
    -      <vbox class="securityLevel-radio-option privacy-detailedoption info-box-container">
    
    95
    -        <radio
    
    96
    -          value="safest"
    
    97
    -          data-l10n-id="security-level-preferences-level-safest"
    
    98
    -          aria-describedby="securityLevelSummary-safest"
    
    99
    -        />
    
    100
    -        <vbox id="securityLevelSummary-safest" class="indent">
    
    101
    -          <label data-l10n-id="security-level-summary-safest" />
    
    102
    -          <vbox class="privacy-extra-information">
    
    103
    -            <vbox class="indent">
    
    104
    -              <hbox class="extra-information-label">
    
    105
    -                <label
    
    106
    -                  class="content-blocking-label"
    
    107
    -                  data-l10n-id="security-level-preferences-bullet-disabled-javascript"
    
    108
    -                />
    
    109
    -              </hbox>
    
    110
    -              <hbox class="extra-information-label">
    
    111
    -                <label
    
    112
    -                  class="content-blocking-label"
    
    113
    -                  data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols-and-images"
    
    114
    -                />
    
    115
    -              </hbox>
    
    116
    -              <hbox class="extra-information-label">
    
    117
    -                <label
    
    118
    -                  class="content-blocking-label"
    
    119
    -                  data-l10n-id="security-level-preferences-bullet-limit-media"
    
    120
    -                />
    
    121
    -              </hbox>
    
    122
    -            </vbox>
    
    123
    -          </vbox>
    
    124
    -        </vbox>
    
    125
    -      </vbox>
    
    126
    -    </radiogroup>
    
    20
    +    <html:div id="security-level-current" class="security-level-grid">
    
    21
    +      <html:img
    
    22
    +        class="security-level-icon"
    
    23
    +        alt=""
    
    24
    +      />
    
    25
    +      <html:div
    
    26
    +        class="security-level-current-badge"
    
    27
    +        data-l10n-id="security-level-preferences-current-badge"
    
    28
    +      ></html:div>
    
    29
    +      <html:button
    
    30
    +        id="security-level-change"
    
    31
    +        data-l10n-id="security-level-preferences-change-button"
    
    32
    +      ></html:button>
    
    33
    +    </html:div>
    
    127 34
       </vbox>
    
    128 35
     </groupbox>

  • browser/components/securitylevel/jar.mn
    ... ... @@ -4,3 +4,5 @@ browser.jar:
    4 4
         content/browser/securitylevel/securityLevelButton.css      (content/securityLevelButton.css)
    
    5 5
         content/browser/securitylevel/securityLevelPreferences.css (content/securityLevelPreferences.css)
    
    6 6
         content/browser/securitylevel/securityLevelIcon.svg        (content/securityLevelIcon.svg)
    
    7
    +    content/browser/securitylevel/securityLevelDialog.xhtml    (content/securityLevelDialog.xhtml)
    
    8
    +    content/browser/securitylevel/securityLevelDialog.js       (content/securityLevelDialog.js)

  • browser/components/securitylevel/moz.build
    1 1
     JAR_MANIFESTS += ["jar.mn"]
    
    2
    +
    
    3
    +EXTRA_JS_MODULES += [
    
    4
    +    "SecurityLevelUIUtils.sys.mjs",
    
    5
    +]

  • browser/modules/SecurityLevelRestartNotification.sys.mjs
    1
    +const lazy = {};
    
    2
    +
    
    3
    +ChromeUtils.defineESModuleGetters(lazy, {
    
    4
    +  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
    
    5
    +  SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
    
    6
    +});
    
    7
    +
    
    8
    +ChromeUtils.defineLazyGetter(lazy, "NotificationStrings", function () {
    
    9
    +  return new Localization([
    
    10
    +    "branding/brand.ftl",
    
    11
    +    "toolkit/global/base-browser.ftl",
    
    12
    +  ]);
    
    13
    +});
    
    14
    +
    
    15
    +/**
    
    16
    + * Interface for showing the security level restart notification on desktop.
    
    17
    + */
    
    18
    +export const SecurityLevelRestartNotification = {
    
    19
    +  /**
    
    20
    +   * Whether we have already been initialised.
    
    21
    +   *
    
    22
    +   * @type {boolean}
    
    23
    +   */
    
    24
    +  _initialized: false,
    
    25
    +
    
    26
    +  /**
    
    27
    +   * Called when the UI is ready to show a notification.
    
    28
    +   */
    
    29
    +  ready() {
    
    30
    +    if (this._initialized) {
    
    31
    +      return;
    
    32
    +    }
    
    33
    +    this._initialized = true;
    
    34
    +    lazy.SecurityLevelPrefs.setRestartNotificationHandler(this);
    
    35
    +  },
    
    36
    +
    
    37
    +  /**
    
    38
    +   * Show the restart notification, and perform the restart if the user agrees.
    
    39
    +   */
    
    40
    +  async tryRestartBrowser() {
    
    41
    +    const [titleText, bodyText, primaryButtonText, secondaryButtonText] =
    
    42
    +      await lazy.NotificationStrings.formatValues([
    
    43
    +        { id: "security-level-restart-prompt-title" },
    
    44
    +        { id: "security-level-restart-prompt-body" },
    
    45
    +        { id: "security-level-restart-prompt-button-restart" },
    
    46
    +        { id: "security-level-restart-prompt-button-ignore" },
    
    47
    +      ]);
    
    48
    +    const buttonFlags =
    
    49
    +      Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
    
    50
    +      Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1;
    
    51
    +
    
    52
    +    const propBag = await Services.prompt.asyncConfirmEx(
    
    53
    +      lazy.BrowserWindowTracker.getTopWindow()?.browsingContext ?? null,
    
    54
    +      Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
    
    55
    +      titleText,
    
    56
    +      bodyText,
    
    57
    +      buttonFlags,
    
    58
    +      primaryButtonText,
    
    59
    +      secondaryButtonText,
    
    60
    +      null,
    
    61
    +      null,
    
    62
    +      null,
    
    63
    +      {}
    
    64
    +    );
    
    65
    +
    
    66
    +    if (propBag.get("buttonNumClicked") === 0) {
    
    67
    +      Services.startup.quit(
    
    68
    +        Services.startup.eAttemptQuit | Services.startup.eRestart
    
    69
    +      );
    
    70
    +    }
    
    71
    +  },
    
    72
    +};

  • browser/modules/moz.build
    ... ... @@ -136,6 +136,7 @@ EXTRA_JS_MODULES += [
    136 136
         "PopupBlockerObserver.sys.mjs",
    
    137 137
         "ProcessHangMonitor.sys.mjs",
    
    138 138
         "Sanitizer.sys.mjs",
    
    139
    +    "SecurityLevelRestartNotification.sys.mjs",
    
    139 140
         "SelectionChangedMenulist.sys.mjs",
    
    140 141
         "SharingUtils.sys.mjs",
    
    141 142
         "SiteDataManager.sys.mjs",
    

  • toolkit/components/extensions/ExtensionParent.sys.mjs
    ... ... @@ -2348,6 +2348,7 @@ async function torSendExtensionMessage(extensionId, message) {
    2348 2348
       const result = await ProxyMessenger.conduit.castRuntimeMessage("messenger", {
    
    2349 2349
         extensionId,
    
    2350 2350
         holder: new StructuredCloneHolder("torSendExtensionMessage", null, message),
    
    2351
    +    query: true,
    
    2351 2352
         firstResponse: true,
    
    2352 2353
         sender: {
    
    2353 2354
           id: extensionId,
    

  • toolkit/components/securitylevel/SecurityLevel.sys.mjs
    ... ... @@ -16,6 +16,7 @@ const BrowserTopics = Object.freeze({
    16 16
     // The Security Settings prefs in question.
    
    17 17
     const kSliderPref = "browser.security_level.security_slider";
    
    18 18
     const kCustomPref = "browser.security_level.security_custom";
    
    19
    +const kNoScriptInitedPref = "browser.security_level.noscript_inited";
    
    19 20
     
    
    20 21
     // __getPrefValue(prefName)__
    
    21 22
     // Returns the current value of a preference, regardless of its type.
    
    ... ... @@ -32,11 +33,11 @@ var getPrefValue = function (prefName) {
    32 33
       }
    
    33 34
     };
    
    34 35
     
    
    35
    -// __bindPref(prefName, prefHandler, init)__
    
    36
    +// __bindPref(prefName, prefHandler)__
    
    36 37
     // Applies prefHandler whenever the value of the pref changes.
    
    37 38
     // If init is true, applies prefHandler to the current value.
    
    38
    -// Returns a zero-arg function that unbinds the pref.
    
    39
    -var bindPref = function (prefName, prefHandler, init = false) {
    
    39
    +// Returns the observer that was added.
    
    40
    +var bindPref = function (prefName, prefHandler) {
    
    40 41
       let update = () => {
    
    41 42
           prefHandler(getPrefValue(prefName));
    
    42 43
         },
    
    ... ... @@ -48,21 +49,9 @@ var bindPref = function (prefName, prefHandler, init = false) {
    48 49
           },
    
    49 50
         };
    
    50 51
       Services.prefs.addObserver(prefName, observer);
    
    51
    -  if (init) {
    
    52
    -    update();
    
    53
    -  }
    
    54
    -  return () => {
    
    55
    -    Services.prefs.removeObserver(prefName, observer);
    
    56
    -  };
    
    52
    +  return observer;
    
    57 53
     };
    
    58 54
     
    
    59
    -// __bindPrefAndInit(prefName, prefHandler)__
    
    60
    -// Applies prefHandler to the current value of pref specified by prefName.
    
    61
    -// Re-applies prefHandler whenever the value of the pref changes.
    
    62
    -// Returns a zero-arg function that unbinds the pref.
    
    63
    -var bindPrefAndInit = (prefName, prefHandler) =>
    
    64
    -  bindPref(prefName, prefHandler, true);
    
    65
    -
    
    66 55
     async function waitForExtensionMessage(extensionId, checker = () => {}) {
    
    67 56
       const { torWaitForExtensionMessage } = lazy.ExtensionParent;
    
    68 57
       if (torWaitForExtensionMessage) {
    
    ... ... @@ -74,7 +63,7 @@ async function waitForExtensionMessage(extensionId, checker = () => {}) {
    74 63
     async function sendExtensionMessage(extensionId, message) {
    
    75 64
       const { torSendExtensionMessage } = lazy.ExtensionParent;
    
    76 65
       if (torSendExtensionMessage) {
    
    77
    -    return torSendExtensionMessage(extensionId, message);
    
    66
    +    return await torSendExtensionMessage(extensionId, message);
    
    78 67
       }
    
    79 68
       return undefined;
    
    80 69
     }
    
    ... ... @@ -199,14 +188,8 @@ var initializeNoScriptControl = () => {
    199 188
         // `browser.runtime.onMessage.addListener(...)` in NoScript's bg/main.js.
    
    200 189
     
    
    201 190
         // TODO: Is there a better way?
    
    202
    -    let sendNoScriptSettings = settings =>
    
    203
    -      sendExtensionMessage(noscriptID, settings);
    
    204
    -
    
    205
    -    // __setNoScriptSafetyLevel(safetyLevel)__.
    
    206
    -    // Set NoScript settings according to a particular safety level
    
    207
    -    // (security slider level): 0 = Standard, 1 = Safer, 2 = Safest
    
    208
    -    let setNoScriptSafetyLevel = safetyLevel =>
    
    209
    -      sendNoScriptSettings(noscriptSettings(safetyLevel));
    
    191
    +    let sendNoScriptSettings = async settings =>
    
    192
    +      await sendExtensionMessage(noscriptID, settings);
    
    210 193
     
    
    211 194
         // __securitySliderToSafetyLevel(sliderState)__.
    
    212 195
         // Converts the "browser.security_level.security_slider" pref value
    
    ... ... @@ -216,36 +199,46 @@ var initializeNoScriptControl = () => {
    216 199
     
    
    217 200
         // Wait for the first message from NoScript to arrive, and then
    
    218 201
         // bind the security_slider pref to the NoScript settings.
    
    219
    -    let messageListener = a => {
    
    202
    +    let messageListener = async a => {
    
    220 203
           try {
    
    221 204
             logger.debug("Message received from NoScript:", a);
    
    222
    -        let noscriptPersist = Services.prefs.getBoolPref(
    
    223
    -          "browser.security_level.noscript_persist",
    
    224
    -          false
    
    225
    -        );
    
    205
    +        const persistPref = "browser.security_level.noscript_persist";
    
    206
    +        let noscriptPersist = Services.prefs.getBoolPref(persistPref, false);
    
    226 207
             let noscriptInited = Services.prefs.getBoolPref(
    
    227
    -          "browser.security_level.noscript_inited",
    
    208
    +          kNoScriptInitedPref,
    
    228 209
               false
    
    229 210
             );
    
    230
    -        // Set the noscript safety level once if we have never run noscript
    
    231
    -        // before, or if we are not allowing noscript per-site settings to be
    
    232
    -        // persisted between browser sessions. Otherwise make sure that the
    
    233
    -        // security slider position, if changed, will rewrite the noscript
    
    234
    -        // settings.
    
    235
    -        bindPref(
    
    236
    -          kSliderPref,
    
    237
    -          sliderState =>
    
    238
    -            setNoScriptSafetyLevel(securitySliderToSafetyLevel(sliderState)),
    
    239
    -          !noscriptPersist || !noscriptInited
    
    240
    -        );
    
    241
    -        if (!noscriptInited) {
    
    242
    -          Services.prefs.setBoolPref(
    
    243
    -            "browser.security_level.noscript_inited",
    
    244
    -            true
    
    211
    +        // Set the noscript safety level once at startup.
    
    212
    +        // If a user has set noscriptPersist, then we only send this if the
    
    213
    +        // security level was changed in a previous session.
    
    214
    +        // NOTE: We do not re-send this when the security_slider preference
    
    215
    +        // changes mid-session because this should always require a restart.
    
    216
    +        if (noscriptPersist && noscriptInited) {
    
    217
    +          logger.warn(
    
    218
    +            `Not initialising NoScript since the user has set ${persistPref}`
    
    245 219
               );
    
    220
    +          return;
    
    246 221
             }
    
    222
    +        // Read the security level, even if the user has the "custom"
    
    223
    +        // preference.
    
    224
    +        const securityIndex = Services.prefs.getIntPref(kSliderPref, 0);
    
    225
    +        const safetyLevel = securitySliderToSafetyLevel(securityIndex);
    
    226
    +        // May throw if NoScript fails to apply the settings:
    
    227
    +        const noscriptResult = await sendNoScriptSettings(
    
    228
    +          noscriptSettings(safetyLevel)
    
    229
    +        );
    
    230
    +        // Mark the NoScript extension as initialised so we do not reset it
    
    231
    +        // at the next startup for noscript_persist users.
    
    232
    +        Services.prefs.setBoolPref(kNoScriptInitedPref, true);
    
    233
    +        logger.info("NoScript successfully initialised.");
    
    234
    +        // In the future NoScript may tell us more about how it applied our
    
    235
    +        // settings, e.g. if user is overriding per-site permissions.
    
    236
    +        // Up to NoScript 12.6 noscriptResult is undefined.
    
    237
    +        logger.debug("NoScript response:", noscriptResult);
    
    247 238
           } catch (e) {
    
    248
    -        logger.exception(e);
    
    239
    +        logger.error("Could not apply NoScript settings", e);
    
    240
    +        // Treat as a custom security level for the rest of the session.
    
    241
    +        Services.prefs.setBoolPref(kCustomPref, true);
    
    249 242
           }
    
    250 243
         };
    
    251 244
         waitForExtensionMessage(noscriptID, a => a.__meta.name === "started").then(
    
    ... ... @@ -254,6 +247,8 @@ var initializeNoScriptControl = () => {
    254 247
         logger.info("Listening for messages from NoScript.");
    
    255 248
       } catch (e) {
    
    256 249
         logger.exception(e);
    
    250
    +    // Treat as a custom security level for the rest of the session.
    
    251
    +    Services.prefs.setBoolPref(kCustomPref, true);
    
    257 252
       }
    
    258 253
     };
    
    259 254
     
    
    ... ... @@ -283,16 +278,60 @@ const kSecuritySettings = {
    283 278
     
    
    284 279
     // ### Prefs
    
    285 280
     
    
    281
    +/**
    
    282
    + * Amend the security level index to a standard value.
    
    283
    + *
    
    284
    + * @param {integer} index - The input index value.
    
    285
    + * @returns {integer} - A standard index value.
    
    286
    + */
    
    287
    +function fixupIndex(index) {
    
    288
    +  if (!Number.isInteger(index) || index < 1 || index > 4) {
    
    289
    +    // Unexpected value out of range, go to the "safest" level as a fallback.
    
    290
    +    return 1;
    
    291
    +  }
    
    292
    +  if (index === 3) {
    
    293
    +    // Migrate from old medium-low (3) to new medium (2).
    
    294
    +    return 2;
    
    295
    +  }
    
    296
    +  return index;
    
    297
    +}
    
    298
    +
    
    299
    +/**
    
    300
    + * A list of preference observers that should be disabled whilst we write our
    
    301
    + * preference values.
    
    302
    + *
    
    303
    + * @type {{ prefName: string, observer: object }[]}
    
    304
    + */
    
    305
    +const prefObservers = [];
    
    306
    +
    
    286 307
     // __write_setting_to_prefs(settingIndex)__.
    
    287 308
     // Take a given setting index and write the appropriate pref values
    
    288 309
     // to the pref database.
    
    289 310
     var write_setting_to_prefs = function (settingIndex) {
    
    290
    -  Object.keys(kSecuritySettings).forEach(prefName =>
    
    291
    -    Services.prefs.setBoolPref(
    
    292
    -      prefName,
    
    293
    -      kSecuritySettings[prefName][settingIndex]
    
    294
    -    )
    
    295
    -  );
    
    311
    +  settingIndex = fixupIndex(settingIndex);
    
    312
    +  // Don't want to trigger our internal observers when setting ourselves.
    
    313
    +  for (const { prefName, observer } of prefObservers) {
    
    314
    +    Services.prefs.removeObserver(prefName, observer);
    
    315
    +  }
    
    316
    +  try {
    
    317
    +    // Make sure noscript is re-initialised at the next startup when the
    
    318
    +    // security level changes.
    
    319
    +    Services.prefs.setBoolPref(kNoScriptInitedPref, false);
    
    320
    +    Services.prefs.setIntPref(kSliderPref, settingIndex);
    
    321
    +    // NOTE: We do not clear kCustomPref. Instead, we rely on the preference
    
    322
    +    // being cleared on the next startup.
    
    323
    +    Object.keys(kSecuritySettings).forEach(prefName =>
    
    324
    +      Services.prefs.setBoolPref(
    
    325
    +        prefName,
    
    326
    +        kSecuritySettings[prefName][settingIndex]
    
    327
    +      )
    
    328
    +    );
    
    329
    +  } finally {
    
    330
    +    // Re-add the observers.
    
    331
    +    for (const { prefName, observer } of prefObservers) {
    
    332
    +      Services.prefs.addObserver(prefName, observer);
    
    333
    +    }
    
    334
    +  }
    
    296 335
     };
    
    297 336
     
    
    298 337
     // __read_setting_from_prefs()__.
    
    ... ... @@ -321,24 +360,6 @@ var read_setting_from_prefs = function (prefNames) {
    321 360
       return null;
    
    322 361
     };
    
    323 362
     
    
    324
    -// __watch_security_prefs(onSettingChanged)__.
    
    325
    -// Whenever a pref bound to the security slider changes, onSettingChanged
    
    326
    -// is called with the new security setting value (1,2,3,4 or null).
    
    327
    -// Returns a zero-arg function that ends this binding.
    
    328
    -var watch_security_prefs = function (onSettingChanged) {
    
    329
    -  let prefNames = Object.keys(kSecuritySettings);
    
    330
    -  let unbindFuncs = [];
    
    331
    -  for (let prefName of prefNames) {
    
    332
    -    unbindFuncs.push(
    
    333
    -      bindPrefAndInit(prefName, () =>
    
    334
    -        onSettingChanged(read_setting_from_prefs())
    
    335
    -      )
    
    336
    -    );
    
    337
    -  }
    
    338
    -  // Call all the unbind functions.
    
    339
    -  return () => unbindFuncs.forEach(unbind => unbind());
    
    340
    -};
    
    341
    -
    
    342 363
     // __initialized__.
    
    343 364
     // Have we called initialize() yet?
    
    344 365
     var initializedSecPrefs = false;
    
    ... ... @@ -354,36 +375,82 @@ var initializeSecurityPrefs = function () {
    354 375
       }
    
    355 376
       logger.info("Initializing security-prefs.js");
    
    356 377
       initializedSecPrefs = true;
    
    357
    -  // When security_custom is set to false, apply security_slider setting
    
    358
    -  // to the security-sensitive prefs.
    
    359
    -  bindPrefAndInit(kCustomPref, function (custom) {
    
    360
    -    if (custom === false) {
    
    361
    -      write_setting_to_prefs(Services.prefs.getIntPref(kSliderPref));
    
    362
    -    }
    
    363
    -  });
    
    364
    -  // If security_slider is given a new value, then security_custom should
    
    365
    -  // be set to false.
    
    366
    -  bindPref(kSliderPref, function (prefIndex) {
    
    378
    +
    
    379
    +  const wasCustom = Services.prefs.getBoolPref(kCustomPref, false);
    
    380
    +  // For new profiles with no user preference, the security level should be "4"
    
    381
    +  // and it should not be custom.
    
    382
    +  let desiredIndex = Services.prefs.getIntPref(kSliderPref, 4);
    
    383
    +  desiredIndex = fixupIndex(desiredIndex);
    
    384
    +  // Make sure the user has a set preference user value.
    
    385
    +  Services.prefs.setIntPref(kSliderPref, desiredIndex);
    
    386
    +  Services.prefs.setBoolPref(kCustomPref, wasCustom);
    
    387
    +
    
    388
    +  // Make sure that the preference values at application startup match the
    
    389
    +  // expected values for the desired security level. See tor-browser#43783.
    
    390
    +
    
    391
    +  // NOTE: We assume that the controlled preference values that are read prior
    
    392
    +  // to profile-after-change do not change in value before this method is
    
    393
    +  // called. I.e. we expect the current preference values to match the
    
    394
    +  // preference values that were used during the application initialisation.
    
    395
    +  const effectiveIndex = read_setting_from_prefs();
    
    396
    +
    
    397
    +  if (wasCustom && effectiveIndex !== null) {
    
    398
    +    logger.info(`Custom startup values match index ${effectiveIndex}`);
    
    399
    +    // Do not consider custom any more.
    
    400
    +    // NOTE: This level needs to be set before it is read elsewhere. In
    
    401
    +    // particular, for the NoScript addon.
    
    367 402
         Services.prefs.setBoolPref(kCustomPref, false);
    
    368
    -    write_setting_to_prefs(prefIndex);
    
    403
    +    Services.prefs.setIntPref(kSliderPref, effectiveIndex);
    
    404
    +  } else if (!wasCustom && effectiveIndex !== desiredIndex) {
    
    405
    +    // NOTE: We assume all our controlled preferences require a restart.
    
    406
    +    // In practice, only a subset of these preferences may actually require a
    
    407
    +    // restart, so we could switch their values. But we treat them all the same
    
    408
    +    // for simplicity, consistency and stability in case mozilla changes the
    
    409
    +    // restart requirements.
    
    410
    +    logger.info(`Startup values do not match for index ${desiredIndex}`);
    
    411
    +    SecurityLevelPrefs.requireRestart();
    
    412
    +  }
    
    413
    +
    
    414
    +  // Start listening for external changes to the controlled preferences.
    
    415
    +  prefObservers.push({
    
    416
    +    prefName: kCustomPref,
    
    417
    +    observer: bindPref(kCustomPref, custom => {
    
    418
    +      // Custom flag was removed mid-session. Requires a restart to apply the
    
    419
    +      // security level.
    
    420
    +      if (custom === false) {
    
    421
    +        logger.info("Custom flag was cleared externally");
    
    422
    +        SecurityLevelPrefs.requireRestart();
    
    423
    +      }
    
    424
    +    }),
    
    369 425
       });
    
    370
    -  // If a security-sensitive pref changes, then decide if the set of pref values
    
    371
    -  // constitutes a security_slider setting or a custom value.
    
    372
    -  watch_security_prefs(settingIndex => {
    
    373
    -    if (settingIndex === null) {
    
    374
    -      Services.prefs.setBoolPref(kCustomPref, true);
    
    375
    -    } else {
    
    376
    -      Services.prefs.setIntPref(kSliderPref, settingIndex);
    
    377
    -      Services.prefs.setBoolPref(kCustomPref, false);
    
    378
    -    }
    
    426
    +  prefObservers.push({
    
    427
    +    prefName: kSliderPref,
    
    428
    +    observer: bindPref(kSliderPref, () => {
    
    429
    +      // Security level was changed mid-session. Requires a restart to apply.
    
    430
    +      logger.info("Security level was changed externally");
    
    431
    +      SecurityLevelPrefs.requireRestart();
    
    432
    +    }),
    
    379 433
       });
    
    380
    -  // Migrate from old medium-low (3) to new medium (2).
    
    381
    -  if (
    
    382
    -    Services.prefs.getBoolPref(kCustomPref) === false &&
    
    383
    -    Services.prefs.getIntPref(kSliderPref) === 3
    
    384
    -  ) {
    
    385
    -    Services.prefs.setIntPref(kSliderPref, 2);
    
    386
    -    write_setting_to_prefs(2);
    
    434
    +
    
    435
    +  for (const prefName of Object.keys(kSecuritySettings)) {
    
    436
    +    prefObservers.push({
    
    437
    +      prefName,
    
    438
    +      observer: bindPref(prefName, () => {
    
    439
    +        logger.warn(
    
    440
    +          `The controlled preference ${prefName} was changed externally.` +
    
    441
    +            " Treating as a custom security level."
    
    442
    +        );
    
    443
    +        // Something outside of this module changed the preference value for a
    
    444
    +        // preference we control.
    
    445
    +        // Always treat as a custom security level for the rest of this session,
    
    446
    +        // even if the new preference values match a pre-set security level. We
    
    447
    +        // do this because some controlled preferences require a restart to be
    
    448
    +        // properly applied. See tor-browser#43783.
    
    449
    +        // In the case where it does match a pre-set security level, the custom
    
    450
    +        // flag will be cleared at the next startup.
    
    451
    +        Services.prefs.setBoolPref(kCustomPref, true);
    
    452
    +      }),
    
    453
    +    });
    
    387 454
       }
    
    388 455
     
    
    389 456
       logger.info("security-prefs.js initialization complete");
    
    ... ... @@ -437,8 +504,9 @@ export class SecurityLevel {
    437 504
     
    
    438 505
       init() {
    
    439 506
         migratePreferences();
    
    440
    -    initializeNoScriptControl();
    
    507
    +    // Fixup our preferences before we pass on the security level to NoScript.
    
    441 508
         initializeSecurityPrefs();
    
    509
    +    initializeNoScriptControl();
    
    442 510
       }
    
    443 511
     
    
    444 512
       observe(aSubject, aTopic) {
    
    ... ... @@ -448,10 +516,19 @@ export class SecurityLevel {
    448 516
       }
    
    449 517
     }
    
    450 518
     
    
    519
    +/**
    
    520
    + * @typedef {object} SecurityLevelRestartNotificationHandler
    
    521
    + *
    
    522
    + * An object that can serve the user a restart notification.
    
    523
    + *
    
    524
    + * @property {Function} tryRestartBrowser - The method that should be called to
    
    525
    + *   ask the user to restart the browser.
    
    526
    + */
    
    527
    +
    
    451 528
     /*
    
    452 529
       Security Level Prefs
    
    453 530
     
    
    454
    -  Getters and Setters for relevant torbutton prefs
    
    531
    +  Getters and Setters for relevant security level prefs
    
    455 532
     */
    
    456 533
     export const SecurityLevelPrefs = {
    
    457 534
       SecurityLevels: Object.freeze({
    
    ... ... @@ -462,6 +539,14 @@ export const SecurityLevelPrefs = {
    462 539
       security_slider_pref: "browser.security_level.security_slider",
    
    463 540
       security_custom_pref: "browser.security_level.security_custom",
    
    464 541
     
    
    542
    +  /**
    
    543
    +   * The current security level preference.
    
    544
    +   *
    
    545
    +   * This ignores any custom settings the user may have changed, and just
    
    546
    +   * gives the underlying security level.
    
    547
    +   *
    
    548
    +   * @type {?string}
    
    549
    +   */
    
    465 550
       get securityLevel() {
    
    466 551
         // Set the default return value to 0, which won't match anything in
    
    467 552
         // SecurityLevels.
    
    ... ... @@ -471,18 +556,146 @@ export const SecurityLevelPrefs = {
    471 556
         )?.[0];
    
    472 557
       },
    
    473 558
     
    
    474
    -  set securityLevel(level) {
    
    475
    -    const val = this.SecurityLevels[level];
    
    476
    -    if (val !== undefined) {
    
    477
    -      Services.prefs.setIntPref(this.security_slider_pref, val);
    
    478
    -    }
    
    559
    +  /**
    
    560
    +   * Set the desired security level just before a restart.
    
    561
    +   *
    
    562
    +   * The caller must restart the browser after calling this method.
    
    563
    +   *
    
    564
    +   * @param {string} level - The name of the new security level to set.
    
    565
    +   */
    
    566
    +  setSecurityLevelBeforeRestart(level) {
    
    567
    +    write_setting_to_prefs(this.SecurityLevels[level]);
    
    479 568
       },
    
    480 569
     
    
    570
    +  /**
    
    571
    +   * Whether the user has any custom setting values that do not match a pre-set
    
    572
    +   * security level.
    
    573
    +   *
    
    574
    +   * @type {boolean}
    
    575
    +   */
    
    481 576
       get securityCustom() {
    
    482 577
         return Services.prefs.getBoolPref(this.security_custom_pref);
    
    483 578
       },
    
    484 579
     
    
    485
    -  set securityCustom(val) {
    
    486
    -    Services.prefs.setBoolPref(this.security_custom_pref, val);
    
    580
    +  /**
    
    581
    +   * A summary of the current security level.
    
    582
    +   *
    
    583
    +   * If the user has some custom settings, this returns "custom". Otherwise
    
    584
    +   * returns the name of the security level.
    
    585
    +   *
    
    586
    +   * @type {string}
    
    587
    +   */
    
    588
    +  get securityLevelSummary() {
    
    589
    +    if (this.securityCustom) {
    
    590
    +      return "custom";
    
    591
    +    }
    
    592
    +    return this.securityLevel ?? "custom";
    
    593
    +  },
    
    594
    +
    
    595
    +  /**
    
    596
    +   * Whether the browser should be restarted to apply the security level.
    
    597
    +   *
    
    598
    +   * @type {boolean}
    
    599
    +   */
    
    600
    +  _needRestart: false,
    
    601
    +
    
    602
    +  /**
    
    603
    +   * The external handler that can show a notification to the user, if any.
    
    604
    +   *
    
    605
    +   * @type {?SecurityLevelRestartNotificationHandler}
    
    606
    +   */
    
    607
    +  _restartNotificationHandler: null,
    
    608
    +
    
    609
    +  /**
    
    610
    +   * Set the external handler for showing notifications to the user.
    
    611
    +   *
    
    612
    +   * This should only be called once per session once the handler is ready to
    
    613
    +   * show a notification, which may occur immediately during this call.
    
    614
    +   *
    
    615
    +   * @param {SecurityLevelRestartNotificationHandler} handler - The new handler
    
    616
    +   *   to use.
    
    617
    +   */
    
    618
    +  setRestartNotificationHandler(handler) {
    
    619
    +    logger.info("Restart notification handler is set");
    
    620
    +    this._restartNotificationHandler = handler;
    
    621
    +    if (this._needRestart) {
    
    622
    +      // Show now using the new handler.
    
    623
    +      this._tryShowRestartNotification();
    
    624
    +    }
    
    625
    +  },
    
    626
    +
    
    627
    +  /**
    
    628
    +   * A promise for any ongoing notification prompt task.
    
    629
    +   *
    
    630
    +   * @type {Promise}
    
    631
    +   */
    
    632
    +  _restartNotificationPromise: null,
    
    633
    +
    
    634
    +  /**
    
    635
    +   * Try show a notification to the user.
    
    636
    +   *
    
    637
    +   * If no notification handler has been attached yet, this will do nothing.
    
    638
    +   */
    
    639
    +  async _tryShowRestartNotification() {
    
    640
    +    if (!this._restartNotificationHandler) {
    
    641
    +      logger.info("Missing a restart notification handler");
    
    642
    +      // This may be added later in the session.
    
    643
    +      return;
    
    644
    +    }
    
    645
    +
    
    646
    +    const prevPromise = this._restartNotificationPromise;
    
    647
    +    let resolve;
    
    648
    +    ({ promise: this._restartNotificationPromise, resolve } =
    
    649
    +      Promise.withResolvers());
    
    650
    +    await prevPromise;
    
    651
    +
    
    652
    +    try {
    
    653
    +      await this._restartNotificationHandler?.tryRestartBrowser();
    
    654
    +    } finally {
    
    655
    +      // Allow the notification to be shown again.
    
    656
    +      resolve();
    
    657
    +    }
    
    658
    +  },
    
    659
    +
    
    660
    +  /**
    
    661
    +   * Mark the session as requiring a restart to apply a change in security
    
    662
    +   * level.
    
    663
    +   *
    
    664
    +   * The security level will immediately be switched to "custom", and the user
    
    665
    +   * may be shown a notification to restart the browser.
    
    666
    +   */
    
    667
    +  requireRestart() {
    
    668
    +    logger.warn("The browser needs to be restarted to set the security level");
    
    669
    +    // Treat as a custom security level for the rest of the session.
    
    670
    +    // At the next startup, the custom flag may be cleared if the settings are
    
    671
    +    // as expected.
    
    672
    +    Services.prefs.setBoolPref(kCustomPref, true);
    
    673
    +    this._needRestart = true;
    
    674
    +
    
    675
    +    // NOTE: We need to change the controlled security level preferences in
    
    676
    +    // response to the desired change in security level. We could either:
    
    677
    +    // 1. Only change the controlled preferences after the user confirms a
    
    678
    +    //    restart. Or
    
    679
    +    // 2. Change the controlled preferences and then try and ask the user to
    
    680
    +    //    restart.
    
    681
    +    //
    
    682
    +    // We choose the latter:
    
    683
    +    // 1. To allow users to manually restart.
    
    684
    +    // 2. If the user ignores or misses the notification, they will at least be
    
    685
    +    //    in the correct state when the browser starts again. Although they will
    
    686
    +    //    be in a custom/undefined state in the mean time.
    
    687
    +    // 3. Currently Android relies on triggering the change in security level
    
    688
    +    //    by setting the browser.security_level.security_slider preference
    
    689
    +    //    value. So it currently uses this path. So we need to set the values
    
    690
    +    //    now, before it preforms a restart.
    
    691
    +    // TODO: Have android use the `setSecurityLevelBeforeRestart` method
    
    692
    +    // instead of setting the security_slider preference value directly, so that
    
    693
    +    // it knows exactly when it can restart the browser. tor-browser#43820
    
    694
    +    write_setting_to_prefs(Services.prefs.getIntPref(kSliderPref, 0));
    
    695
    +    // NOTE: Even though we have written the preferences, the session should
    
    696
    +    // still be marked as "custom" because:
    
    697
    +    // 1. Some preferences require a browser restart to be applied.
    
    698
    +    // 2. NoScript has not been updated with the new settings.
    
    699
    +    this._tryShowRestartNotification();
    
    487 700
       },
    
    488 701
     }; /* Security Level Prefs */

  • toolkit/locales/en-US/toolkit/global/base-browser.ftl
    ... ... @@ -123,10 +123,6 @@ security-level-toolbar-button-custom =
    123 123
     
    
    124 124
     # Uses sentence case in English (US).
    
    125 125
     security-level-panel-heading = Security level
    
    126
    -
    
    127
    -security-level-panel-level-standard = Standard
    
    128
    -security-level-panel-level-safer = Safer
    
    129
    -security-level-panel-level-safest = Safest
    
    130 126
     security-level-panel-learn-more-link = Learn more
    
    131 127
     # Button to open security level settings.
    
    132 128
     security-level-panel-open-settings-button = Settings…
    
    ... ... @@ -136,6 +132,19 @@ security-level-panel-open-settings-button = Settings…
    136 132
     security-level-preferences-heading = Security Level
    
    137 133
     security-level-preferences-overview = Disable certain web features that can be used to attack your security and anonymity.
    
    138 134
     security-level-preferences-learn-more-link = Learn more
    
    135
    +# Text for a badge that labels the currently active security level.
    
    136
    +# The text in between '<span>' and '</span>' should contain some kind of bracket, like '(' and ')', or other punctuation used in your language to separate out text from its surrounding context. This will not be visible, but will be use for screen readers to make it clear that the text is not part of the same sentence. For example, in US English this would be read as "(Current level)", and the full line of text would be read as "Safest (Current level)".
    
    137
    +security-level-preferences-current-badge = <span>(</span>Current level<span>)</span>
    
    138
    +security-level-preferences-change-button = Change…
    
    139
    +
    
    140
    +## Security level settings dialog.
    
    141
    +
    
    142
    +security-level-dialog-window =
    
    143
    +    .title = Change security level
    
    144
    +
    
    145
    +# '-brand-short-name' is the localized browser name, like "Tor Browser".
    
    146
    +security-level-dialog-restart-description = You will need to restart { -brand-short-name } to apply any changes. This will close all windows and tabs.
    
    147
    +
    
    139 148
     security-level-preferences-level-standard =
    
    140 149
         .label = Standard
    
    141 150
     security-level-preferences-level-safer =
    
    ... ... @@ -143,6 +152,16 @@ security-level-preferences-level-safer =
    143 152
     security-level-preferences-level-safest =
    
    144 153
         .label = Safest
    
    145 154
     
    
    155
    +security-level-dialog-save-restart =
    
    156
    +    .label = Save and restart
    
    157
    +
    
    158
    +## Security level names shown in the security panel and settings.
    
    159
    +
    
    160
    +security-level-panel-level-standard = Standard
    
    161
    +security-level-panel-level-safer = Safer
    
    162
    +security-level-panel-level-safest = Safest
    
    163
    +security-level-panel-level-custom = Custom
    
    164
    +
    
    146 165
     ## Security level summaries shown in security panel and settings.
    
    147 166
     
    
    148 167
     security-level-summary-standard = All browser and website features are enabled.
    
    ... ... @@ -161,13 +180,13 @@ security-level-preferences-bullet-limit-font-and-symbols-and-images = Some fonts
    161 180
     ## Custom security level.
    
    162 181
     ## Some custom preferences configuration has placed the user outside one of the standard three levels.
    
    163 182
     
    
    164
    -# Shown in the security level panel as an orange badge next to the expected level.
    
    165
    -security-level-panel-custom-badge = Custom
    
    166
    -# Shown in the security level settings in a warning box.
    
    167
    -security-level-preferences-custom-heading = Custom security level configured
    
    168 183
     # Description of custom state and recommended action.
    
    169 184
     # Shown in the security level panel and settings.
    
    170 185
     security-level-summary-custom = Your custom browser preferences have resulted in unusual security settings. For security and privacy reasons, we recommend you choose one of the default security levels.
    
    171
    -# Button to undo custom changes to the security level and place the user in one of the standard security levels.
    
    172
    -# Shown in the security level panel and settings.
    
    173
    -security-level-restore-defaults-button = Restore defaults
    186
    +
    
    187
    +## Security level restart prompt.
    
    188
    +
    
    189
    +security-level-restart-prompt-title = Your security level settings require a restart
    
    190
    +security-level-restart-prompt-body = You must restart { -brand-short-name } for your security level settings to be applied. This will close all your windows and tabs.
    
    191
    +security-level-restart-prompt-button-restart = Restart
    
    192
    +security-level-restart-prompt-button-ignore = Ignore