From a285cf2b5ea7b9231216a11eefa35355e873482a Mon Sep 17 00:00:00 2001 From: Eric Bidelman Date: Sun, 16 Oct 2016 18:09:16 +0100 Subject: [PATCH] DBW: add no Mutation Events audit --- .../audits/dobetterweb/no-mutation-events.js | 77 +++++++++++++++++++ lighthouse-core/config/dobetterweb.json | 5 ++ .../gatherers/dobetterweb/mutation-events.js | 65 ++++++++++++++++ .../dobetterweb/no-mutation-events-test.js | 69 +++++++++++++++++ 4 files changed, 216 insertions(+) create mode 100644 lighthouse-core/audits/dobetterweb/no-mutation-events.js create mode 100644 lighthouse-core/gather/gatherers/dobetterweb/mutation-events.js create mode 100644 lighthouse-core/test/audits/dobetterweb/no-mutation-events-test.js diff --git a/lighthouse-core/audits/dobetterweb/no-mutation-events.js b/lighthouse-core/audits/dobetterweb/no-mutation-events.js new file mode 100644 index 000000000000..fec98b7cec04 --- /dev/null +++ b/lighthouse-core/audits/dobetterweb/no-mutation-events.js @@ -0,0 +1,77 @@ +/** + * @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 is using Mutation Events (and suggest + * MutationObservers instead). + */ + +'use strict'; + +const url = require('url'); +const Audit = require('../audit'); +const Formatter = require('../../formatters/formatter'); + +class NoMutationEventsAudit extends Audit { + + /** + * @return {!AuditMeta} + */ + static get meta() { + return { + category: 'JavaScript', + name: 'no-mutation-events', + description: 'Site does not Mutation Events in its own scripts', + helpText: 'Using Mutation events degrades application performance. They are deprecated in the DOM events spec, replaced by MutationObservers.', + requiredArtifacts: ['URL', 'MutationEventUse'] + }; + } + + /** + * @param {!Artifacts} artifacts + * @return {!AuditResult} + */ + static audit(artifacts) { + if (typeof artifacts.MutationEventUse === 'undefined' || + artifacts.MutationEventUse === -1) { + return NoMutationEventsAudit.generateAuditResult({ + rawValue: -1, + debugString: 'MutationEventUse gatherer did not run' + }); + } + + const pageHost = url.parse(artifacts.URL.finalUrl).host; + // Filter usage from other hosts. + const results = artifacts.MutationEventUse.usage.filter(err => { + return url.parse(err.url).host === pageHost; + }).map(err => { + return Object.assign({ + label: `line: ${err.line}, col: ${err.col}` + }, err); + }); + + return NoMutationEventsAudit.generateAuditResult({ + rawValue: results.length === 0, + extendedInfo: { + formatter: Formatter.SUPPORTED_FORMATS.URLLIST, + value: results + } + }); + } +} + +module.exports = NoMutationEventsAudit; diff --git a/lighthouse-core/config/dobetterweb.json b/lighthouse-core/config/dobetterweb.json index ee29c4d0f5de..85afe88c2b18 100644 --- a/lighthouse-core/config/dobetterweb.json +++ b/lighthouse-core/config/dobetterweb.json @@ -9,6 +9,7 @@ "../gather/gatherers/dobetterweb/console-time-usage", "../gather/gatherers/dobetterweb/datenow", "../gather/gatherers/dobetterweb/document-write", + "../gather/gatherers/dobetterweb/mutation-events", "../gather/gatherers/dobetterweb/websql" ] }], @@ -18,6 +19,7 @@ "../audits/dobetterweb/no-console-time", "../audits/dobetterweb/no-datenow", "../audits/dobetterweb/no-document-write", + "../audits/dobetterweb/no-mutation-events", "../audits/dobetterweb/no-old-flexbox", "../audits/dobetterweb/no-websql", "../audits/dobetterweb/uses-http2", @@ -65,6 +67,9 @@ }, "no-console-time": { "expectedValue": false + }, + "no-mutation-events": { + "expectedValue": false } } }, { diff --git a/lighthouse-core/gather/gatherers/dobetterweb/mutation-events.js b/lighthouse-core/gather/gatherers/dobetterweb/mutation-events.js new file mode 100644 index 000000000000..ddb729a031e6 --- /dev/null +++ b/lighthouse-core/gather/gatherers/dobetterweb/mutation-events.js @@ -0,0 +1,65 @@ +/** + * @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(). + */ + +'use strict'; + +const Gatherer = require('../gatherer'); + +const MUTATION_EVENTS = [ + 'DOMAttrModified', + 'DOMAttributeNameChanged', + 'DOMCharacterDataModified', + 'DOMElementNameChanged', + 'DOMNodeInserted', + 'DOMNodeInsertedIntoDocument', + 'DOMNodeRemoved', + 'DOMNodeRemovedFromDocument', + 'DOMSubtreeModified' +]; + +class MutationEventUse extends Gatherer { + + beforePass(options) { + this.collectUsage = options.driver.captureFunctionCallSites('addEventListener'); + this.collectUsage2 = options.driver.captureFunctionCallSites('document.addEventListener'); + // TODO: document.body appears to not be defined by this time. + // this.collectUsage3 = options.driver.captureFunctionCallSites('document.body.addEventListener'); + } + + afterPass() { + const promise = this.collectUsage().then(results1 => { + return this.collectUsage2().then(results2 => results1.concat(results2)); + }); + + return promise.then(uses => { + uses = uses.filter(use => { + const eventName = use.args[0]; + return MUTATION_EVENTS.indexOf(eventName) !== -1; + }); + this.artifact.usage = uses; + }, _ => { + this.artifact = -1; + return; + }); + } +} + +module.exports = MutationEventUse; diff --git a/lighthouse-core/test/audits/dobetterweb/no-mutation-events-test.js b/lighthouse-core/test/audits/dobetterweb/no-mutation-events-test.js new file mode 100644 index 000000000000..9af18e6da52e --- /dev/null +++ b/lighthouse-core/test/audits/dobetterweb/no-mutation-events-test.js @@ -0,0 +1,69 @@ +/** + * 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 NoMutationEventsAudit = require('../../../audits/dobetterweb/no-mutation-events.js'); +const assert = require('assert'); + +const URL = 'https://example.com'; + +/* eslint-env mocha */ + +describe('Page does not use mutation events', () => { + it('fails when no input present', () => { + const auditResult = NoMutationEventsAudit.audit({}); + assert.equal(auditResult.rawValue, -1); + assert.ok(auditResult.debugString); + }); + + it('passes when mutation events are not used', () => { + const auditResult = NoMutationEventsAudit.audit({ + MutationEventUse: {usage: []}, + URL: {finalUrl: URL}, + }); + assert.equal(auditResult.rawValue, true); + assert.equal(auditResult.extendedInfo.value.length, 0); + }); + + it('passes when mutation events are used on a different origin', () => { + const auditResult = NoMutationEventsAudit.audit({ + MutationEventUse: { + usage: [ + {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 mutation events are used on the origin', () => { + const auditResult = NoMutationEventsAudit.audit({ + MutationEventUse: { + usage: [ + {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); + }); +});