tor-browser

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

browser_notification_unexpected_script.js (16565B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/
      3 */
      4 
      5 const TEST_URL =
      6  getRootDirectory(gTestPath).replace(
      7    "chrome://mochitests/content",
      8    "https://example.com"
      9  ) + "empty_file.html";
     10 
     11 /*
     12 * Register cleanup function to reset prefs after other tasks have run.
     13 */
     14 
     15 add_setup(async function () {
     16  // Per browser_enable_DRM_prompt.js , SpecialPowers.pushPrefEnv has
     17  // problems with buttons on the notification bar toggling the prefs.
     18  // So manually reset the prefs the UI we're testing toggles.
     19 
     20  // This preference is needed because tests run with
     21  // xpinstall.signatures.required=false to install a helper extension
     22  // This triggers the JSHacks exemption, which we need to disable
     23  // for our test to work.
     24  Services.prefs.setBoolPref(
     25    "security.parent_unrestricted_js_loads.skip_jshacks",
     26    true
     27  );
     28  // This preference is needed to prevent the Opt builds from crashing.
     29  // Currently we allow a single crash from an unexpected script load,
     30  // to try and understand the problem better via crash reports.
     31  // (It has not worked.)
     32  let originalCrashes = Services.prefs.getIntPref(
     33    "security.crash_tracking.js_load_1.maxCrashes",
     34    0
     35  );
     36  Services.prefs.setIntPref("security.crash_tracking.js_load_1.maxCrashes", 0);
     37 
     38  registerCleanupFunction(function () {
     39    Services.prefs.setBoolPref(
     40      "security.block_parent_unrestricted_js_loads.temporary",
     41      false
     42    );
     43    Services.prefs.setBoolPref(
     44      "security.allow_parent_unrestricted_js_loads",
     45      false
     46    );
     47    Services.prefs.setBoolPref(
     48      "security.parent_unrestricted_js_loads.skip_jshacks",
     49      false
     50    );
     51    Services.prefs.setIntPref(
     52      "security.crash_tracking.js_load_1.maxCrashes",
     53      originalCrashes
     54    );
     55  });
     56 });
     57 
     58 async function runWorkflow(actions_to_take, expected_results) {
     59  // ============================================================
     60  // ============================================================
     61  // Reset things for each invoation
     62  Services.obs.notifyObservers(
     63    null,
     64    "UnexpectedJavaScriptLoad-ResetNotification"
     65  );
     66  Services.fog.testResetFOG();
     67 
     68  await SpecialPowers.pushPrefEnv({
     69    set: [
     70      [
     71        "datareporting.healthreport.uploadEnabled",
     72        actions_to_take.enable_telemetry,
     73      ],
     74    ],
     75  });
     76 
     77  // ============================================================
     78  // ============================================================
     79  // Set up expected results
     80  expected_results = expected_results || {};
     81 
     82  // All of these functions take an integer indicating the number of telemetry events
     83  // They return true if that number is expected.
     84  expected_results.inforBarDismissed = function (num_events) {
     85    return num_events == 0;
     86  };
     87 
     88  expected_results.dialogDismissed = function (num_events) {
     89    return num_events == 0;
     90  };
     91 
     92  expected_results.moreInfoOpened = function (num_events) {
     93    return num_events == 0;
     94  };
     95 
     96  if (actions_to_take.click_block === true) {
     97    expected_results.first_paragraph_text =
     98      "unexpected-script-load-detail-1-block";
     99    expected_results.report_checkbox_checked_by_default = true;
    100    expected_results.block_pref_set = true;
    101    expected_results.allow_pref_set = false;
    102    expected_results.scriptBlockedOpened = function (num_events) {
    103      return num_events > 0;
    104    };
    105    expected_results.scriptAllowedOpened = function (num_events) {
    106      return num_events == 0;
    107    };
    108    expected_results.scriptBlocked = function (num_events) {
    109      return num_events > 0;
    110    };
    111    expected_results.scriptAllowed = function (num_events) {
    112      return num_events == 0;
    113    };
    114  } else {
    115    expected_results.first_paragraph_text =
    116      "unexpected-script-load-detail-1-allow";
    117    expected_results.report_checkbox_checked_by_default = false;
    118    expected_results.block_pref_set = false;
    119    expected_results.allow_pref_set = true;
    120    expected_results.scriptBlockedOpened = function (num_events) {
    121      return num_events == 0;
    122    };
    123    expected_results.scriptAllowedOpened = function (num_events) {
    124      return num_events > 0;
    125    };
    126    expected_results.scriptBlocked = function (num_events) {
    127      return num_events == 0;
    128    };
    129    expected_results.scriptAllowed = function (num_events) {
    130      return num_events > 0;
    131    };
    132  }
    133 
    134  if (actions_to_take.report) {
    135    expected_results.should_have_report = true;
    136  } else {
    137    expected_results.should_have_report = false;
    138  }
    139 
    140  if (actions_to_take.report_email) {
    141    expected_results.should_have_report_email = true;
    142  } else {
    143    expected_results.should_have_report_email = false;
    144  }
    145 
    146  // ============================================================
    147  // ============================================================
    148  let window = BrowserWindowTracker.getTopWindow();
    149  let notificationShownPromise = BrowserTestUtils.waitForGlobalNotificationBar(
    150    window,
    151    "unexpected-script-notification"
    152  );
    153 
    154  // ============================================================
    155  // ============================================================
    156  // Main body of test - enclosed in a function so we can put it
    157  // first (in the file) before the telemetry assertions
    158  let runTest = async () => {
    159    // ============================================================
    160    // Trigger the notification
    161    let sandbox = Cu.Sandbox(null);
    162    try {
    163      // This will trigger the unexpected script load notification.
    164      // On Nightly, where we enforce the restriction, it will also
    165      // cause an error, necessitating the try/catch.
    166      Cu.evalInSandbox(
    167        "let x = 1",
    168        sandbox,
    169        "1.8",
    170        "https://example.net/script.js",
    171        1
    172      );
    173    } catch (e) {}
    174 
    175    let notification = await notificationShownPromise;
    176 
    177    // ============================================================
    178    // Verify the notification bar showed.
    179    ok(notification, "Notification should be visible");
    180    is(
    181      notification.getAttribute("value"),
    182      "unexpected-script-notification",
    183      "Should be showing the right notification"
    184    );
    185 
    186    // ============================================================
    187    // Verify the buttons are there.
    188    let buttons = notification.buttonContainer.querySelectorAll(
    189      ".notification-button"
    190    );
    191    is(buttons.length, 2, "Should have two buttons.");
    192    let learnMoreLinks = notification.querySelectorAll(".notification-link");
    193    is(learnMoreLinks.length, 1, "Should have one learn more link.");
    194 
    195    // ============================================================
    196    // Open the dialog by clicking the Block Button
    197    let dialogShownPromise = new Promise(resolve => {
    198      BrowserTestUtils.waitForEvent(
    199        window.document.getElementById("window-modal-dialog"),
    200        "dialogopen",
    201        false,
    202        () => {
    203          return true;
    204        }
    205      ).then(event => {
    206        resolve(event.originalTarget);
    207      });
    208    });
    209 
    210    // On the notification bar, the order of the buttons is constant: always [Allow] [Block]
    211    // it only changes on the dialog, but we choose buttons there based on id
    212    let buttonIndex = actions_to_take.click_block ? 1 : 0;
    213    buttons[buttonIndex].click();
    214 
    215    await dialogShownPromise;
    216 
    217    // ============================================================
    218    // Confirm we opened the dialog
    219    is(
    220      window?.gDialogBox?.dialog?._openedURL,
    221      "chrome://browser/content/security/unexpectedScriptLoad.xhtml",
    222      "Should have an open dialog"
    223    );
    224 
    225    // ============================================================
    226    // Verify the dialog says the right thing
    227    let firstParagraph =
    228      window.gDialogBox.dialog._frame.contentDocument.getElementById(
    229        "unexpected-script-load-detail-1"
    230      );
    231    isnot(firstParagraph, null, "Should have one detail paragraph.");
    232    is(
    233      firstParagraph.getAttribute("data-l10n-id"),
    234      expected_results.first_paragraph_text,
    235      "Should have the right detail text."
    236    );
    237 
    238    // ============================================================
    239    // Verify the telemetry message
    240    let telemetryMessage =
    241      window.gDialogBox.dialog._frame.contentDocument.getElementById(
    242        "telemetry-disabled-message"
    243      );
    244    isnot(telemetryMessage, null, "Should have one telemetry message.");
    245    is(
    246      telemetryMessage.hasAttribute("hidden"),
    247      !!actions_to_take.enable_telemetry,
    248      `Telemetry Message should ${actions_to_take.enable_telemetry ? "" : "not"} be visible`
    249    );
    250 
    251    // ============================================================
    252    // Verify the report checkbox is checked
    253    let reportCheckbox =
    254      window.gDialogBox.dialog._frame.contentDocument.getElementById(
    255        "reportCheckbox"
    256      );
    257    isnot(reportCheckbox, null, "Should have one report checkbox.");
    258    is(
    259      reportCheckbox.checked,
    260      expected_results.report_checkbox_checked_by_default &&
    261        actions_to_take.enable_telemetry,
    262      `Report checkbox should ${expected_results.report_checkbox_checked_by_default && actions_to_take.enable_telemetry ? "" : "not"} be checked by default`
    263    );
    264 
    265    // ============================================================
    266    // Fill in the checkboxes and email field as appropriate
    267    if (actions_to_take.report) {
    268      reportCheckbox.checked = true;
    269    }
    270 
    271    let emailCheckbox =
    272      window.gDialogBox.dialog._frame.contentDocument.getElementById(
    273        "emailCheckbox"
    274      );
    275    isnot(emailCheckbox, null, "Should have one email checkbox.");
    276    is(
    277      emailCheckbox.checked,
    278      false,
    279      "Email checkbox should not be checked by default"
    280    );
    281 
    282    if (actions_to_take.report_email) {
    283      emailCheckbox.checked = true;
    284      let emailField =
    285        window.gDialogBox.dialog._frame.contentDocument.getElementById(
    286          "emailInput"
    287        );
    288      isnot(emailField, null, "Should have one email field.");
    289      emailField.value = "test@example.com";
    290    }
    291 
    292    // ============================================================
    293    // Find the Buttons
    294    let blockButton =
    295      window.gDialogBox.dialog._frame.contentDocument.getElementById(
    296        "block-button"
    297      );
    298    isnot(blockButton, null, "Should have one block button.");
    299 
    300    let allowButton =
    301      window.gDialogBox.dialog._frame.contentDocument.getElementById(
    302        "allow-button"
    303      );
    304    isnot(allowButton, null, "Should have one allow button.");
    305 
    306    // ============================================================
    307    // Prepare to close the dialog
    308    let dialogClosePromise = new Promise(resolve => {
    309      BrowserTestUtils.waitForEvent(
    310        window.document.getElementById("window-modal-dialog"),
    311        "dialogclose",
    312        false,
    313        () => {
    314          return true;
    315        }
    316      ).then(event => {
    317        resolve(event.originalTarget);
    318      });
    319    });
    320 
    321    // ============================================================
    322    // Take an action and close the dialog
    323    if (actions_to_take.click_block) {
    324      blockButton.click();
    325    } else {
    326      allowButton.click();
    327    }
    328 
    329    await dialogClosePromise;
    330 
    331    // ============================================================
    332    // Confirm the action did the right thing
    333    let isBlocked = Services.prefs.getBoolPref(
    334      "security.block_parent_unrestricted_js_loads.temporary",
    335      false
    336    );
    337    is(
    338      isBlocked,
    339      expected_results.block_pref_set,
    340      `Should ${expected_results.block_pref_set ? "" : "not"} have set the pref`
    341    );
    342 
    343    let isAllowed = Services.prefs.getBoolPref(
    344      "security.allow_parent_unrestricted_js_loads",
    345      false
    346    );
    347    is(
    348      isAllowed,
    349      expected_results.allow_pref_set,
    350      `Should ${expected_results.allow_pref_set ? "" : "not"} have set the allow pref`
    351    );
    352  };
    353 
    354  // ============================================================
    355  // ============================================================
    356  // Now test the telemetry.
    357  //   (in actually this function call runs the test itself, as the above
    358  //    is all just a function definition, but it's awkward that the order
    359  //    has to be this way, so we're jumping through a hoop so you can
    360  //    read the file in the same order it executes in.)
    361  await GleanPings.unexpectedScriptLoad.testSubmission(async () => {
    362    isnot(
    363      // We always see this one
    364      Glean.unexpectedScriptLoad.infobarShown.testGetValue()?.length ?? 0,
    365      0,
    366      "Should have recorded an infobarShown telemetry event"
    367    );
    368    ok(
    369      expected_results.inforBarDismissed(
    370        Glean.unexpectedScriptLoad.infobarDismissed.testGetValue()?.length ?? 0
    371      ),
    372      "InfoBarDismissed telemetry event"
    373    );
    374    ok(
    375      expected_results.dialogDismissed(
    376        Glean.unexpectedScriptLoad.dialogDismissed.testGetValue()?.length ?? 0
    377      ),
    378      "DialogDismissed telemetry event"
    379    );
    380    let scriptBlockedOpenedSeen =
    381      Glean.unexpectedScriptLoad.scriptBlockedOpened.testGetValue()?.length ??
    382      0;
    383    ok(
    384      expected_results.scriptBlockedOpened(scriptBlockedOpenedSeen),
    385      `ScriptBlockedOpened telemetry event: saw ${scriptBlockedOpenedSeen}`
    386    );
    387    let scriptAllowedOpenedSeen =
    388      Glean.unexpectedScriptLoad.scriptAllowedOpened.testGetValue()?.length ??
    389      0;
    390    ok(
    391      expected_results.scriptAllowedOpened(scriptAllowedOpenedSeen),
    392      `scriptAllowedOpened telemetry event: saw ${scriptAllowedOpenedSeen}`
    393    );
    394    ok(
    395      expected_results.moreInfoOpened(
    396        Glean.unexpectedScriptLoad.moreInfoOpened.testGetValue()?.length ?? 0
    397      ),
    398      "moreInfoOpened telemetry event"
    399    );
    400    let scriptBlockedSeen =
    401      Glean.unexpectedScriptLoad.scriptBlocked.testGetValue()?.length ?? 0;
    402    ok(
    403      expected_results.scriptBlocked(scriptBlockedSeen),
    404      `scriptBlocked telemetry event: saw ${scriptBlockedSeen}`
    405    );
    406    let scriptAllowedSeen =
    407      Glean.unexpectedScriptLoad.scriptAllowed.testGetValue()?.length ?? 0;
    408    ok(
    409      expected_results.scriptAllowed(scriptAllowedSeen),
    410      `scriptAllowed telemetry event: saw ${scriptAllowedSeen}`
    411    );
    412    let script_reported_event =
    413      Glean.unexpectedScriptLoad.scriptReported.testGetValue();
    414    if (script_reported_event && expected_results.should_have_report) {
    415      script_reported_event = script_reported_event[0];
    416      is(
    417        script_reported_event.extra.script_url,
    418        "https://example.net/script.js",
    419        "Should have recorded the script URL"
    420      );
    421      if (actions_to_take.report_email) {
    422        is(
    423          script_reported_event.extra.user_email,
    424          "test@example.com",
    425          "Should have recorded the email address"
    426        );
    427      } else {
    428        is(
    429          script_reported_event.extra.user_email,
    430          undefined,
    431          "Should not have recorded the email address"
    432        );
    433      }
    434      ok(true, "Should have recorded a scriptReported telemetry event");
    435    } else if (script_reported_event && !expected_results.should_have_report) {
    436      ok(false, "Should not have recorded a scriptReported telemetry event");
    437    } else if (!script_reported_event && expected_results.should_have_report) {
    438      ok(false, "Should have recorded a scriptReported telemetry event");
    439    } else if (!script_reported_event && !expected_results.should_have_report) {
    440      ok(true, "Should not have recorded a scriptReported telemetry event");
    441    } else {
    442      ok(false, "How did we get here?");
    443    }
    444  }, runTest);
    445 
    446  // Cleanup
    447  Services.prefs.clearUserPref(
    448    "security.block_parent_unrestricted_js_loads.temporary"
    449  );
    450  Services.prefs.clearUserPref("security.allow_parent_unrestricted_js_loads");
    451 
    452  await SpecialPowers.popPrefEnv();
    453 }
    454 
    455 add_task(async function test_block_workflow() {
    456  await runWorkflow({
    457    click_block: true,
    458    report: true,
    459    report_email: true,
    460    enable_telemetry: true,
    461  });
    462 });
    463 
    464 add_task(async function test_allow_workflow() {
    465  await runWorkflow({
    466    click_block: false,
    467    report: true,
    468    report_email: true,
    469  });
    470 });
    471 
    472 add_task(async function test_allow_with_no_report_workflow() {
    473  await runWorkflow({
    474    click_block: false,
    475    report: false,
    476    report_email: false,
    477  });
    478 });
    479 
    480 add_task(async function test_block_with_no_report_email_workflow() {
    481  await runWorkflow({
    482    click_block: true,
    483    report: true,
    484    report_email: false,
    485    enable_telemetry: true,
    486  });
    487 });
    488 
    489 add_task(async function test_block_workflow_no_telemetry() {
    490  await runWorkflow({
    491    click_block: true,
    492    report: true,
    493    report_email: true,
    494    enable_telemetry: false,
    495  });
    496 });