File: utils.sub.js

package info (click to toggle)
firefox 143.0.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,617,328 kB
  • sloc: cpp: 7,478,492; javascript: 6,417,157; ansic: 3,720,058; python: 1,396,372; xml: 627,523; asm: 438,677; java: 186,156; sh: 63,477; makefile: 19,171; objc: 13,059; perl: 12,983; yacc: 4,583; cs: 3,846; pascal: 3,405; lex: 1,720; ruby: 1,003; exp: 762; php: 436; lisp: 258; awk: 247; sql: 66; sed: 53; csh: 10
file content (283 lines) | stat: -rw-r--r-- 10,067 bytes parent folder | download | duplicates (4)
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
/**
 * Utilities for initiating prefetch via speculation rules.
 */

// Resolved URL to find this script.
const SR_PREFETCH_UTILS_URL = new URL(document.currentScript.src, document.baseURI);

// If (and only if) you are writing a test that depends on
// `requires: ["anonymous-client-ip-when-cross-origin"]`, then you must use this
// host as the cross-origin host. (If you need a generic cross-origin host, use
// `get_host_info().NOTSAMESITE_HOST` or similar instead.)
//
// TODO(domenic): document in the web platform tests server infrastructure that
// such a host must exist, and possibly separate it from `{{hosts[alt][]}}`.
const CROSS_ORIGIN_HOST_THAT_WORKS_WITH_ACIWCO = "{{hosts[alt][]}}";

class PrefetchAgent extends RemoteContext {
  constructor(uuid, t) {
    super(uuid);
    this.t = t;
  }

  getExecutorURL(options = {}) {
    let {hostname, username, password, protocol, executor, ...extra} = options;
    let params = new URLSearchParams({uuid: this.context_id, ...extra});
    if(executor === undefined) {
      executor = "executor.sub.html";
    }
    let url = new URL(`${executor}?${params}`, SR_PREFETCH_UTILS_URL);
    if(hostname !== undefined) {
      url.hostname = hostname;
    }
    if(username !== undefined) {
      url.username = username;
    }
    if(password !== undefined) {
      url.password = password;
    }
    if(protocol !== undefined) {
      url.protocol = protocol;
      url.port = protocol === "https" ? "{{ports[https][0]}}" : "{{ports[http][0]}}";
    }
    return url;
  }

  // Requests prefetch via speculation rules.
  //
  // In the future, this should also use browser hooks to force the prefetch to
  // occur despite heuristic matching, etc., and await the completion of the
  // prefetch.
  async forceSinglePrefetch(url, extra = {}, wait_for_completion = true) {
    return this.forceSpeculationRules(
      {
        prefetch: [{source: 'list', urls: [url], ...extra}]
      }, wait_for_completion);
  }

  async forceSpeculationRules(rules, wait_for_completion = true) {
    await this.execute_script((rules) => {
      insertSpeculationRules(rules);
    }, [rules]);
    if (!wait_for_completion) {
      return Promise.resolve();
    }
    return new Promise(resolve => this.t.step_timeout(resolve, 2000));
  }

  // `url` is the URL to navigate.
  //
  // `expectedDestinationUrl` is the expected URL after navigation.
  // When omitted, `url` is used. When explicitly null, the destination URL is
  // not validated.
  async navigate(url, {expectedDestinationUrl} = {}) {
    await this.execute_script((url) => {
      window.executor.suspend(() => {
        location.href = url;
      });
    }, [url]);
    if (expectedDestinationUrl === undefined) {
      expectedDestinationUrl = url;
    }
    if (expectedDestinationUrl) {
      expectedDestinationUrl.username = '';
      expectedDestinationUrl.password = '';
      assert_equals(
          await this.execute_script(() => location.href),
          expectedDestinationUrl.toString(),
          "expected navigation to reach destination URL");
    }
    await this.execute_script(() => {});
  }

  async getRequestHeaders() {
    return this.execute_script(() => requestHeaders);
  }

  async getResponseCookies() {
    return this.execute_script(() => {
      let cookie = {};
      document.cookie.split(/\s*;\s*/).forEach((kv)=>{
        let [key, value] = kv.split(/\s*=\s*/);
        cookie[key] = value;
      });
      return cookie;
    });
  }

  async getRequestCookies() {
    return this.execute_script(() => window.requestCookies);
  }

  async getRequestCredentials() {
    return this.execute_script(() => window.requestCredentials);
  }

  async setReferrerPolicy(referrerPolicy) {
    return this.execute_script(referrerPolicy => {
      const meta = document.createElement("meta");
      meta.name = "referrer";
      meta.content = referrerPolicy;
      document.head.append(meta);
    }, [referrerPolicy]);
  }

  async getDeliveryType(){
    return this.execute_script(() => {
      return performance.getEntriesByType("navigation")[0].deliveryType;
    });
  }
}

// Produces a URL with a UUID which will record when it's prefetched.
// |extra_params| can be specified to add extra search params to the generated
// URL.
function getPrefetchUrl(extra_params={}) {
  let params = new URLSearchParams({ uuid: token(), ...extra_params });
  return new URL(`prefetch.py?${params}`, SR_PREFETCH_UTILS_URL);
}

// Produces n URLs with unique UUIDs which will record when they are prefetched.
function getPrefetchUrlList(n) {
  return Array.from({ length: n }, () => getPrefetchUrl());
}

async function isUrlPrefetched(url) {
  let response = await fetch(url, {redirect: 'follow'});
  assert_true(response.ok);
  return response.json();
}

// Must also include /common/utils.js and /common/dispatcher/dispatcher.js to use this.
async function spawnWindowWithReference(t, options = {}, uuid = token()) {
  let agent = new PrefetchAgent(uuid, t);
  let w = window.open(agent.getExecutorURL(options), '_blank', options);
  t.add_cleanup(() => w.close());
  return {"agent":agent, "window":w};
}

// Must also include /common/utils.js and /common/dispatcher/dispatcher.js to use this.
async function spawnWindow(t, options = {}, uuid = token()) {
  let agent_window_pair = await spawnWindowWithReference(t, options, uuid);
  return agent_window_pair.agent;
}

function insertSpeculationRules(body) {
  let script = document.createElement('script');
  script.type = 'speculationrules';
  script.textContent = JSON.stringify(body);
  document.head.appendChild(script);
}

// Creates and appends <a href=|href|> to |insertion point|. If
// |insertion_point| is not specified, document.body is used.
function addLink(href, insertion_point=document.body) {
  const a = document.createElement('a');
  a.href = href;
  insertion_point.appendChild(a);
  return a;
}

// Inserts a prefetch document rule with |predicate|. |predicate| can be
// undefined, in which case the default predicate will be used (i.e. all links
// in document will match).
function insertDocumentRule(predicate, extra_options={}) {
  insertSpeculationRules({
    prefetch: [{
      source: 'document',
      eagerness: 'immediate',
      where: predicate,
      ...extra_options
    }]
  });
}

function assert_prefetched (requestHeaders, description) {
  assert_in_array(requestHeaders.purpose, [undefined, "prefetch"], "The vendor-specific header Purpose, if present, must be 'prefetch'.");
  assert_in_array(requestHeaders['sec-purpose'],
                  ["prefetch", "prefetch;anonymous-client-ip"], description);
}

function assert_prefetched_anonymous_client_ip(requestHeaders, description) {
  assert_in_array(requestHeaders.purpose, [undefined, "prefetch"], "The vendor-specific header Purpose, if present, must be 'prefetch'.");
  assert_equals(requestHeaders['sec-purpose'],
                "prefetch;anonymous-client-ip",
                description);
}

function assert_not_prefetched (requestHeaders, description){
  assert_equals(requestHeaders.purpose, undefined, description);
  assert_equals(requestHeaders['sec-purpose'], undefined, description);
}

// If the prefetch request is intercepted and modified by ServiceWorker,
// - "Sec-Purpose: prefetch" header is dropped in Step 33 of
//   https://fetch.spec.whatwg.org/#dom-request
//   because it's a https://fetch.spec.whatwg.org/#forbidden-request-header.
// - "Purpose: prefetch" can still be sent.
// Note that this check passes also for non-prefetch requests, so additional
// checks are needed to distinguish from non-prefetch requests.
function assert_prefetched_without_sec_purpose(requestHeaders, description) {
  assert_in_array(requestHeaders.purpose, [undefined, "prefetch"],
      "The vendor-specific header Purpose, if present, must be 'prefetch'.");
  assert_equals(requestHeaders['sec-purpose'], undefined, description);
}

// For ServiceWorker tests.
// `interceptedRequest` is an element of `interceptedRequests` in
// `resources/basic-service-worker.js`.

// The ServiceWorker fetch handler intercepted a prefetching request.
function assert_intercept_prefetch(interceptedRequest, expectedUrl) {
  assert_equals(interceptedRequest.request.url, expectedUrl.toString(),
      "intercepted request URL.");

  assert_prefetched(interceptedRequest.request.headers,
      "Prefetch request should be intercepted.");

  if (new URL(location.href).searchParams.has('clientId')) {
    // https://github.com/WICG/nav-speculation/issues/346
    // https://crbug.com/404294123
    assert_equals(interceptedRequest.resultingClientId, "",
        "resultingClientId shouldn't be exposed.");

    // https://crbug.com/404286918
    // `assert_not_equals()` isn't used for now to create stable failure diffs.
    assert_false(interceptedRequest.clientId === "",
        "clientId should be initiator.");
  }
}

// The ServiceWorker fetch handler intercepted a non-prefetching request.
function assert_intercept_non_prefetch(interceptedRequest, expectedUrl) {
  assert_equals(interceptedRequest.request.url, expectedUrl.toString(),
      "intercepted request URL.");

  assert_not_prefetched(interceptedRequest.request.headers,
      "Non-prefetch request should be intercepted.");

  if (new URL(location.href).searchParams.has('clientId')) {
    // Because this is an ordinal non-prefetch request, `resultingClientId`
    // can be set as normal.
    assert_not_equals(interceptedRequest.resultingClientId, "",
        "resultingClientId can be exposed.");

    assert_not_equals(interceptedRequest.clientId, "",
        "clientId should be initiator.");
  }
}

function assert_served_by_navigation_preload(requestHeaders) {
  assert_equals(
    requestHeaders['service-worker-navigation-preload'],
    'true',
    'Service-Worker-Navigation-Preload');
}

// Use nvs_header query parameter to ask the wpt server
// to populate No-Vary-Search response header.
function addNoVarySearchHeaderUsingQueryParam(url, value){
  if(value){
    url.searchParams.append("nvs_header", value);
  }
}