scroll-to-text-fragment.html (10344B)
1 <!doctype html> 2 <title>Navigating to a text fragment directive</title> 3 <meta charset=utf-8> 4 <link rel="help" href="https://wicg.github.io/ScrollToTextFragment/"> 5 <meta name="timeout" content="long"> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="/resources/testdriver.js"></script> 9 <script src="/resources/testdriver-vendor.js"></script> 10 <script src="/common/utils.js"></script> 11 <script src="stash.js"></script> 12 <!-- 13 This test suite performs scroll to text navigations to 14 scroll-to-text-fragment-target.html and then checks the results, which are 15 communicated back from the target page via the WPT Stash server (see stash.py). 16 This structure is necessary because scroll to text security restrictions 17 specifically restrict the navigator from being able to observe the result of 18 the navigation, e.g. the target page cannot have a window opener. 19 --> 20 <script> 21 let test_cases = [ 22 // Test non-text fragment directives 23 { 24 fragment: '#', 25 expect_position: 'top', 26 description: 'Empty hash should scroll to top' 27 }, 28 { 29 fragment: '#:~:text=this,is,test,page', 30 expect_position: 'top', 31 description: 'Text directive with invalid syntax (context terms without "-") should not parse as a text directive' 32 }, 33 { 34 fragment: '#:~:text=foo-', 35 expect_position: 'top', 36 description: 'Text directive with invalid syntax (only prefix, no start text) should not parse as a text directive' 37 }, 38 { 39 fragment: '#:~:text=-foo', 40 expect_position: 'top', 41 description: 'Text directive with invalid syntax (only suffix, no start text) should not parse as a text directive' 42 }, 43 { 44 fragment: '#element:~:directive', 45 expect_position: 'element', 46 description: 'Generic fragment directive with existing element fragment should scroll to element' 47 }, 48 { 49 fragment: '#:~:TEXT=test', 50 expect_position: 'top', 51 description: 'Uppercase TEXT directive should not parse as a text directive' 52 }, 53 // Test exact text matching, with all combinations of context terms 54 { 55 fragment: '#:~:text=test', 56 expect_position: 'text', 57 description: 'Exact text with no context should match text' 58 }, 59 { 60 fragment: '#:~:text=TEST', 61 expect_position: 'text', 62 description: 'Case-insensitive search with no context should match text' 63 }, 64 { 65 fragment: '#:~:text=this is a-,test', 66 expect_position: 'text', 67 description: 'Exact text with prefix should match text' 68 }, 69 { 70 fragment: '#:~:text=test,-page', 71 expect_position: 'text', 72 description: 'Exact text with suffix should match text' 73 }, 74 { 75 fragment: '#:~:text=this is a-,test,-page', 76 expect_position: 'text', 77 description: 'Exact text with prefix and suffix should match text' 78 }, 79 // Test tricky edge case where prefix and query are equal 80 { 81 fragment: '#:~:text=foo-,foo,-bar', 82 expect_position: 'text', 83 description: 'Exact text with prefix and suffix and query equals prefix.' 84 }, 85 // Test text range matching, with all combinations of context terms 86 { 87 fragment: '#:~:text=this,page', 88 expect_position: 'text', 89 description: 'Text range with no context should match text' 90 }, 91 { 92 fragment: '#:~:text=this-,is,test', 93 expect_position: 'text', 94 description: 'Text range with prefix should match text' 95 }, 96 { 97 fragment: '#:~:text=this,test,-page', 98 expect_position: 'text', 99 description: 'Text range with suffix should match text' 100 }, 101 { 102 fragment: '#:~:text=this-,is,test,-page', 103 expect_position: 'text', 104 description: 'Text range with prefix and suffix should match text' 105 }, 106 // Test partially non-matching text ranges 107 { 108 fragment: '#:~:text=this,none', 109 expect_position: 'top', 110 description: 'Text range with non-matching endText should not match' 111 }, 112 { 113 fragment: '#:~:text=none,page', 114 expect_position: 'top', 115 description: 'Text range with non-matching startText should not match' 116 }, 117 // Test non-matching context terms 118 { 119 fragment: '#:~:text=this-,is,page,-none', 120 expect_position: 'top', 121 description: 'Text range with prefix and nonmatching suffix should not match' 122 }, 123 { 124 fragment: '#:~:text=none-,this,test,-page', 125 expect_position: 'top', 126 description: 'Text range with nonmatching prefix and matching suffix should not match' 127 }, 128 // Test percent encoded characters 129 { 130 fragment: '#:~:text=this%20is%20a%20test%20page', 131 expect_position: 'text', 132 description: 'Exact text with percent encoded spaces should match text' 133 }, 134 { 135 fragment: '#:~:text=test%20pag', 136 expect_position: 'top', 137 description: 'Non-whole-word exact text with spaces should not match' 138 }, 139 { 140 fragment: '#:~:text=%26%2C%2D', 141 expect_position: 'text', 142 description: 'Fragment directive with percent encoded syntactical characters "&,-" should match text' 143 }, 144 { 145 fragment: '#:~:text=%E3%83%8D%E3%82%B3', 146 expect_position: 'text', 147 description: 'Fragment directive with percent encoded non-ASCII unicode character should match text' 148 }, 149 { 150 fragment: '#:~:text=!$\'()*+./:;=?@_~', 151 expect_position: 'text', 152 description: 'Fragment directive with all TextMatchChars should match text' 153 }, 154 // Test multiple text directives 155 { 156 fragment: '#:~:text=this&text=test,page', 157 expect_position: 'text', 158 description: 'Multiple matching exact texts should match text' 159 }, 160 { 161 fragment: '#:~:text=tes&text=age', 162 expect_position: 'top', 163 description: 'Multiple non-whole-word exact texts should not match' 164 }, 165 { 166 fragment: '#:~:text=none&text=test%20page', 167 expect_position: 'text', 168 description: 'A non-matching text directive followed by a matching text directive should match and scroll into view the second text directive' 169 }, 170 { 171 fragment: '#:~:text=test%20page&directive', 172 expect_position: 'text', 173 description: 'Text directive followed by non-text directive should match text' 174 }, 175 { 176 fragment: '#:~:text=test&directive&text=page', 177 expect_position: 'text', 178 description: 'Multiple text directives and a non-text directive should match text' 179 }, 180 // Test text directive behavior when there's an element fragment identifier 181 { 182 fragment: '#element:~:text=test', 183 expect_position: 'text', 184 description: 'Text directive with existing element fragment should match and scroll into view text' 185 }, 186 { 187 fragment: '#pagestate:~:text=test', 188 expect_position: 'text', 189 description: 'Text directive with nonexistent element fragment should match and scroll into view text' 190 }, 191 { 192 fragment: '#element:~:text=nomatch', 193 expect_position: 'element', 194 description: 'Non-matching text directive with existing element fragment should scroll to element' 195 }, 196 { 197 fragment: '#pagestate:~:text=nomatch', 198 expect_position: 'top', 199 description: 'Non-matching text directive with nonexistent element fragment should not match and not scroll' 200 }, 201 // Test ambiguous text matches disambiguated by context terms 202 { 203 fragment: '#:~:text=more-,test%20page', 204 expect_position: 'more-text', 205 description: 'Multiple match text directive disambiguated by prefix should match the prefixed text' 206 }, 207 { 208 fragment: '#:~:text=test%20page,-text', 209 expect_position: 'more-text', 210 description: 'Multiple match text directive disambiguated by suffix should match the suffixed text' 211 }, 212 { 213 fragment: '#:~:text=more-,test%20page,-text', 214 expect_position: 'more-text', 215 description: 'Multiple match text directive disambiguated by prefix and suffix should match the text with the given context' 216 }, 217 // Test context terms separated by node boundaries 218 { 219 fragment: '#:~:text=prefix-,test%20page,-suffix', 220 expect_position: 'cross-node-context', 221 description: 'Text directive should match when context terms are separated by node boundaries' 222 }, 223 // Test text directive within shadow DOM 224 { 225 fragment: '#:~:text=shadow%20text', 226 expect_position: 'shadow', 227 description: 'Text directive should match text within shadow DOM' 228 }, 229 // Test text directive within hidden and display none elements. These cases should not scroll into 230 // view, but still "match" in that they should be highlighted or otherwise visibly indicated 231 // if they were to become visible. 232 { 233 fragment: '#:~:text=hidden%20text', 234 expect_position: 'top', 235 description: 'Text directive should not scroll to hidden text' 236 }, 237 { 238 fragment: '#:~:text=display%20none', 239 expect_position: 'top', 240 description: 'Text directive should not scroll to display none text' 241 }, 242 // Test horizontal scroll into view 243 { 244 fragment: '#:~:text=horizontally%20scrolled%20text', 245 expect_position: 'horizontal-scroll', 246 description: 'Text directive should horizontally scroll into view' 247 }, 248 { 249 fragment: '#:~:text=Element,This', 250 expect_position: 'element', 251 description: 'Text directive that spans a range larger than the viewport should scroll the start into view' 252 } 253 ]; 254 255 for (const test_case of test_cases) { 256 promise_test(t => new Promise((resolve, reject) => { 257 let key = token(); 258 259 test_driver.bless('Open a URL with a text fragment directive', () => { 260 window.open(`scroll-to-text-fragment-target.html?key=${key}${test_case.fragment}`, '_blank', 'noopener'); 261 }); 262 263 fetchResults(key, resolve, reject); 264 }).then(data => { 265 // If the position is not 'top', the :target element should be the positioned element. 266 assert_true(data.scrollPosition == 'top' || data.target == data.scrollPosition); 267 assert_equals(data.href.indexOf(':~:'), -1, 'Expected fragment directive to be stripped from the URL.'); 268 assert_true(data.performanceNavigationEntries.every(entry => entry.indexOf(':~:') === -1), 'Expected fragment directive to be stripped from the URL in navigation performance metrics.') 269 assert_true(data.performanceResourcesEntries.every(entry => entry.indexOf(':~:') === -1), 'Expected fragment directive to be stripped from the URL in resource performance metrics.') 270 assert_equals(data.scrollPosition, test_case.expect_position, 271 `Expected ${test_case.fragment} (${test_case.description}) to scroll to ${test_case.expect_position}.`); 272 }), `Test navigation with fragment: ${test_case.description}.`); 273 } 274 </script>