Skip to content

Commit

Permalink
misc(treemap): remove postMessage. refactor options input (#13356)
Browse files Browse the repository at this point in the history
  • Loading branch information
connorjclark authored Nov 16, 2021
1 parent 763bb63 commit b7f09e6
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 89 deletions.
91 changes: 48 additions & 43 deletions treemap/app/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ if (!logEl) {
throw new Error('logger element not found');
}
const logger = new Logger(logEl);
// `getGistFileContentAsJson` expects logger to be defined globally.
window.logger = logger;

/** @type {TreemapViewer} */
let treemapViewer;
Expand Down Expand Up @@ -787,16 +789,40 @@ class LighthouseTreemap {
}

/**
* Coerce json into LH.Treemap.Options
* Accepts if json is an lhr, or {lhr: ...} or {lighthouseResult: ...}
* Throws error if json does not match expectations.
* @param {any} json
* @return {LH.Treemap.Options}
*/
convertToOptions(json) {
coerceToOptions(json) {
/** @type {LH.Treemap.Options['lhr']|null} */
let lhr = null;
if (json && typeof json === 'object') {
if (json.audits) json = {lhr: json};
if (json.lhr && json.lhr.audits && typeof json.lhr.audits === 'object') return json;
for (const maybeLhr of [json, json.lhr, json.lighthouseResult]) {
if (maybeLhr && maybeLhr.audits && typeof maybeLhr.audits === 'object') {
lhr = maybeLhr;
break;
}
}
}

if (!lhr) {
throw new Error('provided json is not a Lighthouse result');
}

if (!lhr.audits['script-treemap-data']) {
throw new Error('provided Lighthouse result is missing audit: `script-treemap-data`');
}

throw new Error('unknown json');
if (lhr === json.lhr) {
// Special case: file was {lhr: ...} and potentially has other properties.
// LH.Treemap.Options only has `lhr` currently, but may have more in the future.
return json;
}

// json was exactly a LHR, or a PSI result object aka {lighthouseResult}
return {lhr};
}

/**
Expand All @@ -818,7 +844,7 @@ class LighthouseTreemap {
const gistId = match[0];
history.pushState({}, '', `${LighthouseTreemap.APP_URL}?gist=${gistId}`);
const json = await this._github.getGistFileContentAsJson(gistId);
const options = this.convertToOptions(json);
const options = this.coerceToOptions(json);
this.init(options);
}
} catch (err) {
Expand All @@ -834,7 +860,7 @@ class LighthouseTreemap {
let options;
try {
json = JSON.parse(str);
options = this.convertToOptions(json);
options = this.coerceToOptions(json);
} catch (e) {
logger.error('Could not parse JSON file.');
}
Expand Down Expand Up @@ -867,7 +893,7 @@ class LighthouseTreemap {
// Try paste as json content.
try {
const json = JSON.parse(e.clipboardData.getData('text'));
const options = this.convertToOptions(json);
const options = this.coerceToOptions(json);
this.init(options);

if (window.ga) {
Expand Down Expand Up @@ -898,47 +924,26 @@ async function main() {

if (window.__treemapOptions) {
// Prefer the hardcoded options from a saved HTML file above all.
app.init(window.__treemapOptions);
app.init(app.coerceToOptions(window.__treemapOptions));
} else if ('debug' in params) {
const response = await fetch('debug.json');
app.init(await response.json());
const json = await response.json();
const options = app.coerceToOptions(json);
app.init(options);
} else if (params.lhr) {
const options = {
lhr: params.lhr,
};
const options = app.coerceToOptions(params.lhr);
app.init(options);
} else if (params.gist) {
let json;
let options;
try {
json = await app._github.getGistFileContentAsJson(params.gist || '');
options = app.convertToOptions(json);
} catch (err) {
logger.log(err);
}
if (options) app.init(options);
} else {
// TODO: remove for v8.
window.addEventListener('message', e => {
if (e.source !== self.opener) return;

/** @type {LH.Treemap.Options} */
const options = e.data;
const {lhr} = options;
if (!lhr) return logger.error('Error: Invalid options');

const documentUrl = lhr.requestedUrl;
if (!documentUrl) return logger.error('Error: Invalid options');

app.init(options);
});
}

// TODO: remove for v8.
// If the page was opened as a popup, tell the opening window we're ready.
if (self.opener && !self.opener.closed) {
self.opener.postMessage({opened: true}, '*');
const json = await app._github.getGistFileContentAsJson(params.gist || '');
const options = app.coerceToOptions(json);
app.init(options);
}
}

document.addEventListener('DOMContentLoaded', main);
document.addEventListener('DOMContentLoaded', async () => {
try {
await main();
} catch (err) {
logger.error(err);
}
});
88 changes: 42 additions & 46 deletions treemap/test/treemap-test-pptr.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,16 @@ describe('Lighthouse Treemap', () => {
expect(options.lhr.requestedUrl).toBe(debugOptions.lhr.requestedUrl);
});

// TODO: remove for v8
async function loadFromPostMessage(options) {
const openerPage = await browser.newPage();
await openerPage.evaluate((treemapUrl, options) => {
const popup = window.open(treemapUrl);
window.addEventListener('message', () => {
popup.postMessage(options, new URL(treemapUrl).origin);
});
}, treemapUrl, options);
await new Promise(resolve => browser.on('targetcreated', resolve));
const target = (await browser.targets()).find(target => target.url() === treemapUrl);
page = await target.page();
/**
* @param {{options: any, usesGzip: boolean}}
*/
async function loadFromEncodedUrl({options, useGzip}) {
const json = JSON.stringify(options);
const encoded = await page.evaluate(`
${getTextEncodingCode()}
TextEncoding.toBase64(${JSON.stringify(json)}, {gzip: ${useGzip}});
`);
await page.goto(`${treemapUrl}?gzip=${useGzip ? '1' : '0'}#${encoded}`);
await page.waitForFunction(() => {
if (window.__treemapOptions) return true;

Expand All @@ -101,53 +99,51 @@ describe('Lighthouse Treemap', () => {
});
}

it('from window postMessage', async () => {
await loadFromPostMessage(debugOptions);
const optionsInPage = await page.evaluate(() => window.__treemapOptions);
expect(optionsInPage.lhr.requestedUrl).toBe(debugOptions.lhr.requestedUrl);
});

it('handles errors', async () => {
await loadFromPostMessage({});
const optionsInPage = await page.evaluate(() => window.__treemapOptions);
expect(optionsInPage).toBeUndefined();
const error = await page.evaluate(() => document.querySelector('#lh-log').textContent);
expect(error).toBe('Error: Invalid options');
});

it('from encoded fragment (gzip)', async () => {
it('from encoded fragment (no gzip)', async () => {
const options = JSON.parse(JSON.stringify(debugOptions));
options.lhr.requestedUrl += '😃😃😃';
const json = JSON.stringify(options);
const encoded = await page.evaluate(`
${getTextEncodingCode()}
TextEncoding.toBase64(${JSON.stringify(json)}, {gzip: true});
`);

await page.goto(`${treemapUrl}?gzip=1#${encoded}`);
await page.waitForFunction(
() => window.__treemapOptions || document.body.textContent.startsWith('Error'));
await loadFromEncodedUrl({options, usesGzip: false});

const optionsInPage = await page.evaluate(() => window.__treemapOptions);
expect(optionsInPage.lhr.requestedUrl).toBe(options.lhr.requestedUrl);
});

it('from encoded fragment (no gzip)', async () => {
it('from encoded fragment (gzip)', async () => {
const options = JSON.parse(JSON.stringify(debugOptions));
options.lhr.requestedUrl += '😃😃😃';
const json = JSON.stringify(options);
const encoded = await page.evaluate(`
${getTextEncodingCode()}
TextEncoding.toBase64(${JSON.stringify(json)}, {gzip: false});
`);

await page.goto(`${treemapUrl}#${encoded}`);
await page.waitForFunction(
() => window.__treemapOptions || document.body.textContent.startsWith('Error'));
await loadFromEncodedUrl({options, usesGzip: true});

const optionsInPage = await page.evaluate(() => window.__treemapOptions);
expect(optionsInPage.lhr.requestedUrl).toBe(options.lhr.requestedUrl);
});

describe('handles errors', () => {
const errorTestCases = [
{
options: {lhr: 'lol'},
error: 'Error: provided json is not a Lighthouse result',
},
{
options: {lhr: {noaudits: {}}},
error: 'Error: provided json is not a Lighthouse result',
},
{
options: {lhr: {audits: {}}},
error: 'Error: provided Lighthouse result is missing audit: `script-treemap-data`',
},
];
for (let i = 0; i < errorTestCases.length; i++) {
it(`case #${i + 1}`, async () => {
const testCase = errorTestCases[i];
await loadFromEncodedUrl({options: testCase.options, usesGzip: false});
const optionsInPage = await page.evaluate(() => window.__treemapOptions);
expect(optionsInPage).toBeUndefined();
const error = await page.evaluate(() => document.querySelector('#lh-log').textContent);
expect(error).toBe(testCase.error);
pageErrors = [];
});
}
});
});

describe('renders correctly', () => {
Expand Down

0 comments on commit b7f09e6

Please sign in to comment.