Skip to content

Commit

Permalink
perform XHR in the main context rather than the worker's to preserve …
Browse files Browse the repository at this point in the history
…referrer information

IE11 doesn't send the correct referrer for XHRs in a web worker. Instead, we're now doing the request from the main context and transfering the result back to the worker. Most modern browsers will continue to use the Fetch API and will not be affected by this change.
  • Loading branch information
kkaefer committed Jan 25, 2019
1 parent a4b8281 commit 23a17ff
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 50 deletions.
7 changes: 6 additions & 1 deletion src/style/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import GlyphManager from '../render/glyph_manager';
import Light from './light';
import LineAtlas from '../render/line_atlas';
import { pick, clone, extend, deepEqual, filterObject, mapObject } from '../util/util';
import { getJSON, getReferrer, ResourceType } from '../util/ajax';
import { getJSON, getReferrer, makeRequest, ResourceType } from '../util/ajax';
import { isMapboxURL, normalizeStyleURL } from '../util/mapbox';
import browser from '../util/browser';
import Dispatcher from '../util/dispatcher';
Expand Down Expand Up @@ -51,6 +51,7 @@ import type {Callback} from '../types/callback';
import type EvaluationParameters from './evaluation_parameters';
import type {Placement} from '../symbol/placement';
import type {Cancelable} from '../types/cancelable';
import type {RequestParameters, ResponseCallback} from '../util/ajax';
import type {GeoJSON} from '@mapbox/geojson-types';
import type {
LayerSpecification,
Expand Down Expand Up @@ -1213,6 +1214,10 @@ class Style extends Evented {
getGlyphs(mapId: string, params: {stacks: {[string]: Array<number>}}, callback: Callback<{[string]: {[number]: ?StyleGlyph}}>) {
this.glyphManager.getGlyphs(params.stacks, callback);
}

getResource(mapId: string, params: RequestParameters, callback: ResponseCallback<any>): Cancelable {
return makeRequest(params, callback);
}
}

Style.getSourceType = getSourceType;
Expand Down
25 changes: 22 additions & 3 deletions src/util/actor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { bindAll } from './util';
import { serialize, deserialize } from './web_worker_transfer';

import type {Transferable} from '../types/transferable';
import type {Cancelable} from '../types/cancelable';

/**
* An implementation of the [Actor design pattern](http://en.wikipedia.org/wiki/Actor_model)
Expand Down Expand Up @@ -42,7 +43,7 @@ class Actor {
* @param targetMapId A particular mapId to which to send this message.
* @private
*/
send(type: string, data: mixed, callback: ?Function, targetMapId: ?string) {
send(type: string, data: mixed, callback: ?Function, targetMapId: ?string): ?Cancelable {
const id = callback ? `${this.mapId}:${this.callbackID++}` : null;
if (callback) this.callbacks[id] = callback;
const buffers: Array<Transferable> = [];
Expand All @@ -53,6 +54,16 @@ class Actor {
id: String(id),
data: serialize(data, buffers)
}, buffers);
if (callback) {
return {
cancel: () => this.target.postMessage({
targetMapId,
sourceMapId: this.mapId,
type: '<cancel>',
id: String(id)
})
};
}
}

receive(message: Object) {
Expand All @@ -64,6 +75,7 @@ class Actor {
return;

const done = (err, data) => {
delete this.callbacks[id];
const buffers: Array<Transferable> = [];
this.target.postMessage({
sourceMapId: this.mapId,
Expand All @@ -74,7 +86,7 @@ class Actor {
}, buffers);
};

if (data.type === '<response>') {
if (data.type === '<response>' || data.type === '<cancel>') {
callback = this.callbacks[data.id];
delete this.callbacks[data.id];
if (callback && data.error) {
Expand All @@ -84,7 +96,14 @@ class Actor {
}
} else if (typeof data.id !== 'undefined' && this.parent[data.type]) {
// data.type == 'loadTile', 'removeTile', etc.
this.parent[data.type](data.sourceMapId, deserialize(data.data), done);
// Add a placeholder so that we can discover when the done callback was called already.
this.callbacks[data.id] = null;
const cancelable = this.parent[data.type](data.sourceMapId, deserialize(data.data), done);
if (cancelable && this.callbacks[data.id] === null) {
// Only add the cancelable callback if the done callback wasn't already called.
// Otherwise we will never be able to delete it.
this.callbacks[data.id] = cancelable;
}
} else if (typeof data.id !== 'undefined' && this.parent.getWorkerSource) {
// data.type == sourcetype.method
const keys = data.type.split('.');
Expand Down
21 changes: 16 additions & 5 deletions src/util/ajax.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,18 @@ class AJAXError extends Error {
}
}

function isWorker() {
return typeof WorkerGlobalScope !== 'undefined' && typeof self !== 'undefined' &&
self instanceof WorkerGlobalScope && self.worker;
}

// Ensure that we're sending the correct referrer from blob URL worker bundles.
// For files loaded from the local file system, `location.origin` will be set
// to the string(!) "null" (Firefox), or "file://" (Chrome, Safari, Edge, IE),
// and we will set an empty referrer. Otherwise, we're using the document's URL.
/* global self, WorkerGlobalScope */
export const getReferrer = typeof WorkerGlobalScope !== 'undefined' &&
typeof self !== 'undefined' &&
self instanceof WorkerGlobalScope ?
() => self.worker && self.worker.referrer :
export const getReferrer = isWorker() ?
() => self.worker.referrer :
() => {
const origin = window.location.origin;
if (origin && origin !== 'null' && origin !== 'file://') {
Expand Down Expand Up @@ -158,7 +161,15 @@ function makeXMLHttpRequest(requestParameters: RequestParameters, callback: Resp
return { cancel: () => xhr.abort() };
}

const makeRequest = window.fetch && window.Request && window.AbortController ? makeFetchRequest : makeXMLHttpRequest;
export const makeRequest = function(requestParameters: RequestParameters, callback: ResponseCallback<any>): Cancelable {
if (window.fetch && window.Request && window.AbortController && !/^file:/.test(requestParameters.url)) {
return makeFetchRequest(requestParameters, callback);
} else if (isWorker() && self.worker.actor) {
return self.worker.actor.send('getResource', requestParameters, callback);
} else {
return makeXMLHttpRequest(requestParameters, callback);
}
};

export const getJSON = function(requestParameters: RequestParameters, callback: ResponseCallback<Object>): Cancelable {
return makeRequest(extend(requestParameters, { type: 'json' }), callback);
Expand Down
74 changes: 33 additions & 41 deletions test/ajax_stubs.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,43 +28,50 @@ if (typeof Object.freeze == 'function') {
Object.freeze(ResourceType);
}

function cached(data, callback) {
setImmediate(() => {
callback(null, data);
});
}

export const getReferrer = () => undefined;

export const getJSON = function({ url }, callback) {
if (cache[url]) return cached(cache[url], callback);
return request(url, (error, response, body) => {
export const makeRequest = function({ url, encoding, transform }, callback) {
if (cache[url]) {
return transform(cache[url], callback);
}
return request({ url, encoding }, (error, response, data) => {
if (!error && response.statusCode >= 200 && response.statusCode < 300) {
let data;
try {
data = JSON.parse(body);
} catch (err) {
return callback(err);
}
cache[url] = data;
callback(null, data);
transform(data, callback);
} else {
callback(error || new Error(response.statusCode));
}
});
};

const parseJSON = (data, callback) => {
let result;
try {
result = JSON.parse(data);
return setImmediate(callback, null, result);
} catch (err) {
return setImmediate(callback, err);
}
};

const parseArrayBuffer = (data, callback) => {
return setImmediate(callback, null, data);
};

const parsePNG = (data, callback) => {
return new PNG().parse(data, callback);
};

export const getJSON = function({ url }, callback) {
return makeRequest({ url, encoding: 'utf8', transform: parseJSON }, callback);
};

export const getArrayBuffer = function({ url }, callback) {
if (cache[url]) return cached(cache[url], callback);
return request({ url, encoding: null }, (error, response, body) => {
if (!error && response.statusCode >= 200 && response.statusCode < 300) {
cache[url] = body;
callback(null, body);
} else {
if (!error) error = { status: +response.statusCode };
callback(error);
}
});
return makeRequest({ url, encoding: null, transform: parseArrayBuffer }, callback);
};

export const getImage = function({ url }, callback) {
return makeRequest({ url, encoding: null, transform: parsePNG }, callback);
};

export const postData = function({ url, body }, callback) {
Expand All @@ -77,21 +84,6 @@ export const postData = function({ url, body }, callback) {
});
};

export const getImage = function({ url }, callback) {
if (cache[url]) return cached(cache[url], callback);
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);
cache[url] = png;
callback(null, png);
});
} else {
callback(error || {status: response.statusCode});
}
});
};

browser.getImageData = function({width, height, data}) {
return {width, height, data: new Uint8Array(data)};
};
Expand Down

0 comments on commit 23a17ff

Please sign in to comment.