summaryrefslogtreecommitdiffstats
path: root/dom/media/doctor/test/browser/browser_decoderDoctor.js
blob: c502131fec64259599d64051e43aff8751b5c0af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
"use strict";

// 'data' contains the notification data object:
// - data.type must be provided.
// - data.isSolved and data.decoderDoctorReportId will be added if not provided
//   (false and "testReportId" resp.)
// - Other fields (e.g.: data.formats) may be provided as needed.
// 'notificationMessage': Expected message in the notification bar.
//   Falsy if nothing is expected after the notification is sent, in which case
//   we won't have further checks, so the following parameters are not needed.
// 'label': Expected button label. Falsy if no button is expected, in which case
//   we won't have further checks, so the following parameters are not needed.
// 'accessKey': Expected access key for the button.
// 'tabChecker': function(openedTab) called with the opened tab that resulted
//   from clicking the button.
async function test_decoder_doctor_notification(
  data,
  notificationMessage,
  label,
  accessKey,
  isLink,
  tabChecker
) {
  const TEST_URL = "https://example.org";
  // A helper closure to test notifications in same or different origins.
  // 'test_cross_origin' is used to determine if the observers used in the test
  // are notified in the same frame (when false) or in a cross origin iframe
  // (when true).
  async function create_tab_and_test(test_cross_origin) {
    await BrowserTestUtils.withNewTab(
      { gBrowser, url: TEST_URL },
      async function (browser) {
        let awaitNotificationBar;
        if (notificationMessage) {
          awaitNotificationBar = BrowserTestUtils.waitForNotificationBar(
            gBrowser,
            browser,
            "decoder-doctor-notification"
          );
        }

        await SpecialPowers.spawn(
          browser,
          [data, test_cross_origin],
          /* eslint-disable-next-line no-shadow */
          async function (data, test_cross_origin) {
            if (!test_cross_origin) {
              // Notify in the same origin.
              Services.obs.notifyObservers(
                content.window,
                "decoder-doctor-notification",
                JSON.stringify(data)
              );
              return;
              // Done notifying in the same origin.
            }

            // Notify in a different origin.
            const CROSS_ORIGIN_URL = "https://example.com";
            let frame = content.document.createElement("iframe");
            frame.src = CROSS_ORIGIN_URL;
            await new Promise(resolve => {
              frame.addEventListener("load", () => {
                resolve();
              });
              content.document.body.appendChild(frame);
            });

            await content.SpecialPowers.spawn(
              frame,
              [data],
              async function (
                /* eslint-disable-next-line no-shadow */
                data
              ) {
                Services.obs.notifyObservers(
                  content.window,
                  "decoder-doctor-notification",
                  JSON.stringify(data)
                );
              }
            );
            // Done notifying in a different origin.
          }
        );

        if (!notificationMessage) {
          ok(
            true,
            "Tested notifying observers with a nonsensical message, no effects expected"
          );
          return;
        }

        let notification;
        try {
          notification = await awaitNotificationBar;
        } catch (ex) {
          ok(false, ex);
          return;
        }
        ok(notification, "Got decoder-doctor-notification notification");
        if (label?.l10nId) {
          // Without the following statement, the
          // test_cannot_initialize_pulseaudio
          // will permanently fail on Linux.
          if (label.l10nId === "moz-support-link-text") {
            MozXULElement.insertFTLIfNeeded(
              "browser/components/mozSupportLink.ftl"
            );
          }
          label = await document.l10n.formatValue(label.l10nId);
        }
        if (isLink) {
          let link = notification.messageText.querySelector("a");
          if (link) {
            // Seems to be a Windows specific quirk, but without this
            // mutation observer the notification.messageText.textContent
            // will not be updated. This will cause consistent failures
            // on Windows.
            await BrowserTestUtils.waitForMutationCondition(
              link,
              { childList: true },
              () => link.textContent.trim()
            );
          }
        }
        is(
          notification.messageText.textContent,
          notificationMessage + (isLink && label ? ` ${label}` : ""),
          "notification message should match expectation"
        );

        let button = notification.buttonContainer.querySelector("button");
        let link = notification.messageText.querySelector("a");
        if (!label) {
          ok(!button, "There should not be a button");
          ok(!link, "There should not be a link");
          return;
        }

        if (isLink) {
          ok(!button, "There should not be a button");
          is(link.innerText, label, `notification link should be '${label}'`);
          ok(
            !link.hasAttribute("accesskey"),
            "notification link should not have accesskey"
          );
        } else {
          ok(!link, "There should not be a link");
          is(
            button.getAttribute("label"),
            label,
            `notification button should be '${label}'`
          );
          is(
            button.getAttribute("accesskey"),
            accessKey,
            "notification button should have accesskey"
          );
        }

        if (!tabChecker) {
          ok(false, "Test implementation error: Missing tabChecker");
          return;
        }
        let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser);
        if (button) {
          button.click();
        } else {
          link.click();
        }
        let openedTab = await awaitNewTab;
        tabChecker(openedTab);
        BrowserTestUtils.removeTab(openedTab);
      }
    );
  }

  if (typeof data.type === "undefined") {
    ok(false, "Test implementation error: data.type must be provided");
    return;
  }
  data.isSolved = data.isSolved || false;
  if (typeof data.decoderDoctorReportId === "undefined") {
    data.decoderDoctorReportId = "testReportId";
  }

  // Test same origin.
  await create_tab_and_test(false);
  // Test cross origin.
  await create_tab_and_test(true);
}

function tab_checker_for_sumo(expectedPath) {
  return function (openedTab) {
    let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
    let url = baseURL + expectedPath;
    is(
      openedTab.linkedBrowser.currentURI.spec,
      url,
      `Expected '${url}' in new tab`
    );
  };
}

function tab_checker_for_webcompat(expectedParams) {
  return function (openedTab) {
    let urlString = openedTab.linkedBrowser.currentURI.spec;
    let endpoint = Services.prefs.getStringPref(
      "media.decoder-doctor.new-issue-endpoint",
      ""
    );
    ok(
      urlString.startsWith(endpoint),
      `Expected URL starting with '${endpoint}', got '${urlString}'`
    );
    let params = new URL(urlString).searchParams;
    for (let k in expectedParams) {
      if (!params.has(k)) {
        ok(false, `Expected ${k} in webcompat URL`);
      } else {
        is(
          params.get(k),
          expectedParams[k],
          `Expected ${k}='${expectedParams[k]}' in webcompat URL`
        );
      }
    }
  };
}

add_task(async function test_platform_decoder_not_found() {
  let message = "";
  let decoderDoctorReportId = "";
  let isLinux = AppConstants.platform == "linux";
  if (isLinux) {
    message = gNavigatorBundle.getString("decoder.noCodecsLinux.message");
    decoderDoctorReportId = "MediaPlatformDecoderNotFound";
  } else if (AppConstants.platform == "win") {
    message = gNavigatorBundle.getString("decoder.noHWAcceleration.message");
    decoderDoctorReportId = "MediaWMFNeeded";
  }

  await test_decoder_doctor_notification(
    {
      type: "platform-decoder-not-found",
      decoderDoctorReportId,
      formats: "testFormat",
    },
    message,
    isLinux ? "" : { l10nId: "moz-support-link-text" },
    isLinux ? "" : gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
    true,
    tab_checker_for_sumo("fix-video-audio-problems-firefox-windows")
  );
});

add_task(async function test_cannot_initialize_pulseaudio() {
  let message = "";
  // This is only sent on Linux.
  if (AppConstants.platform == "linux") {
    message = gNavigatorBundle.getString("decoder.noPulseAudio.message");
  }

  await test_decoder_doctor_notification(
    { type: "cannot-initialize-pulseaudio", formats: "testFormat" },
    message,
    { l10nId: "moz-support-link-text" },
    gNavigatorBundle.getString("decoder.noCodecs.accesskey"),
    true,
    tab_checker_for_sumo("fix-common-audio-and-video-issues")
  );
});

add_task(async function test_unsupported_libavcodec() {
  let message = "";
  // This is only sent on Linux.
  if (AppConstants.platform == "linux") {
    message = gNavigatorBundle.getString(
      "decoder.unsupportedLibavcodec.message"
    );
  }

  await test_decoder_doctor_notification(
    { type: "unsupported-libavcodec", formats: "testFormat" },
    message
  );
});

add_task(async function test_decode_error() {
  await SpecialPowers.pushPrefEnv({
    set: [
      [
        "media.decoder-doctor.new-issue-endpoint",
        "http://example.com/webcompat",
      ],
      ["browser.fixup.fallback-to-https", false],
    ],
  });
  let message = gNavigatorBundle.getString("decoder.decodeError.message");
  await test_decoder_doctor_notification(
    {
      type: "decode-error",
      decodeIssue: "DecodeIssue",
      docURL: "DocURL",
      resourceURL: "ResURL",
    },
    message,
    gNavigatorBundle.getString("decoder.decodeError.button"),
    gNavigatorBundle.getString("decoder.decodeError.accesskey"),
    false,
    tab_checker_for_webcompat({
      url: "DocURL",
      label: "type-media",
      problem_type: "video_bug",
      details: JSON.stringify({
        "Technical Information:": "DecodeIssue",
        "Resource:": "ResURL",
      }),
    })
  );
});

add_task(async function test_decode_warning() {
  await SpecialPowers.pushPrefEnv({
    set: [
      [
        "media.decoder-doctor.new-issue-endpoint",
        "http://example.com/webcompat",
      ],
    ],
  });
  let message = gNavigatorBundle.getString("decoder.decodeWarning.message");
  await test_decoder_doctor_notification(
    {
      type: "decode-warning",
      decodeIssue: "DecodeIssue",
      docURL: "DocURL",
      resourceURL: "ResURL",
    },
    message,
    gNavigatorBundle.getString("decoder.decodeError.button"),
    gNavigatorBundle.getString("decoder.decodeError.accesskey"),
    false,
    tab_checker_for_webcompat({
      url: "DocURL",
      label: "type-media",
      problem_type: "video_bug",
      details: JSON.stringify({
        "Technical Information:": "DecodeIssue",
        "Resource:": "ResURL",
      }),
    })
  );
});