Skip to content

Commit 5a8e2a0

Browse files
sadym-chromiumDevtools-frontend LUCI CQ
authored andcommitted
Node screenshots in OOPiF
Trying to allow for node screenshots in OOPiF by: 1. Relying on DOM.getBoxModel 2. Traversing CDP targets Tested manually on https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/backdrop-filter#examples Bug: 40241918 Change-Id: I616d010c12a116658f882cecb52d4317f6028133 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7456254 Reviewed-by: Alex Rudenko <alexrudenko@chromium.org> Reviewed-by: Ergün Erdoğmuş <ergunsh@chromium.org> Commit-Queue: Maksim Sadym <sadym@chromium.org>
1 parent bdd5124 commit 5a8e2a0

File tree

1 file changed

+101
-62
lines changed

1 file changed

+101
-62
lines changed

front_end/panels/emulation/DeviceModeWrapper.ts

Lines changed: 101 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -123,73 +123,47 @@ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
123123
return;
124124
}
125125

126+
// Resolve to a remote object to ensure the node is alive in the context.
126127
const object = await node.resolveToObject();
127128
if (!object) {
128129
return;
129130
}
130-
const result = await object.callFunction(function(this: Element) {
131-
function getFrameOffset(frame: Element|null): {x: number, y: number} {
132-
if (!frame) {
133-
return {x: 0, y: 0};
134-
}
135-
136-
// The offset of the frame's content relative to the frame element
137-
// contains the border width and the padding.
138-
// The border width.
139-
const borderTop = frame.clientTop;
140-
const borderLeft = frame.clientLeft;
141-
142-
// The padding can be retrieved via computed styles.
143-
const styles = window.getComputedStyle(frame);
144-
const paddingTop = parseFloat(styles.paddingTop);
145-
const paddingLeft = parseFloat(styles.paddingLeft);
146-
147-
// The position of the frame in it's parent.
148-
const rect = frame.getBoundingClientRect();
149-
150-
// The offset of the parent frame's content relative to the
151-
// document. If there is no parent frame, the offset is 0.
152-
// In case of OOPiF, there is no access to the parent frame's
153-
// offset.
154-
const parentFrameOffset = getFrameOffset(frame.ownerDocument.defaultView?.frameElement ?? null);
155-
156-
// The scroll position of the frame.
157-
const scrollX = frame.ownerDocument.defaultView?.scrollX ?? 0;
158-
const scrollY = frame.ownerDocument.defaultView?.scrollY ?? 0;
159-
160-
return {
161-
x: parentFrameOffset.x + rect.left + borderLeft + paddingLeft + scrollX,
162-
y: parentFrameOffset.y + rect.top + borderTop + paddingTop + scrollY,
163-
};
164-
}
165-
166-
// The bounding client rect of the node relative to the viewport.
167-
const rect = this.getBoundingClientRect();
168-
const frameOffset = getFrameOffset(this.ownerDocument.defaultView?.frameElement ?? null);
169-
170-
// The scroll position of the frame.
171-
const scrollX = this.ownerDocument.defaultView?.scrollX ?? 0;
172-
const scrollY = this.ownerDocument.defaultView?.scrollY ?? 0;
173-
174-
// The offset of the node's content relative to the top-level
175-
// document is the sum of the element offset relative to the
176-
// document's viewport, the document's scroll position, and the
177-
// parent's offset relative to the top-level document.
178-
return JSON.stringify({
179-
x: rect.left + frameOffset.x + scrollX,
180-
y: rect.top + frameOffset.y + scrollY,
181-
width: rect.width,
182-
height: rect.height,
183-
scale: 1,
184-
});
185-
});
186-
if (!result.object) {
187-
throw new Error('Clipping error: could not get object data.');
131+
132+
// Get the Box Model via CDP.
133+
// This returns the quads relative to the target's viewport.
134+
// We use the 'border' quad to include the border and padding in the screenshot,
135+
// matching the 'width' and 'height' properties which are also Border Box dimensions.
136+
const nodeBoxModel = await node.boxModel();
137+
if (!nodeBoxModel) {
138+
throw new Error(`Unable to get box model of the node: ${new Error().stack}`);
139+
}
140+
const nodeBorderQuad = nodeBoxModel.border;
141+
142+
// Get Layout Metrics to account for the Visual Viewport scroll and zoom.
143+
const metrics = await node.domModel().target().pageAgent().invoke_getLayoutMetrics();
144+
if (metrics.getError()) {
145+
throw new Error(`Unable to get metrics: ${new Error().stack}`);
188146
}
189-
const clip = (JSON.parse((result.object.value as string)));
190-
const response = await node.domModel().target().pageAgent().invoke_getLayoutMetrics();
191-
const error = response.getError();
192-
const zoom = !error && response.visualViewport.zoom || 1;
147+
148+
const scrollX = metrics.cssVisualViewport.pageX;
149+
const scrollY = metrics.cssVisualViewport.pageY;
150+
151+
// Calculate the global offset for OOPiFs (Out-of-Process iframes).
152+
// This accounts for the position of the target's frame within the main page.
153+
const {x: oopifOffsetX, y: oopifOffsetY} = await getOopifOffset(node.domModel().target());
154+
155+
// Assemble the final Clip.
156+
// The absolute coordinates are: Global (OOPiF) + Viewport Scroll + Local Node Position (Border Box).
157+
const clip = {
158+
x: oopifOffsetX + scrollX + nodeBorderQuad[0],
159+
y: oopifOffsetY + scrollY + nodeBorderQuad[1],
160+
width: nodeBoxModel.width,
161+
height: nodeBoxModel.height,
162+
scale: 1,
163+
};
164+
165+
// Apply Zoom factor.
166+
const zoom = metrics.cssVisualViewport.zoom ?? 1;
193167
clip.x *= zoom;
194168
clip.y *= zoom;
195169
clip.width *= zoom;
@@ -210,3 +184,68 @@ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
210184
return false;
211185
}
212186
}
187+
188+
/**
189+
* Calculate the offset of the "Local Root" frame relative to the "Global Root" (the main frame).
190+
* This involves traversing the CDP Targets for OOPiFs.
191+
*/
192+
async function getOopifOffset(target: SDK.Target.Target|null): Promise<{x: number, y: number}> {
193+
if (!target) {
194+
return {x: 0, y: 0};
195+
}
196+
197+
// Get the parent target. If there's no parent (we are at root) or it's not a frame, we are done.
198+
const parentTarget = target.parentTarget();
199+
if (!parentTarget || parentTarget.type() !== SDK.Target.Type.FRAME) {
200+
return {x: 0, y: 0};
201+
}
202+
203+
// Identify the current frame's ID to find its owner in the parent.
204+
const frameId = target.model(SDK.ResourceTreeModel.ResourceTreeModel)?.mainFrame?.id;
205+
if (!frameId) {
206+
return {x: 0, y: 0};
207+
}
208+
209+
// Get the DOMModel of the parent to query the frame owner element.
210+
const parentDOMModel = parentTarget.model(SDK.DOMModel.DOMModel);
211+
if (!parentDOMModel) {
212+
return {x: 0, y: 0};
213+
}
214+
215+
// Retrieve the frame owner node (e.g. the <iframe> element) in the parent's document.
216+
const frameOwnerDeferred = await parentDOMModel.getOwnerNodeForFrame(frameId);
217+
const frameOwner = await frameOwnerDeferred?.resolvePromise();
218+
if (!frameOwner) {
219+
return {x: 0, y: 0};
220+
}
221+
222+
// Get the content box of the iframe element.
223+
// This is relative to the parent target's viewport.
224+
const boxModel = await frameOwner.boxModel();
225+
if (!boxModel) {
226+
return {x: 0, y: 0};
227+
}
228+
229+
// content is a Quad [x1, y1, x2, y2, x3, y3, x4, y4]
230+
const contentQuad = boxModel.content;
231+
const iframeContentX = contentQuad[0];
232+
const iframeContentY = contentQuad[1];
233+
234+
// Get the scroll position of the parent target to convert viewport-relative coordinates
235+
// to document-relative coordinates.
236+
const parentMetrics = await parentTarget.pageAgent().invoke_getLayoutMetrics();
237+
if (parentMetrics.getError()) {
238+
return {x: 0, y: 0};
239+
}
240+
241+
const scrollX = parentMetrics.cssVisualViewport.pageX;
242+
const scrollY = parentMetrics.cssVisualViewport.pageY;
243+
244+
// Recursively add the offset of the parent target itself (if it is also an OOPiF).
245+
const parentOffset = await getOopifOffset(parentTarget);
246+
247+
return {
248+
x: iframeContentX + scrollX + parentOffset.x,
249+
y: iframeContentY + scrollY + parentOffset.y,
250+
};
251+
}

0 commit comments

Comments
 (0)