tor-browser

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

browser_decoderDoctor.js (11130B)


      1 "use strict";
      2 
      3 // 'data' contains the notification data object:
      4 // - data.type must be provided.
      5 // - data.isSolved and data.decoderDoctorReportId will be added if not provided
      6 //   (false and "testReportId" resp.)
      7 // - Other fields (e.g.: data.formats) may be provided as needed.
      8 // 'notificationMessage': Expected message in the notification bar.
      9 //   Falsy if nothing is expected after the notification is sent, in which case
     10 //   we won't have further checks, so the following parameters are not needed.
     11 // 'label': Expected button label. Falsy if no button is expected, in which case
     12 //   we won't have further checks, so the following parameters are not needed.
     13 // 'accessKey': Expected access key for the button.
     14 // 'tabChecker': function(openedTab) called with the opened tab that resulted
     15 //   from clicking the button.
     16 async function test_decoder_doctor_notification(
     17  data,
     18  notificationMessage,
     19  label,
     20  accessKey,
     21  isLink,
     22  tabChecker
     23 ) {
     24  const TEST_URL = "https://example.org";
     25  // A helper closure to test notifications in same or different origins.
     26  // 'test_cross_origin' is used to determine if the observers used in the test
     27  // are notified in the same frame (when false) or in a cross origin iframe
     28  // (when true).
     29  async function create_tab_and_test(test_cross_origin) {
     30    await BrowserTestUtils.withNewTab(
     31      { gBrowser, url: TEST_URL },
     32      async function (browser) {
     33        let awaitNotificationBar;
     34        if (notificationMessage) {
     35          awaitNotificationBar = BrowserTestUtils.waitForNotificationBar(
     36            gBrowser,
     37            browser,
     38            "decoder-doctor-notification"
     39          );
     40        }
     41 
     42        await SpecialPowers.spawn(
     43          browser,
     44          [data, test_cross_origin],
     45          /* eslint-disable-next-line no-shadow */
     46          async function (data, test_cross_origin) {
     47            if (!test_cross_origin) {
     48              // Notify in the same origin.
     49              Services.obs.notifyObservers(
     50                content.window,
     51                "decoder-doctor-notification",
     52                JSON.stringify(data)
     53              );
     54              return;
     55              // Done notifying in the same origin.
     56            }
     57 
     58            // Notify in a different origin.
     59            const CROSS_ORIGIN_URL = "https://example.com";
     60            let frame = content.document.createElement("iframe");
     61            frame.src = CROSS_ORIGIN_URL;
     62            await new Promise(resolve => {
     63              frame.addEventListener("load", () => {
     64                resolve();
     65              });
     66              content.document.body.appendChild(frame);
     67            });
     68 
     69            await content.SpecialPowers.spawn(
     70              frame,
     71              [data],
     72              async function (
     73                /* eslint-disable-next-line no-shadow */
     74                data
     75              ) {
     76                Services.obs.notifyObservers(
     77                  content.window,
     78                  "decoder-doctor-notification",
     79                  JSON.stringify(data)
     80                );
     81              }
     82            );
     83            // Done notifying in a different origin.
     84          }
     85        );
     86 
     87        if (!notificationMessage) {
     88          ok(
     89            true,
     90            "Tested notifying observers with a nonsensical message, no effects expected"
     91          );
     92          return;
     93        }
     94 
     95        let notification;
     96        try {
     97          notification = await awaitNotificationBar;
     98        } catch (ex) {
     99          ok(false, ex);
    100          return;
    101        }
    102        ok(notification, "Got decoder-doctor-notification notification");
    103        if (label?.l10nId) {
    104          // Without the following statement, the
    105          // test_cannot_initialize_pulseaudio
    106          // will permanently fail on Linux.
    107          if (label.l10nId === "moz-support-link-text") {
    108            MozXULElement.insertFTLIfNeeded(
    109              "toolkit/global/mozSupportLink.ftl"
    110            );
    111          }
    112          label = await document.l10n.formatValue(label.l10nId);
    113        }
    114        if (isLink) {
    115          let link = notification.supportLinkEls[0];
    116          if (link) {
    117            // Seems to be a Windows specific quirk, but without this
    118            // mutation observer the notification.messageText.textContent
    119            // will not be updated. This will cause consistent failures
    120            // on Windows.
    121            await BrowserTestUtils.waitForMutationCondition(
    122              link,
    123              { childList: true },
    124              () => link.textContent.trim()
    125            );
    126          }
    127        }
    128        is(
    129          notification.messageText.textContent.trim(),
    130          notificationMessage,
    131          "notification message should match expectation"
    132        );
    133 
    134        let button = notification.buttonContainer.querySelector("button");
    135        let link = notification.supportLinkEls[0];
    136        if (!label) {
    137          ok(!button, "There should not be a button");
    138          ok(!link, "There should not be a link");
    139          return;
    140        }
    141 
    142        if (isLink) {
    143          ok(!button, "There should not be a button");
    144          is(link.textContent, label, `notification link should be '${label}'`);
    145          ok(
    146            !link.hasAttribute("accesskey"),
    147            "notification link should not have accesskey"
    148          );
    149        } else {
    150          ok(!link, "There should not be a link");
    151          is(
    152            button.getAttribute("label"),
    153            label,
    154            `notification button should be '${label}'`
    155          );
    156          is(
    157            button.getAttribute("accesskey"),
    158            accessKey,
    159            "notification button should have accesskey"
    160          );
    161        }
    162 
    163        if (!tabChecker) {
    164          ok(false, "Test implementation error: Missing tabChecker");
    165          return;
    166        }
    167        let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser);
    168        if (button) {
    169          button.click();
    170        } else {
    171          link.click();
    172        }
    173        let openedTab = await awaitNewTab;
    174        tabChecker(openedTab);
    175        BrowserTestUtils.removeTab(openedTab);
    176      }
    177    );
    178  }
    179 
    180  if (typeof data.type === "undefined") {
    181    ok(false, "Test implementation error: data.type must be provided");
    182    return;
    183  }
    184  data.isSolved = data.isSolved || false;
    185  if (typeof data.decoderDoctorReportId === "undefined") {
    186    data.decoderDoctorReportId = "testReportId";
    187  }
    188 
    189  // Test same origin.
    190  await create_tab_and_test(false);
    191  // Test cross origin.
    192  await create_tab_and_test(true);
    193 }
    194 
    195 function tab_checker_for_sumo(expectedPath) {
    196  return function (openedTab) {
    197    let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
    198    let url = baseURL + expectedPath;
    199    is(
    200      openedTab.linkedBrowser.currentURI.spec,
    201      url,
    202      `Expected '${url}' in new tab`
    203    );
    204  };
    205 }
    206 
    207 function tab_checker_for_webcompat(expectedParams) {
    208  return function (openedTab) {
    209    let urlString = openedTab.linkedBrowser.currentURI.spec;
    210    let endpoint = Services.prefs.getStringPref(
    211      "media.decoder-doctor.new-issue-endpoint",
    212      ""
    213    );
    214    ok(
    215      urlString.startsWith(endpoint),
    216      `Expected URL starting with '${endpoint}', got '${urlString}'`
    217    );
    218    let params = new URL(urlString).searchParams;
    219    for (let k in expectedParams) {
    220      if (!params.has(k)) {
    221        ok(false, `Expected ${k} in webcompat URL`);
    222      } else {
    223        is(
    224          params.get(k),
    225          expectedParams[k],
    226          `Expected ${k}='${expectedParams[k]}' in webcompat URL`
    227        );
    228      }
    229    }
    230  };
    231 }
    232 
    233 add_task(async function test_platform_decoder_not_found() {
    234  let message = "";
    235  let decoderDoctorReportId = "";
    236  let isLinux = AppConstants.platform == "linux";
    237  if (isLinux) {
    238    message = gNavigatorBundle.getString("decoder.noCodecsLinux.message");
    239    decoderDoctorReportId = "MediaPlatformDecoderNotFound";
    240  } else if (AppConstants.platform == "win") {
    241    message = gNavigatorBundle.getString("decoder.noHWAcceleration.message");
    242    decoderDoctorReportId = "MediaWMFNeeded";
    243  }
    244 
    245  await test_decoder_doctor_notification(
    246    {
    247      type: "platform-decoder-not-found",
    248      decoderDoctorReportId,
    249      formats: "testFormat",
    250    },
    251    message,
    252    isLinux ? "" : { l10nId: "moz-support-link-text" },
    253    isLinux ? "" : gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
    254    true,
    255    tab_checker_for_sumo("fix-video-audio-problems-firefox-windows")
    256  );
    257 });
    258 
    259 add_task(async function test_cannot_initialize_pulseaudio() {
    260  let message = "";
    261  // This is only sent on Linux.
    262  if (AppConstants.platform == "linux") {
    263    message = gNavigatorBundle.getString("decoder.noPulseAudio.message");
    264  }
    265 
    266  await test_decoder_doctor_notification(
    267    { type: "cannot-initialize-pulseaudio", formats: "testFormat" },
    268    message,
    269    { l10nId: "moz-support-link-text" },
    270    gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
    271    true,
    272    tab_checker_for_sumo("fix-common-audio-and-video-issues")
    273  );
    274 });
    275 
    276 add_task(async function test_unsupported_libavcodec() {
    277  let message = "";
    278  // This is only sent on Linux.
    279  if (AppConstants.platform == "linux") {
    280    message = gNavigatorBundle.getString(
    281      "decoder.unsupportedLibavcodec.message"
    282    );
    283  }
    284 
    285  await test_decoder_doctor_notification(
    286    { type: "unsupported-libavcodec", formats: "testFormat" },
    287    message
    288  );
    289 });
    290 
    291 add_task(async function test_decode_error() {
    292  await SpecialPowers.pushPrefEnv({
    293    set: [
    294      [
    295        "media.decoder-doctor.new-issue-endpoint",
    296        "http://example.com/webcompat",
    297      ],
    298      ["browser.fixup.fallback-to-https", false],
    299    ],
    300  });
    301  let message = gNavigatorBundle.getString("decoder.decodeError.message");
    302  await test_decoder_doctor_notification(
    303    {
    304      type: "decode-error",
    305      decodeIssue: "DecodeIssue",
    306      docURL: "DocURL",
    307      resourceURL: "ResURL",
    308    },
    309    message,
    310    gNavigatorBundle.getString("decoder.decodeError.button"),
    311    gNavigatorBundle.getString("decoder.decodeError.accesskey"),
    312    false,
    313    tab_checker_for_webcompat({
    314      url: "DocURL",
    315      label: "type-media",
    316      problem_type: "video_bug",
    317      details: JSON.stringify({
    318        "Technical Information:": "DecodeIssue",
    319        "Resource:": "ResURL",
    320      }),
    321    })
    322  );
    323 });
    324 
    325 add_task(async function test_decode_warning() {
    326  await SpecialPowers.pushPrefEnv({
    327    set: [
    328      [
    329        "media.decoder-doctor.new-issue-endpoint",
    330        "http://example.com/webcompat",
    331      ],
    332    ],
    333  });
    334  let message = gNavigatorBundle.getString("decoder.decodeWarning.message");
    335  await test_decoder_doctor_notification(
    336    {
    337      type: "decode-warning",
    338      decodeIssue: "DecodeIssue",
    339      docURL: "DocURL",
    340      resourceURL: "ResURL",
    341    },
    342    message,
    343    gNavigatorBundle.getString("decoder.decodeError.button"),
    344    gNavigatorBundle.getString("decoder.decodeError.accesskey"),
    345    false,
    346    tab_checker_for_webcompat({
    347      url: "DocURL",
    348      label: "type-media",
    349      problem_type: "video_bug",
    350      details: JSON.stringify({
    351        "Technical Information:": "DecodeIssue",
    352        "Resource:": "ResURL",
    353      }),
    354    })
    355  );
    356 });