diff --git a/lighthouse-core/audits/content-width.js b/lighthouse-core/audits/content-width.js new file mode 100644 index 000000000000..196bf936f6b6 --- /dev/null +++ b/lighthouse-core/audits/content-width.js @@ -0,0 +1,68 @@ +/** + * @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 Audit = require('./audit'); + +class ContentWidth extends Audit { + /** + * @return {!AuditMeta} + */ + static get meta() { + return { + category: 'Mobile Friendly', + name: 'content-width', + description: 'Content is sized correctly for the viewport', + requiredArtifacts: ['ContentWidth'] + }; + } + + /** + * @param {!Artifacts} artifacts + * @return {!AuditResult} + */ + static audit(artifacts) { + if (typeof artifacts.ContentWidth === 'undefined' || + typeof artifacts.ContentWidth.scrollWidth === 'undefined' || + typeof artifacts.ContentWidth.viewportWidth === 'undefined') { + return ContentWidth.generateAuditResult({ + rawValue: false, + debugString: 'Unable to find scroll and viewport widths.' + }); + } + + const widthsMatch = + artifacts.ContentWidth.scrollWidth === artifacts.ContentWidth.viewportWidth; + + return ContentWidth.generateAuditResult({ + rawValue: widthsMatch, + debugString: this.createDebugString(widthsMatch, artifacts.ContentWidth) + }); + } + + static createDebugString(match, artifact) { + if (match) { + return ''; + } + + return 'The content scroll size is ' + artifact.scrollWidth + 'px, ' + + 'whereas the viewport size is ' + artifact.viewportWidth + 'px.'; + } +} + +module.exports = ContentWidth; diff --git a/lighthouse-core/closure/typedefs/Artifacts.js b/lighthouse-core/closure/typedefs/Artifacts.js index 779129d0fedf..17a7ed17d324 100644 --- a/lighthouse-core/closure/typedefs/Artifacts.js +++ b/lighthouse-core/closure/typedefs/Artifacts.js @@ -73,3 +73,6 @@ Artifacts.prototype.CriticalRequestChains; /** @type {{first: number, complete: number, duration: number, frames: !Array, debugString: (string|undefined)}} */ Artifacts.prototype.Speedline; + +/** @type {{scrollWidth: number, viewportWidth: number}} */ +Artifacts.prototype.ContentWidth; diff --git a/lighthouse-core/config/default.json b/lighthouse-core/config/default.json index 00230e360aca..3ff3056361ac 100644 --- a/lighthouse-core/config/default.json +++ b/lighthouse-core/config/default.json @@ -14,7 +14,8 @@ "accessibility", "screenshots", "critical-request-chains", - "speedline" + "speedline", + "content-width" ] }, { @@ -61,7 +62,8 @@ "color-contrast", "image-alt", "label", - "tabindex" + "tabindex", + "content-width" ], "aggregations": [{ @@ -225,6 +227,10 @@ "viewport": { "rawValue": true, "weight": 1 + }, + "content-width": { + "value": true, + "weight": 1 } } }] diff --git a/lighthouse-core/driver/gatherers/content-width.js b/lighthouse-core/driver/gatherers/content-width.js new file mode 100644 index 000000000000..8fae8d395f21 --- /dev/null +++ b/lighthouse-core/driver/gatherers/content-width.js @@ -0,0 +1,52 @@ +/** + * @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 Gather = require('./gather'); + +/* global window, __returnResults */ + +/* istanbul ignore next */ +function getContentWidth() { + // window.innerWidth to get the scrollable size of the window (irrespective of zoom) + // window.outerWidth to get the size of the visible area + __returnResults({ + scrollWidth: window.innerWidth, + viewportWidth: window.outerWidth + }); +} + +class ContentWidth extends Gather { + + afterPass(options) { + const driver = options.driver; + + return driver.evaluateAsync(`(${getContentWidth.toString()}())`) + + .then(returnedValue => { + this.artifact = returnedValue; + }, _ => { + this.artifact = { + scrollWidth: -1, + viewportWidth: -1 + }; + return; + }); + } +} + +module.exports = ContentWidth; diff --git a/lighthouse-core/test/audits/content-width.js b/lighthouse-core/test/audits/content-width.js new file mode 100644 index 000000000000..c82b8f494dc9 --- /dev/null +++ b/lighthouse-core/test/audits/content-width.js @@ -0,0 +1,43 @@ +/** + * 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/content-width.js'); +const assert = require('assert'); + +/* global describe, it*/ + +describe('Mobile-friendly: content-width audit', () => { + it('fails when no input present', () => { + return assert.equal(Audit.audit({}).rawValue, false); + }); + + it('fails when scroll width differs from viewport width', () => { + return assert.equal(Audit.audit({ + ContentWidth: { + scrollWidth: 100, + viewportWidth: 300 + } + }).rawValue, false); + }); + + it('passes when widths match', () => { + return assert.equal(Audit.audit({ + ContentWidth: { + scrollWidth: 300, + viewportWidth: 300 + } + }).rawValue, true); + }); +}); diff --git a/lighthouse-core/test/driver/gatherers/content-width.js b/lighthouse-core/test/driver/gatherers/content-width.js new file mode 100644 index 000000000000..8afa398adfab --- /dev/null +++ b/lighthouse-core/test/driver/gatherers/content-width.js @@ -0,0 +1,59 @@ +/** + * 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 ContentWidthGatherer = require('../../../driver/gatherers/content-width'); +const assert = require('assert'); +let contentWidthGatherer; + +describe('Viewport gatherer', () => { + // Reset the Gatherer before each test. + beforeEach(() => { + contentWidthGatherer = new ContentWidthGatherer(); + }); + + it('returns an artifact', () => { + return contentWidthGatherer.afterPass({ + driver: { + evaluateAsync() { + return Promise.resolve({ + scrollWidth: 400, + viewportWidth: 400 + }); + } + } + }).then(_ => { + assert.ok(typeof contentWidthGatherer.artifact === 'object'); + assert.ok(contentWidthGatherer.artifact.viewportWidth === 400); + }); + }); + + it('handles driver failure', () => { + return contentWidthGatherer.afterPass({ + driver: { + evaluateAsync() { + return Promise.reject('such a fail'); + } + } + }).then(_ => { + assert(false); + }).catch(_ => { + assert.equal(contentWidthGatherer.artifact.scrollWidth, -1); + }); + }); +});