summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/scroll-to-text-fragment/scroll-to-text-fragment.html
blob: 73931d4b0e617dcf07fd50f90c15a83dc664d277 (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
<!doctype html>
<title>Navigating to a text fragment directive</title>
<meta charset=utf-8>
<link rel="help" href="https://wicg.github.io/ScrollToTextFragment/">
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/common/utils.js"></script>
<script src="stash.js"></script>
<!--
  This test suite performs scroll to text navigations to
  scroll-to-text-fragment-target.html and then checks the results, which are
  communicated back from the target page via the WPT Stash server (see stash.py).
  This structure is necessary because scroll to text security restrictions
  specifically restrict the navigator from being able to observe the result of
  the navigation, e.g. the target page cannot have a window opener.
-->
<script>
let test_cases = [
  // Test non-text fragment directives
  {
    fragment: '#',
    expect_position: 'top',
    description: 'Empty hash should scroll to top'
  },
  {
    fragment: '#:~:text=this,is,test,page',
    expect_position: 'top',
    description: 'Text directive with invalid syntax (context terms without "-") should not parse as a text directive'
  },
  {
    fragment: '#element:~:directive',
    expect_position: 'element',
    description: 'Generic fragment directive with existing element fragment should scroll to element'
  },
  {
    fragment: '#:~:TEXT=test',
    expect_position: 'top',
    description: 'Uppercase TEXT directive should not parse as a text directive'
  },
  // Test exact text matching, with all combinations of context terms
  {
    fragment: '#:~:text=test',
    expect_position: 'text',
    description:  'Exact text with no context should match text'
  },
  {
    fragment: '#:~:text=this is a-,test',
    expect_position: 'text',
    description: 'Exact text with prefix should match text'
  },
  {
    fragment: '#:~:text=test,-page',
    expect_position: 'text',
    description: 'Exact text with suffix should match text'
  },
  {
    fragment: '#:~:text=this is a-,test,-page',
    expect_position: 'text',
    description: 'Exact text with prefix and suffix should match text'
  },
  // Test tricky edge case where prefix and query are equal
  {
    fragment: '#:~:text=foo-,foo,-bar',
    expect_position: 'text',
    description: 'Exact text with prefix and suffix and query equals prefix.'
  },
  // Test text range matching, with all combinations of context terms
  {
    fragment: '#:~:text=this,page',
    expect_position: 'text',
    description: 'Text range with no context should match text'
  },
  {
    fragment: '#:~:text=this-,is,test',
    expect_position: 'text',
    description: 'Text range with prefix should match text'
  },
  {
    fragment: '#:~:text=this,test,-page',
    expect_position: 'text',
    description: 'Text range with suffix should match text'
  },
  {
    fragment: '#:~:text=this-,is,test,-page',
    expect_position: 'text',
    description: 'Text range with prefix and suffix should match text'
  },
  // Test partially non-matching text ranges
  {
    fragment: '#:~:text=this,none',
    expect_position: 'top',
    description: 'Text range with non-matching endText should not match'
  },
  {
    fragment: '#:~:text=none,page',
    expect_position: 'top',
    description: 'Text range with non-matching startText should not match'
  },
  // Test non-matching context terms
  {
    fragment: '#:~:text=this-,is,page,-none',
    expect_position: 'top',
    description: 'Text range with prefix and nonmatching suffix should not match'
  },
  {
    fragment: '#:~:text=none-,this,test,-page',
    expect_position: 'top',
    description: 'Text range with nonmatching prefix and matching suffix should not match'
  },
  // Test percent encoded characters
  {
    fragment: '#:~:text=this%20is%20a%20test%20page',
    expect_position: 'text',
    description: 'Exact text with percent encoded spaces should match text'
  },
  {
    fragment: '#:~:text=test%20pag',
    expect_position: 'top',
    description: 'Non-whole-word exact text with spaces should not match'
  },
  {
    fragment: '#:~:text=%26%2C%2D',
    expect_position: 'text',
    description: 'Fragment directive with percent encoded syntactical characters "&,-" should match text'
  },
  {
    fragment: '#:~:text=%E3%83%8D%E3%82%B3',
    expect_position: 'text',
    description: 'Fragment directive with percent encoded non-ASCII unicode character should match text'
  },
  {
    fragment: '#:~:text=!$\'()*+./:;=?@_~',
    expect_position: 'text',
    description: 'Fragment directive with all TextMatchChars should match text'
  },
  // Test multiple text directives
  {
    fragment: '#:~:text=this&text=test,page',
    expect_position: 'text',
    description: 'Multiple matching exact texts should match text'
  },
  {
    fragment: '#:~:text=tes&text=age',
    expect_position: 'top',
    description: 'Multiple non-whole-word exact texts should not match'
  },
  {
    fragment: '#:~:text=none&text=test%20page',
    expect_position: 'text',
    description: 'A non-matching text directive followed by a matching text directive should match and scroll into view the second text directive'
  },
  {
    fragment: '#:~:text=test%20page&directive',
    expect_position: 'text',
    description: 'Text directive followed by non-text directive should match text'
  },
  {
    fragment: '#:~:text=test&directive&text=page',
    expect_position: 'text',
    description: 'Multiple text directives and a non-text directive should match text'
  },
  // Test text directive behavior when there's an element fragment identifier
  {
    fragment: '#element:~:text=test',
    expect_position: 'text',
    description: 'Text directive with existing element fragment should match and scroll into view text'
  },
  {
    fragment: '#pagestate:~:text=test',
    expect_position: 'text',
    description: 'Text directive with nonexistent element fragment should match and scroll into view text'
  },
  {
    fragment: '#element:~:text=nomatch',
    expect_position: 'element',
    description: 'Non-matching text directive with existing element fragment should scroll to element'
  },
  {
    fragment: '#pagestate:~:text=nomatch',
    expect_position: 'top',
    description: 'Non-matching text directive with nonexistent element fragment should not match and not scroll'
  },
  // Test ambiguous text matches disambiguated by context terms
  {
    fragment: '#:~:text=more-,test%20page',
    expect_position: 'more-text',
    description: 'Multiple match text directive disambiguated by prefix should match the prefixed text'
  },
  {
    fragment: '#:~:text=test%20page,-text',
    expect_position: 'more-text',
    description: 'Multiple match text directive disambiguated by suffix should match the suffixed text'
  },
  {
    fragment: '#:~:text=more-,test%20page,-text',
    expect_position: 'more-text',
    description: 'Multiple match text directive disambiguated by prefix and suffix should match the text with the given context'
  },
  // Test context terms separated by node boundaries
  {
    fragment: '#:~:text=prefix-,test%20page,-suffix',
    expect_position: 'cross-node-context',
    description: 'Text directive should match when context terms are separated by node boundaries'
  },
  // Test text directive within shadow DOM
  {
    fragment: '#:~:text=shadow%20text',
    expect_position: 'shadow',
    description: 'Text directive should match text within shadow DOM'
  },
  // Test text directive within hidden and display none elements. These cases should not scroll into
  // view, but still "match" in that they should be highlighted or otherwise visibly indicated
  // if they were to become visible.
  {
    fragment: '#:~:text=hidden%20text',
    expect_position: 'top',
    description: 'Text directive should not scroll to hidden text'
  },
  {
    fragment: '#:~:text=display%20none',
    expect_position: 'top',
    description: 'Text directive should not scroll to display none text'
  },
  // Test horizontal scroll into view
  {
    fragment: '#:~:text=horizontally%20scrolled%20text',
    expect_position: 'horizontal-scroll',
    description: 'Text directive should horizontally scroll into view'
  }
];

for (const test_case of test_cases) {
  promise_test(t => new Promise((resolve, reject) => {
    let key = token();

    test_driver.bless('Open a URL with a text fragment directive', () => {
      window.open(`scroll-to-text-fragment-target.html?key=${key}${test_case.fragment}`, '_blank', 'noopener');
    });

    fetchResults(key, resolve, reject);
  }).then(data => {
    // If the position is not 'top', the :target element should be the positioned element.
    assert_true(data.scrollPosition == 'top' || data.target == data.scrollPosition);
    assert_equals(data.href.indexOf(':~:'), -1, 'Expected fragment directive to be stripped from the URL.');
    assert_equals(data.scrollPosition, test_case.expect_position,
                  `Expected ${test_case.fragment} (${test_case.description}) to scroll to ${test_case.expect_position}.`);
  }), `Test navigation with fragment: ${test_case.description}.`);
}
</script>