Skip to content

Commit 943bc6a

Browse files
authored
Capture sync async with getBoundingClientRect (#35)
* Refactor layout method to accept target element for improved flexibility * refactor: simplify layout method by using logical OR for body assignment * refactor: replace magic numbers with named constants for media query breakpoints * refactor: replace repeated calls to getContentDocument with a variable for improved readability
1 parent 56f2d01 commit 943bc6a

File tree

6 files changed

+66
-38
lines changed

6 files changed

+66
-38
lines changed

experimental/responsive-design/dist/app.js

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

experimental/responsive-design/src/lib/components/app-ribbon.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ class AppRibbon extends LightDOMLitElement {
5858
// ResizeObserver is used primarily to exercise this API as part of the benchmark.
5959
// While CSS or window.matchMedia could potentially be used instead.
6060
const breakpoint = breakpoints.find((bp) => width >= bp.minWidth);
61-
this.visibleButtons = breakpoint ? this.buttons.slice(0, breakpoint.buttons) : this.buttons.slice(0, 2);
62-
this.requestUpdate();
61+
const newButtonCount = breakpoint ? breakpoint.buttons : 2;
62+
63+
if (this.visibleButtons.length !== newButtonCount)
64+
this.visibleButtons = this.buttons.slice(0, newButtonCount);
6365
}
6466

6567
_getVisibleButtonsTemplate() {

experimental/responsive-design/src/lib/components/chat-input.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class ChatInput extends LightDOMLitElement {
2222

2323
_sendMessage() {
2424
if (this.value.trim())
25-
this.dispatchEvent(new CustomEvent("send-chat", { bubbles: true, composed: true }));
25+
this.dispatchEvent(new CustomEvent("send-chat"));
2626
}
2727

2828
render() {

experimental/responsive-design/src/lib/components/information-window.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ class InformationWindow extends LitElement {
5656
this._isChatExpanded = event.detail.isExpanded;
5757
this._currentIndex = 0;
5858
this.updateCarousel();
59-
this.requestUpdate();
6059
}
6160

6261
previousCard() {

resources/benchmark-runner.mjs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ class Page {
3030
return this._frame.contentWindow.localStorage;
3131
}
3232

33-
layout() {
34-
const body = this._frame ? this._frame.contentDocument.body : document.body;
33+
layout(target = null) {
34+
// If target is provided, it should be a body element to force layout on
35+
// Otherwise force layout on the current page's body
36+
const body = target || this._frame?.contentDocument?.body || document.body;
37+
3538
const value = forceLayout(body, params.layoutMode);
3639
body._leakedLayoutValue = value; // Prevent dead code elimination.
3740
}
@@ -64,13 +67,13 @@ class Page {
6467
* @param {string[]} [path] An array containing a path to the parent element.
6568
* @returns PageElement | null
6669
*/
67-
querySelector(selector, path = [], inIframe = false) {
70+
querySelector(selector, path = []) {
6871
const lookupStartNode = this._frame.contentDocument;
6972
const element = getParent(lookupStartNode, path).querySelector(selector);
7073

7174
if (element === null)
7275
return null;
73-
return this._wrapElement(inIframe ? element.contentDocument : element);
76+
return this.wrapElement(element);
7477
}
7578

7679
/**
@@ -92,15 +95,15 @@ class Page {
9295
const lookupStartNode = this._frame.contentDocument;
9396
const elements = Array.from(getParent(lookupStartNode, path).querySelectorAll(selector));
9497
for (let i = 0; i < elements.length; i++)
95-
elements[i] = this._wrapElement(elements[i]);
98+
elements[i] = this.wrapElement(elements[i]);
9699
return elements;
97100
}
98101

99102
getElementById(id) {
100103
const element = this._frame.contentDocument.getElementById(id);
101104
if (element === null)
102105
return null;
103-
return this._wrapElement(element);
106+
return this.wrapElement(element);
104107
}
105108

106109
call(functionName) {
@@ -115,10 +118,10 @@ class Page {
115118
}
116119

117120
callToGetElement(functionName) {
118-
return this._wrapElement(this._frame.contentWindow[functionName]());
121+
return this.wrapElement(this._frame.contentWindow[functionName]());
119122
}
120123

121-
_wrapElement(element) {
124+
wrapElement(element) {
122125
return new PageElement(element);
123126
}
124127

@@ -139,6 +142,14 @@ class PageElement {
139142
this.#node = node;
140143
}
141144

145+
getContentDocument() {
146+
if (this.#node.nodeName?.toLowerCase() !== "iframe")
147+
throw new Error("getContentDocument() called on non-iframe element");
148+
if (!this.#node.contentDocument)
149+
throw new Error("Iframe contentDocument is not available.");
150+
return this.#node.contentDocument;
151+
}
152+
142153
setValue(value) {
143154
this.#node.value = value;
144155
}

resources/tests.mjs

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,30 +1106,34 @@ Suites.push({
11061106
},
11071107
tests: [
11081108
new BenchmarkTestStep("LoadChatAndExpandRecipes", async (page) => {
1109-
const iframeElement = page.querySelector("#content-iframe", [], true);
1110-
const resumePreviousChatBtn = iframeElement.querySelectorInShadowRoot("#resume-previous-chat-btn", ["cooking-app", "chat-window"]);
1109+
const iframeElement = page.querySelector("#content-iframe");
1110+
const iframeDoc = iframeElement.getContentDocument();
1111+
const iframeContent = page.wrapElement(iframeDoc);
1112+
const resumePreviousChatBtn = iframeContent.querySelectorInShadowRoot("#resume-previous-chat-btn", ["cooking-app", "chat-window"]);
11111113
resumePreviousChatBtn.click();
1112-
page.layout();
1114+
page.layout(iframeDoc.body);
11131115

11141116
// Navigate through the restaurants
1115-
const nextRestaurantBtn = iframeElement.querySelectorInShadowRoot("#next-restaurant-btn", ["cooking-app", "information-window"]);
1116-
const restaurantCards = iframeElement.querySelectorAllInShadowRoot("restaurant-card", ["cooking-app", "information-window"]);
1117+
const nextRestaurantBtn = iframeContent.querySelectorInShadowRoot("#next-restaurant-btn", ["cooking-app", "information-window"]);
1118+
const restaurantCards = iframeContent.querySelectorAllInShadowRoot("restaurant-card", ["cooking-app", "information-window"]);
11171119
const numOfRestaurantCards = restaurantCards.length - 1; // since 1 is already visible
11181120
for (let i = 0; i < numOfRestaurantCards; i++) {
11191121
nextRestaurantBtn.click();
1120-
page.layout();
1122+
page.layout(iframeDoc.body);
11211123
}
11221124

11231125
// Expand recipes
1124-
const showMoreBtn = iframeElement.querySelectorAllInShadowRoot(".show-more-btn", ["cooking-app", "main-content", "recipe-grid"]);
1126+
const showMoreBtn = iframeContent.querySelectorAllInShadowRoot(".show-more-btn", ["cooking-app", "main-content", "recipe-grid"]);
11251127
for (const btn of showMoreBtn) {
11261128
btn.click();
1127-
page.layout();
1129+
page.layout(iframeDoc.body);
11281130
}
11291131
}),
11301132
new BenchmarkTestStep("ReduceWidthIn5Steps", async (page) => {
11311133
const iframeElement = page.querySelector("#content-iframe");
1134+
const iframeDoc = iframeElement.getContentDocument();
11321135
const widths = [768, 704, 640, 560, 480];
1136+
const MATCH_MEDIA_QUERY_BREAKPOINT = 640;
11331137

11341138
// The matchMedia query is "(max-width: 640px)"
11351139
// Starting from a width > 640px, we'll only get 1 event when crossing to <= 640px
@@ -1140,47 +1144,56 @@ Suites.push({
11401144

11411145
for (const width of widths) {
11421146
iframeElement.setWidth(`${width}px`);
1143-
page.layout();
1147+
page.layout(iframeDoc.body);
1148+
if (width === MATCH_MEDIA_QUERY_BREAKPOINT)
1149+
await resizeWorkPromise;
11441150
}
11451151

1146-
await resizeWorkPromise;
1152+
page.layout(iframeDoc.body);
1153+
await new Promise((resolve) => requestAnimationFrame(() => setTimeout(resolve, 0)));
11471154
}),
11481155
new BenchmarkTestStep("ScrollToChatAndSendMessages", async (page) => {
1149-
const iframeElement = page.querySelector("#content-iframe", [], true);
1156+
const iframeElement = page.querySelector("#content-iframe");
1157+
const iframeDoc = iframeElement.getContentDocument();
1158+
const iframeContent = page.wrapElement(iframeDoc);
11501159

11511160
// Navigate through the carousel items
1152-
const nextItemBtn = iframeElement.querySelectorInShadowRoot("#next-item-carousel-btn", ["cooking-app", "main-content", "recipe-carousel"]);
1153-
const recipeCarouselItems = iframeElement.querySelectorAllInShadowRoot(".carousel-item", ["cooking-app", "main-content", "recipe-carousel"]);
1161+
const nextItemBtn = iframeContent.querySelectorInShadowRoot("#next-item-carousel-btn", ["cooking-app", "main-content", "recipe-carousel"]);
1162+
const recipeCarouselItems = iframeContent.querySelectorAllInShadowRoot(".carousel-item", ["cooking-app", "main-content", "recipe-carousel"]);
11541163
const numOfCarouselItems = recipeCarouselItems.length - 3; // since 3 are already visible
11551164
for (let i = 0; i < numOfCarouselItems; i++) {
11561165
nextItemBtn.click();
1157-
page.layout();
1166+
page.layout(iframeDoc.body);
11581167
}
11591168

11601169
// Collapse recipes
1161-
const showMoreBtn = iframeElement.querySelectorAllInShadowRoot(".show-more-btn", ["cooking-app", "main-content", "recipe-grid"]);
1170+
const showMoreBtn = iframeContent.querySelectorAllInShadowRoot(".show-more-btn", ["cooking-app", "main-content", "recipe-grid"]);
11621171
for (const btn of showMoreBtn) {
11631172
btn.click();
1164-
page.layout();
1173+
page.layout(iframeDoc.body);
11651174
}
11661175

1167-
const element = iframeElement.querySelectorInShadowRoot("#chat-window", ["cooking-app", "chat-window"]);
1176+
const element = iframeContent.querySelectorInShadowRoot("#chat-window", ["cooking-app", "chat-window"]);
11681177
element.scrollIntoView({ behavior: "instant" });
1169-
page.layout();
1178+
page.layout(iframeDoc.body);
11701179

11711180
const messagesToBeSent = ["Please generate an image of Tomato Soup.", "Try again, but make the soup look thicker.", "Try again, but make the soup served in a rustic bowl and include a sprinkle of fresh herbs on top."];
11721181

1173-
const chatInput = iframeElement.querySelectorInShadowRoot("#chat-input", ["cooking-app", "chat-window"]);
1182+
const chatInput = iframeContent.querySelectorInShadowRoot("#chat-input", ["cooking-app", "chat-window"]);
11741183
for (const message of messagesToBeSent) {
11751184
chatInput.setValue(message);
11761185
chatInput.dispatchEvent("input");
11771186
chatInput.enter("keydown");
1178-
page.layout();
1187+
page.layout(iframeDoc.body);
11791188
}
1189+
1190+
await new Promise((resolve) => requestAnimationFrame(() => setTimeout(resolve, 0)));
11801191
}),
11811192
new BenchmarkTestStep("IncreaseWidthIn5Steps", async (page) => {
11821193
const iframeElement = page.querySelector("#content-iframe");
1194+
const iframeDoc = iframeElement.getContentDocument();
11831195
const widths = [560, 640, 704, 768, 800];
1196+
const MATCH_MEDIA_QUERY_BREAKPOINT = 704;
11841197

11851198
// The matchMedia query is "(max-width: 640px)"
11861199
// Starting from a width <= 640px, we'll get 1 event when crossing back to > 640px.
@@ -1191,10 +1204,13 @@ Suites.push({
11911204

11921205
for (const width of widths) {
11931206
iframeElement.setWidth(`${width}px`);
1194-
page.layout();
1207+
page.layout(iframeDoc.body);
1208+
if (width === MATCH_MEDIA_QUERY_BREAKPOINT)
1209+
await resizeWorkPromise;
11951210
}
11961211

1197-
await resizeWorkPromise;
1212+
page.layout(iframeDoc.body);
1213+
await new Promise((resolve) => requestAnimationFrame(() => setTimeout(resolve, 0)));
11981214
}),
11991215
],
12001216
});

0 commit comments

Comments
 (0)