From c24473f2c4468d579a29de49b8e67904817d20d3 Mon Sep 17 00:00:00 2001 From: Lucas Wojciechowski Date: Thu, 1 Sep 2016 15:17:53 -0700 Subject: [PATCH] Remove fake "browser" and "dom" modules (#3106) * Eliminate browser DOM module * Eliminate browser browser module * Remove outdated comment * Fix test-suite * Ensure CI fails if test-suite fails * Fix failing test-suite tests --- ci/test.sh | 4 +- js/ui/map.js | 2 +- js/util/browser.js | 72 +++++++++++++++++++-------- js/util/browser/browser.js | 96 ------------------------------------ js/util/browser/canvas.js | 8 +-- js/util/browser/dom.js | 80 ------------------------------ js/util/canvas.js | 20 +++----- js/util/dom.js | 86 +++++++++++++++++++++++++++----- js/util/window.js | 10 +++- package.json | 2 - test/js/ui/map.test.js | 39 ++++----------- test/js/ui/marker.test.js | 47 +++++------------- test/js/util/browser.test.js | 5 -- test/js/util/mapbox.test.js | 9 +++- test/suite_implementation.js | 22 ++++----- 15 files changed, 186 insertions(+), 316 deletions(-) delete mode 100755 js/util/browser/browser.js delete mode 100644 js/util/browser/dom.js diff --git a/ci/test.sh b/ci/test.sh index 8850476ead3..07fd3733667 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -23,11 +23,9 @@ npm run build-dev tap --reporter dot --coverage --no-coverage-report test/js test/build/webpack.test.js # run render tests -istanbul cover --dir .nyc_output --include-pid --report none --print none test/render.test.js && +istanbul cover --dir .nyc_output --include-pid --report none --print none test/render.test.js istanbul cover --dir .nyc_output --include-pid --report none --print none test/query.test.js # send coverage report to coveralls nyc report --reporter=lcov (node ./node_modules/coveralls/bin/coveralls.js < ./coverage/lcov.info) || true - -exit $EXIT_CODE diff --git a/js/ui/map.js b/js/ui/map.js index 9793db1c49e..9f995c9baa8 100755 --- a/js/ui/map.js +++ b/js/ui/map.js @@ -3,7 +3,7 @@ var Canvas = require('../util/canvas'); var util = require('../util/util'); var browser = require('../util/browser'); -var window = require('../util/browser').window; +var window = require('../util/window'); var Evented = require('../util/evented'); var DOM = require('../util/dom'); diff --git a/js/util/browser.js b/js/util/browser.js index db9dbb3b34c..49f5b78e447 100755 --- a/js/util/browser.js +++ b/js/util/browser.js @@ -1,29 +1,44 @@ 'use strict'; +/** + * @module browser + * @private + */ + var window = require('./window'); -exports.window = window; -exports.document = window.document; -/* - * When browserify builds Mapbox GL JS, it redirects all require() statements - * from this file, js/util/browser.js, to js/util/browser/browser.js. - * The latter relies on running in a real browser: 'window' must be defined, - * as well as other browser-specific globals. This file, on the other hand, - * is comfortable running under node.js, which is why it's the default require: - * it's used for tests. +/** + * Provides a function that outputs milliseconds: either performance.now() + * or a fallback to Date.now() */ +module.exports.now = (function() { + if (window.performance && + window.performance.now) { + return window.performance.now.bind(window.performance); + } else { + return Date.now.bind(Date); + } +}()); + +var frame = window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + window.msRequestAnimationFrame; exports.frame = function(fn) { - return setImmediate(fn); + return frame(fn); }; +var cancel = window.cancelAnimationFrame || + window.mozCancelAnimationFrame || + window.webkitCancelAnimationFrame || + window.msCancelAnimationFrame; + exports.cancelFrame = function(id) { - return clearImmediate(id); + cancel(id); }; -module.exports.now = Date.now.bind(Date); - -exports.timed = function(fn, dur, ctx) { +exports.timed = function (fn, dur, ctx) { if (!dur) { fn.call(ctx, 1); return null; @@ -49,11 +64,28 @@ exports.timed = function(fn, dur, ctx) { return function() { abort = true; }; }; -exports.supported = function () { - return true; -}; +/** + * Test if the current browser supports Mapbox GL JS + * @param {Object} options + * @param {boolean} [options.failIfMajorPerformanceCaveat=false] Return `false` + * if the performance of Mapbox GL JS would be dramatically worse than + * expected (i.e. a software renderer would be used) + * @return {boolean} + */ +exports.supported = require('mapbox-gl-supported'); + +exports.hardwareConcurrency = window.navigator.hardwareConcurrency || 4; + +Object.defineProperty(exports, 'devicePixelRatio', { + get: function() { return window.devicePixelRatio; } +}); -exports.devicePixelRatio = 1; -exports.hardwareConcurrency = 8; exports.supportsWebp = false; -exports.supportsGeolocation = false; + +var webpImgTest = window.document.createElement('img'); +webpImgTest.onload = function() { + exports.supportsWebp = true; +}; +webpImgTest.src = 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAQAAAAfQ//73v/+BiOh/AAA='; + +exports.supportsGeolocation = !!window.navigator.geolocation; diff --git a/js/util/browser/browser.js b/js/util/browser/browser.js deleted file mode 100755 index 7aae4fc1464..00000000000 --- a/js/util/browser/browser.js +++ /dev/null @@ -1,96 +0,0 @@ -'use strict'; - -/** - * Unlike js/util/browser.js, this code is written with the expectation - * of a browser environment with a global 'window' object - * @module browser - * @private - */ - -var window = require('./window'); - -exports.window = window; -exports.document = window.document; - -/** - * Provides a function that outputs milliseconds: either performance.now() - * or a fallback to Date.now() - */ -module.exports.now = (function() { - if (window.performance && - window.performance.now) { - return window.performance.now.bind(window.performance); - } else { - return Date.now.bind(Date); - } -}()); - -var frame = window.requestAnimationFrame || - window.mozRequestAnimationFrame || - window.webkitRequestAnimationFrame || - window.msRequestAnimationFrame; - -exports.frame = function(fn) { - return frame(fn); -}; - -var cancel = window.cancelAnimationFrame || - window.mozCancelAnimationFrame || - window.webkitCancelAnimationFrame || - window.msCancelAnimationFrame; - -exports.cancelFrame = function(id) { - cancel(id); -}; - -exports.timed = function (fn, dur, ctx) { - if (!dur) { - fn.call(ctx, 1); - return null; - } - - var abort = false, - start = module.exports.now(); - - function tick(now) { - if (abort) return; - now = module.exports.now(); - - if (now >= start + dur) { - fn.call(ctx, 1); - } else { - fn.call(ctx, (now - start) / dur); - exports.frame(tick); - } - } - - exports.frame(tick); - - return function() { abort = true; }; -}; - -/** - * Test if the current browser supports Mapbox GL JS - * @param {Object} options - * @param {boolean} [options.failIfMajorPerformanceCaveat=false] Return `false` - * if the performance of Mapbox GL JS would be dramatically worse than - * expected (i.e. a software renderer would be used) - * @return {boolean} - */ -exports.supported = require('mapbox-gl-supported'); - -exports.hardwareConcurrency = window.navigator.hardwareConcurrency || 4; - -Object.defineProperty(exports, 'devicePixelRatio', { - get: function() { return window.devicePixelRatio; } -}); - -exports.supportsWebp = false; - -var webpImgTest = window.document.createElement('img'); -webpImgTest.onload = function() { - exports.supportsWebp = true; -}; -webpImgTest.src = 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAQAAAAfQ//73v/+BiOh/AAA='; - -exports.supportsGeolocation = !!window.navigator.geolocation; diff --git a/js/util/browser/canvas.js b/js/util/browser/canvas.js index 6252d8246e1..6d4022f3504 100644 --- a/js/util/browser/canvas.js +++ b/js/util/browser/canvas.js @@ -6,14 +6,14 @@ var window = require('./window'); module.exports = Canvas; -function Canvas(parent, container) { +function Canvas(map, container) { this.canvas = window.document.createElement('canvas'); - if (parent && container) { + if (map && container) { this.canvas.style.position = 'absolute'; this.canvas.classList.add('mapboxgl-canvas'); - this.canvas.addEventListener('webglcontextlost', parent._contextLost.bind(parent), false); - this.canvas.addEventListener('webglcontextrestored', parent._contextRestored.bind(parent), false); + this.canvas.addEventListener('webglcontextlost', map._contextLost.bind(map), false); + this.canvas.addEventListener('webglcontextrestored', map._contextRestored.bind(map), false); this.canvas.setAttribute('tabindex', 0); container.appendChild(this.canvas); } diff --git a/js/util/browser/dom.js b/js/util/browser/dom.js deleted file mode 100644 index 660be9aad65..00000000000 --- a/js/util/browser/dom.js +++ /dev/null @@ -1,80 +0,0 @@ -'use strict'; - -var Point = require('point-geometry'); -var window = require('./window'); - -exports.create = function (tagName, className, container) { - var el = window.document.createElement(tagName); - if (className) el.className = className; - if (container) container.appendChild(el); - return el; -}; - -var docStyle = window.document.documentElement.style; - -function testProp(props) { - for (var i = 0; i < props.length; i++) { - if (props[i] in docStyle) { - return props[i]; - } - } -} - -var selectProp = testProp(['userSelect', 'MozUserSelect', 'WebkitUserSelect', 'msUserSelect']), - userSelect; -exports.disableDrag = function () { - if (selectProp) { - userSelect = docStyle[selectProp]; - docStyle[selectProp] = 'none'; - } -}; -exports.enableDrag = function () { - if (selectProp) { - docStyle[selectProp] = userSelect; - } -}; - -var transformProp = testProp(['transform', 'WebkitTransform']); -exports.setTransform = function(el, value) { - el.style[transformProp] = value; -}; - -// Suppress the next click, but only if it's immediate. -function suppressClick(e) { - e.preventDefault(); - e.stopPropagation(); - window.removeEventListener('click', suppressClick, true); -} -exports.suppressClick = function() { - window.addEventListener('click', suppressClick, true); - window.setTimeout(function() { - window.removeEventListener('click', suppressClick, true); - }, 0); -}; - -exports.mousePos = function (el, e) { - var rect = el.getBoundingClientRect(); - e = e.touches ? e.touches[0] : e; - return new Point( - e.clientX - rect.left - el.clientLeft, - e.clientY - rect.top - el.clientTop - ); -}; - -exports.touchPos = function (el, e) { - var rect = el.getBoundingClientRect(), - points = []; - for (var i = 0; i < e.touches.length; i++) { - points.push(new Point( - e.touches[i].clientX - rect.left - el.clientLeft, - e.touches[i].clientY - rect.top - el.clientTop - )); - } - return points; -}; - -exports.remove = function(node) { - if (node.parentNode) { - node.parentNode.removeChild(node); - } -}; diff --git a/js/util/canvas.js b/js/util/canvas.js index 631000d9905..a504967fd1c 100644 --- a/js/util/canvas.js +++ b/js/util/canvas.js @@ -1,14 +1,11 @@ 'use strict'; -// Stub implementation for headless rendering with node. The browser implementation -// is in js/browser/ui/canvas.js. - var gl = require('gl'); -var browser = require('./browser'); +var window = require('./window'); module.exports = Canvas; -function Canvas(parent, container) { +function Canvas(map) { var requiredContextAttributes = { antialias: false, alpha: true, @@ -18,17 +15,16 @@ function Canvas(parent, container) { }; this.context = gl( - ((container && container.offsetWidth) || 512) * browser.devicePixelRatio, - ((container && container.offsetHeight) || 512) * browser.devicePixelRatio, - requiredContextAttributes); + map.getContainer().offsetWidth * window.devicePixelRatio, + map.getContainer().offsetHeight * window.devicePixelRatio, + requiredContextAttributes + ); } -Canvas.prototype.resize = function() { -}; +Canvas.prototype.resize = function() {}; Canvas.prototype.getWebGLContext = function() { return this.context; }; -Canvas.prototype.getElement = function() { -}; +Canvas.prototype.getElement = function() {}; diff --git a/js/util/dom.js b/js/util/dom.js index bd53f74671e..660be9aad65 100644 --- a/js/util/dom.js +++ b/js/util/dom.js @@ -1,18 +1,80 @@ 'use strict'; +var Point = require('point-geometry'); +var window = require('./window'); + exports.create = function (tagName, className, container) { - return { - offsetWidth: container ? container.offsetWidth : null, - offsetHeight: container ? container.offsetHeight : null, - remove: function () {}, - addEventListener: function() {}, - classList: { - add: function () {} - }, - appendChild: function () {} - }; + var el = window.document.createElement(tagName); + if (className) el.className = className; + if (container) container.appendChild(el); + return el; +}; + +var docStyle = window.document.documentElement.style; + +function testProp(props) { + for (var i = 0; i < props.length; i++) { + if (props[i] in docStyle) { + return props[i]; + } + } +} + +var selectProp = testProp(['userSelect', 'MozUserSelect', 'WebkitUserSelect', 'msUserSelect']), + userSelect; +exports.disableDrag = function () { + if (selectProp) { + userSelect = docStyle[selectProp]; + docStyle[selectProp] = 'none'; + } +}; +exports.enableDrag = function () { + if (selectProp) { + docStyle[selectProp] = userSelect; + } +}; + +var transformProp = testProp(['transform', 'WebkitTransform']); +exports.setTransform = function(el, value) { + el.style[transformProp] = value; +}; + +// Suppress the next click, but only if it's immediate. +function suppressClick(e) { + e.preventDefault(); + e.stopPropagation(); + window.removeEventListener('click', suppressClick, true); +} +exports.suppressClick = function() { + window.addEventListener('click', suppressClick, true); + window.setTimeout(function() { + window.removeEventListener('click', suppressClick, true); + }, 0); }; -exports.remove = function() {}; +exports.mousePos = function (el, e) { + var rect = el.getBoundingClientRect(); + e = e.touches ? e.touches[0] : e; + return new Point( + e.clientX - rect.left - el.clientLeft, + e.clientY - rect.top - el.clientTop + ); +}; -exports.setTransform = function() {}; +exports.touchPos = function (el, e) { + var rect = el.getBoundingClientRect(), + points = []; + for (var i = 0; i < e.touches.length; i++) { + points.push(new Point( + e.touches[i].clientX - rect.left - el.clientLeft, + e.touches[i].clientY - rect.top - el.clientTop + )); + } + return points; +}; + +exports.remove = function(node) { + if (node.parentNode) { + node.parentNode.removeChild(node); + } +}; diff --git a/js/util/window.js b/js/util/window.js index a2674f1757a..5ddad0ed7b5 100644 --- a/js/util/window.js +++ b/js/util/window.js @@ -1,4 +1,12 @@ 'use strict'; var jsdom = require('jsdom'); -module.exports = jsdom.jsdom().defaultView; + +var window = jsdom.jsdom().defaultView; + +window.requestAnimationFrame = function(callback) { return setImmediate(callback, 0); }; +window.cancelAnimationFrame = clearImmediate; + +window.devicePixelRatio = 1; + +module.exports = window; diff --git a/package.json b/package.json index 172de694cd7..c18cc56b5a9 100644 --- a/package.json +++ b/package.json @@ -81,10 +81,8 @@ }, "browser": { "./js/util/ajax.js": "./js/util/browser/ajax.js", - "./js/util/browser.js": "./js/util/browser/browser.js", "./js/util/window.js": "./js/util/browser/window.js", "./js/util/canvas.js": "./js/util/browser/canvas.js", - "./js/util/dom.js": "./js/util/browser/dom.js", "./js/util/web_worker.js": "./js/util/browser/web_worker.js" }, "scripts": { diff --git a/test/js/ui/map.test.js b/test/js/ui/map.test.js index a2ed709da21..a4cd5fdce5d 100755 --- a/test/js/ui/map.test.js +++ b/test/js/ui/map.test.js @@ -2,7 +2,7 @@ var test = require('tap').test; var extend = require('../../../js/util/util').extend; -var window = require('../../../js/util/browser').window; +var window = require('../../../js/util/window'); var Map = require('../../../js/ui/map'); var Style = require('../../../js/style/style'); var LngLat = require('../../../js/geo/lng_lat'); @@ -13,15 +13,12 @@ var fixedNum = fixed.Num; var fixedLngLat = fixed.LngLat; function createMap(options, callback) { + var container = window.document.createElement('div'); + container.offsetWidth = 200; + container.offsetHeight = 200; + var map = new Map(extend({ - container: { - offsetWidth: 200, - offsetHeight: 200, - classList: { - add: function() {}, - remove: function() {} - } - }, + container: container, interactive: false, attributionControl: false, trackResize: true, @@ -487,26 +484,10 @@ test('Map', function(t) { }); t.test('#remove', function(t) { - var map = createMap(), - removedCanvas, - removedControls; - - map._canvasContainer.parentNode = { - removeChild: function (child) { - t.equal(child, map._canvasContainer); - removedCanvas = true; - } - }; - map._controlContainer.parentNode = { - removeChild: function (child) { - t.equal(child, map._controlContainer); - removedControls = true; - } - }; - - t.equal(map.remove(), undefined); - t.ok(removedCanvas); - t.ok(removedControls); + var map = createMap(); + t.equal(map.getContainer().childNodes.length, 2); + map.remove(); + t.equal(map.getContainer().childNodes.length, 0); t.end(); }); diff --git a/test/js/ui/marker.test.js b/test/js/ui/marker.test.js index 4348708aab0..fc4861d94a3 100644 --- a/test/js/ui/marker.test.js +++ b/test/js/ui/marker.test.js @@ -1,36 +1,16 @@ 'use strict'; var test = require('tap').test; -var extend = require('../../../js/util/util').extend; var window = require('../../../js/util/window'); var Map = require('../../../js/ui/map'); var Marker = require('../../../js/ui/marker'); var Popup = require('../../../js/ui/popup'); -function createMap(options, callback) { - var map = new Map(extend({ - container: { - offsetWidth: 200, - offsetHeight: 200, - classList: { - add: function() {}, - remove: function() {} - } - }, - attributionControl: false, - trackResize: true, - style: { - "version": 8, - "sources": {}, - "layers": [] - } - }, options)); - - if (callback) map.on('load', function () { - callback(null, map); - }); - - return map; +function createMap() { + var container = window.document.createElement('div'); + container.offsetWidth = 512; + container.offsetHeight = 512; + return new Map({container: container}); } test('Marker', function (t) { @@ -43,20 +23,16 @@ test('Marker', function (t) { t.test('marker is added to map', function (t) { var map = createMap(); - var el = window.document.createElement('div'); - map.on('load', function () { - var marker = new Marker(el).setLngLat([-77.01866, 38.888]); - t.ok(marker.addTo(map) instanceof Marker, 'marker.addTo(map) returns Marker instance'); - t.ok(marker._map, 'marker instance is bound to map instance'); - t.end(); - }); + var marker = new Marker(window.document.createElement('div')).setLngLat([-77.01866, 38.888]); + t.ok(marker.addTo(map) instanceof Marker, 'marker.addTo(map) returns Marker instance'); + t.ok(marker._map, 'marker instance is bound to map instance'); + t.end(); }); t.test('popups can be bound to marker instance', function (t) { var map = createMap(); - var el = window.document.createElement('div'); var popup = new Popup(); - var marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map); + var marker = new Marker(window.document.createElement('div')).setLngLat([-77.01866, 38.888]).addTo(map); marker.setPopup(popup); t.ok(marker.getPopup() instanceof Popup, 'popup created with Popup instance'); t.end(); @@ -64,8 +40,7 @@ test('Marker', function (t) { t.test('popups can be unbound from a marker instance', function (t) { var map = createMap(); - var el = window.document.createElement('div'); - var marker = new Marker(el).setLngLat([-77.01866, 38.888]).addTo(map); + var marker = new Marker(window.document.createElement('div')).setLngLat([-77.01866, 38.888]).addTo(map); marker.setPopup(new Popup()); t.ok(marker.getPopup() instanceof Popup); t.ok(marker.setPopup() instanceof Marker, 'passing no argument to Marker.setPopup() is valid'); diff --git a/test/js/util/browser.test.js b/test/js/util/browser.test.js index b195bbe87d3..980be4179e4 100644 --- a/test/js/util/browser.test.js +++ b/test/js/util/browser.test.js @@ -4,11 +4,6 @@ var test = require('tap').test; var browser = require('../../../js/util/browser'); test('browser', function(t) { - t.test('supported', function(t) { - t.equal(browser.supported(), true); - t.end(); - }); - t.test('frame', function(t) { var id = browser.frame(function() { t.pass('called frame'); diff --git a/test/js/util/mapbox.test.js b/test/js/util/mapbox.test.js index f8212a06881..a2dd8c0b514 100644 --- a/test/js/util/mapbox.test.js +++ b/test/js/util/mapbox.test.js @@ -4,6 +4,7 @@ var test = require('tap').test; var mapbox = require('../../../js/util/mapbox'); var config = require('../../../js/util/config'); var browser = require('../../../js/util/browser'); +var window = require('../../../js/util/window'); test("mapbox", function(t) { var mapboxSource = 'mapbox://user.map'; @@ -142,6 +143,8 @@ test("mapbox", function(t) { }); t.test('.normalizeTileURL', function(t) { + browser.supportsWebp = false; + t.test('does nothing on 1x devices', function(t) { t.equal(mapbox.normalizeTileURL('http://path.png/tile.png', mapboxSource), 'http://path.png/tile.png'); t.equal(mapbox.normalizeTileURL('http://path.png/tile.png32', mapboxSource), 'http://path.png/tile.png32'); @@ -150,12 +153,12 @@ test("mapbox", function(t) { }); t.test('inserts @2x on 2x devices', function(t) { - browser.devicePixelRatio = 2; + window.devicePixelRatio = 2; t.equal(mapbox.normalizeTileURL('http://path.png/tile.png', mapboxSource), 'http://path.png/tile@2x.png'); t.equal(mapbox.normalizeTileURL('http://path.png/tile.png32', mapboxSource), 'http://path.png/tile@2x.png32'); t.equal(mapbox.normalizeTileURL('http://path.png/tile.jpg70', mapboxSource), 'http://path.png/tile@2x.jpg70'); t.equal(mapbox.normalizeTileURL('http://path.png/tile.png?access_token=foo', mapboxSource), 'http://path.png/tile@2x.png?access_token=foo'); - browser.devicePixelRatio = 1; + window.devicePixelRatio = 1; t.end(); }); @@ -205,6 +208,8 @@ test("mapbox", function(t) { t.end(); }); + browser.supportsWebp = true; + t.end(); }); diff --git a/test/suite_implementation.js b/test/suite_implementation.js index a6427fde05f..36683840079 100644 --- a/test/suite_implementation.js +++ b/test/suite_implementation.js @@ -1,8 +1,7 @@ 'use strict'; var Map = require('../js/ui/map'); -var browser = require('../js/util/browser'); - +var window = require('../js/util/window'); module.exports = function(style, options, _callback) { var wasCallbackCalled = false; @@ -13,17 +12,14 @@ module.exports = function(style, options, _callback) { } } - browser.devicePixelRatio = options.pixelRatio; + window.devicePixelRatio = options.pixelRatio; + + var container = window.document.createElement('div'); + container.offsetHeight = options.height; + container.offsetWidth = options.width; var map = new Map({ - container: { - offsetWidth: options.width, - offsetHeight: options.height, - classList: { - add: function() {}, - remove: function() {} - } - }, + container: container, style: style, classes: options.classes, interactive: false, @@ -41,8 +37,8 @@ module.exports = function(style, options, _callback) { map.once('load', function() { applyOperations(map, options.operations, function() { - var w = options.width * browser.devicePixelRatio; - var h = options.height * browser.devicePixelRatio; + var w = options.width * window.devicePixelRatio; + var h = options.height * window.devicePixelRatio; var pixels = new Uint8Array(w * h * 4); gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels);