File: link-multiple-load-events.html

package info (click to toggle)
thunderbird 1%3A143.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 4,703,968 kB
  • sloc: cpp: 7,770,492; javascript: 5,943,842; ansic: 3,918,754; python: 1,418,263; xml: 653,354; asm: 474,045; java: 183,079; sh: 111,238; makefile: 20,410; perl: 14,359; objc: 13,059; yacc: 4,583; pascal: 3,405; lex: 1,720; ruby: 999; exp: 762; sql: 715; awk: 580; php: 436; lisp: 430; sed: 69; csh: 10
file content (139 lines) | stat: -rw-r--r-- 4,941 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
<!DOCTYPE html>
<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-link-element">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script>
function getTimeoutPromise(t, msg) {
  return new Promise((resolve, reject) => {
    t.step_timeout(() => reject(`Timeout reached: ${msg}`), 2000);
  });
}

// The tests generated from this loop are important to ensure that a link's load
// or error event handler never incurs infinite recursion by virtue of
// re-setting a link element's attribute to the value it already has. This is
// what thwarted the first attempt at fixing https://crbug.com/41436016 in
// Chromium.
const attributes_to_test = ['rel', 'href', 'type', 'media'];
for (const attribute of attributes_to_test) {
  promise_test(async t => {
    const id = token();
    const link = document.createElement('link');
    let count = 0;
    let response = null;

    link.onerror = t.unreached_func('Sheet should load successfully');
    const firstLoadPromise = new Promise(resolve => {
      link.onload = resolve;
    });

    // Set all attributes to a sensible default, so when we re-assign one of
    // them (`attribute`) idempotently later, the value doesn't change.
    link.rel = 'stylesheet';
    link.media = 'all';
    link.type = 'text/css';
    link.href = new URL(`stylesheet.py?id=${id}`, location.href);

    document.head.append(link);
    t.add_cleanup(() => {
      link.remove();
    });

    await Promise.race([firstLoadPromise, getTimeoutPromise(t, 'first resource')]);
    response = await fetch(`stylesheet.py?id=${id}&count=foo`);
    count = await response.text();
    assert_equals(count, '1', "server sees first style sheet request");

    const secondLoadPromise = new Promise((resolve, reject) => {
      link.onload = () => reject('second load event unexpectedly fired');
    });
    const expectedTimeoutPromise = new Promise(resolve => {
      t.step_timeout(resolve, 2000);
    });

    link[attribute] = link[attribute];
    await Promise.race([expectedTimeoutPromise, secondLoadPromise]);
    response = await fetch(`stylesheet.py?id=${id}&count=foo`);
    count = await response.text();
    assert_equals(count, '0',
        "server does not see second request to the same style sheet");
  }, `<link> cannot request the same resource twice by touching the ` +
     `'${attribute}' attribute, if its value never changes`);
}


promise_test(async t => {
  const id = token();
  const link = document.createElement('link');
  link.rel = 'preload';
  link.as = 'style';
  document.head.append(link);
  t.add_cleanup(() => {
    link.remove();
  });

  let count = 0;
  let response = null;

  link.onerror = t.unreached_func('Sheet should load successfully');
  const firstLoadPromise = new Promise(resolve => {
    link.onload = resolve;
  });

  link.href = `stylesheet.py?id=${id}`;
  await Promise.race([firstLoadPromise, getTimeoutPromise(t, 'first resource')]);
  response = await fetch(`stylesheet.py?id=${id}&count=foo`);
  count = await response.text();
  assert_equals(count, '1', "server sees preload request");

  const secondLoadPromise = new Promise(resolve => {
    link.onload = resolve;
  });

  // Switching from `rel=preload` => `rel=stylesheet` triggers the stylesheet
  // processing model. The resource loads from the preload cache and never
  // touches the network, but the load event does fire again.
  link.rel = 'stylesheet';
  await Promise.race([secondLoadPromise,
      getTimeoutPromise(t, 'rel=stylesheet using preload cache')]);
  response = await fetch(`stylesheet.py?id=${id}&count=foo`);
  count = await response.text();
  assert_equals(count, '0', "server does not see second request to the same style sheet");
}, "<link> load event can fire twice for the same href resource, based on " +
   "'rel' attribute mutations");

promise_test(async t => {
  const link = document.createElement('link');
  link.rel = 'stylesheet';
  document.head.append(link);
  t.add_cleanup(() => {
    link.remove();
  });

  link.onerror = t.unreached_func('Sheet should load successfully');
  const firstLoadPromise = new Promise(resolve => {
    link.onload = resolve;
  });

  link.href = 'style.css?first';
  await Promise.race([firstLoadPromise, getTimeoutPromise(t, 'first resource')]);

  const secondLoadPromise = new Promise(resolve => {
    link.onload = resolve;
  });

  link.href = 'style.css?second';
  await Promise.race([secondLoadPromise, getTimeoutPromise(t, 'second resource')]);

  const thirdLoadPromise = new Promise(resolve => {
    link.onload = resolve;
  });

  link.href = 'style.css?third';
  await Promise.race([thirdLoadPromise, getTimeoutPromise(t, 'third resource')]);
}, "<link> load event fires for each DIFFERENT stylesheet it loads");
</script>
</head>
</html>