ma1 pushed to branch tor-browser-115.24.0esr-13.5-1 at The Tor Project / Applications / Tor Browser

Commits:

17 changed files:

Changes:

  • browser/components/BrowserGlue.sys.mjs
    ... ... @@ -59,6 +59,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
    59 59
       Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
    
    60 60
       ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
    
    61 61
       SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.sys.mjs",
    
    62
    +  SecurityLevelRestartNotification:
    
    63
    +    "resource:///modules/SecurityLevelRestartNotification.sys.mjs",
    
    62 64
       SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
    
    63 65
       SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
    
    64 66
       ShellService: "resource:///modules/ShellService.sys.mjs",
    
    ... ... @@ -1907,6 +1909,8 @@ BrowserGlue.prototype = {
    1907 1909
     
    
    1908 1910
         lazy.DragDropFilter.init();
    
    1909 1911
     
    
    1912
    +    lazy.SecurityLevelRestartNotification.ready();
    
    1913
    +
    
    1910 1914
         lazy.TorProviderBuilder.firstWindowLoaded();
    
    1911 1915
     
    
    1912 1916
         ClipboardPrivacy.startup();
    

  • 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.
    
    ... ... @@ -200,19 +188,7 @@ var SecurityLevelPanel = {
    200 188
         }
    
    201 189
     
    
    202 190
         // get security prefs
    
    203
    -    const level = SecurityLevelPrefs.securityLevel;
    
    204
    -    const custom = SecurityLevelPrefs.securityCustom;
    
    205
    -
    
    206
    -    // only visible when user is using custom settings
    
    207
    -    this._elements.customName.hidden = !custom;
    
    208
    -    this._elements.restoreDefaultsButton.hidden = !custom;
    
    209
    -    if (custom) {
    
    210
    -      this._elements.settingsButton.removeAttribute("default");
    
    211
    -      this._elements.restoreDefaultsButton.setAttribute("default", "true");
    
    212
    -    } else {
    
    213
    -      this._elements.settingsButton.setAttribute("default", "true");
    
    214
    -      this._elements.restoreDefaultsButton.removeAttribute("default");
    
    215
    -    }
    
    191
    +    const level = SecurityLevelPrefs.securityLevelSummary;
    
    216 192
     
    
    217 193
         // Descriptions change based on security level
    
    218 194
         this._elements.background.setAttribute("level", level);
    
    ... ... @@ -231,12 +207,13 @@ var SecurityLevelPanel = {
    231 207
             l10nIdLevel = "security-level-panel-level-safest";
    
    232 208
             l10nIdSummary = "security-level-summary-safest";
    
    233 209
             break;
    
    210
    +      case "custom":
    
    211
    +        l10nIdLevel = "security-level-panel-level-custom";
    
    212
    +        l10nIdSummary = "security-level-summary-custom";
    
    213
    +        break;
    
    234 214
           default:
    
    235 215
             throw Error(`Unhandled level: ${level}`);
    
    236 216
         }
    
    237
    -    if (custom) {
    
    238
    -      l10nIdSummary = "security-level-summary-custom";
    
    239
    -    }
    
    240 217
     
    
    241 218
         document.l10n.setAttributes(this._elements.levelName, l10nIdLevel);
    
    242 219
         document.l10n.setAttributes(this._elements.summary, l10nIdSummary);
    
    ... ... @@ -270,13 +247,6 @@ var SecurityLevelPanel = {
    270 247
         this._elements.panel.hidePopup();
    
    271 248
       },
    
    272 249
     
    
    273
    -  restoreDefaults() {
    
    274
    -    SecurityLevelPrefs.securityCustom = false;
    
    275
    -    // Move focus to the settings button since restore defaults button will
    
    276
    -    // become hidden.
    
    277
    -    this._elements.settingsButton.focus();
    
    278
    -  },
    
    279
    -
    
    280 250
       openSecuritySettings() {
    
    281 251
         openPreferences("privacy-securitylevel");
    
    282 252
         this.hide();
    
    ... ... @@ -302,62 +272,64 @@ var SecurityLevelPanel = {
    302 272
     
    
    303 273
     var SecurityLevelPreferences = {
    
    304 274
       _securityPrefsBranch: null,
    
    275
    +
    
    305 276
       /**
    
    306
    -   * The notification box shown when the user has a custom security setting.
    
    307
    -   *
    
    308
    -   * @type {Element}
    
    309
    -   */
    
    310
    -  _customNotification: null,
    
    311
    -  /**
    
    312
    -   * The radiogroup for this preference.
    
    313
    -   *
    
    314
    -   * @type {Element}
    
    315
    -   */
    
    316
    -  _radiogroup: null,
    
    317
    -  /**
    
    318
    -   * A list of radio options and their containers.
    
    277
    +   * The element that shows the current security level.
    
    319 278
        *
    
    320
    -   * @type {Array<object>}
    
    279
    +   * @type {?Element}
    
    321 280
        */
    
    322
    -  _radioOptions: null,
    
    281
    +  _currentEl: null,
    
    323 282
     
    
    324 283
       _populateXUL() {
    
    325
    -    this._customNotification = document.getElementById(
    
    326
    -      "securityLevel-customNotification"
    
    284
    +    this._currentEl = document.getElementById("security-level-current");
    
    285
    +    const changeButton = document.getElementById("security-level-change");
    
    286
    +    const badgeEl = this._currentEl.querySelector(
    
    287
    +      ".security-level-current-badge"
    
    327 288
         );
    
    328
    -    document
    
    329
    -      .getElementById("securityLevel-restoreDefaults")
    
    330
    -      .addEventListener("command", () => {
    
    331
    -        SecurityLevelPrefs.securityCustom = false;
    
    332
    -      });
    
    333 289
     
    
    334
    -    this._radiogroup = document.getElementById("securityLevel-radiogroup");
    
    290
    +    for (const { level, nameId } of [
    
    291
    +      { level: "standard", nameId: "security-level-panel-level-standard" },
    
    292
    +      { level: "safer", nameId: "security-level-panel-level-safer" },
    
    293
    +      { level: "safest", nameId: "security-level-panel-level-safest" },
    
    294
    +      { level: "custom", nameId: "security-level-panel-level-custom" },
    
    295
    +    ]) {
    
    296
    +      // Classes that control visibility:
    
    297
    +      // security-level-current-standard
    
    298
    +      // security-level-current-safer
    
    299
    +      // security-level-current-safest
    
    300
    +      // security-level-current-custom
    
    301
    +      const visibilityClass = `security-level-current-${level}`;
    
    302
    +      const nameEl = document.createElement("div");
    
    303
    +      nameEl.classList.add("security-level-name", visibilityClass);
    
    304
    +      document.l10n.setAttributes(nameEl, nameId);
    
    305
    +
    
    306
    +      const descriptionEl = SecurityLevelUIUtils.createDescriptionElement(
    
    307
    +        level,
    
    308
    +        document
    
    309
    +      );
    
    310
    +      descriptionEl.classList.add(visibilityClass);
    
    335 311
     
    
    336
    -    this._radioOptions = Array.from(
    
    337
    -      this._radiogroup.querySelectorAll(".securityLevel-radio-option"),
    
    338
    -      container => {
    
    339
    -        return { container, radio: container.querySelector("radio") };
    
    340
    -      }
    
    341
    -    );
    
    312
    +      this._currentEl.insertBefore(nameEl, badgeEl);
    
    313
    +      this._currentEl.insertBefore(descriptionEl, changeButton);
    
    314
    +    }
    
    342 315
     
    
    343
    -    this._radiogroup.addEventListener("select", () => {
    
    344
    -      SecurityLevelPrefs.securityLevel = this._radiogroup.value;
    
    316
    +    changeButton.addEventListener("click", () => {
    
    317
    +      this._openDialog();
    
    345 318
         });
    
    346 319
       },
    
    347 320
     
    
    321
    +  _openDialog() {
    
    322
    +    gSubDialog.open(
    
    323
    +      "chrome://browser/content/securitylevel/securityLevelDialog.xhtml",
    
    324
    +      { features: "resizable=yes" }
    
    325
    +    );
    
    326
    +  },
    
    327
    +
    
    348 328
       _configUIFromPrefs() {
    
    349
    -    this._radiogroup.value = SecurityLevelPrefs.securityLevel;
    
    350
    -    const isCustom = SecurityLevelPrefs.securityCustom;
    
    351
    -    this._radiogroup.disabled = isCustom;
    
    352
    -    this._customNotification.hidden = !isCustom;
    
    353
    -    // Have the container's selection CSS class match the selection state of the
    
    354
    -    // radio elements.
    
    355
    -    for (const { container, radio } of this._radioOptions) {
    
    356
    -      container.classList.toggle(
    
    357
    -        "securityLevel-radio-option-selected",
    
    358
    -        radio.selected
    
    359
    -      );
    
    360
    -    }
    
    329
    +    // Set a data-current-level attribute for showing the current security
    
    330
    +    // level, and hiding the rest.
    
    331
    +    this._currentEl.dataset.currentLevel =
    
    332
    +      SecurityLevelPrefs.securityLevelSummary;
    
    361 333
       },
    
    362 334
     
    
    363 335
       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
    -}
    \ No newline at end of file

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

  • browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
    ... ... @@ -18,108 +18,19 @@
    18 18
             data-l10n-id="security-level-preferences-learn-more-link"
    
    19 19
           ></html:a>
    
    20 20
         </description>
    
    21
    -    <hbox
    
    22
    -      id="securityLevel-customNotification"
    
    23
    -      class="info-box-container"
    
    24
    -      flex="1"
    
    25
    -    >
    
    26
    -      <hbox class="info-icon-container">
    
    27
    -        <image class="info-icon securityLevel-custom-warning-icon"/>
    
    28
    -      </hbox>
    
    29
    -      <vbox flex="1">
    
    30
    -        <label
    
    31
    -          id="securityLevel-customHeading"
    
    32
    -          data-l10n-id="security-level-preferences-custom-heading"
    
    33
    -        />
    
    34
    -        <description
    
    35
    -          id="securityLevel-customDescription"
    
    36
    -          data-l10n-id="security-level-summary-custom"
    
    37
    -          flex="1"
    
    38
    -        />
    
    39
    -      </vbox>
    
    40
    -      <hbox align="center">
    
    41
    -        <button
    
    42
    -          id="securityLevel-restoreDefaults"
    
    43
    -          data-l10n-id="security-level-restore-defaults-button"
    
    44
    -        />
    
    45
    -      </hbox>
    
    46
    -    </hbox>
    
    47
    -    <radiogroup id="securityLevel-radiogroup">
    
    48
    -      <vbox class="securityLevel-radio-option">
    
    49
    -        <radio
    
    50
    -          value="standard"
    
    51
    -          data-l10n-id="security-level-preferences-level-standard"
    
    52
    -          aria-describedby="securityLevelSummary-standard"
    
    53
    -        />
    
    54
    -        <vbox id="securityLevelSummary-standard">
    
    55
    -          <description
    
    56
    -            class="summary indent"
    
    57
    -            flex="1"
    
    58
    -            data-l10n-id="security-level-summary-standard"
    
    59
    -          />
    
    60
    -        </vbox>
    
    61
    -      </vbox>
    
    62
    -      <vbox class="securityLevel-radio-option">
    
    63
    -        <!-- NOTE: We point the accessible description to the wrapping vbox
    
    64
    -          - rather than its first description element. This means that when the
    
    65
    -          - securityLevel-descriptionList is shown or hidden, its text content
    
    66
    -          - is included or excluded from the accessible description,
    
    67
    -          - respectively. -->
    
    68
    -        <radio
    
    69
    -          value="safer"
    
    70
    -          data-l10n-id="security-level-preferences-level-safer"
    
    71
    -          aria-describedby="securityLevelSummary-safer"
    
    72
    -        />
    
    73
    -        <vbox id="securityLevelSummary-safer">
    
    74
    -          <description
    
    75
    -            class="summary indent"
    
    76
    -            flex="1"
    
    77
    -            data-l10n-id="security-level-summary-safer"
    
    78
    -          />
    
    79
    -          <vbox class="securityLevel-descriptionList indent">
    
    80
    -            <description
    
    81
    -              class="indent"
    
    82
    -              data-l10n-id="security-level-preferences-bullet-https-only-javascript"
    
    83
    -            />
    
    84
    -            <description
    
    85
    -              class="indent"
    
    86
    -              data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols"
    
    87
    -            />
    
    88
    -            <description
    
    89
    -              class="indent"
    
    90
    -              data-l10n-id="security-level-preferences-bullet-limit-media"
    
    91
    -            />
    
    92
    -          </vbox>
    
    93
    -        </vbox>
    
    94
    -      </vbox>
    
    95
    -      <vbox class="securityLevel-radio-option">
    
    96
    -        <radio
    
    97
    -          value="safest"
    
    98
    -          data-l10n-id="security-level-preferences-level-safest"
    
    99
    -          aria-describedby="securityLevelSummary-safest"
    
    100
    -        />
    
    101
    -        <vbox id="securityLevelSummary-safest">
    
    102
    -          <description
    
    103
    -            class="summary indent"
    
    104
    -            flex="1"
    
    105
    -            data-l10n-id="security-level-summary-safest"
    
    106
    -          />
    
    107
    -          <vbox class="securityLevel-descriptionList indent">
    
    108
    -            <description
    
    109
    -              class="indent"
    
    110
    -              data-l10n-id="security-level-preferences-bullet-disabled-javascript"
    
    111
    -            />
    
    112
    -            <description
    
    113
    -              class="indent"
    
    114
    -              data-l10n-id="security-level-preferences-bullet-limit-font-and-symbols-and-images"
    
    115
    -            />
    
    116
    -            <description
    
    117
    -              class="indent"
    
    118
    -              data-l10n-id="security-level-preferences-bullet-limit-media"
    
    119
    -            />
    
    120
    -          </vbox>
    
    121
    -        </vbox>
    
    122
    -      </vbox>
    
    123
    -    </radiogroup>
    
    21
    +    <html:div id="security-level-current" class="security-level-grid">
    
    22
    +      <html:img
    
    23
    +        class="security-level-icon"
    
    24
    +        alt=""
    
    25
    +      />
    
    26
    +      <html:div
    
    27
    +        class="security-level-current-badge"
    
    28
    +        data-l10n-id="security-level-preferences-current-badge"
    
    29
    +      ></html:div>
    
    30
    +      <html:button
    
    31
    +        id="security-level-change"
    
    32
    +        data-l10n-id="security-level-preferences-change-button"
    
    33
    +      ></html:button>
    
    34
    +    </html:div>
    
    124 35
       </vbox>
    
    125 36
     </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
    +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
    
    2
    +
    
    3
    +const lazy = {};
    
    4
    +
    
    5
    +XPCOMUtils.defineLazyModuleGetters(lazy, {
    
    6
    +  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
    
    7
    +});
    
    8
    +
    
    9
    +ChromeUtils.defineESModuleGetters(lazy, {
    
    10
    +  SecurityLevelPrefs: "resource://gre/modules/SecurityLevel.sys.mjs",
    
    11
    +});
    
    12
    +
    
    13
    +ChromeUtils.defineLazyGetter(lazy, "NotificationStrings", function () {
    
    14
    +  return new Localization([
    
    15
    +    "branding/brand.ftl",
    
    16
    +    "toolkit/global/base-browser.ftl",
    
    17
    +  ]);
    
    18
    +});
    
    19
    +
    
    20
    +/**
    
    21
    + * Interface for showing the security level restart notification on desktop.
    
    22
    + */
    
    23
    +export const SecurityLevelRestartNotification = {
    
    24
    +  /**
    
    25
    +   * Whether we have already been initialised.
    
    26
    +   *
    
    27
    +   * @type {boolean}
    
    28
    +   */
    
    29
    +  _initialized: false,
    
    30
    +
    
    31
    +  /**
    
    32
    +   * Called when the UI is ready to show a notification.
    
    33
    +   */
    
    34
    +  ready() {
    
    35
    +    if (this._initialized) {
    
    36
    +      return;
    
    37
    +    }
    
    38
    +    this._initialized = true;
    
    39
    +    lazy.SecurityLevelPrefs.setRestartNotificationHandler(this);
    
    40
    +  },
    
    41
    +
    
    42
    +  /**
    
    43
    +   * Show the restart notification, and perform the restart if the user agrees.
    
    44
    +   */
    
    45
    +  async tryRestartBrowser() {
    
    46
    +    const [titleText, bodyText, primaryButtonText, secondaryButtonText] =
    
    47
    +      await lazy.NotificationStrings.formatValues([
    
    48
    +        { id: "security-level-restart-prompt-title" },
    
    49
    +        { id: "security-level-restart-prompt-body" },
    
    50
    +        { id: "security-level-restart-prompt-button-restart" },
    
    51
    +        { id: "security-level-restart-prompt-button-ignore" },
    
    52
    +      ]);
    
    53
    +    const buttonFlags =
    
    54
    +      Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
    
    55
    +      Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1;
    
    56
    +
    
    57
    +    const propBag = await Services.prompt.asyncConfirmEx(
    
    58
    +      lazy.BrowserWindowTracker.getTopWindow()?.browsingContext ?? null,
    
    59
    +      Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
    
    60
    +      titleText,
    
    61
    +      bodyText,
    
    62
    +      buttonFlags,
    
    63
    +      primaryButtonText,
    
    64
    +      secondaryButtonText,
    
    65
    +      null,
    
    66
    +      null,
    
    67
    +      null,
    
    68
    +      {}
    
    69
    +    );
    
    70
    +
    
    71
    +    if (propBag.get("buttonNumClicked") === 0) {
    
    72
    +      Services.startup.quit(
    
    73
    +        Services.startup.eAttemptQuit | Services.startup.eRestart
    
    74
    +      );
    
    75
    +    }
    
    76
    +  },
    
    77
    +};

  • browser/modules/moz.build
    ... ... @@ -142,6 +142,7 @@ EXTRA_JS_MODULES += [
    142 142
         "PingCentre.jsm",
    
    143 143
         "ProcessHangMonitor.jsm",
    
    144 144
         "Sanitizer.sys.mjs",
    
    145
    +    "SecurityLevelRestartNotification.sys.mjs",
    
    145 146
         "SelectionChangedMenulist.jsm",
    
    146 147
         "SiteDataManager.jsm",
    
    147 148
         "SitePermissions.sys.mjs",
    

  • toolkit/components/extensions/ExtensionParent.sys.mjs
    ... ... @@ -2275,6 +2275,7 @@ async function torSendExtensionMessage(extensionId, message) {
    2275 2275
       const result = await ProxyMessenger.conduit.castRuntimeMessage("messenger", {
    
    2276 2276
         extensionId,
    
    2277 2277
         holder: new StructuredCloneHolder("torSendExtensionMessage", null, message),
    
    2278
    +    query: true,
    
    2278 2279
         firstResponse: true,
    
    2279 2280
         sender: {
    
    2280 2281
           id: extensionId,
    

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

  • toolkit/locales/en-US/toolkit/global/base-browser.ftl
    ... ... @@ -118,10 +118,6 @@ security-level-toolbar-button-custom =
    118 118
     
    
    119 119
     # Uses sentence case in English (US).
    
    120 120
     security-level-panel-heading = Security level
    
    121
    -
    
    122
    -security-level-panel-level-standard = Standard
    
    123
    -security-level-panel-level-safer = Safer
    
    124
    -security-level-panel-level-safest = Safest
    
    125 121
     security-level-panel-learn-more-link = Learn more
    
    126 122
     # Button to open security level settings.
    
    127 123
     security-level-panel-open-settings-button = Settings…
    
    ... ... @@ -131,6 +127,19 @@ security-level-panel-open-settings-button = Settings…
    131 127
     security-level-preferences-heading = Security Level
    
    132 128
     security-level-preferences-overview = Disable certain web features that can be used to attack your security and anonymity.
    
    133 129
     security-level-preferences-learn-more-link = Learn more
    
    130
    +# Text for a badge that labels the currently active security level.
    
    131
    +# 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)".
    
    132
    +security-level-preferences-current-badge = <span>(</span>Current level<span>)</span>
    
    133
    +security-level-preferences-change-button = Change…
    
    134
    +
    
    135
    +## Security level settings dialog.
    
    136
    +
    
    137
    +security-level-dialog-window =
    
    138
    +    .title = Change security level
    
    139
    +
    
    140
    +# '-brand-short-name' is the localized browser name, like "Tor Browser".
    
    141
    +security-level-dialog-restart-description = You will need to restart { -brand-short-name } to apply any changes. This will close all windows and tabs.
    
    142
    +
    
    134 143
     security-level-preferences-level-standard =
    
    135 144
         .label = Standard
    
    136 145
     security-level-preferences-level-safer =
    
    ... ... @@ -138,6 +147,16 @@ security-level-preferences-level-safer =
    138 147
     security-level-preferences-level-safest =
    
    139 148
         .label = Safest
    
    140 149
     
    
    150
    +security-level-dialog-save-restart =
    
    151
    +    .label = Save and restart
    
    152
    +
    
    153
    +## Security level names shown in the security panel and settings.
    
    154
    +
    
    155
    +security-level-panel-level-standard = Standard
    
    156
    +security-level-panel-level-safer = Safer
    
    157
    +security-level-panel-level-safest = Safest
    
    158
    +security-level-panel-level-custom = Custom
    
    159
    +
    
    141 160
     ## Security level summaries shown in security panel and settings.
    
    142 161
     
    
    143 162
     security-level-summary-standard = All browser and website features are enabled.
    
    ... ... @@ -156,16 +175,16 @@ security-level-preferences-bullet-limit-font-and-symbols-and-images = Some fonts
    156 175
     ## Custom security level.
    
    157 176
     ## Some custom preferences configuration has placed the user outside one of the standard three levels.
    
    158 177
     
    
    159
    -# Shown in the security level panel as an orange badge next to the expected level.
    
    160
    -security-level-panel-custom-badge = Custom
    
    161
    -# Shown in the security level settings in a warning box.
    
    162
    -security-level-preferences-custom-heading = Custom security level configured
    
    163 178
     # Description of custom state and recommended action.
    
    164 179
     # Shown in the security level panel and settings.
    
    165 180
     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.
    
    166
    -# Button to undo custom changes to the security level and place the user in one of the standard security levels.
    
    167
    -# Shown in the security level panel and settings.
    
    168
    -security-level-restore-defaults-button = Restore defaults
    
    181
    +
    
    182
    +## Security level restart prompt.
    
    183
    +
    
    184
    +security-level-restart-prompt-title = Your security level settings require a restart
    
    185
    +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.
    
    186
    +security-level-restart-prompt-button-restart = Restart
    
    187
    +security-level-restart-prompt-button-ignore = Ignore
    
    169 188
     
    
    170 189
     ## Notification for dropped operating system support.
    
    171 190