Skip to content

Commit 4a66309

Browse files
paullewispaulirish
authored andcommitted
Checks cache for start URL (#507)
1 parent bd5f8e1 commit 4a66309

File tree

6 files changed

+321
-7
lines changed

6 files changed

+321
-7
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
* @license
3+
* Copyright 2016 Google Inc. All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
'use strict';
19+
20+
const url = require('url');
21+
const Audit = require('./audit');
22+
23+
class CacheStartUrl extends Audit {
24+
/**
25+
* @return {!AuditMeta}
26+
*/
27+
static get meta() {
28+
return {
29+
category: 'Manifest',
30+
name: 'cache-start-url',
31+
description: 'Cache contains start_url from manifest',
32+
requiredArtifacts: ['CacheContents', 'Manifest', 'URL']
33+
};
34+
}
35+
36+
/**
37+
* @param {!Artifacts} artifacts
38+
* @return {!AuditResult}
39+
*/
40+
static audit(artifacts) {
41+
let cacheHasStartUrl = false;
42+
43+
if (!(artifacts.Manifest &&
44+
artifacts.Manifest.value &&
45+
artifacts.Manifest.value.start_url &&
46+
Array.isArray(artifacts.CacheContents) &&
47+
artifacts.URL)) {
48+
return CacheStartUrl.generateAuditResult({
49+
rawValue: false
50+
});
51+
}
52+
53+
const manifest = artifacts.Manifest.value;
54+
const cacheContents = artifacts.CacheContents;
55+
const baseURL = artifacts.URL;
56+
57+
// Remove any UTM strings.
58+
const startURL = url.resolve(baseURL, manifest.start_url.raw).toString();
59+
const altStartURL = startURL
60+
.replace(/\?utm_([^=]*)=([^&]|$)*/, '')
61+
.replace(/\?$/, '');
62+
63+
// Now find the start_url in the cacheContents. This test is less than ideal since the Service
64+
// Worker can rewrite a request from the start URL to anything else in the cache, and so a TODO
65+
// here would be to resolve this more completely by asking the Service Worker about the start
66+
// URL. However that would also necessitate the cache contents gatherer relying on the manifest
67+
// gather rather than being independent of it.
68+
cacheHasStartUrl = cacheContents.find(req => {
69+
return (startURL === req || altStartURL === req);
70+
});
71+
72+
return CacheStartUrl.generateAuditResult({
73+
rawValue: (cacheHasStartUrl !== undefined)
74+
});
75+
}
76+
}
77+
78+
module.exports = CacheStartUrl;

lighthouse-core/closure/typedefs/Artifacts.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,6 @@ Artifacts.prototype.Speedline;
7676

7777
/** @type {{scrollWidth: number, viewportWidth: number}} */
7878
Artifacts.prototype.ContentWidth;
79+
80+
/** @type {!Array<string>} */
81+
Artifacts.prototype.CacheContents;

lighthouse-core/config/default.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"screenshots",
1616
"critical-request-chains",
1717
"speedline",
18-
"content-width"
18+
"content-width",
19+
"cache-contents"
1920
]
2021
},
2122
{
@@ -63,7 +64,8 @@
6364
"image-alt",
6465
"label",
6566
"tabindex",
66-
"content-width"
67+
"content-width",
68+
"cache-start-url"
6769
],
6870

6971
"aggregations": [{
@@ -83,12 +85,9 @@
8385
"rawValue": true,
8486
"weight": 1
8587
},
86-
"manifest-start-url": {
88+
"cache-start-url": {
8789
"rawValue": true,
88-
"weight": 0,
89-
"comingSoon": true,
90-
"description": "Manifest's start_url is in cache storage for offline use",
91-
"category": "Offline"
90+
"weight": 1
9291
}
9392
}
9493
},{
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @license
3+
* Copyright 2016 Google Inc. All rights reserved.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
'use strict';
18+
19+
/* global __returnResults, caches */
20+
21+
const Gather = require('./gather');
22+
23+
// This is run in the page, not Lighthouse itself.
24+
/* istanbul ignore next */
25+
function getCacheContents() {
26+
// Get every cache by name.
27+
caches.keys()
28+
29+
// Open each one.
30+
.then(cacheNames => Promise.all(cacheNames.map(cacheName => caches.open(cacheName))))
31+
32+
.then(caches => {
33+
const requests = [];
34+
35+
// Take each cache and get any requests is contains, and bounce each one down to its URL.
36+
return Promise.all(caches.map(cache => {
37+
return cache.keys()
38+
.then(reqs => {
39+
requests.push(...reqs.map(r => r.url));
40+
});
41+
})).then(_ => {
42+
// __returnResults is magically inserted by driver.evaluateAsync
43+
__returnResults(requests);
44+
});
45+
});
46+
}
47+
48+
class CacheContents extends Gather {
49+
static _error(errorString) {
50+
return {
51+
raw: undefined,
52+
value: undefined,
53+
debugString: errorString
54+
};
55+
}
56+
57+
afterPass(options) {
58+
const driver = options.driver;
59+
60+
return driver
61+
.evaluateAsync(`(${getCacheContents.toString()}())`)
62+
.then(returnedValue => {
63+
if (!returnedValue) {
64+
this.artifact = CacheContents._error('Unable to retrieve cache contents');
65+
return;
66+
}
67+
this.artifact = returnedValue;
68+
}, _ => {
69+
this.artifact = CacheContents._error('Unable to retrieve cache contents');
70+
});
71+
}
72+
}
73+
74+
module.exports = CacheContents;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Copyright 2016 Google Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
const Audit = require('../../audits/cache-start-url.js');
17+
const assert = require('assert');
18+
const manifestSrc = JSON.stringify(require('../fixtures/manifest.json'));
19+
const manifestParser = require('../../lib/manifest-parser');
20+
const Manifest = manifestParser(manifestSrc);
21+
const CacheContents = ['https://another.example.com/', 'https://example.com/'];
22+
const URL = 'https://example.com';
23+
const AltURL = 'https://example.com/?utm_source=http203';
24+
25+
/* global describe, it*/
26+
27+
describe('Cache: start_url audit', () => {
28+
it('fails when no manifest present', () => {
29+
return assert.equal(Audit.audit({Manifest: {
30+
value: undefined
31+
}}).rawValue, false);
32+
});
33+
34+
it('fails when an empty manifest is present', () => {
35+
return assert.equal(Audit.audit({Manifest: {}}).rawValue, false);
36+
});
37+
38+
it('fails when no cache contents given', () => {
39+
return assert.equal(Audit.audit({Manifest, URL}).rawValue, false);
40+
});
41+
42+
it('fails when no URL given', () => {
43+
return assert.equal(Audit.audit({Manifest, CacheContents}).rawValue, false);
44+
});
45+
46+
// Need to disable camelcase check for dealing with short_name.
47+
/* eslint-disable camelcase */
48+
it('fails when a manifest contains no start_url', () => {
49+
const inputs = {
50+
Manifest: {
51+
start_url: null
52+
}
53+
};
54+
55+
return assert.equal(Audit.audit(inputs).rawValue, false);
56+
});
57+
58+
/* eslint-enable camelcase */
59+
60+
it('succeeds when given a manifest with a start_url, cache contents, and a URL', () => {
61+
return assert.equal(Audit.audit({
62+
Manifest,
63+
CacheContents,
64+
URL
65+
}).rawValue, true);
66+
});
67+
68+
it('handles URLs with utm params', () => {
69+
return assert.equal(Audit.audit({
70+
Manifest,
71+
CacheContents,
72+
URL: AltURL
73+
}).rawValue, true);
74+
});
75+
});
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Copyright 2016 Google Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
'use strict';
17+
18+
/* eslint-env mocha */
19+
20+
const CacheContentGather = require('../../../driver/gatherers/cache-contents');
21+
const assert = require('assert');
22+
let cacheContentGather;
23+
24+
const isExpectedOutput = artifact => {
25+
return 'raw' in artifact && 'value' in artifact;
26+
};
27+
28+
describe('Cache Contents gatherer', () => {
29+
// Reset the Gatherer before each test.
30+
beforeEach(() => {
31+
cacheContentGather = new CacheContentGather();
32+
});
33+
34+
it('fails gracefully', () => {
35+
return cacheContentGather.afterPass({
36+
driver: {
37+
evaluateAsync() {
38+
return Promise.resolve();
39+
}
40+
}
41+
}).then(_ => {
42+
assert.ok(typeof cacheContentGather.artifact === 'object');
43+
});
44+
});
45+
46+
it('handles driver failure', () => {
47+
return cacheContentGather.afterPass({
48+
driver: {
49+
evaluateAsync() {
50+
return Promise.reject('such a fail');
51+
}
52+
}
53+
}).then(_ => {
54+
assert(false);
55+
}).catch(_ => {
56+
assert.ok(isExpectedOutput(cacheContentGather.artifact));
57+
});
58+
});
59+
60+
it('propagates error retrieving the results', () => {
61+
const error = 'Unable to retrieve cache contents';
62+
return cacheContentGather.afterPass({
63+
driver: {
64+
evaluateAsync() {
65+
return Promise.reject(error);
66+
}
67+
}
68+
}).then(_ => {
69+
assert.ok(cacheContentGather.artifact.debugString === error);
70+
});
71+
});
72+
73+
it('creates an object for valid results', () => {
74+
return cacheContentGather.afterPass({
75+
driver: {
76+
evaluateAsync() {
77+
return Promise.resolve(['a', 'b', 'c']);
78+
}
79+
}
80+
}).then(_ => {
81+
assert.ok(Array.isArray(cacheContentGather.artifact));
82+
assert.equal(cacheContentGather.artifact[0], 'a');
83+
});
84+
});
85+
});

0 commit comments

Comments
 (0)