tor-browser

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

browser_doctor_notification.js (9233B)


      1 /**
      2 * This test is used to test whether the decoder doctor would report the error
      3 * on the notification banner (checking that by observing message) or on the web
      4 * console (checking that by listening to the test event).
      5 * Error should be reported after calling `DecoderDoctorDiagnostics::StoreXXX`
      6 * methods.
      7 * - StoreFormatDiagnostics() [for checking if type is supported]
      8 * - StoreDecodeError() [when decode error occurs]
      9 * - StoreEvent() [for reporting audio sink error]
     10 */
     11 
     12 // Only types being listed here would be allowed to display on a
     13 // notification banner. Otherwise, the error would only be showed on the
     14 // web console.
     15 var gAllowedNotificationTypes =
     16  "MediaWMFNeeded,MediaFFMpegNotFound,MediaUnsupportedLibavcodec,MediaDecodeError,MediaCannotInitializePulseAudio,";
     17 
     18 // Used to check if the mime type in the notification is equal to what we set
     19 // before. This mime type doesn't reflect the real world siutation, i.e. not
     20 // every error listed in this test would happen on this type. An example, ffmpeg
     21 // not found would only happen on H264/AAC media.
     22 const gMimeType = "video/mp4";
     23 
     24 add_task(async function setupTestingPref() {
     25  await SpecialPowers.pushPrefEnv({
     26    set: [
     27      ["media.decoder-doctor.testing", true],
     28      ["media.decoder-doctor.verbose", true],
     29      ["media.decoder-doctor.notifications-allowed", gAllowedNotificationTypes],
     30    ],
     31  });
     32  // transfer types to lower cases in order to match with `DecoderDoctorReportType`
     33  gAllowedNotificationTypes = gAllowedNotificationTypes.toLowerCase();
     34 });
     35 
     36 add_task(async function testWMFIsNeeded() {
     37  const tab = await createTab("about:blank");
     38  await setFormatDiagnosticsReportForMimeType(tab, {
     39    type: "platform-decoder-not-found",
     40    decoderDoctorReportId: "mediawmfneeded",
     41    formats: gMimeType,
     42  });
     43  BrowserTestUtils.removeTab(tab);
     44 });
     45 
     46 add_task(async function testFFMpegNotFound() {
     47  const tab = await createTab("about:blank");
     48  await setFormatDiagnosticsReportForMimeType(tab, {
     49    type: "platform-decoder-not-found",
     50    decoderDoctorReportId: "mediaplatformdecodernotfound",
     51    formats: gMimeType,
     52  });
     53  BrowserTestUtils.removeTab(tab);
     54 });
     55 
     56 add_task(async function testLibAVCodecUnsupported() {
     57  const tab = await createTab("about:blank");
     58  await setFormatDiagnosticsReportForMimeType(tab, {
     59    type: "unsupported-libavcodec",
     60    decoderDoctorReportId: "mediaunsupportedlibavcodec",
     61    formats: gMimeType,
     62  });
     63  BrowserTestUtils.removeTab(tab);
     64 });
     65 
     66 add_task(async function testCanNotPlayNoDecoder() {
     67  const tab = await createTab("about:blank");
     68  await setFormatDiagnosticsReportForMimeType(tab, {
     69    type: "cannot-play",
     70    decoderDoctorReportId: "mediacannotplaynodecoders",
     71    formats: gMimeType,
     72  });
     73  BrowserTestUtils.removeTab(tab);
     74 });
     75 
     76 add_task(async function testNoDecoder() {
     77  const tab = await createTab("about:blank");
     78  await setFormatDiagnosticsReportForMimeType(tab, {
     79    type: "can-play-but-some-missing-decoders",
     80    decoderDoctorReportId: "medianodecoders",
     81    formats: gMimeType,
     82  });
     83  BrowserTestUtils.removeTab(tab);
     84 });
     85 
     86 const gErrorList = [
     87  "NS_ERROR_DOM_MEDIA_ABORT_ERR",
     88  "NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR",
     89  "NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR",
     90  "NS_ERROR_DOM_MEDIA_DECODE_ERR",
     91  "NS_ERROR_DOM_MEDIA_FATAL_ERR",
     92  "NS_ERROR_DOM_MEDIA_METADATA_ERR",
     93  "NS_ERROR_DOM_MEDIA_OVERFLOW_ERR",
     94  "NS_ERROR_DOM_MEDIA_MEDIASINK_ERR",
     95  "NS_ERROR_DOM_MEDIA_DEMUXER_ERR",
     96  "NS_ERROR_DOM_MEDIA_CDM_ERR",
     97  "NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR",
     98 ];
     99 
    100 add_task(async function testDecodeError() {
    101  const type = "decode-error";
    102  const decoderDoctorReportId = "mediadecodeerror";
    103  for (let error of gErrorList) {
    104    const tab = await createTab("about:blank");
    105    info(`first to try if the error is not allowed to be reported`);
    106    // No error is allowed to be reported in the notification banner.
    107    await SpecialPowers.pushPrefEnv({
    108      set: [["media.decoder-doctor.decode-errors-allowed", ""]],
    109    });
    110    await setDecodeError(tab, {
    111      type,
    112      decoderDoctorReportId,
    113      error,
    114      shouldReportNotification: false,
    115    });
    116 
    117    // If the notification type is `MediaDecodeError` and the error type is
    118    // listed in the pref, then the error would be reported to the
    119    // notification banner.
    120    info(`Then to try if the error is allowed to be reported`);
    121    await SpecialPowers.pushPrefEnv({
    122      set: [["media.decoder-doctor.decode-errors-allowed", error]],
    123    });
    124    await setDecodeError(tab, {
    125      type,
    126      decoderDoctorReportId,
    127      error,
    128      shouldReportNotification: true,
    129    });
    130    BrowserTestUtils.removeTab(tab);
    131  }
    132 });
    133 
    134 add_task(async function testAudioSinkFailedStartup() {
    135  const tab = await createTab("about:blank");
    136  await setAudioSinkFailedStartup(tab, {
    137    type: "cannot-initialize-pulseaudio",
    138    decoderDoctorReportId: "mediacannotinitializepulseaudio",
    139    // This error comes with `*`, see `DecoderDoctorDiagnostics::StoreEvent`
    140    formats: "*",
    141  });
    142  BrowserTestUtils.removeTab(tab);
    143 });
    144 
    145 /**
    146 * Following are helper functions
    147 */
    148 async function createTab(url) {
    149  let tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, url);
    150  // Create observer in the content process in order to check the decoder
    151  // doctor's notification that would be sent when an error occurs.
    152  await SpecialPowers.spawn(tab.linkedBrowser, [], _ => {
    153    content._notificationName = "decoder-doctor-notification";
    154    content._obs = {
    155      observe(subject, topic, data) {
    156        let { type, decoderDoctorReportId, formats } = JSON.parse(data);
    157        decoderDoctorReportId = decoderDoctorReportId.toLowerCase();
    158        info(`received '${type}:${decoderDoctorReportId}:${formats}'`);
    159        if (!this._resolve) {
    160          ok(false, "receive unexpected notification?");
    161        }
    162        if (
    163          type == this._type &&
    164          decoderDoctorReportId == this._decoderDoctorReportId &&
    165          formats == this._formats
    166        ) {
    167          ok(true, `received correct notification`);
    168          Services.obs.removeObserver(content._obs, content._notificationName);
    169          this._resolve();
    170          this._resolve = null;
    171        }
    172      },
    173      // Return a promise that will be resolved once receiving a notification
    174      // which has equal data with the input parameters.
    175      waitFor({ type, decoderDoctorReportId, formats }) {
    176        if (this._resolve) {
    177          ok(false, "already has a pending promise!");
    178          return Promise.reject();
    179        }
    180        Services.obs.addObserver(content._obs, content._notificationName);
    181        return new Promise(resolve => {
    182          info(`waiting for '${type}:${decoderDoctorReportId}:${formats}'`);
    183          this._resolve = resolve;
    184          this._type = type;
    185          this._decoderDoctorReportId = decoderDoctorReportId;
    186          this._formats = formats;
    187        });
    188      },
    189    };
    190    content._waitForReport = (params, shouldReportNotification) => {
    191      const reportToConsolePromise = new Promise(r => {
    192        content.document.addEventListener(
    193          "mozreportmediaerror",
    194          _ => {
    195            r();
    196          },
    197          { once: true }
    198        );
    199      });
    200      const reportToNotificationBannerPromise = shouldReportNotification
    201        ? content._obs.waitFor(params)
    202        : Promise.resolve();
    203      info(
    204        `waitForConsole=true, waitForNotificationBanner=${shouldReportNotification}`
    205      );
    206      return Promise.all([
    207        reportToConsolePromise,
    208        reportToNotificationBannerPromise,
    209      ]);
    210    };
    211  });
    212  return tab;
    213 }
    214 
    215 async function setFormatDiagnosticsReportForMimeType(tab, params) {
    216  const shouldReportNotification = gAllowedNotificationTypes.includes(
    217    params.decoderDoctorReportId
    218  );
    219  await SpecialPowers.spawn(
    220    tab.linkedBrowser,
    221    [params, shouldReportNotification],
    222    async (params, shouldReportNotification) => {
    223      const video = content.document.createElement("video");
    224      SpecialPowers.wrap(video).setFormatDiagnosticsReportForMimeType(
    225        params.formats,
    226        params.decoderDoctorReportId
    227      );
    228      await content._waitForReport(params, shouldReportNotification);
    229    }
    230  );
    231  ok(true, `finished check for ${params.decoderDoctorReportId}`);
    232 }
    233 
    234 async function setDecodeError(tab, params) {
    235  info(`start check for ${params.error}`);
    236  await SpecialPowers.spawn(tab.linkedBrowser, [params], async params => {
    237    const video = content.document.createElement("video");
    238    SpecialPowers.wrap(video).setDecodeError(params.error);
    239    await content._waitForReport(params, params.shouldReportNotification);
    240  });
    241  ok(true, `finished check for ${params.error}`);
    242 }
    243 
    244 async function setAudioSinkFailedStartup(tab, params) {
    245  const shouldReportNotification = gAllowedNotificationTypes.includes(
    246    params.decoderDoctorReportId
    247  );
    248  await SpecialPowers.spawn(
    249    tab.linkedBrowser,
    250    [params, shouldReportNotification],
    251    async (params, shouldReportNotification) => {
    252      const video = content.document.createElement("video");
    253      const waitPromise = content._waitForReport(
    254        params,
    255        shouldReportNotification
    256      );
    257      SpecialPowers.wrap(video).setAudioSinkFailedStartup();
    258      await waitPromise;
    259    }
    260  );
    261 }