summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/static/browser_parsable_script.js
blob: 982ef2f91ac2fd7fd160e2929ffe161bc81acc2e (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
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

/* This list allows pre-existing or 'unfixable' JS issues to remain, while we
 * detect newly occurring issues in shipping JS. It is a list of regexes
 * matching files which have errors:
 */

requestLongerTimeout(2);

const kWhitelist = new Set([
  /browser\/content\/browser\/places\/controller.js$/,
]);

const kESModuleList = new Set([
  /browser\/lockwise-card.js$/,
  /browser\/monitor-card.js$/,
  /browser\/proxy-card.js$/,
  /browser\/vpn-card.js$/,
  /toolkit\/content\/global\/certviewer\/components\/.*\.js$/,
  /toolkit\/content\/global\/certviewer\/.*\.js$/,
  /chrome\/pdfjs\/content\/web\/.*\.js$/,
]);

// Normally we would use reflect.jsm to get Reflect.parse. However, if
// we do that, then all the AST data is allocated in reflect.jsm's
// zone. That exposes a bug in our GC. The GC collects reflect.jsm's
// zone but not the zone in which our test code lives (since no new
// data is being allocated in it). The cross-compartment wrappers in
// our zone that point to the AST data never get collected, and so the
// AST data itself is never collected. We need to GC both zones at
// once to fix the problem.
const init = Cc["@mozilla.org/jsreflect;1"].createInstance();
init();

/**
 * Check if an error should be ignored due to matching one of the whitelist
 * objects defined in kWhitelist
 *
 * @param uri the uri to check against the whitelist
 * @return true if the uri should be skipped, false otherwise.
 */
function uriIsWhiteListed(uri) {
  for (let whitelistItem of kWhitelist) {
    if (whitelistItem.test(uri.spec)) {
      return true;
    }
  }
  return false;
}

/**
 * Check if a URI should be parsed as an ES module.
 *
 * @param uri the uri to check against the ES module list
 * @return true if the uri should be parsed as a module, otherwise parse it as a script.
 */
function uriIsESModule(uri) {
  if (uri.filePath.endsWith(".mjs")) {
    return true;
  }

  for (let whitelistItem of kESModuleList) {
    if (whitelistItem.test(uri.spec)) {
      return true;
    }
  }
  return false;
}

function parsePromise(uri, parseTarget) {
  let promise = new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open("GET", uri, true);
    xhr.onreadystatechange = function () {
      if (this.readyState == this.DONE) {
        let scriptText = this.responseText;
        try {
          info(`Checking ${parseTarget} ${uri}`);
          let parseOpts = {
            source: uri,
            target: parseTarget,
          };
          Reflect.parse(scriptText, parseOpts);
          resolve(true);
        } catch (ex) {
          let errorMsg = "Script error reading " + uri + ": " + ex;
          ok(false, errorMsg);
          resolve(false);
        }
      }
    };
    xhr.onerror = error => {
      ok(false, "XHR error reading " + uri + ": " + error);
      resolve(false);
    };
    xhr.overrideMimeType("application/javascript");
    xhr.send(null);
  });
  return promise;
}

add_task(async function checkAllTheJS() {
  // In debug builds, even on a fast machine, collecting the file list may take
  // more than 30 seconds, and parsing all files may take four more minutes.
  // For this reason, this test must be explictly requested in debug builds by
  // using the "--setpref parse=<filter>" argument to mach.  You can specify:
  //  - A case-sensitive substring of the file name to test (slow).
  //  - A single absolute URI printed out by a previous run (fast).
  //  - An empty string to run the test on all files (slowest).
  let parseRequested = Services.prefs.prefHasUserValue("parse");
  let parseValue = parseRequested && Services.prefs.getCharPref("parse");
  if (SpecialPowers.isDebugBuild) {
    if (!parseRequested) {
      ok(
        true,
        "Test disabled on debug build. To run, execute: ./mach" +
          " mochitest-browser --setpref parse=<case_sensitive_filter>" +
          " browser/base/content/test/general/browser_parsable_script.js"
      );
      return;
    }
    // Request a 15 minutes timeout (30 seconds * 30) for debug builds.
    requestLongerTimeout(30);
  }

  let uris;
  // If an absolute URI is specified on the command line, use it immediately.
  if (parseValue && parseValue.includes(":")) {
    uris = [NetUtil.newURI(parseValue)];
  } else {
    let appDir = Services.dirsvc.get("GreD", Ci.nsIFile);
    // This asynchronously produces a list of URLs (sadly, mostly sync on our
    // test infrastructure because it runs against jarfiles there, and
    // our zipreader APIs are all sync)
    let startTimeMs = Date.now();
    info("Collecting URIs");
    uris = await generateURIsFromDirTree(appDir, [".js", ".jsm", ".mjs"]);
    info("Collected URIs in " + (Date.now() - startTimeMs) + "ms");

    // Apply the filter specified on the command line, if any.
    if (parseValue) {
      uris = uris.filter(uri => {
        if (uri.spec.includes(parseValue)) {
          return true;
        }
        info("Not checking filtered out " + uri.spec);
        return false;
      });
    }
  }

  // We create an array of promises so we can parallelize all our parsing
  // and file loading activity:
  await PerfTestHelpers.throttledMapPromises(uris, uri => {
    if (uriIsWhiteListed(uri)) {
      info("Not checking whitelisted " + uri.spec);
      return undefined;
    }
    let target = "script";
    if (uriIsESModule(uri)) {
      target = "module";
    }
    return parsePromise(uri.spec, target);
  });
  ok(true, "All files parsed");
});