diff --git a/bench/benchmarks/buffer.js b/bench/benchmarks/buffer.js index 58a4ce291c2..3f4bc66389d 100644 --- a/bench/benchmarks/buffer.js +++ b/bench/benchmarks/buffer.js @@ -24,7 +24,7 @@ module.exports = function run() { const evented = new Evented(); const stylesheetURL = `https://api.mapbox.com/styles/v1/mapbox/streets-v9?access_token=${accessToken}`; - ajax.getJSON(stylesheetURL, (err, stylesheet) => { + ajax.getJSON({ url: stylesheetURL }, (err, stylesheet) => { if (err) return evented.fire('error', {error: err}); evented.fire('log', { @@ -82,6 +82,12 @@ module.exports = function run() { return evented; }; +class StubMap extends Evented { + _transformRequest(url) { + return { url }; + } +} + function preloadAssets(stylesheet, callback) { const assets = { glyphs: {}, @@ -89,7 +95,7 @@ function preloadAssets(stylesheet, callback) { tiles: {} }; - const style = new Style(stylesheet); + const style = new Style(stylesheet, new StubMap()); style.on('style.load', () => { function getGlyphs(params, callback) { @@ -107,7 +113,7 @@ function preloadAssets(stylesheet, callback) { } function getTile(url, callback) { - ajax.getArrayBuffer(url, (err, response) => { + ajax.getArrayBuffer({ url }, (err, response) => { assets.tiles[url] = response.data; callback(err, response.data); }); diff --git a/bench/benchmarks/geojson_setdata_large.js b/bench/benchmarks/geojson_setdata_large.js index 364dfe2c170..10f1df25541 100644 --- a/bench/benchmarks/geojson_setdata_large.js +++ b/bench/benchmarks/geojson_setdata_large.js @@ -14,7 +14,7 @@ module.exports = function() { evented.fire('log', {message: 'downloading large geojson'}); }, 0); - ajax.getJSON('http://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_land.geojson', (err, data) => { + ajax.getJSON({ url: 'http://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_land.geojson' }, (err, data) => { evented.fire('log', {message: 'starting test'}); if (err) return evented.fire('error', {error: err}); diff --git a/bench/benchmarks/style_load.js b/bench/benchmarks/style_load.js index b026fa95c23..f08e527ca73 100644 --- a/bench/benchmarks/style_load.js +++ b/bench/benchmarks/style_load.js @@ -12,8 +12,13 @@ module.exports = function() { const evented = new Evented(); + class StubMap extends Evented { + _transformRequest(url) { + return { url }; + } + } const stylesheetURL = `https://api.mapbox.com/styles/v1/mapbox/streets-v9?access_token=${accessToken}`; - ajax.getJSON(stylesheetURL, (err, json) => { + ajax.getJSON({ url: stylesheetURL }, (err, json) => { if (err) { return evented.fire('error', {error: err}); } @@ -23,7 +28,7 @@ module.exports = function() { asyncTimesSeries(20, (callback) => { const timeStart = performance.now(); - new Style(json) + new Style(json, new StubMap()) .on('error', (err) => { evented.fire('error', { error: err }); }) diff --git a/src/source/geojson_source.js b/src/source/geojson_source.js index 5cb6516c171..d73f4fb112b 100644 --- a/src/source/geojson_source.js +++ b/src/source/geojson_source.js @@ -4,6 +4,7 @@ const Evented = require('../util/evented'); const util = require('../util/util'); const window = require('../util/window'); const EXTENT = require('../data/extent'); +const ResourceType = require('../util/ajax').ResourceType; import type {Source} from './source'; import type Map from '../ui/map'; @@ -138,8 +139,8 @@ class GeoJSONSource extends Evented implements Source { } onAdd(map: Map) { - this.load(); this.map = map; + this.load(); } /** @@ -170,7 +171,7 @@ class GeoJSONSource extends Evented implements Source { const options = util.extend({}, this.workerOptions); const data = this._data; if (typeof data === 'string') { - options.url = resolveURL(data); + options.request = this.map._transformRequest(resolveURL(data), ResourceType.Source); } else { options.data = JSON.stringify(data); } diff --git a/src/source/geojson_worker_source.js b/src/source/geojson_worker_source.js index 836daa6432d..96b4e930d24 100644 --- a/src/source/geojson_worker_source.js +++ b/src/source/geojson_worker_source.js @@ -18,11 +18,12 @@ import type {Actor} from '../util/actor'; import type StyleLayerIndex from '../style/style_layer_index'; import type {LoadVectorDataCallback} from './vector_tile_worker_source'; +import type {RequestParameters} from '../util/ajax'; export type GeoJSON = Object; export type LoadGeoJSONParameters = { - url?: string, + request?: RequestParameters, data?: string, source: string, superclusterOptions?: Object, @@ -164,8 +165,8 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource { // origin or absolute path. // ie: /foo/bar.json or http://example.com/bar.json // but not ../foo/bar.json - if (params.url) { - ajax.getJSON(params.url, callback); + if (params.request) { + ajax.getJSON(params.request, callback); } else if (typeof params.data === 'string') { try { return callback(null, JSON.parse(params.data)); diff --git a/src/source/image_source.js b/src/source/image_source.js index c9a20cddfdb..e82d4954f65 100644 --- a/src/source/image_source.js +++ b/src/source/image_source.js @@ -91,7 +91,7 @@ class ImageSource extends Evented implements Source { this.url = this.options.url; - ajax.getImage(this.options.url, (err, image) => { + ajax.getImage(this.map._transformRequest(this.url, ajax.ResourceType.Image), (err, image) => { if (err) { this.fire('error', {error: err}); } else if (image) { @@ -109,11 +109,8 @@ class ImageSource extends Evented implements Source { } onAdd(map: Map) { - this.load(); this.map = map; - if (this.image) { - this.setCoordinates(this.coordinates); - } + this.load(); } /** @@ -181,7 +178,7 @@ class ImageSource extends Evented implements Source { } prepare() { - if (Object.keys(this.tiles).length === 0 === 0 || !this.image) return; + if (Object.keys(this.tiles).length === 0 || !this.image) return; this._prepareImage(this.map.painter.gl, this.image); } @@ -230,7 +227,7 @@ class ImageSource extends Evented implements Source { serialize(): Object { return { type: 'image', - urls: this.url, + urls: this.options.url, coordinates: this.coordinates }; } diff --git a/src/source/load_tilejson.js b/src/source/load_tilejson.js index e064c671b46..e49e228e4d3 100644 --- a/src/source/load_tilejson.js +++ b/src/source/load_tilejson.js @@ -5,7 +5,9 @@ const ajax = require('../util/ajax'); const browser = require('../util/browser'); const normalizeURL = require('../util/mapbox').normalizeSourceURL; -module.exports = function(options: any, callback: Callback) { +import type {RequestTransformFunction} from '../ui/map'; + +module.exports = function(options: any, requestTransformFn: RequestTransformFunction, callback: Callback) { const loaded = function(err, tileJSON: any) { if (err) { return callback(err); @@ -22,7 +24,7 @@ module.exports = function(options: any, callback: Callback) { }; if (options.url) { - ajax.getJSON(normalizeURL(options.url), loaded); + ajax.getJSON(requestTransformFn(normalizeURL(options.url), ajax.ResourceType.Source), loaded); } else { browser.frame(() => loaded(null, options)); } diff --git a/src/source/raster_tile_source.js b/src/source/raster_tile_source.js index 0b8862a5f36..f3ce7cd0cd9 100644 --- a/src/source/raster_tile_source.js +++ b/src/source/raster_tile_source.js @@ -52,7 +52,7 @@ class RasterTileSource extends Evented implements Source { load() { this.fire('dataloading', {dataType: 'source'}); - loadTileJSON(this._options, (err, tileJSON) => { + loadTileJSON(this._options, this.map._transformRequest, (err, tileJSON) => { if (err) { this.fire('error', err); } else if (tileJSON) { @@ -69,8 +69,8 @@ class RasterTileSource extends Evented implements Source { } onAdd(map: Map) { - this.load(); this.map = map; + this.load(); } setBounds(bounds?: [number, number, number, number]) { @@ -91,7 +91,7 @@ class RasterTileSource extends Evented implements Source { loadTile(tile: Tile, callback: Callback) { const url = normalizeURL(tile.coord.url(this.tiles, null, this.scheme), this.url, this.tileSize); - tile.request = ajax.getImage(url, (err, img) => { + tile.request = ajax.getImage(this.map._transformRequest(url, ajax.ResourceType.Tile), (err, img) => { delete tile.request; if (tile.aborted) { diff --git a/src/source/rtl_text_plugin.js b/src/source/rtl_text_plugin.js index db13b9fe2af..e9a6b88c2e8 100644 --- a/src/source/rtl_text_plugin.js +++ b/src/source/rtl_text_plugin.js @@ -38,7 +38,7 @@ module.exports.setRTLTextPlugin = function(pluginURL: string, callback: ErrorCal } pluginRequested = true; module.exports.errorCallback = callback; - ajax.getArrayBuffer(pluginURL, (err, response) => { + ajax.getArrayBuffer({ url: pluginURL }, (err, response) => { if (err) { callback(err); } else if (response) { diff --git a/src/source/vector_tile_source.js b/src/source/vector_tile_source.js index abf271cff1d..6e8c50dc2a7 100644 --- a/src/source/vector_tile_source.js +++ b/src/source/vector_tile_source.js @@ -5,6 +5,7 @@ const util = require('../util/util'); const loadTileJSON = require('./load_tilejson'); const normalizeURL = require('../util/mapbox').normalizeTileURL; const TileBounds = require('./tile_bounds'); +const ResourceType = require('../util/ajax').ResourceType; import type {Source} from './source'; import type TileCoord from './tile_coord'; @@ -56,7 +57,7 @@ class VectorTileSource extends Evented implements Source { load() { this.fire('dataloading', {dataType: 'source'}); - loadTileJSON(this._options, (err, tileJSON) => { + loadTileJSON(this._options, this.map._transformRequest, (err, tileJSON) => { if (err) { this.fire('error', err); } else if (tileJSON) { @@ -84,8 +85,8 @@ class VectorTileSource extends Evented implements Source { } onAdd(map: Map) { - this.load(); this.map = map; + this.load(); } serialize() { @@ -94,8 +95,9 @@ class VectorTileSource extends Evented implements Source { loadTile(tile: Tile, callback: Callback) { const overscaling = tile.coord.z > this.maxzoom ? Math.pow(2, tile.coord.z - this.maxzoom) : 1; + const url = normalizeURL(tile.coord.url(this.tiles, this.maxzoom, this.scheme), this.url); const params = { - url: normalizeURL(tile.coord.url(this.tiles, this.maxzoom, this.scheme), this.url), + request: this.map._transformRequest(url, ResourceType.Tile), uid: tile.uid, coord: tile.coord, zoom: tile.coord.z, diff --git a/src/source/vector_tile_worker_source.js b/src/source/vector_tile_worker_source.js index dbd3c028abf..c225aebd46a 100644 --- a/src/source/vector_tile_worker_source.js +++ b/src/source/vector_tile_worker_source.js @@ -40,7 +40,7 @@ export type LoadVectorData = (params: WorkerTileParameters, callback: LoadVector * @private */ function loadVectorTile(params: WorkerTileParameters, callback: LoadVectorDataCallback) { - const xhr = ajax.getArrayBuffer(params.url, (err, response) => { + const xhr = ajax.getArrayBuffer(params.request, (err, response) => { if (err) { callback(err); } else if (response) { diff --git a/src/source/video_source.js b/src/source/video_source.js index 47c874da1bb..e0cda3f6504 100644 --- a/src/source/video_source.js +++ b/src/source/video_source.js @@ -96,8 +96,8 @@ class VideoSource extends ImageSource { onAdd(map: Map) { if (this.map) return; - this.load(); this.map = map; + this.load(); if (this.video) { this.video.play(); this.setCoordinates(this.coordinates); diff --git a/src/source/worker.js b/src/source/worker.js index 58ca20bb49e..15fb4dd43ed 100644 --- a/src/source/worker.js +++ b/src/source/worker.js @@ -108,7 +108,7 @@ class Worker { * function taking `(name, workerSourceObject)`. * @private */ - loadWorkerSource(map: string, params: {url: string}, callback: Callback) { + loadWorkerSource(map: string, params: { url: string }, callback: Callback) { try { this.self.importScripts(params.url); callback(); diff --git a/src/source/worker_source.js b/src/source/worker_source.js index a444ce25d11..7296fb8975d 100644 --- a/src/source/worker_source.js +++ b/src/source/worker_source.js @@ -7,6 +7,7 @@ import type {SerializedBucket} from '../data/bucket'; import type {SerializedFeatureIndex} from '../data/feature_index'; import type {SerializedCollisionTile} from '../symbol/collision_tile'; import type {SerializedStructArray} from '../util/struct_array'; +import type {RequestParameters} from '../util/ajax'; export type TileParameters = { source: string, @@ -23,7 +24,7 @@ export type PlacementConfig = { export type WorkerTileParameters = TileParameters & { coord: TileCoord, - url: string, + request: RequestParameters, zoom: number, maxZoom: number, tileSize: number, diff --git a/src/style/image_sprite.js b/src/style/image_sprite.js index 78d847b9465..dcdfa786e2c 100644 --- a/src/style/image_sprite.js +++ b/src/style/image_sprite.js @@ -5,6 +5,8 @@ const ajax = require('../util/ajax'); const browser = require('../util/browser'); const normalizeURL = require('../util/mapbox').normalizeSpriteURL; +import type {RequestTransformFunction} from '../ui/map'; + class SpritePosition { x: number; y: number; @@ -27,19 +29,22 @@ class ImageSprite extends Evented { base: string; retina: boolean; + transformRequestFn: RequestTransformFunction; data: ?{[string]: SpritePosition}; imgData: ?Uint8ClampedArray; width: ?number; - constructor(base: string, eventedParent?: Evented) { + constructor(base: string, transformRequestCallback: RequestTransformFunction, eventedParent?: Evented) { super(); this.base = base; this.retina = browser.devicePixelRatio > 1; this.setEventedParent(eventedParent); + this.transformRequestFn = transformRequestCallback; const format = this.retina ? '@2x' : ''; - - ajax.getJSON(normalizeURL(base, format, '.json'), (err, data) => { + let url = normalizeURL(base, format, '.json'); + const jsonRequest = transformRequestCallback(url, ajax.ResourceType.SpriteJSON); + ajax.getJSON(jsonRequest, (err, data) => { if (err) { this.fire('error', {error: err}); } else if (data) { @@ -47,8 +52,9 @@ class ImageSprite extends Evented { if (this.imgData) this.fire('data', {dataType: 'style'}); } }); - - ajax.getImage(normalizeURL(base, format, '.png'), (err, img) => { + url = normalizeURL(base, format, '.png'); + const imageRequest = transformRequestCallback(url, ajax.ResourceType.SpriteImage); + ajax.getImage(imageRequest, (err, img) => { if (err) { this.fire('error', {error: err}); } else if (img) { @@ -71,7 +77,7 @@ class ImageSprite extends Evented { resize(/*gl*/) { if (browser.devicePixelRatio > 1 !== this.retina) { - const newSprite = new ImageSprite(this.base); + const newSprite = new ImageSprite(this.base, this.transformRequestFn); newSprite.on('data', () => { this.data = newSprite.data; this.imgData = newSprite.imgData; diff --git a/src/style/style.js b/src/style/style.js index 43f1787b045..3ba70b80025 100644 --- a/src/style/style.js +++ b/src/style/style.js @@ -85,6 +85,10 @@ class Style extends Evented { } }); + const transformRequest = (url, resourceType) => { + return this.map ? this.map._transformRequest(url, resourceType) : { url }; + }; + const stylesheetLoaded = (err, stylesheet) => { if (err) { this.fire('error', {error: err}); @@ -103,17 +107,17 @@ class Style extends Evented { } if (stylesheet.sprite) { - this.sprite = new ImageSprite(stylesheet.sprite, this); + this.sprite = new ImageSprite(stylesheet.sprite, transformRequest, this); } - this.glyphSource = new GlyphSource(stylesheet.glyphs, options.localIdeographFontFamily, this); + this.glyphSource = new GlyphSource(stylesheet.glyphs, options.localIdeographFontFamily, transformRequest, this); this._resolve(); this.fire('data', {dataType: 'style'}); this.fire('style.load'); }; if (typeof stylesheet === 'string') { - ajax.getJSON(mapbox.normalizeStyleURL(stylesheet), stylesheetLoaded); + ajax.getJSON(transformRequest(mapbox.normalizeStyleURL(stylesheet), ajax.ResourceType.Style), stylesheetLoaded); } else { browser.frame(stylesheetLoaded.bind(this, null, stylesheet)); } diff --git a/src/symbol/glyph_source.js b/src/symbol/glyph_source.js index df04c2f7eae..f0e0743a7aa 100644 --- a/src/symbol/glyph_source.js +++ b/src/symbol/glyph_source.js @@ -11,6 +11,7 @@ const Evented = require('../util/evented'); import type {Glyph, GlyphStack} from '../util/glyphs'; import type {Rect} from '../symbol/glyph_atlas'; +import type {RequestTransformFunction} from '../ui/map'; // A simplified representation of the glyph containing only the properties needed for shaping. class SimpleGlyph { @@ -42,11 +43,12 @@ class GlyphSource extends Evented { loading: {[string]: {[number]: Array}}; localIdeographFontFamily: string; tinySDFs: {[string]: TinySDF}; + transformRequestCallback: RequestTransformFunction; /** * @param {string} url glyph template url */ - constructor(url: string, localIdeographFontFamily: string, eventedParent?: Evented) { + constructor(url: string, localIdeographFontFamily: string, transformRequestCallback: RequestTransformFunction, eventedParent?: Evented) { super(); this.url = url && normalizeURL(url); this.atlases = {}; @@ -55,6 +57,7 @@ class GlyphSource extends Evented { this.localIdeographFontFamily = localIdeographFontFamily; this.tinySDFs = {}; this.setEventedParent(eventedParent); + this.transformRequestCallback = transformRequestCallback; } getSimpleGlyphs(fontstack: string, glyphIDs: Array, uid: number, callback: (err: ?Error, glyphs: {[number]: SimpleGlyph}, fontstack: string) => void) { @@ -163,7 +166,8 @@ class GlyphSource extends Evented { } loadPBF(url: string, callback: Callback<{data: ArrayBuffer}>) { - ajax.getArrayBuffer(url, callback); + const request = this.transformRequestCallback ? this.transformRequestCallback(url, ajax.ResourceType.Glyphs) : { url }; + ajax.getArrayBuffer(request, callback); } loadRange(fontstack: string, range: number, callback: (err: ?Error, range: ?number, glyphs: ?Glyphs) => void) { diff --git a/src/ui/map.js b/src/ui/map.js index 9599f069f9f..fdf718b9c37 100755 --- a/src/ui/map.js +++ b/src/ui/map.js @@ -25,6 +25,7 @@ const isSupported = require('mapbox-gl-supported'); import type {LngLatLike} from '../geo/lng_lat'; import type {LngLatBoundsLike} from '../geo/lng_lat_bounds'; +import type {RequestParameters} from '../util/ajax'; /* eslint-disable no-use-before-define */ type IControl = { @@ -33,6 +34,9 @@ type IControl = { } /* eslint-enable no-use-before-define */ +type ResourceTypeEnum = $Keys; +export type RequestTransformFunction = (url: string, resourceType?: ResourceTypeEnum) => RequestParameters; + type MapOptions = { hash?: boolean, interactive?: boolean, @@ -60,7 +64,8 @@ type MapOptions = { bearing?: number, pitch?: number, renderWorldCopies?: boolean, - maxTileCacheSize?: number + maxTileCacheSize?: number, + transformRequest?: RequestTransformFunction }; type MapEvent = @@ -125,7 +130,9 @@ const defaultOptions = { refreshExpiredTiles: true, - maxTileCacheSize: null + maxTileCacheSize: null, + + transformRequest: null }; /** @@ -196,13 +203,24 @@ const defaultOptions = { * for locally overriding generation of glyphs in the 'CJK Unified Ideographs' and 'Hangul Syllables' ranges. * In these ranges, font settings from the map's style will be ignored, except for font-weight keywords (light/regular/medium/bold). * The purpose of this option is to avoid bandwidth-intensive glyph server requests. (see [Use locally generated ideographs](https://www.mapbox.com/mapbox-gl-js/example/local-ideographs)) + * @param {RequestTransformFunction} [options.transformRequest=null] A callback run before the Map makes a request for an external URL. The callback can be used to modify the url, set headers, or set the credentials property for cross-origin requests. + * Expected to return an object with a `url` property and optionally `headers` and `credentials` properties. * @example * var map = new mapboxgl.Map({ * container: 'map', * center: [-122.420679, 37.772537], * zoom: 13, * style: style_object, - * hash: true + * hash: true, + * transformRequest: (url, resourceType)=> { + * if(resourceType == 'Source' && url.startsWith('http://myHost') { + * return { + * url: url.replace('http', 'https'), + * headers: { 'my-custom-header': true}, + * credentials: 'include' // Include cookies for cross-origin requests + * } + * } + * } * }); * @see [Display a map](https://www.mapbox.com/mapbox-gl-js/examples/) */ @@ -221,6 +239,7 @@ class Map extends Camera { _repaint: ?boolean; _vertices: ?boolean; _canvas: HTMLCanvasElement; + _transformRequest: RequestTransformFunction; constructor(options: MapOptions) { options = util.extend({}, defaultOptions, options); @@ -240,6 +259,9 @@ class Map extends Camera { this._bearingSnap = options.bearingSnap; this._refreshExpiredTiles = options.refreshExpiredTiles; + const transformRequestFn = options.transformRequest; + this._transformRequest = transformRequestFn ? (url, type) => transformRequestFn(url, type) || ({ url }) : (url) => ({ url }); + if (typeof options.container === 'string') { this._container = window.document.getElementById(options.container); if (!this._container) throw new Error(`Container '${options.container}' not found.`); @@ -1134,7 +1156,7 @@ class Map extends Camera { * @see [Add an icon to the map](https://www.mapbox.com/mapbox-gl-js/example/add-image/) */ loadImage(url: string, callback: Function) { - ajax.getImage(url, callback); + ajax.getImage(this._transformRequest(url, ajax.ResourceType.Image), callback); } /** diff --git a/src/util/ajax.js b/src/util/ajax.js index c01e25f91e0..1425af84ce2 100644 --- a/src/util/ajax.js +++ b/src/util/ajax.js @@ -2,6 +2,41 @@ const window = require('./window'); +/** + * The type of a resource. + * @private + * @readonly + * @enum {string} + */ +const ResourceType = { + Unknown: 'Unknown', + Style: 'Style', + Source: 'Source', + Tile: 'Tile', + Glyphs: 'Glyphs', + SpriteImage: 'SpriteImage', + SpriteJSON: 'SpriteJSON', + Image: 'Image' +}; +exports.ResourceType = ResourceType; + +if (typeof Object.freeze == 'function') { + Object.freeze(ResourceType); +} + +/** + * A `RequestParameters` object to be returned from Map.options.transformRequest callbacks. + * @typedef {Object} RequestParameters + * @property {string} url The URL to be requested. + * @property {Object} headers The headers to be sent with the request. + * @property {string} credentials `'same-origin'|'include'` Use 'include' to send cookies with cross-origin requests. + */ +export type RequestParameters = { + url: string, + headers?: Object, + credentials? : 'same-origin' | 'include' +}; + class AJAXError extends Error { status: number; constructor(message: string, status: number) { @@ -10,9 +45,19 @@ class AJAXError extends Error { } } -exports.getJSON = function(url: string, callback: Callback) { +function makeRequest(requestParameters: RequestParameters) : XMLHttpRequest { const xhr: XMLHttpRequest = new window.XMLHttpRequest(); - xhr.open('GET', url, true); + + xhr.open('GET', requestParameters.url, true); + for (const k in requestParameters.headers) { + xhr.setRequestHeader(k, requestParameters.headers[k]); + } + xhr.withCredentials = requestParameters.credentials === 'include'; + return xhr; +} + +exports.getJSON = function(requestParameters: RequestParameters, callback: Callback) { + const xhr = makeRequest(requestParameters); xhr.setRequestHeader('Accept', 'application/json'); xhr.onerror = function() { callback(new Error(xhr.statusText)); @@ -34,9 +79,8 @@ exports.getJSON = function(url: string, callback: Callback) { return xhr; }; -exports.getArrayBuffer = function(url: string, callback: Callback<{data: ArrayBuffer, cacheControl: ?string, expires: ?string}>) { - const xhr: XMLHttpRequest = new window.XMLHttpRequest(); - xhr.open('GET', url, true); +exports.getArrayBuffer = function(requestParameters: RequestParameters, callback: Callback<{data: ArrayBuffer, cacheControl: ?string, expires: ?string}>) { + const xhr = makeRequest(requestParameters); xhr.responseType = 'arraybuffer'; xhr.onerror = function() { callback(new Error(xhr.statusText)); @@ -68,10 +112,10 @@ function sameOrigin(url) { const transparentPngUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII='; -exports.getImage = function(url: string, callback: Callback) { +exports.getImage = function(requestParameters: RequestParameters, callback: Callback) { // request the image with XHR to work around caching issues // see https://github.com/mapbox/mapbox-gl-js/issues/1470 - return exports.getArrayBuffer(url, (err, imgData) => { + return exports.getArrayBuffer(requestParameters, (err, imgData) => { if (err) { callback(err); } else if (imgData) { diff --git a/test/suite_implementation.js b/test/suite_implementation.js index 8d6a60e170e..4ccf8a04c4b 100644 --- a/test/suite_implementation.js +++ b/test/suite_implementation.js @@ -126,7 +126,7 @@ function cached(data, callback) { }); } -sinon.stub(ajax, 'getJSON').callsFake((url, callback) => { +sinon.stub(ajax, 'getJSON').callsFake(({ url }, callback) => { if (cache[url]) return cached(cache[url], callback); return request(url, (error, response, body) => { if (!error && response.statusCode >= 200 && response.statusCode < 300) { @@ -144,9 +144,9 @@ sinon.stub(ajax, 'getJSON').callsFake((url, callback) => { }); }); -sinon.stub(ajax, 'getArrayBuffer').callsFake((url, callback) => { +sinon.stub(ajax, 'getArrayBuffer').callsFake(({ url }, callback) => { if (cache[url]) return cached(cache[url], callback); - return request({url: url, encoding: null}, (error, response, body) => { + return request({ url, encoding: null }, (error, response, body) => { if (!error && response.statusCode >= 200 && response.statusCode < 300) { cache[url] = {data: body}; callback(null, {data: body}); @@ -156,9 +156,9 @@ sinon.stub(ajax, 'getArrayBuffer').callsFake((url, callback) => { }); }); -sinon.stub(ajax, 'getImage').callsFake((url, callback) => { +sinon.stub(ajax, 'getImage').callsFake(({ url }, callback) => { if (cache[url]) return cached(cache[url], callback); - return request({url: url, encoding: null}, (error, response, body) => { + return request({ url, encoding: null }, (error, response, body) => { if (!error && response.statusCode >= 200 && response.statusCode < 300) { new PNG().parse(body, (err, png) => { if (err) return callback(err); @@ -178,7 +178,7 @@ sinon.stub(browser, 'getImageData').callsFake((img) => { // Hack: since node doesn't have any good video codec modules, just grab a png with // the first frame and fake the video API. sinon.stub(ajax, 'getVideo').callsFake((urls, callback) => { - return request({url: urls[0], encoding: null}, (error, response, body) => { + return request({ url: urls[0], encoding: null }, (error, response, body) => { if (!error && response.statusCode >= 200 && response.statusCode < 300) { new PNG().parse(body, (err, png) => { if (err) return callback(err); diff --git a/test/unit/source/geojson_source.test.js b/test/unit/source/geojson_source.test.js index 8ee875411e2..71d6d4baabd 100644 --- a/test/unit/source/geojson_source.test.js +++ b/test/unit/source/geojson_source.test.js @@ -139,6 +139,17 @@ test('GeoJSONSource#update', (t) => { }, mockDispatcher).load(); }); + t.test('transforms url before making request', (t) => { + const mapStub = { + _transformRequest: (url) => { return { url }; } + }; + const transformSpy = t.spy(mapStub, '_transformRequest'); + const source = new GeoJSONSource('id', {data: 'https://example.com/data.geojson'}, mockDispatcher); + source.onAdd(mapStub); + t.ok(transformSpy.calledOnce); + t.equal(transformSpy.getCall(0).args[0], 'https://example.com/data.geojson'); + t.end(); + }); t.test('fires event when metadata loads', (t) => { const mockDispatcher = { send: function(message, args, callback) { @@ -202,9 +213,12 @@ test('GeoJSONSource#update', (t) => { }); test('GeoJSONSource#serialize', (t) => { - + const mapStub = { + _transformRequest: (url) => { return { url }; } + }; t.test('serialize source with inline data', (t) => { const source = new GeoJSONSource('id', {data: hawkHill}, mockDispatcher); + source.map = mapStub; source.load(); t.deepEqual(source.serialize(), { type: 'geojson', @@ -215,6 +229,7 @@ test('GeoJSONSource#serialize', (t) => { t.test('serialize source with url', (t) => { const source = new GeoJSONSource('id', {data: 'local://data.json'}, mockDispatcher); + source.map = mapStub; source.load(); t.deepEqual(source.serialize(), { type: 'geojson', @@ -225,6 +240,7 @@ test('GeoJSONSource#serialize', (t) => { t.test('serialize source with updated data', (t) => { const source = new GeoJSONSource('id', {data: {}}, mockDispatcher); + source.map = mapStub; source.load(); source.setData(hawkHill); t.deepEqual(source.serialize(), { diff --git a/test/unit/source/image_source.test.js b/test/unit/source/image_source.test.js new file mode 100644 index 00000000000..d4735e518b2 --- /dev/null +++ b/test/unit/source/image_source.test.js @@ -0,0 +1,96 @@ +'use strict'; + +const test = require('mapbox-gl-js-test').test; +const ImageSource = require('../../../src/source/image_source'); +const Evented = require('../../../src/util/evented'); +const Transform = require('../../../src/geo/transform'); +const util = require('../../../src/util/util'); +const ajax = require('../../../src/util/ajax'); + +function createSource(options) { + options = util.extend({ + coordinates: [[0, 0], [1, 0], [1, 1], [0, 1]] + }, options); + + const source = new ImageSource('id', options, { send: function() {} }, options.eventedParent); + return source; +} + +class StubMap extends Evented { + constructor() { + super(); + this.transform = new Transform(); + } + + _transformRequest(url) { + return { url }; + } +} + +test('ImageSource', (t) => { + + t.stub(ajax, 'getImage').callsFake((params, callback) => { callback(null, new ArrayBuffer(1)); }); + + t.test('constructor', (t) => { + const source = createSource({ url : '/image.png' }); + + t.equal(source.minzoom, 0); + t.equal(source.maxzoom, 22); + t.equal(source.tileSize, 512); + t.end(); + }); + + t.test('fires dataloading event', (t) => { + const source = createSource({ url : '/image.png' }); + source.on('dataloading', (e) => { + t.equal(e.dataType, 'source'); + t.end(); + }); + source.onAdd(new StubMap()); + }); + + t.test('transforms url request', (t) => { + const source = createSource({ url : '/image.png' }); + const map = new StubMap(); + const spy = t.spy(map, '_transformRequest'); + source.onAdd(map); + t.ok(spy.calledOnce); + t.equal(spy.getCall(0).args[0], '/image.png'); + t.equal(spy.getCall(0).args[1], 'Image'); + t.end(); + }); + + t.test('fires data event when content is loaded', (t) => { + const source = createSource({ url : '/image.png' }); + source.on('data', (e) => { + if (e.dataType === 'source' && e.sourceDataType === 'content') { + t.ok(typeof source.coord == 'object'); + t.end(); + } + }); + source.onAdd(new StubMap()); + }); + + t.test('fires data event when metadata is loaded', (t) => { + const source = createSource({ url : '/image.png' }); + source.on('data', (e) => { + if (e.dataType === 'source' && e.sourceDataType === 'metadata') { + t.end(); + } + }); + source.onAdd(new StubMap()); + }); + + t.test('serialize url and coordinates', (t) => { + const source = createSource({ url: '/image.png' }); + + const serialized = source.serialize(); + t.equal(serialized.type, 'image'); + t.equal(serialized.urls, '/image.png'); + t.deepEqual(serialized.coordinates, [[0, 0], [1, 0], [1, 1], [0, 1]]); + + t.end(); + }); + + t.end(); +}); diff --git a/test/unit/source/raster_tile_source.test.js b/test/unit/source/raster_tile_source.test.js index c19c34ac4d5..eaa3ae93812 100644 --- a/test/unit/source/raster_tile_source.test.js +++ b/test/unit/source/raster_tile_source.test.js @@ -2,12 +2,13 @@ const test = require('mapbox-gl-js-test').test; const RasterTileSource = require('../../../src/source/raster_tile_source'); const window = require('../../../src/util/window'); +const TileCoord = require('../../../src/source/tile_coord'); - -function createSource(options) { +function createSource(options, transformCallback) { const source = new RasterTileSource('id', options, { send: function() {} }, options.eventedParent); source.onAdd({ - transform: { angle: 0, pitch: 0, showCollisionBoxes: false } + transform: { angle: 0, pitch: 0, showCollisionBoxes: false }, + _transformRequest: transformCallback ? transformCallback : (url) => { return { url }; } }); source.on('error', (e) => { @@ -28,6 +29,26 @@ test('RasterTileSource', (t) => { callback(); }); + t.test('transforms request for TileJSON URL', (t) => { + window.server.respondWith('/source.json', JSON.stringify({ + minzoom: 0, + maxzoom: 22, + attribution: "Mapbox", + tiles: ["http://example.com/{z}/{x}/{y}.png"], + bounds: [-47, -7, -45, -5] + })); + const transformSpy = t.spy((url) => { + return { url }; + }); + + createSource({ url: "/source.json" }, transformSpy); + window.server.respond(); + + t.equal(transformSpy.getCall(0).args[0], '/source.json'); + t.equal(transformSpy.getCall(0).args[1], 'Source'); + t.end(); + }); + t.test('respects TileJSON.bounds', (t)=>{ const source = createSource({ minzoom: 0, @@ -72,6 +93,34 @@ test('RasterTileSource', (t) => { }); window.server.respond(); }); + + t.test('transforms tile urls before requesting', (t) => { + window.server.respondWith('/source.json', JSON.stringify({ + minzoom: 0, + maxzoom: 22, + attribution: "Mapbox", + tiles: ["http://example.com/{z}/{x}/{y}.png"], + bounds: [-47, -7, -45, -5] + })); + const source = createSource({ url: "/source.json" }); + const transformSpy = t.spy(source.map, '_transformRequest'); + source.on('data', (e) => { + if (e.sourceDataType === 'metadata') { + const tile = { + coord: new TileCoord(10, 5, 5, 0), + state: 'loading', + loadVectorData: function () {}, + setExpiryData: function() {} + }; + source.loadTile(tile, () => {}); + t.ok(transformSpy.calledOnce); + t.equal(transformSpy.getCall(0).args[0], 'http://example.com/10/5/5.png'); + t.equal(transformSpy.getCall(0).args[1], 'Tile'); + t.end(); + } + }); + window.server.respond(); + }); t.end(); }); diff --git a/test/unit/source/tile.test.js b/test/unit/source/tile.test.js index af2a42aeb44..c08cf4302ca 100644 --- a/test/unit/source/tile.test.js +++ b/test/unit/source/tile.test.js @@ -61,7 +61,7 @@ test('querySourceFeatures', (t) => { geojsonWrapper.name = '_geojsonTileLayer'; tile.rawTileData = vtpbf({ layers: { '_geojsonTileLayer': geojsonWrapper }}); result = []; - t.doesNotThrow(tile.querySourceFeatures(result)); + t.doesNotThrow(() => { tile.querySourceFeatures(result); }); t.equal(result.length, 0); t.end(); }); diff --git a/test/unit/source/vector_tile_source.test.js b/test/unit/source/vector_tile_source.test.js index 3cb8cc23645..be999c7598f 100644 --- a/test/unit/source/vector_tile_source.test.js +++ b/test/unit/source/vector_tile_source.test.js @@ -6,10 +6,11 @@ const TileCoord = require('../../../src/source/tile_coord'); const window = require('../../../src/util/window'); const Evented = require('../../../src/util/evented'); -function createSource(options) { +function createSource(options, transformCallback) { const source = new VectorTileSource('id', options, { send: function() {} }, options.eventedParent); source.onAdd({ - transform: { angle: 0, pitch: 0, cameraToCenterDistance: 1, cameraToTileDistance: () => { return 1; }, showCollisionBoxes: false } + transform: { angle: 0, pitch: 0, cameraToCenterDistance: 1, cameraToTileDistance: () => { return 1; }, showCollisionBoxes: false }, + _transformRequest: transformCallback ? transformCallback : (url) => { return { url }; } }); source.on('error', (e) => { @@ -68,6 +69,19 @@ test('VectorTileSource', (t) => { window.server.respond(); }); + t.test('transforms the request for TileJSON URL', (t) => { + window.server.respondWith('/source.json', JSON.stringify(require('../../fixtures/source'))); + const transformSpy = t.spy((url) => { + return { url }; + }); + + createSource({ url: "/source.json" }, transformSpy); + window.server.respond(); + t.equal(transformSpy.getCall(0).args[0], '/source.json'); + t.equal(transformSpy.getCall(0).args[1], 'Source'); + t.end(); + }); + t.test('fires event with metadata property', (t) => { window.server.respondWith('/source.json', JSON.stringify(require('../../fixtures/source'))); const source = createSource({ url: "/source.json" }); @@ -134,7 +148,7 @@ test('VectorTileSource', (t) => { source.dispatcher.send = function(type, params) { t.equal(type, 'loadTile'); - t.equal(expectedURL, params.url); + t.equal(expectedURL, params.request.url); t.end(); }; @@ -147,6 +161,30 @@ test('VectorTileSource', (t) => { testScheme('xyz', 'http://example.com/10/5/5.png'); testScheme('tms', 'http://example.com/10/5/1018.png'); + t.test('transforms tile urls before requesting', (t) => { + window.server.respondWith('/source.json', JSON.stringify(require('../../fixtures/source'))); + + const source = createSource({ url: "/source.json" }); + const transformSpy = t.spy(source.map, '_transformRequest'); + source.on('data', (e) => { + if (e.sourceDataType === 'metadata') { + const tile = { + coord: new TileCoord(10, 5, 5, 0), + state: 'loading', + loadVectorData: function () {}, + setExpiryData: function() {} + }; + source.loadTile(tile, () => {}); + t.ok(transformSpy.calledOnce); + t.equal(transformSpy.getCall(0).args[0], 'http://example.com/10/5/5.png'); + t.equal(transformSpy.getCall(0).args[1], 'Tile'); + t.end(); + } + }); + + window.server.respond(); + }); + t.test('reloads a loading tile properly', (t) => { const source = createSource({ tiles: ["http://example.com/{z}/{x}/{y}.png"] diff --git a/test/unit/source/vector_tile_worker_source.test.js b/test/unit/source/vector_tile_worker_source.test.js index fcbfd5f0462..e08c3dd6307 100644 --- a/test/unit/source/vector_tile_worker_source.test.js +++ b/test/unit/source/vector_tile_worker_source.test.js @@ -11,7 +11,7 @@ test('abortTile', (t) => { source.loadTile({ source: 'source', uid: 0, - url: 'http://localhost:2900/abort' + request: { url: 'http://localhost:2900/abort' } }, t.fail); source.abortTile({ diff --git a/test/unit/source/worker.test.js b/test/unit/source/worker.test.js index e3ebc0015a9..4e418c418b9 100644 --- a/test/unit/source/worker.test.js +++ b/test/unit/source/worker.test.js @@ -16,7 +16,7 @@ test('load tile', (t) => { type: 'vector', source: 'source', uid: 0, - url: '/error' // Sinon fake server gives 404 responses by default + request: { url: '/error' }// Sinon fake server gives 404 responses by default }, (err) => { t.ok(err); window.restore(); diff --git a/test/unit/style/style.test.js b/test/unit/style/style.test.js index 6ba2ab95f31..1aa99c0ceaf 100644 --- a/test/unit/style/style.test.js +++ b/test/unit/style/style.test.js @@ -40,6 +40,17 @@ function createGeoJSONSource() { }; } +class StubMap extends Evented { + constructor() { + super(); + this.transform = new Transform(); + } + + _transformRequest(url) { + return { url }; + } +} + test('Style', (t) => { t.afterEach((callback) => { window.restore(); @@ -106,6 +117,19 @@ test('Style', (t) => { window.server.respond(); }); + t.test('transforms style URL before request', (t) => { + window.useFakeXMLHttpRequest(); + // window.server.respondWith('/style.json', JSON.stringify(require('../../fixtures/style'))); + const map = new StubMap(); + const transformSpy = t.spy(map, '_transformRequest'); + new Style('/style.json', map); + // window.server.respond(); + t.ok(transformSpy.calledOnce); + t.equal(transformSpy.getCall(0).args[0], '/style.json'); + t.equal(transformSpy.getCall(0).args[1], 'Style'); + t.end(); + }); + t.test('creates sources', (t) => { const style = new Style(util.extend(createStyleJSON(), { "sources": { @@ -114,13 +138,31 @@ test('Style', (t) => { "tiles": [] } } - })); + }), new StubMap()); style.on('style.load', () => { t.ok(style.sourceCaches['mapbox'] instanceof SourceCache); t.end(); }); }); + t.test('transforms sprite json and image URLs before request', (t) => { + window.useFakeXMLHttpRequest(); + const map = new StubMap(); + const transformSpy = t.spy(map, '_transformRequest'); + const style = new Style(util.extend(createStyleJSON(), { + "sprite": "http://example.com/sprites/bright-v8" + }), map); + style.on('style.load', () => { + t.equal(transformSpy.callCount, 2); + t.equal(transformSpy.getCall(0).args[0], 'http://example.com/sprites/bright-v8.json'); + t.equal(transformSpy.getCall(0).args[1], 'SpriteJSON'); + t.equal(transformSpy.getCall(1).args[0], 'http://example.com/sprites/bright-v8.png'); + t.equal(transformSpy.getCall(1).args[1], 'SpriteImage'); + t.end(); + }); + + }); + t.test('validates the style by default', (t) => { const style = new Style(createStyleJSON({version: 'invalid'})); @@ -171,7 +213,7 @@ test('Style', (t) => { 'source': '-source-id-', 'source-layer': '-source-layer-' }] - })); + }), new StubMap()); style.on('style.load', () => { style.removeSource('-source-id-'); @@ -263,7 +305,7 @@ test('Style#update', (t) => { 'source-layer': 'source-layer', 'type': 'fill' }] - }); + }, new StubMap()); style.on('error', (error) => { t.error(error); }); @@ -298,7 +340,7 @@ test('Style#_resolve', (t) => { "source-layer": "source-layer", "type": "fill" }] - }); + }, new StubMap()); style.on('error', (error) => { t.error(error); }); @@ -326,7 +368,7 @@ test('Style#_resolve', (t) => { "type": "fill", "layout": {"visibility": "none"} }] - }); + }, new StubMap()); style.on('error', (event) => { t.error(event.error); }); @@ -383,7 +425,7 @@ test('Style#setState', (t) => { type: 'raster', url: '/tilejson.json' }; - const style = new Style(initial); + const style = new Style(initial, new StubMap()); style.on('style.load', () => { t.stub(style, 'removeSource').callsFake(() => t.fail('removeSource called')); t.stub(style, 'addSource').callsFake(() => t.fail('addSource called')); @@ -419,7 +461,7 @@ test('Style#setState', (t) => { test('Style#addSource', (t) => { t.test('throw before loaded', (t) => { - const style = new Style(createStyleJSON()), + const style = new Style(createStyleJSON(), new StubMap()), source = createSource(); t.throws(() => { style.addSource('source-id', source); @@ -430,7 +472,7 @@ test('Style#addSource', (t) => { }); t.test('throw if missing source type', (t) => { - const style = new Style(createStyleJSON()), + const style = new Style(createStyleJSON(), new StubMap()), source = createSource(); delete source.type; @@ -444,7 +486,7 @@ test('Style#addSource', (t) => { }); t.test('fires "data" event', (t) => { - const style = new Style(createStyleJSON()), + const style = new Style(createStyleJSON(), new StubMap()), source = createSource(); style.once('data', t.end); style.on('style.load', () => { @@ -454,7 +496,7 @@ test('Style#addSource', (t) => { }); t.test('throws on duplicates', (t) => { - const style = new Style(createStyleJSON()), + const style = new Style(createStyleJSON(), new StubMap()), source = createSource(); style.on('style.load', () => { style.addSource('source-id', source); @@ -466,7 +508,7 @@ test('Style#addSource', (t) => { }); t.test('emits on invalid source', (t) => { - const style = new Style(createStyleJSON()); + const style = new Style(createStyleJSON(), new StubMap()); style.on('style.load', () => { style.on('error', () => { t.notOk(style.sourceCaches['source-id']); @@ -488,7 +530,7 @@ test('Style#addSource', (t) => { id: 'background', type: 'background' }] - })); + }), new StubMap()); const source = createSource(); style.on('style.load', () => { @@ -523,7 +565,7 @@ test('Style#removeSource', (t) => { "tiles": [] } } - })); + }), new StubMap()); t.throws(() => { style.removeSource('source-id'); }, Error, /load/i); @@ -533,7 +575,7 @@ test('Style#removeSource', (t) => { }); t.test('fires "data" event', (t) => { - const style = new Style(createStyleJSON()), + const style = new Style(createStyleJSON(), new StubMap()), source = createSource(); style.once('data', t.end); style.on('style.load', () => { @@ -546,7 +588,7 @@ test('Style#removeSource', (t) => { t.test('clears tiles', (t) => { const style = new Style(createStyleJSON({ sources: {'source-id': createGeoJSONSource()} - })); + }), new StubMap()); style.on('style.load', () => { const sourceCache = style.sourceCaches['source-id']; @@ -560,7 +602,7 @@ test('Style#removeSource', (t) => { t.test('emits errors for an invalid style', (t) => { const stylesheet = createStyleJSON(); stylesheet.version = 'INVALID'; - const style = new Style(stylesheet); + const style = new Style(stylesheet, new StubMap()); style.on('error', (e) => { t.deepEqual(e.error.message, 'version: expected one of [8], INVALID found'); t.end(); @@ -568,7 +610,7 @@ test('Style#removeSource', (t) => { }); t.test('throws on non-existence', (t) => { - const style = new Style(createStyleJSON()); + const style = new Style(createStyleJSON(), new StubMap()); style.on('style.load', () => { t.throws(() => { style.removeSource('source-id'); @@ -578,7 +620,7 @@ test('Style#removeSource', (t) => { }); t.test('tears down source event forwarding', (t) => { - const style = new Style(createStyleJSON()); + const style = new Style(createStyleJSON(), new StubMap()); let source = createSource(); style.on('style.load', () => { @@ -604,7 +646,7 @@ test('Style#removeSource', (t) => { test('Style#addLayer', (t) => { t.test('throw before loaded', (t) => { - const style = new Style(createStyleJSON()), + const style = new Style(createStyleJSON(), new StubMap()), layer = {id: 'background', type: 'background'}; t.throws(() => { style.addLayer(layer); @@ -615,7 +657,7 @@ test('Style#addLayer', (t) => { }); t.test('sets up layer event forwarding', (t) => { - const style = new Style(createStyleJSON()); + const style = new Style(createStyleJSON(), new StubMap()); style.on('error', (e) => { t.deepEqual(e.layer, {id: 'background'}); @@ -638,7 +680,7 @@ test('Style#addLayer', (t) => { // At least one source must be added to trigger the load event dummy: { type: "vector", tiles: [] } } - })); + }), new StubMap()); style.on('style.load', () => { const source = createSource(); @@ -665,7 +707,7 @@ test('Style#addLayer', (t) => { }); t.test('emits error on invalid layer', (t) => { - const style = new Style(createStyleJSON()); + const style = new Style(createStyleJSON(), new StubMap()); style.on('style.load', () => { style.on('error', () => { t.notOk(style.getLayer('background')); @@ -689,7 +731,7 @@ test('Style#addLayer', (t) => { "tiles": [] } } - })); + }), new StubMap()); const layer = { "id": "symbol", "type": "symbol", @@ -722,7 +764,7 @@ test('Style#addLayer', (t) => { "source-layer": "boxmap", "filter": ["==", "id", 0] }] - })); + }), new StubMap()); const layer = { "id": "my-layer", @@ -758,7 +800,7 @@ test('Style#addLayer', (t) => { "source-layer": "boxmap", "filter": ["==", "id", 0] }] - })); + }), new StubMap()); const layer = { "id": "my-layer", @@ -779,7 +821,7 @@ test('Style#addLayer', (t) => { }); t.test('fires "data" event', (t) => { - const style = new Style(createStyleJSON()), + const style = new Style(createStyleJSON(), new StubMap()), layer = {id: 'background', type: 'background'}; style.once('data', t.end); @@ -791,7 +833,7 @@ test('Style#addLayer', (t) => { }); t.test('emits error on duplicates', (t) => { - const style = new Style(createStyleJSON()), + const style = new Style(createStyleJSON(), new StubMap()), layer = {id: 'background', type: 'background'}; style.on('error', (e) => { @@ -816,7 +858,7 @@ test('Style#addLayer', (t) => { id: 'b', type: 'background' }] - })), + }), new StubMap()), layer = {id: 'c', type: 'background'}; style.on('style.load', () => { @@ -835,7 +877,7 @@ test('Style#addLayer', (t) => { id: 'b', type: 'background' }] - })), + }), new StubMap()), layer = {id: 'c', type: 'background'}; style.on('style.load', () => { @@ -853,7 +895,7 @@ test('Style#addLayer', (t) => { data: { type: 'FeatureCollection', features: [] } } } - })); + }), new StubMap()); const layer = { id: 'dummy', @@ -879,7 +921,7 @@ test('Style#removeLayer', (t) => { t.test('throw before loaded', (t) => { const style = new Style(createStyleJSON({ "layers": [{id: 'background', type: 'background'}] - })); + }), new StubMap()); t.throws(() => { style.removeLayer('background'); }, Error, /load/i); @@ -889,7 +931,7 @@ test('Style#removeLayer', (t) => { }); t.test('fires "data" event', (t) => { - const style = new Style(createStyleJSON()), + const style = new Style(createStyleJSON(), new StubMap()), layer = {id: 'background', type: 'background'}; style.once('data', t.end); @@ -907,7 +949,7 @@ test('Style#removeLayer', (t) => { id: 'background', type: 'background' }] - })); + }), new StubMap()); style.on('error', () => { t.fail(); @@ -926,7 +968,7 @@ test('Style#removeLayer', (t) => { }); t.test('fires an error on non-existence', (t) => { - const style = new Style(createStyleJSON()); + const style = new Style(createStyleJSON(), new StubMap()); style.on('style.load', () => { style.on('error', ({ error }) => { @@ -1344,7 +1386,7 @@ test('Style#queryRenderedFeatures', (t) => { "something": "else" } }] - }); + }, new StubMap()); style.on('style.load', () => { style._applyClasses([]); diff --git a/test/unit/symbol/glyph_source.test.js b/test/unit/symbol/glyph_source.test.js index bed1485dfc0..93b3a9aeb06 100644 --- a/test/unit/symbol/glyph_source.test.js +++ b/test/unit/symbol/glyph_source.test.js @@ -1,6 +1,7 @@ 'use strict'; const test = require('mapbox-gl-js-test').test; +const ajax = require('../../../src/util/ajax'); const GlyphSource = require('../../../src/symbol/glyph_source'); const fs = require('fs'); @@ -11,7 +12,7 @@ const mockTinySDF = { function createSource(t, localIdeographFontFamily) { const aPBF = fs.readFileSync('./test/fixtures/0-255.pbf'); - const source = new GlyphSource("https://localhost/fonts/v1{fontstack}/{range}.pbf", localIdeographFontFamily); + const source = new GlyphSource("https://localhost/fonts/v1/{fontstack}/{range}.pbf", localIdeographFontFamily); t.stub(source, 'createTinySDF').returns(mockTinySDF); // It would be better to mock with FakeXMLHttpRequest, but the binary encoding // doesn't survive the mocking @@ -34,6 +35,18 @@ test('GlyphSource', (t) => { }); }); + t.test('transforms glyph URL before request', (t) => { + t.stub(ajax, 'getArrayBuffer').callsFake((url, cb) => cb()); + const transformSpy = t.stub().callsFake((url) => { return { url }; }); + const source = new GlyphSource("https://localhost/fonts/v1/{fontstack}/{range}.pbf", false, transformSpy); + + source.loadPBF("https://localhost/fonts/v1/Arial Unicode MS/0-255.pbf", () => { + t.ok(transformSpy.calledOnce); + t.equal(transformSpy.getCall(0).args[0], "https://localhost/fonts/v1/Arial Unicode MS/0-255.pbf"); + t.end(); + }); + }); + t.test('requests remote CJK PBF', (t) => { const source = createSource(t); source.getSimpleGlyphs("Arial Unicode MS", [0x5e73], 0, (err, glyphs, fontName) => { diff --git a/test/unit/util/ajax.test.js b/test/unit/util/ajax.test.js index f2cb8ee5707..1a322a329fa 100644 --- a/test/unit/util/ajax.test.js +++ b/test/unit/util/ajax.test.js @@ -19,7 +19,7 @@ test('ajax', (t) => { window.server.respondWith(request => { request.respond(200, {'Content-Type': 'image/png'}, ''); }); - ajax.getArrayBuffer('', (error) => { + ajax.getArrayBuffer({ url:'' }, (error) => { t.pass('called getArrayBuffer'); t.ok(error, 'should error when the status is 200 without content.'); t.end(); @@ -31,7 +31,7 @@ test('ajax', (t) => { window.server.respondWith(request => { request.respond(404); }); - ajax.getArrayBuffer('', (error) => { + ajax.getArrayBuffer({ url:'' }, (error) => { t.equal(error.status, 404); t.end(); }); @@ -42,7 +42,7 @@ test('ajax', (t) => { window.server.respondWith(request => { request.respond(200, {'Content-Type': 'application/json'}, '{"foo": "bar"}'); }); - ajax.getJSON('', (error, body) => { + ajax.getJSON({ url:'' }, (error, body) => { t.error(error); t.deepEqual(body, {foo: 'bar'}); t.end(); @@ -54,7 +54,7 @@ test('ajax', (t) => { window.server.respondWith(request => { request.respond(200, {'Content-Type': 'application/json'}, 'how do i even'); }); - ajax.getJSON('', (error) => { + ajax.getJSON({ url:'' }, (error) => { t.ok(error); t.end(); }); @@ -65,7 +65,7 @@ test('ajax', (t) => { window.server.respondWith(request => { request.respond(404); }); - ajax.getJSON('', (error) => { + ajax.getJSON({ url:'' }, (error) => { t.equal(error.status, 404); t.end(); });