Skip to content

Commit 5488cbe

Browse files
authoredMar 8, 2022
core(full-page-screenshot): leave emulated width unchanged (#13643)
1 parent 57c7fea commit 5488cbe

File tree

2 files changed

+63
-38
lines changed

2 files changed

+63
-38
lines changed
 

‎lighthouse-core/gather/gatherers/full-page-screenshot.js

+33-25
Original file line numberDiff line numberDiff line change
@@ -70,21 +70,22 @@ class FullPageScreenshot extends FRGatherer {
7070

7171
/**
7272
* @param {LH.Gatherer.FRTransitionalContext} context
73+
* @param {{height: number, width: number, mobile: boolean}} deviceMetrics
7374
* @return {Promise<LH.Artifacts.FullPageScreenshot['screenshot']>}
7475
*/
75-
async _takeScreenshot(context) {
76+
async _takeScreenshot(context, deviceMetrics) {
7677
const session = context.driver.defaultSession;
7778
const maxTextureSize = await this.getMaxTextureSize(context);
7879
const metrics = await session.sendCommand('Page.getLayoutMetrics');
7980

80-
// Width should match emulated width, without considering content overhang.
81-
// Both layoutViewport and visualViewport capture this. visualViewport accounts
82-
// for page zoom/scale, which we currently don't account for (or expect). So we use layoutViewport.width.
83-
// Note: If the page is zoomed, many assumptions fail.
84-
//
85-
// Height should be as tall as the content. So we use contentSize.height
86-
const width = Math.min(metrics.layoutViewport.clientWidth, maxTextureSize);
87-
const height = Math.min(metrics.contentSize.height, maxTextureSize);
81+
// Height should be as tall as the content.
82+
// Scale the emulated height to reach the content height.
83+
const fullHeight = Math.round(
84+
deviceMetrics.height *
85+
metrics.contentSize.height /
86+
metrics.layoutViewport.clientHeight
87+
);
88+
const height = Math.min(fullHeight, maxTextureSize);
8889

8990
// Setup network monitor before we change the viewport.
9091
const networkMonitor = new NetworkMonitor(session);
@@ -98,13 +99,10 @@ class FullPageScreenshot extends FRGatherer {
9899
await networkMonitor.enable();
99100

100101
await session.sendCommand('Emulation.setDeviceMetricsOverride', {
101-
// If we're gathering with mobile screenEmulation on (overlay scrollbars, etc), continue to use that for this screenshot.
102-
mobile: context.settings.screenEmulation.mobile,
103-
height,
104-
width,
102+
mobile: deviceMetrics.mobile,
105103
deviceScaleFactor: 1,
106-
scale: 1,
107-
screenOrientation: {angle: 0, type: 'portraitPrimary'},
104+
height,
105+
width: 0, // Leave width unchanged
108106
});
109107

110108
// Now that the viewport is taller, give the page some time to fetch new resources that
@@ -127,7 +125,7 @@ class FullPageScreenshot extends FRGatherer {
127125

128126
return {
129127
data,
130-
width,
128+
width: deviceMetrics.width,
131129
height,
132130
};
133131
}
@@ -185,29 +183,37 @@ class FullPageScreenshot extends FRGatherer {
185183
const session = context.driver.defaultSession;
186184
const executionContext = context.driver.executionContext;
187185
const settings = context.settings;
188-
189-
// In case some other program is controlling emulation, remember what the device looks
190-
// like now and reset after gatherer is done.
191-
let observedDeviceMetrics;
192186
const lighthouseControlsEmulation = !settings.screenEmulation.disabled;
187+
188+
// Make a copy so we don't modify the config settings.
189+
/** @type {{width: number, height: number, deviceScaleFactor: number, mobile: boolean}} */
190+
const deviceMetrics = {...settings.screenEmulation};
191+
192+
// In case some other program is controlling emulation, remember what the device looks like now and reset after gatherer is done.
193+
// If we're gathering with mobile screenEmulation on (overlay scrollbars, etc), continue to use that for this screenshot.
193194
if (!lighthouseControlsEmulation) {
194-
observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
195+
const observedDeviceMetrics = await executionContext.evaluate(getObservedDeviceMetrics, {
195196
args: [],
196197
useIsolation: true,
197198
deps: [kebabCaseToCamelCase],
198199
});
200+
deviceMetrics.height = observedDeviceMetrics.height;
201+
deviceMetrics.width = observedDeviceMetrics.width;
202+
deviceMetrics.deviceScaleFactor = observedDeviceMetrics.deviceScaleFactor;
203+
// If screen emulation is disabled, use formFactor to determine if we are on mobile.
204+
deviceMetrics.mobile = settings.formFactor === 'mobile';
199205
}
200206

201207
try {
202208
return {
203-
screenshot: await this._takeScreenshot(context),
209+
screenshot: await this._takeScreenshot(context, deviceMetrics),
204210
nodes: await this._resolveNodes(context),
205211
};
206212
} finally {
207213
// Revert resized page.
208214
if (lighthouseControlsEmulation) {
209215
await emulation.emulate(session, settings);
210-
} else if (observedDeviceMetrics) {
216+
} else {
211217
// Best effort to reset emulation to what it was.
212218
// https://github.com/GoogleChrome/lighthouse/pull/10716#discussion_r428970681
213219
// TODO: seems like this would be brittle. Should at least work for devtools, but what
@@ -216,8 +222,10 @@ class FullPageScreenshot extends FRGatherer {
216222
// and then just call that to reset?
217223
// https://github.com/GoogleChrome/lighthouse/issues/11122
218224
await session.sendCommand('Emulation.setDeviceMetricsOverride', {
219-
mobile: settings.formFactor === 'mobile',
220-
...observedDeviceMetrics,
225+
mobile: deviceMetrics.mobile,
226+
deviceScaleFactor: deviceMetrics.deviceScaleFactor,
227+
height: deviceMetrics.height,
228+
width: 0, // Leave width unchanged
221229
});
222230
}
223231
}

‎lighthouse-core/test/gather/gatherers/full-page-screenshot-test.js

+30-13
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ jest.setTimeout(10_000);
2929

3030
beforeEach(() => {
3131
contentSize = {width: 100, height: 100};
32-
screenSize = {dpr: 1};
32+
screenSize = {width: 100, height: 100, dpr: 1};
3333
screenshotData = [];
3434
mockContext = createMockContext();
35-
mockContext.driver.defaultSession.sendCommand.mockImplementation(method => {
35+
mockContext.driver.defaultSession.sendCommand.mockImplementation((method) => {
3636
if (method === 'Page.getLayoutMetrics') {
3737
return {
3838
contentSize,
3939
// See comment within _takeScreenshot() implementation
40-
layoutViewport: {clientWidth: contentSize.width, clientHeight: contentSize.height},
40+
layoutViewport: {clientWidth: screenSize.width, clientHeight: screenSize.height},
4141
};
4242
}
4343
if (method === 'Page.captureScreenshot') {
@@ -76,10 +76,13 @@ describe('FullPageScreenshot gatherer', () => {
7676
it('captures a full-page screenshot', async () => {
7777
const fpsGatherer = new FullPageScreenshotGatherer();
7878
contentSize = {width: 412, height: 2000};
79+
screenSize = {width: 412, height: 412};
7980

8081
mockContext.settings = {
8182
formFactor: 'mobile',
8283
screenEmulation: {
84+
height: screenSize.height,
85+
width: screenSize.width,
8386
mobile: true,
8487
disabled: false,
8588
},
@@ -99,17 +102,29 @@ describe('FullPageScreenshot gatherer', () => {
99102
it('resets the emulation correctly when Lighthouse controls it', async () => {
100103
const fpsGatherer = new FullPageScreenshotGatherer();
101104
contentSize = {width: 412, height: 2000};
105+
screenSize = {width: 412, height: 412};
106+
102107
mockContext.settings = {
103108
formFactor: 'mobile',
104109
screenEmulation: {
110+
height: screenSize.height,
111+
width: screenSize.width,
105112
mobile: true,
106113
disabled: false,
107114
},
108115
};
109116

110117
await fpsGatherer.getArtifact(mockContext.asContext());
111118

112-
const expectedArgs = {formFactor: 'mobile', screenEmulation: {disabled: false, mobile: true}};
119+
const expectedArgs = {
120+
formFactor: 'mobile',
121+
screenEmulation: {
122+
height: 412,
123+
width: 412,
124+
disabled: false,
125+
mobile: true,
126+
},
127+
};
113128
expect(mocks.emulationMock.emulate).toHaveBeenCalledTimes(1);
114129
expect(mocks.emulationMock.emulate).toHaveBeenCalledWith(
115130
mockContext.driver.defaultSession,
@@ -123,6 +138,8 @@ describe('FullPageScreenshot gatherer', () => {
123138
screenSize = {width: 500, height: 500, dpr: 2};
124139
mockContext.settings = {
125140
screenEmulation: {
141+
height: screenSize.height,
142+
width: screenSize.width,
126143
mobile: true,
127144
disabled: true,
128145
},
@@ -138,7 +155,7 @@ describe('FullPageScreenshot gatherer', () => {
138155
mobile: true,
139156
deviceScaleFactor: 1,
140157
height: 1500,
141-
width: 500,
158+
width: 0,
142159
})
143160
);
144161

@@ -149,11 +166,7 @@ describe('FullPageScreenshot gatherer', () => {
149166
mobile: true,
150167
deviceScaleFactor: 2,
151168
height: 500,
152-
width: 500,
153-
screenOrientation: {
154-
type: 'landscapePrimary',
155-
angle: 30,
156-
},
169+
width: 0,
157170
})
158171
);
159172
});
@@ -162,10 +175,12 @@ describe('FullPageScreenshot gatherer', () => {
162175
const fpsGatherer = new FullPageScreenshotGatherer();
163176

164177
contentSize = {width: 412, height: 100000};
165-
screenSize = {dpr: 1};
178+
screenSize = {width: 412, height: 412, dpr: 1};
166179
mockContext.settings = {
167180
formFactor: 'mobile',
168181
screenEmulation: {
182+
height: screenSize.height,
183+
width: screenSize.width,
169184
mobile: true,
170185
disabled: false,
171186
},
@@ -175,10 +190,12 @@ describe('FullPageScreenshot gatherer', () => {
175190

176191
expect(mockContext.driver.defaultSession.sendCommand).toHaveBeenCalledWith(
177192
'Emulation.setDeviceMetricsOverride',
178-
expect.objectContaining({
193+
{
194+
mobile: true,
179195
deviceScaleFactor: 1,
196+
width: 0,
180197
height: maxTextureSizeMock,
181-
})
198+
}
182199
);
183200
});
184201
});

0 commit comments

Comments
 (0)
Please sign in to comment.