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

Commits:

19 changed files:

Changes:

  • browser/components/BrowserGlue.sys.mjs
    ... ... @@ -82,6 +82,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
    82 82
       ScreenshotsUtils: "resource:///modules/ScreenshotsUtils.sys.mjs",
    
    83 83
       SearchSERPCategorization: "resource:///modules/SearchSERPTelemetry.sys.mjs",
    
    84 84
       SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.sys.mjs",
    
    85
    +  SecurityLevelRestartNotification:
    
    86
    +    "resource:///modules/SecurityLevelRestartNotification.sys.mjs",
    
    85 87
       SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
    
    86 88
       SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",
    
    87 89
       ShellService: "resource:///modules/ShellService.sys.mjs",
    
    ... ... @@ -2030,6 +2032,8 @@ BrowserGlue.prototype = {
    2030 2032
     
    
    2031 2033
         lazy.DragDropFilter.init();
    
    2032 2034
     
    
    2035
    +    lazy.SecurityLevelRestartNotification.ready();
    
    2036
    +
    
    2033 2037
         lazy.TorProviderBuilder.firstWindowLoaded();
    
    2034 2038
     
    
    2035 2039
         lazy.TorSettingsNotification.ready();
    

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

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

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

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

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

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

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

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

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

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

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

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

  • browser/modules/moz.build
    ... ... @@ -126,6 +126,7 @@ EXTRA_JS_MODULES += [
    126 126
         "PermissionUI.sys.mjs",
    
    127 127
         "ProcessHangMonitor.sys.mjs",
    
    128 128
         "Sanitizer.sys.mjs",
    
    129
    +    "SecurityLevelRestartNotification.sys.mjs",
    
    129 130
         "SelectionChangedMenulist.sys.mjs",
    
    130 131
         "SiteDataManager.sys.mjs",
    
    131 132
         "SitePermissions.sys.mjs",
    

  • mobile/android/geckoview/src/main/java/org/mozilla/geckoview/TorAndroidIntegration.java
    ... ... @@ -49,6 +49,9 @@ public class TorAndroidIntegration implements BundleEventListener {
    49 49
       private static final String EVENT_SETTINGS_CHANGED = "GeckoView:Tor:SettingsChanged";
    
    50 50
     
    
    51 51
       // Events we emit
    
    52
    +  // TODO: Integrate the security level API. tor-browser#43820
    
    53
    +  private static final String EVENT_SECURITY_LEVEL_GET = "GeckoView:Tor:SecurityLevelGet";
    
    54
    +  private static final String EVENT_SECURITY_LEVEL_SET_BEFORE_RESTART = "GeckoView:Tor:SecurityLevelSetBeforeRestart";
    
    52 55
       private static final String EVENT_SETTINGS_GET = "GeckoView:Tor:SettingsGet";
    
    53 56
       private static final String EVENT_SETTINGS_SET = "GeckoView:Tor:SettingsSet";
    
    54 57
       private static final String EVENT_BOOTSTRAP_BEGIN = "GeckoView:Tor:BootstrapBegin";
    

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

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

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

  • toolkit/modules/TorAndroidIntegration.sys.mjs
    ... ... @@ -11,6 +11,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
    11 11
       TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
    
    12 12
       TorProviderTopics: "resource://gre/modules/TorProviderBuilder.sys.mjs",
    
    13 13
       TorSettings: "resource://gre/modules/TorSettings.sys.mjs",
    
    14
    +  SecurityLevelPrefs: "resources://gre/modules/SecurityLevel.sys.mjs",
    
    14 15
     });
    
    15 16
     
    
    16 17
     const Prefs = Object.freeze({
    
    ... ... @@ -34,6 +35,8 @@ const EmittedEvents = Object.freeze({
    34 35
     });
    
    35 36
     
    
    36 37
     const ListenedEvents = Object.freeze({
    
    38
    +  securityLevelGet: "GeckoView:Tor:SecurityLevelGet",
    
    39
    +  securityLevelSetBeforeRestart: "GeckoView:Tor:SecurityLevelSetBeforeRestart",
    
    37 40
       settingsGet: "GeckoView:Tor:SettingsGet",
    
    38 41
       // The data is passed directly to TorSettings.
    
    39 42
       settingsSet: "GeckoView:Tor:SettingsSet",
    
    ... ... @@ -171,6 +174,20 @@ class TorAndroidIntegrationImpl {
    171 174
         logger.debug(`Received event ${event}`, data);
    
    172 175
         try {
    
    173 176
           switch (event) {
    
    177
    +        case ListenedEvents.securityLevelGet:
    
    178
    +          // "standard"/"safer"/"safest"
    
    179
    +          // TODO: Switch to securityLevelSummary to allow android to handle
    
    180
    +          // "custom" security level. tor-browser#43819
    
    181
    +          callback?.onSuccess(lazy.SecurityLevelPrefs.securityLevel);
    
    182
    +          break;
    
    183
    +        case ListenedEvents.securityLevelSetBeforeRestart:
    
    184
    +          lazy.SecurityLevelPrefs.setSecurityLevelBeforeRestart(data.levelName);
    
    185
    +          // Let the caller know that the setting is applied and the browser
    
    186
    +          // should be restarted now.
    
    187
    +          // NOTE: The caller must wait for this callback before triggering
    
    188
    +          // the restart.
    
    189
    +          callback?.onSuccess();
    
    190
    +          break;
    
    174 191
             case ListenedEvents.settingsGet:
    
    175 192
               callback?.onSuccess(lazy.TorSettings.getSettings());
    
    176 193
               return;