Skip to content

Commit

Permalink
Checks cache for start URL (#507)
Browse files Browse the repository at this point in the history
  • Loading branch information
paullewis authored and paulirish committed Jul 14, 2016
1 parent bd5f8e1 commit 4a66309
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 7 deletions.
78 changes: 78 additions & 0 deletions lighthouse-core/audits/cache-start-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* @license
* Copyright 2016 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const url = require('url');
const Audit = require('./audit');

class CacheStartUrl extends Audit {
/**
* @return {!AuditMeta}
*/
static get meta() {
return {
category: 'Manifest',
name: 'cache-start-url',
description: 'Cache contains start_url from manifest',
requiredArtifacts: ['CacheContents', 'Manifest', 'URL']
};
}

/**
* @param {!Artifacts} artifacts
* @return {!AuditResult}
*/
static audit(artifacts) {
let cacheHasStartUrl = false;

if (!(artifacts.Manifest &&
artifacts.Manifest.value &&
artifacts.Manifest.value.start_url &&
Array.isArray(artifacts.CacheContents) &&
artifacts.URL)) {
return CacheStartUrl.generateAuditResult({
rawValue: false
});
}

const manifest = artifacts.Manifest.value;
const cacheContents = artifacts.CacheContents;
const baseURL = artifacts.URL;

// Remove any UTM strings.
const startURL = url.resolve(baseURL, manifest.start_url.raw).toString();
const altStartURL = startURL
.replace(/\?utm_([^=]*)=([^&]|$)*/, '')
.replace(/\?$/, '');

// Now find the start_url in the cacheContents. This test is less than ideal since the Service
// Worker can rewrite a request from the start URL to anything else in the cache, and so a TODO
// here would be to resolve this more completely by asking the Service Worker about the start
// URL. However that would also necessitate the cache contents gatherer relying on the manifest
// gather rather than being independent of it.
cacheHasStartUrl = cacheContents.find(req => {
return (startURL === req || altStartURL === req);
});

return CacheStartUrl.generateAuditResult({
rawValue: (cacheHasStartUrl !== undefined)
});
}
}

module.exports = CacheStartUrl;
3 changes: 3 additions & 0 deletions lighthouse-core/closure/typedefs/Artifacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,6 @@ Artifacts.prototype.Speedline;

/** @type {{scrollWidth: number, viewportWidth: number}} */
Artifacts.prototype.ContentWidth;

/** @type {!Array<string>} */
Artifacts.prototype.CacheContents;
13 changes: 6 additions & 7 deletions lighthouse-core/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"screenshots",
"critical-request-chains",
"speedline",
"content-width"
"content-width",
"cache-contents"
]
},
{
Expand Down Expand Up @@ -63,7 +64,8 @@
"image-alt",
"label",
"tabindex",
"content-width"
"content-width",
"cache-start-url"
],

"aggregations": [{
Expand All @@ -83,12 +85,9 @@
"rawValue": true,
"weight": 1
},
"manifest-start-url": {
"cache-start-url": {
"rawValue": true,
"weight": 0,
"comingSoon": true,
"description": "Manifest's start_url is in cache storage for offline use",
"category": "Offline"
"weight": 1
}
}
},{
Expand Down
74 changes: 74 additions & 0 deletions lighthouse-core/driver/gatherers/cache-contents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* @license
* Copyright 2016 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';

/* global __returnResults, caches */

const Gather = require('./gather');

// This is run in the page, not Lighthouse itself.
/* istanbul ignore next */
function getCacheContents() {
// Get every cache by name.
caches.keys()

// Open each one.
.then(cacheNames => Promise.all(cacheNames.map(cacheName => caches.open(cacheName))))

.then(caches => {
const requests = [];

// Take each cache and get any requests is contains, and bounce each one down to its URL.
return Promise.all(caches.map(cache => {
return cache.keys()
.then(reqs => {
requests.push(...reqs.map(r => r.url));
});
})).then(_ => {
// __returnResults is magically inserted by driver.evaluateAsync
__returnResults(requests);
});
});
}

class CacheContents extends Gather {
static _error(errorString) {
return {
raw: undefined,
value: undefined,
debugString: errorString
};
}

afterPass(options) {
const driver = options.driver;

return driver
.evaluateAsync(`(${getCacheContents.toString()}())`)
.then(returnedValue => {
if (!returnedValue) {
this.artifact = CacheContents._error('Unable to retrieve cache contents');
return;
}
this.artifact = returnedValue;
}, _ => {
this.artifact = CacheContents._error('Unable to retrieve cache contents');
});
}
}

module.exports = CacheContents;
75 changes: 75 additions & 0 deletions lighthouse-core/test/audits/cache-start-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Copyright 2016 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const Audit = require('../../audits/cache-start-url.js');
const assert = require('assert');
const manifestSrc = JSON.stringify(require('../fixtures/manifest.json'));
const manifestParser = require('../../lib/manifest-parser');
const Manifest = manifestParser(manifestSrc);
const CacheContents = ['https://another.example.com/', 'https://example.com/'];
const URL = 'https://example.com';
const AltURL = 'https://example.com/?utm_source=http203';

/* global describe, it*/

describe('Cache: start_url audit', () => {
it('fails when no manifest present', () => {
return assert.equal(Audit.audit({Manifest: {
value: undefined
}}).rawValue, false);
});

it('fails when an empty manifest is present', () => {
return assert.equal(Audit.audit({Manifest: {}}).rawValue, false);
});

it('fails when no cache contents given', () => {
return assert.equal(Audit.audit({Manifest, URL}).rawValue, false);
});

it('fails when no URL given', () => {
return assert.equal(Audit.audit({Manifest, CacheContents}).rawValue, false);
});

// Need to disable camelcase check for dealing with short_name.
/* eslint-disable camelcase */
it('fails when a manifest contains no start_url', () => {
const inputs = {
Manifest: {
start_url: null
}
};

return assert.equal(Audit.audit(inputs).rawValue, false);
});

/* eslint-enable camelcase */

it('succeeds when given a manifest with a start_url, cache contents, and a URL', () => {
return assert.equal(Audit.audit({
Manifest,
CacheContents,
URL
}).rawValue, true);
});

it('handles URLs with utm params', () => {
return assert.equal(Audit.audit({
Manifest,
CacheContents,
URL: AltURL
}).rawValue, true);
});
});
85 changes: 85 additions & 0 deletions lighthouse-core/test/driver/gatherers/cache-contents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Copyright 2016 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';

/* eslint-env mocha */

const CacheContentGather = require('../../../driver/gatherers/cache-contents');
const assert = require('assert');
let cacheContentGather;

const isExpectedOutput = artifact => {
return 'raw' in artifact && 'value' in artifact;
};

describe('Cache Contents gatherer', () => {
// Reset the Gatherer before each test.
beforeEach(() => {
cacheContentGather = new CacheContentGather();
});

it('fails gracefully', () => {
return cacheContentGather.afterPass({
driver: {
evaluateAsync() {
return Promise.resolve();
}
}
}).then(_ => {
assert.ok(typeof cacheContentGather.artifact === 'object');
});
});

it('handles driver failure', () => {
return cacheContentGather.afterPass({
driver: {
evaluateAsync() {
return Promise.reject('such a fail');
}
}
}).then(_ => {
assert(false);
}).catch(_ => {
assert.ok(isExpectedOutput(cacheContentGather.artifact));
});
});

it('propagates error retrieving the results', () => {
const error = 'Unable to retrieve cache contents';
return cacheContentGather.afterPass({
driver: {
evaluateAsync() {
return Promise.reject(error);
}
}
}).then(_ => {
assert.ok(cacheContentGather.artifact.debugString === error);
});
});

it('creates an object for valid results', () => {
return cacheContentGather.afterPass({
driver: {
evaluateAsync() {
return Promise.resolve(['a', 'b', 'c']);
}
}
}).then(_ => {
assert.ok(Array.isArray(cacheContentGather.artifact));
assert.equal(cacheContentGather.artifact[0], 'a');
});
});
});

0 comments on commit 4a66309

Please sign in to comment.