Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: compute iframe parents #636

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions integration/__snapshots__/iframe.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,10 @@ Array [
"left": 0,
"top": 116,
},
Object {
"el": "html",
"left": 0,
"top": 0,
},
]
`;
11 changes: 11 additions & 0 deletions integration/__snapshots__/iframe_outside_viewport.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`target in iframe is outside viewport should scroll top window 1`] = `
Array [
Object {
"el": "html",
"left": 0,
"top": 366,
},
]
`;
2 changes: 1 addition & 1 deletion integration/iframe.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('scrollable element is "overflow: visible" but hidden by iframe', () =>
})
.map(window.mapActions)
})
expect(actual).toHaveLength(1)
expect(actual).toHaveLength(2)
expect(actual[0]).toMatchObject({ el: 'html' })
expect(actual).toMatchSnapshot()
})
Expand Down
12 changes: 12 additions & 0 deletions integration/iframe_outside_viewport.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<script src="../umd/compute-scroll-into-view.js"></script>
<script src="./utils.js"></script>

<div style="height: 100vh"></div>

<iframe srcdoc="
<div class='target' style='background: crimson; height: 100px; width: 100px;'></div>
">
</iframe>
21 changes: 21 additions & 0 deletions integration/iframe_outside_viewport.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
beforeAll(async () => {
await page.goto('http://localhost:3000/integration/iframe_outside_viewport')
})

describe('target in iframe is outside viewport', () => {
test('should scroll top window', async () => {
expect.assertions(3)
const actual = await page.evaluate(() => {
const iframe = document.querySelector('iframe')
const target = iframe.contentDocument.querySelector('.target')
return window
.computeScrollIntoView(target, {
scrollMode: 'always',
})
.map(window.mapActions)
})
expect(actual).toHaveLength(1)
expect(actual[0]).toMatchObject({ el: 'html' })
expect(actual).toMatchSnapshot()
})
})
56 changes: 43 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,14 @@ interface CustomScrollAction {
}

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

function isDocument(el: any): el is Document {
return el != null && typeof el === 'object' && el.nodeType === 9
}

function canOverflow(
overflow: string | null,
skipOverflowHiddenElements?: boolean
Expand Down Expand Up @@ -236,6 +240,34 @@ function alignNearest(
return 0
}

/**
* Get rect considering iframe
*/
function getRect(target: Element) {
const clientRect = target.getBoundingClientRect()
const rect = {
height: clientRect.height,
width: clientRect.width,
top: clientRect.top,
left: clientRect.left,
bottom: clientRect.bottom,
right: clientRect.right,
}
const doc = target.ownerDocument
let childWindow = doc?.defaultView

while (childWindow && window.top !== childWindow) {
const frameRect = childWindow.frameElement.getBoundingClientRect()
rect.top += frameRect.top
rect.bottom += frameRect.top
rect.left += frameRect.left
rect.right += frameRect.left
childWindow = childWindow.parent
}

return rect
}

export default (target: Element, options: Options): CustomScrollAction[] => {
const {
scrollMode,
Expand All @@ -260,9 +292,14 @@ export default (target: Element, options: Options): CustomScrollAction[] => {
// Collect all the scrolling boxes, as defined in the spec: https://drafts.csswg.org/cssom-view/#scrolling-box
const frames: Element[] = []
let cursor = target
while (isElement(cursor) && checkBoundary(cursor)) {
// Move cursor to parent
cursor = cursor.parentNode as Element
while ((isElement(cursor) || isDocument(cursor)) && checkBoundary(cursor)) {
if (isDocument(cursor)) {
const document = cursor
cursor = document.defaultView?.frameElement as Element
} else {
// Move cursor to parent
cursor = cursor.parentNode as Element
}

// Stop when we reach the viewport
if (cursor === scrollingElement) {
Expand Down Expand Up @@ -308,7 +345,7 @@ export default (target: Element, options: Options): CustomScrollAction[] => {
right: targetRight,
bottom: targetBottom,
left: targetLeft,
} = target.getBoundingClientRect()
} = getRect(target)

// These values mutate as we loop through and generate scroll coordinates
let targetBlock: number =
Expand All @@ -332,14 +369,7 @@ export default (target: Element, options: Options): CustomScrollAction[] => {

// @TODO add a shouldScroll hook here that allows userland code to take control

const {
height,
width,
top,
right,
bottom,
left,
} = frame.getBoundingClientRect()
const { height, width, top, right, bottom, left } = getRect(frame)

// If the element is already visible we can end it here
// @TODO targetBlock and targetInline should be taken into account to be compliant with https://github.com/w3c/csswg-drafts/pull/1805/files#diff-3c17f0e43c20f8ecf89419d49e7ef5e0R1333
Expand Down