Skip to content

Commit 4041747

Browse files
committed
fix: compute iframe parents
1 parent eae3654 commit 4041747

6 files changed

+91
-6
lines changed

integration/__snapshots__/iframe.test.js.snap

+5
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,10 @@ Array [
77
"left": 0,
88
"top": 116,
99
},
10+
Object {
11+
"el": "html",
12+
"left": 0,
13+
"top": 0,
14+
},
1015
]
1116
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`target in iframe is outside viewport should scroll top window 1`] = `
4+
Array [
5+
Object {
6+
"el": "html",
7+
"left": 0,
8+
"top": 366,
9+
},
10+
]
11+
`;

integration/iframe.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ describe('scrollable element is "overflow: visible" but hidden by iframe', () =>
1414
})
1515
.map(window.mapActions)
1616
})
17-
expect(actual).toHaveLength(1)
17+
expect(actual).toHaveLength(2)
1818
expect(actual[0]).toMatchObject({ el: 'html' })
1919
expect(actual).toMatchSnapshot()
2020
})
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf-8">
3+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
4+
<script src="../umd/compute-scroll-into-view.js"></script>
5+
<script src="./utils.js"></script>
6+
7+
<div style="height: 100vh"></div>
8+
9+
<iframe srcdoc="
10+
<div class='target' style='background: crimson; height: 100px; width: 100px;'></div>
11+
">
12+
</iframe>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
beforeAll(async () => {
2+
await page.goto('http://localhost:3000/integration/iframe_outside_viewport')
3+
})
4+
5+
describe('target in iframe is outside viewport', () => {
6+
test('should scroll top window', async () => {
7+
expect.assertions(3)
8+
const actual = await page.evaluate(() => {
9+
const iframe = document.querySelector('iframe')
10+
const target = iframe.contentDocument.querySelector('.target')
11+
return window
12+
.computeScrollIntoView(target, {
13+
scrollMode: 'always',
14+
})
15+
.map(window.mapActions)
16+
})
17+
expect(actual).toHaveLength(1)
18+
expect(actual[0]).toMatchObject({ el: 'html' })
19+
expect(actual).toMatchSnapshot()
20+
})
21+
})

src/index.ts

+41-5
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,14 @@ interface CustomScrollAction {
4949
}
5050

5151
// @TODO better shadowdom test, 11 = document fragment
52-
function isElement(el: any) {
52+
function isElement(el: any): el is Element {
5353
return el != null && typeof el === 'object' && el.nodeType === 1
5454
}
5555

56+
function isDocument(el: any): el is Document {
57+
return el != null && typeof el === 'object' && el.nodeType === 9
58+
}
59+
5660
function canOverflow(
5761
overflow: string | null,
5862
skipOverflowHiddenElements?: boolean
@@ -236,6 +240,33 @@ function alignNearest(
236240
return 0
237241
}
238242

243+
/**
244+
* Get rect considering iframe
245+
*/
246+
function getRect(target: Element) {
247+
const clientRect = target.getBoundingClientRect()
248+
const rect = {
249+
height: clientRect.height,
250+
width: clientRect.width,
251+
top: clientRect.top,
252+
left: clientRect.left,
253+
bottom: clientRect.bottom,
254+
right: clientRect.right,
255+
}
256+
const doc = target.ownerDocument
257+
let childWindow = doc?.defaultView
258+
259+
while (childWindow && window.top !== childWindow) {
260+
rect.top += childWindow.frameElement.getBoundingClientRect().top
261+
rect.left += childWindow.frameElement.getBoundingClientRect().left
262+
rect.bottom += childWindow.frameElement.getBoundingClientRect().top
263+
rect.right += childWindow.frameElement.getBoundingClientRect().left
264+
childWindow = childWindow.parent
265+
}
266+
267+
return rect
268+
}
269+
239270
export default (target: Element, options: Options): CustomScrollAction[] => {
240271
const {
241272
scrollMode,
@@ -260,9 +291,14 @@ export default (target: Element, options: Options): CustomScrollAction[] => {
260291
// Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box
261292
const frames: Element[] = []
262293
let cursor = target
263-
while (isElement(cursor) && checkBoundary(cursor)) {
264-
// Move cursor to parent
265-
cursor = cursor.parentNode as Element
294+
while ((isElement(cursor) || isDocument(cursor)) && checkBoundary(cursor)) {
295+
if (isDocument(cursor)) {
296+
const document = cursor
297+
cursor = document.defaultView?.frameElement as Element
298+
} else {
299+
// Move cursor to parent
300+
cursor = cursor.parentNode as Element
301+
}
266302

267303
// Stop when we reach the viewport
268304
if (cursor === scrollingElement) {
@@ -308,7 +344,7 @@ export default (target: Element, options: Options): CustomScrollAction[] => {
308344
right: targetRight,
309345
bottom: targetBottom,
310346
left: targetLeft,
311-
} = target.getBoundingClientRect()
347+
} = getRect(target)
312348

313349
// These values mutate as we loop through and generate scroll coordinates
314350
let targetBlock: number =

0 commit comments

Comments
 (0)