Skip to content

Commit 9b839f7

Browse files
authored
fix(quirksmode): use document.scrollingElement when avilable (#235)
* Importing w3c resources * Setup new testing system * Sync with Prettier * Revert "Sync with Prettier" This reverts commit 7799b6d. * retry * Sync with Prettier * rewrite tests * fix up first test * update test * test workaround * fix up test * fix quirksmode * cleanup * cleanup * use minified bundle to detect mangle problems * nah
1 parent f489a1f commit 9b839f7

File tree

15 files changed

+4034
-184
lines changed

15 files changed

+4034
-184
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ node_modules
33
# Cypress stuff
44
**/cypress/screenshots
55
**/cypress/videos
6+
**/junit
67

78
# Typescript stuff
89
typings
@@ -14,4 +15,5 @@ dist
1415
es
1516
umd
1617
/compute.js
17-
/index.js
18+
/index.js
19+
/types.js

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules
2+
tests/web-platform/resources
23
package.json

src/compute.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ export default (
214214
}
215215

216216
let targetRect = target.getBoundingClientRect()
217-
const viewport = document.documentElement
217+
const viewport = document.scrollingElement || document.documentElement
218218

219219
// Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box
220220
const frames: Element[] = []

src/index.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import compute from './compute'
22
import {
3-
ScrollBehavior,
4-
CustomScrollBehaviorCallback,
53
CustomScrollAction,
4+
CustomScrollBehaviorCallback,
65
Options as BaseOptions,
6+
ScrollBehavior,
77
} from './types'
88

99
export interface StandardBehaviorOptions extends BaseOptions {
@@ -20,6 +20,7 @@ export interface Options<T = any> extends BaseOptions {
2020
// Wait with checking if native smooth-scrolling exists until scrolling is invoked
2121
// This is much more friendly to server side rendering envs, and testing envs like jest
2222
let supportsScrollBehavior
23+
let scrollingElement
2324

2425
const isFunction = (arg: any): arg is Function => {
2526
return typeof arg == 'function'
@@ -32,8 +33,12 @@ const defaultBehavior = (
3233
actions: CustomScrollAction[],
3334
behavior: ScrollBehavior = 'auto'
3435
) => {
36+
// Because of quirksmode
37+
if (!scrollingElement) {
38+
scrollingElement = document.scrollingElement || document.documentElement
39+
}
3540
if (supportsScrollBehavior === undefined) {
36-
supportsScrollBehavior = 'scrollBehavior' in document.documentElement.style
41+
supportsScrollBehavior = 'scrollBehavior' in scrollingElement.style
3742
}
3843

3944
actions.forEach(({ el, top, left }) => {
@@ -42,7 +47,7 @@ const defaultBehavior = (
4247
if (el.scroll && supportsScrollBehavior) {
4348
el.scroll({ top, left, behavior })
4449
} else {
45-
if (el === document.documentElement) {
50+
if (el === scrollingElement) {
4651
window.scrollTo(left, top)
4752
} else {
4853
el.scrollTop = top

tests/web-platform/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Run tests
2+
3+
You'll need two terminal sessions for this.
4+
5+
1. Install deps `yarn`
6+
2. Start the server: `yarn start`
7+
3. In the other session: `yarn test`
8+
9+
You can also open http://localhost:3000 in the browser to run the tests.
10+
11+
# Why are tests setup like this?
12+
13+
Two reasons.
14+
15+
1. It helps with staying in sync with the same tests that browsers run when checking if they are implementing the spec correctly.
16+
2. Easier to run tests in different browsers, compared to if a more conventional cypress or jest setup were used.
17+
18+
## How tests are kept in sync with w3c
19+
20+
The `resources` and `css` folders are rougly equivalent with their counterparts on here: https://github.com/w3c/web-platform-tests
21+
22+
Except that anything that isn't needed to run the tests related to `scrollIntoView` is stripped out.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<!DOCTYPE HTML>
2+
<script src="/node_modules/scroll-into-view-if-needed/umd/scroll-into-view-if-needed.js"></script>
3+
<script src="/resources/testharness.js"></script>
4+
<script src="/resources/testharnessreport.js"></script>
5+
<title>Check End Position of smooth scrollIntoView</title>
6+
<div id="container" style="height: 2500px; width: 2500px;">
7+
<div id="content" style="height: 500px; width: 500px;margin-left: 1000px; margin-right: 1000px; margin-top: 1000px;margin-bottom: 1000px;background-color: red">
8+
</div>
9+
<div id="shadow"></div>
10+
</div>
11+
<script>
12+
var content_height = 500;
13+
var content_width = 500;
14+
var window_height = document.scrollingElement.clientHeight;
15+
var window_width = document.scrollingElement.clientWidth;
16+
var content = document.getElementById("content");
17+
add_completion_callback(() => document.getElementById("container").remove());
18+
19+
function waitForScrollEnd() {
20+
var last_changed_frame = 0;
21+
var last_x = window.scrollX;
22+
var last_y = window.scrollY;
23+
return new Promise((resolve, reject) => {
24+
function tick(frames) {
25+
// We requestAnimationFrame either for 500 frames or until 20 frames with
26+
// no change have been observed.
27+
if (frames >= 500 || frames - last_changed_frame > 20) {
28+
resolve();
29+
} else {
30+
if (window.scrollX != last_x || window.scrollY != last_y) {
31+
last_changed_frame = frames;
32+
last_x = window.scrollX;
33+
last_y = window.scrollY;
34+
}
35+
requestAnimationFrame(tick.bind(null, frames + 1));
36+
}
37+
}
38+
tick(0);
39+
});
40+
}
41+
42+
// When testing manually, we need an additional frame at beginning
43+
// to trigger the effect.
44+
requestAnimationFrame(() => {
45+
promise_test(t => {
46+
window.scrollTo(0, 0);
47+
var expected_x = content.offsetLeft + content_width - window_width;
48+
var expected_y = content.offsetTop + content_height - window_height;
49+
assert_not_equals(window.scrollX, expected_x, "scrollX");
50+
assert_not_equals(window.scrollY, expected_y, "scrollY");
51+
scrollIntoView(content, {behavior: "smooth", block: "nearest", inline:
52+
"nearest"});
53+
return waitForScrollEnd().then(() => {
54+
assert_approx_equals(window.scrollX, expected_x, 1, "scrollX");
55+
assert_approx_equals(window.scrollY, expected_y, 1, "scrollY");
56+
});
57+
}, "Smooth scrollIntoView should scroll the element to the 'nearest' position");
58+
59+
promise_test(t => {
60+
window.scrollTo(0, 0);
61+
var expected_x = content.offsetLeft;
62+
var expected_y = content.offsetTop;
63+
assert_not_equals(window.scrollX, expected_x, "scrollX");
64+
assert_not_equals(window.scrollY, expected_y, "scrollY");
65+
scrollIntoView(content, {behavior: "smooth", block: "start", inline:
66+
"start"});
67+
return waitForScrollEnd().then(() => {
68+
assert_approx_equals(window.scrollX, expected_x, 1, "scrollX");
69+
assert_approx_equals(window.scrollY, expected_y, 1, "scrollY");
70+
});
71+
}, "Smooth scrollIntoView should scroll the element to the 'start' position");
72+
73+
promise_test(t => {
74+
window.scrollTo(0, 0);
75+
var expected_x = content.offsetLeft + (content_width - window_width) / 2;
76+
var expected_y = content.offsetTop + (content_height - window_height) / 2;
77+
assert_not_equals(window.scrollX, expected_x, "scrollX");
78+
assert_not_equals(window.scrollY, expected_y, "scrollY");
79+
scrollIntoView(content, {behavior: "smooth", block: "center", inline:
80+
"center"});
81+
return waitForScrollEnd().then(() => {
82+
assert_approx_equals(window.scrollX, expected_x, 1, "scrollX");
83+
assert_approx_equals(window.scrollY, expected_y, 1, "scrollY");
84+
});
85+
}, "Smooth scrollIntoView should scroll the element to the 'center' position");
86+
87+
promise_test(t => {
88+
window.scrollTo(0, 0);
89+
var expected_x = content.offsetLeft + content_width - window_width;
90+
var expected_y = content.offsetTop + content_height - window_height;
91+
assert_not_equals(window.scrollX, expected_x, "scrollX");
92+
assert_not_equals(window.scrollY, expected_y, "scrollY");
93+
scrollIntoView(content, {behavior: "smooth", block: "end", inline:
94+
"end"});
95+
return waitForScrollEnd().then(() => {
96+
assert_approx_equals(window.scrollX, expected_x, 1, "scrollX");
97+
assert_approx_equals(window.scrollY, expected_y, 1, "scrollY");
98+
});
99+
}, "Smooth scrollIntoView should scroll the element to the 'end' position");
100+
101+
});
102+
</script>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<!DOCTYPE html>
2+
<title>CSSOM View - scrollIntoView</title>
3+
<meta charset="utf-8">
4+
<link rel="author" title="Chris Wu" href="mailto:[email protected]">
5+
<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-element-scrollintoview">
6+
<link rel="help" href="https://heycam.github.io/webidl/#es-operations">
7+
<link rel="help" href="https://heycam.github.io/webidl/#es-overloads">
8+
<meta name="flags" content="dom">
9+
<script src="/node_modules/scroll-into-view-if-needed/umd/scroll-into-view-if-needed.js"></script>
10+
<script src="/resources/testharness.js"></script>
11+
<script src="/resources/testharnessreport.js"></script>
12+
<style>
13+
body.running { margin: 0; padding: 4000px; overflow: hidden }
14+
body.running #testDiv {
15+
width: 200px;
16+
height: 200px;
17+
background-color: lightgreen;
18+
}
19+
</style>
20+
<body class=running>
21+
<div id=testDiv></div>
22+
<div id="log"></div>
23+
<script>
24+
var testDiv = document.getElementById('testDiv');
25+
26+
var expectedXLeft = 4000;
27+
var expectedXRight = 4000 - window.innerWidth + testDiv.clientWidth;
28+
var expectedXCenter = 4000 - (window.innerWidth / 2) + (testDiv.clientWidth / 2);
29+
30+
var expectedYTop = 4000;
31+
var expectedYBottom = 4000 - window.innerHeight + testDiv.clientHeight;
32+
var expectedYCenter = 4000 - (window.innerHeight / 2) + (testDiv.clientHeight / 2);
33+
34+
[
35+
["omitted argument", "nearest", expectedYTop],
36+
[true, "nearest", expectedYTop],
37+
[false, "nearest", expectedYBottom],
38+
[undefined, "nearest", expectedYTop],
39+
[null, "nearest", expectedYTop],
40+
[{}, "nearest", expectedYTop],
41+
[{block: "center", inline: "center"}, expectedXCenter, expectedYCenter],
42+
[{block: "start", inline: "start"}, expectedXLeft, expectedYTop],
43+
[{block: "end", inline: "end"}, expectedXRight, expectedYBottom],
44+
[{block: "nearest", inline: "nearest"}, "nearest", "nearest"],
45+
].forEach(([input, expectedX, expectedY]) => {
46+
test(() => {
47+
window.scrollTo(0, 0);
48+
testScrollIntoView(input);
49+
var x = (expectedX === "nearest") ? expectedXRight : expectedX;
50+
var y = (expectedY === "nearest") ? expectedYBottom : expectedY;
51+
assert_approx_equals(window.scrollX, x, 0.5, 'scrollX');
52+
assert_approx_equals(window.scrollY, y, 0.5, 'scrollY');
53+
}, `scrollIntoView(${format_input(input)}) starting at left,top`);
54+
55+
test(() => {
56+
window.scrollTo(0, 12000);
57+
testScrollIntoView(input);
58+
var x = (expectedX === "nearest") ? expectedXRight : expectedX;
59+
var y = (expectedY === "nearest") ? expectedYTop : expectedY;
60+
assert_approx_equals(window.scrollX, x, 0.5, 'scrollX');
61+
assert_approx_equals(window.scrollY, y, 0.5, 'scrollY');
62+
}, `scrollIntoView(${format_input(input)}) starting at left,bottom`);
63+
64+
test(() => {
65+
window.scrollTo(12000, 0);
66+
testScrollIntoView(input);
67+
var x = (expectedX === "nearest") ? expectedXLeft : expectedX;
68+
var y = (expectedY === "nearest") ? expectedYBottom : expectedY;
69+
assert_approx_equals(window.scrollX, x, 0.5, 'scrollX');
70+
assert_approx_equals(window.scrollY, y, 0.5, 'scrollY');
71+
}, `scrollIntoView(${format_input(input)}) starting at right,top`);
72+
73+
test(() => {
74+
window.scrollTo(12000, 12000);
75+
testScrollIntoView(input);
76+
var x = (expectedX === "nearest") ? expectedXLeft : expectedX;
77+
var y = (expectedY === "nearest") ? expectedYTop : expectedY;
78+
assert_approx_equals(window.scrollX, x, 0.5, 'scrollX');
79+
assert_approx_equals(window.scrollY, y, 0.5, 'scrollY');
80+
}, `scrollIntoView(${format_input(input)}) starting at right,bottom`);
81+
});
82+
83+
function testScrollIntoView(input) {
84+
if (input === "omitted argument") {
85+
scrollIntoView(testDiv);
86+
} else {
87+
scrollIntoView(testDiv, input);
88+
}
89+
}
90+
91+
// This formats dict as a string suitable as test name.
92+
// format_value() is provided by testharness.js,
93+
// which also preserves sign for -0.
94+
function format_dict(dict) {
95+
const props = [];
96+
for (let prop in dict) {
97+
props.push(`${prop}: ${format_value(dict[prop])}`);
98+
}
99+
return `{${props.join(', ')}}`;
100+
}
101+
102+
function format_input(input) {
103+
if (input === "omitted argument") {
104+
return "";
105+
} else if (input === null || typeof input !== "object") {
106+
return format_value(input);
107+
}
108+
return format_dict(input);
109+
}
110+
111+
document.body.classList.remove('running');
112+
window.scrollTo(0, 0);
113+
</script>
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
describe('web-platform-tests by w3c', function() {
2-
it('css/cssom-view/scrollintoview.html', () => {
3-
cy.visit('/')
1+
describe('css/cssom-view', () => {
2+
// @TODO maybe there is a dynamic way to generate a list over tests
43

5-
cy.get('#summary .pass').should('contain', '40 Pass')
6-
})
7-
8-
it('scrollIntoView-smooth.html', () => {
9-
cy.visit('/smooth')
4+
const tests = ['scrollintoview.html', 'scrollIntoView-smooth.html']
5+
tests.forEach(test => {
6+
it(`implements ${test} correctly`, () => {
7+
cy.visit(`/css/cssom-view/${test}`)
108

11-
cy.get('#summary .pass').should('contain', '4 Pass')
9+
cy.get('#summary .pass').should('exist')
10+
cy.get('#summary .fail').should('not.exist')
11+
})
1212
})
1313
})

0 commit comments

Comments
 (0)