Skip to content

Commit

Permalink
Merge pull request #707 from GoogleChrome/datenow2
Browse files Browse the repository at this point in the history
DBW: add audit for Date.now() usage
  • Loading branch information
paulirish committed Sep 27, 2016
2 parents ff4c0cf + 62d8d96 commit 962948f
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 5 deletions.
82 changes: 82 additions & 0 deletions lighthouse-core/audits/dobetterweb/no-datenow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* @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.
*/

/**
* @fileoverview Audit a page to see if it's using Date.now() (instead of a
* newer API like performance.now()).
*/

'use strict';

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

class NoDateNowAudit extends Audit {

/**
* @return {!AuditMeta}
*/
static get meta() {
return {
category: 'JavaScript',
name: 'no-datenow',
description: 'Site does not use Date.now() in its own scripts',
helpText: 'Consider using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/now" target="_blank">performance.now()</a>, which as better precision than <code>Date.now()</code> and always increases at a constant rate, independent of the system clock.',
requiredArtifacts: ['URL', 'DateNowUse']
};
}

/**
* @param {!Artifacts} artifacts
* @return {!AuditResult}
*/
static audit(artifacts) {
if (typeof artifacts.DateNowUse === 'undefined' ||
artifacts.DateNowUse === -1) {
return NoDateNowAudit.generateAuditResult({
rawValue: -1,
debugString: 'DateNowUse gatherer did not run'
});
}

const pageHost = url.parse(artifacts.URL.finalUrl).host;
// Filter out Date.now() usage if script was on another host or an error with
// the same url:line:col combo has already been seen.
const results = artifacts.DateNowUse.dateNowUses.reduce((prev, err) => {
const jsonStr = JSON.stringify(err);
if (url.parse(err.url).host === pageHost && prev.indexOf(jsonStr) === -1) {
prev.push(jsonStr);
}
return prev;
}, []).map(err => {
err = JSON.parse(err);
err.misc = `(line: ${err.line}, col: ${err.col})`;
return err;
});

return NoDateNowAudit.generateAuditResult({
rawValue: results.length === 0,
extendedInfo: {
formatter: Formatter.SUPPORTED_FORMATS.URLLIST,
value: results
}
});
}
}

module.exports = NoDateNowAudit;
15 changes: 12 additions & 3 deletions lighthouse-core/config/dobetterweb.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
"network": true,
"loadPage": true,
"gatherers": [
"../gather/gatherers/dobetterweb/appcache",
"../gather/gatherers/dobetterweb/websql",
"../gather/gatherers/https",
"../gather/gatherers/url"
"../gather/gatherers/url",
"../gather/gatherers/dobetterweb/appcache",
"../gather/gatherers/dobetterweb/datenow",
"../gather/gatherers/dobetterweb/websql"
]
}],

"audits": [
"../audits/dobetterweb/appcache-manifest",
"../audits/dobetterweb/no-datenow",
"../audits/dobetterweb/no-websql",
"../audits/dobetterweb/uses-http2",
"../audits/is-on-https"
Expand Down Expand Up @@ -43,6 +45,13 @@
"description": "Resources made by this application should be severed over HTTP/2 for improved performance."
}
}
}, {
"name": "Using modern JavaScript features",
"criteria": {
"no-datenow": {
"rawValue": false
}
}
}]
}]
}
11 changes: 9 additions & 2 deletions lighthouse-core/formatters/partials/url-list.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
.http-resource__url {
margin-right: 8px;
}
.http-resource__protocol {
.http-resource__protocol,
.http-resource__misc {
color: #999;
}
</style>
Expand All @@ -15,7 +16,13 @@
<summary>URLs</summary>
{{#each this}}
<div class="http-resource">
<span class="http-resource__url">{{this.url}}</span><span class="http-resource__protocol">({{this.protocol}})</span>
<span class="http-resource__url">{{this.url}}</span>
{{#if this.protocol}}
<span class="http-resource__protocol">({{this.protocol}})</span>
{{/if}}
{{#if this.misc}}
<span class="http-resource__misc">{{this.misc}}</span>
{{/if}}
</div>
{{/each}}
</details>
Expand Down
71 changes: 71 additions & 0 deletions lighthouse-core/gather/gatherers/dobetterweb/datenow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* @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.
*/

/**
* @fileoverview Tests whether the page is using Date.now().
*/

/* global window, __returnResults */

'use strict';

const Gatherer = require('../gatherer');

function patchDateNow() {
window.__stackTraceErrors = [];
// Override Date.now() so we know when if it's called.
const origDateNow = Date.now;
const originalPrepareStackTrace = Error.prepareStackTrace;
Date.now = function() {
// See v8's Stack Trace API https://github.com/v8/v8/wiki/Stack-Trace-API#customizing-stack-traces
Error.prepareStackTrace = function(error, structStackTrace) {
const lastCallFrame = structStackTrace[structStackTrace.length - 1];
const file = lastCallFrame.getFileName();
const line = lastCallFrame.getLineNumber();
const col = lastCallFrame.getColumnNumber();
window.__stackTraceErrors.push({url: file, line, col});
return {url: file, line, col}; // return value populates e.stack.
};
Error.captureStackTrace(new Error('__called Date.now()__'));
// Remove custom formatter so future results use v8's formatter.
Error.prepareStackTrace = originalPrepareStackTrace;
return origDateNow();
};
}

function collectDateNowUsage() {
__returnResults(window.__stackTraceErrors);
}

class DateNowUse extends Gatherer {

beforePass(options) {
return options.driver.evaluateScriptOnLoad(`(${patchDateNow.toString()}())`);
}

afterPass(options) {
return options.driver.evaluateAsync(`(${collectDateNowUsage.toString()}())`)
.then(dateNowUses => {
this.artifact.dateNowUses = dateNowUses;
}, _ => {
this.artifact = -1;
return;
});
}
}

module.exports = DateNowUse;
83 changes: 83 additions & 0 deletions lighthouse-core/test/audits/dobetterweb/no-datenow-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* 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 DateNowUseAudit = require('../../../audits/dobetterweb/no-datenow.js');
const assert = require('assert');

const URL = 'https://example.com';

/* eslint-env mocha */

describe('Page does not use Date.now()', () => {
it('fails when no input present', () => {
const auditResult = DateNowUseAudit.audit({});
assert.equal(auditResult.rawValue, -1);
assert.ok(auditResult.debugString);
});

it('passes when Date.now() is not used', () => {
const auditResult = DateNowUseAudit.audit({
DateNowUse: {dateNowUses: []},
URL: {finalUrl: URL},
});
assert.equal(auditResult.rawValue, true);
assert.equal(auditResult.extendedInfo.value.length, 0);
});

it('passes when Date.now() is used on a different origin', () => {
const auditResult = DateNowUseAudit.audit({
DateNowUse: {
dateNowUses: [
{url: 'http://different.com/two', line: 2, col: 2},
{url: 'http://example2.com/two', line: 2, col: 22}
]
},
URL: {finalUrl: URL},
});
assert.equal(auditResult.rawValue, true);
assert.equal(auditResult.extendedInfo.value.length, 0);
});

it('fails when Date.now() is used on the origin', () => {
const auditResult = DateNowUseAudit.audit({
DateNowUse: {
dateNowUses: [
{url: 'http://example.com/one', line: 1, col: 1},
{url: 'http://example.com/two', line: 10, col: 1},
{url: 'http://example2.com/two', line: 2, col: 22}
]
},
URL: {finalUrl: URL},
});
assert.equal(auditResult.rawValue, false);
assert.equal(auditResult.extendedInfo.value.length, 2);
});

it('same file:line:col usage is deduped', () => {
const auditResult = DateNowUseAudit.audit({
DateNowUse: {
dateNowUses: [
{url: 'http://example.com/dupe', line: 1, col: 1},
{url: 'http://example.com/dupe', line: 1, col: 1}
]
},
URL: {finalUrl: URL},
});
assert.equal(auditResult.rawValue, false);
assert.equal(auditResult.extendedInfo.value.length, 1);
});
});

0 comments on commit 962948f

Please sign in to comment.