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: correctly screen elements inside iframes #856

Merged
merged 1 commit into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions src/browser/client-scripts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,11 @@ function prepareScreenshotUnsafe(areas, opts) {
}
}

var viewportWidth = document.documentElement.clientWidth,
viewportHeight = document.documentElement.clientHeight,
documentWidth = document.documentElement.scrollWidth,
documentHeight = document.documentElement.scrollHeight,
var mainDocumentElem = util.getMainDocumentElem(),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If user use command browser.switchToFrame(<iframe>) in test then document.documentElement references to the document inside frame which has its own dimensions. It can't be used because getBoundingClientRect inside iframe returns rect only inside this iframe.

So I should use main document in order to correctly screen element inside iframe.

viewportWidth = mainDocumentElem.clientWidth,
viewportHeight = mainDocumentElem.clientHeight,
documentWidth = mainDocumentElem.scrollWidth,
documentHeight = mainDocumentElem.scrollHeight,
viewPort = new Rect({
left: util.getScrollLeft(scrollElem),
top: util.getScrollTop(scrollElem),
Expand Down Expand Up @@ -381,8 +382,9 @@ function isEditable(element) {
}

function scrollToCaptureAreaInSafari(viewportCurr, captureArea, scrollElem) {
var documentHeight = Math.round(document.documentElement.scrollHeight);
var viewportHeight = Math.round(document.documentElement.clientHeight);
var mainDocumentElem = util.getMainDocumentElem();
var documentHeight = Math.round(mainDocumentElem.scrollHeight);
var viewportHeight = Math.round(mainDocumentElem.clientHeight);
var maxScrollByY = documentHeight - viewportHeight;

scrollElem.scrollTo(viewportCurr.left, Math.min(captureArea.top, maxScrollByY));
Expand Down
75 changes: 74 additions & 1 deletion src/browser/client-scripts/rect.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ Rect.prototype = {

exports.Rect = Rect;
exports.getAbsoluteClientRect = function getAbsoluteClientRect(element, opts) {
var coords = element.getBoundingClientRect();
var coords = getNestedBoundingClientRect(element, window);
var widthRatio = coords.width % opts.viewportWidth;
var heightRatio = coords.height % opts.documentHeight;

Expand All @@ -164,3 +164,76 @@ exports.getAbsoluteClientRect = function getAbsoluteClientRect(element, opts) {

return clientRect.translate(util.getScrollLeft(opts.scrollElem), util.getScrollTop(opts.scrollElem));
};

function getNestedBoundingClientRect(node, boundaryWindow) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found solution in this package - https://github.com/suchipi/get-nested-bounding-client-rect/tree/master. The basic logic is taken from it with some modification for our use case.

var ownerIframe = util.getOwnerIframe(node);
if (ownerIframe === null || util.getOwnerWindow(ownerIframe) === boundaryWindow) {
return node.getBoundingClientRect();
}

var rects = [node.getBoundingClientRect()];
var currentIframe = ownerIframe;

while (currentIframe) {
var rect = getBoundingClientRectWithBorderOffset(currentIframe);
rects.push(rect);

currentIframe = util.getOwnerIframe(currentIframe);
if (currentIframe && util.getOwnerWindow(currentIframe) === boundaryWindow) {
rect = getBoundingClientRectWithBorderOffset(currentIframe);
rects.push(rect);
break;
}
}

return mergeRectOffsets(rects);
}

function getBoundingClientRectWithBorderOffset(node) {
var dimensions = getElementDimensions(node);

return mergeRectOffsets([
node.getBoundingClientRect(),
{
top: dimensions.borderTop,
left: dimensions.borderLeft,
bottom: dimensions.borderBottom,
right: dimensions.borderRight,
x: dimensions.borderLeft,
y: dimensions.borderTop
}
]);
}

function getElementDimensions(element) {
var calculatedStyle = util.getOwnerWindow(element).getComputedStyle(element);

return {
borderLeft: parseFloat(calculatedStyle.borderLeftWidth),
borderRight: parseFloat(calculatedStyle.borderRightWidth),
borderTop: parseFloat(calculatedStyle.borderTopWidth),
borderBottom: parseFloat(calculatedStyle.borderBottomWidth)
};
}

function mergeRectOffsets(rects) {
return rects.reduce(function (previousRect, rect) {
if (previousRect === null) {
return rect;
}

var nextTop = previousRect.top + rect.top;
var nextLeft = previousRect.left + rect.left;

return {
top: nextTop,
left: nextLeft,
width: previousRect.width,
height: previousRect.height,
bottom: nextTop + previousRect.height,
right: nextLeft + previousRect.width,
x: nextLeft,
y: nextTop
};
});
}
35 changes: 35 additions & 0 deletions src/browser/client-scripts/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,38 @@ exports.forEachRoot = function (cb) {

traverseRoots(document.documentElement);
};

exports.getOwnerWindow = function (node) {
if (!node.ownerDocument) {
return null;
}

return node.ownerDocument.defaultView;
};

exports.getOwnerIframe = function (node) {
var nodeWindow = exports.getOwnerWindow(node);
if (nodeWindow) {
return nodeWindow.frameElement;
}

return null;
};

exports.getMainDocumentElem = function (currDocumentElem) {
if (!currDocumentElem) {
currDocumentElem = document.documentElement;
}

var currIframe = exports.getOwnerIframe(currDocumentElem);
if (!currIframe) {
return currDocumentElem;
}

var currWindow = exports.getOwnerWindow(currIframe);
if (!currWindow) {
return currDocumentElem;
}

return exports.getMainDocumentElem(currWindow.document.documentElement);
};
Loading