Skip to content

Commit

Permalink
fix `The onreadystatechange property of XMLHttpRequest.prototype rede…
Browse files Browse the repository at this point in the history
…fined by Angular` (close DevExpress#1283)
  • Loading branch information
LavrovArtem committed Sep 4, 2017
1 parent e4a4096 commit 2cb0448
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 44 deletions.
74 changes: 32 additions & 42 deletions src/client/sandbox/xhr.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,16 @@ export default class XhrSandbox extends SandboxBase {
attach (window) {
super.attach(window);

const xhrSandbox = this;
const xmlHttpRequestProto = window.XMLHttpRequest.prototype;
const xhrSandbox = this;
const xmlHttpRequestProto = window.XMLHttpRequest.prototype;
const xhrConstructorString = nativeMethods.XMLHttpRequest.toString();

const emitXhrCompletedEvent = function () {
if (this.readyState === this.DONE) {
xhrSandbox.emit(xhrSandbox.XHR_COMPLETED_EVENT, { xhr: this });
nativeMethods.xhrRemoveEventListener.call(this, 'readystatechange', emitXhrCompletedEvent);
}
};

const syncCookieWithClient = function () {
if (this.readyState < this.HEADERS_RECEIVED)
Expand All @@ -73,6 +81,20 @@ export default class XhrSandbox extends SandboxBase {
nativeMethods.xhrRemoveEventListener.call(this, 'readystatechange', syncCookieWithClient);
};

const xhrConstructorWrapper = function () {
const xhr = new nativeMethods.XMLHttpRequest();

nativeMethods.xhrAddEventListener.call(xhr, 'readystatechange', emitXhrCompletedEvent);
nativeMethods.xhrAddEventListener.call(xhr, 'readystatechange', syncCookieWithClient);

return xhr;
};

window.XMLHttpRequest = xhrConstructorWrapper;
xhrConstructorWrapper.prototype = xmlHttpRequestProto;
xhrConstructorWrapper.toString = () => xhrConstructorString;
xmlHttpRequestProto.constructor = xhrConstructorWrapper;

xmlHttpRequestProto.abort = function () {
nativeMethods.xhrAbort.apply(this, arguments);
xhrSandbox.emit(xhrSandbox.XHR_ERROR_EVENT, {
Expand All @@ -94,7 +116,6 @@ export default class XhrSandbox extends SandboxBase {
if (typeof arguments[1] === 'string')
arguments[1] = getProxyUrl(arguments[1]);

nativeMethods.xhrAddEventListener.call(this, 'readystatechange', syncCookieWithClient);
nativeMethods.xhrOpen.apply(this, arguments);
};

Expand All @@ -103,55 +124,24 @@ export default class XhrSandbox extends SandboxBase {

xhrSandbox.emit(xhrSandbox.BEFORE_XHR_SEND_EVENT, { xhr });

const orscHandler = () => {
if (this.readyState === 4)
xhrSandbox.emit(xhrSandbox.XHR_COMPLETED_EVENT, { xhr });
};

// NOTE: If we're using the sync mode or if the response is in cache,
// we need to raise the callback manually.
if (this.readyState === 4)
orscHandler();
else {
// NOTE: Get out of the current execution tick and then proxy onreadystatechange,
// because jQuery assigns a handler after the send() method was called.
nativeMethods.setTimeout.call(xhrSandbox.window, () => {
// NOTE: If the state is already changed, we just call the handler without proxying
// onreadystatechange.
if (this.readyState === 4)
orscHandler();

else if (typeof this.onreadystatechange === 'function') {
const originalHandler = this.onreadystatechange;

this.onreadystatechange = progress => {
orscHandler();
originalHandler.call(this, progress);
};
}
else
this.addEventListener('readystatechange', orscHandler, false);
}, 0);
}

// NOTE: Add the XHR request mark, so that a proxy can recognize a request as a XHR request. As all
// requests are passed to the proxy, we need to perform Same Origin Policy compliance checks on the
// server side. So, we pass the CORS support flag to inform the proxy that it can analyze the
// Access-Control_Allow_Origin flag and skip "preflight" requests.
nativeMethods.xhrSetRequestHeader.call(this, XHR_HEADERS.requestMarker, 'true');

nativeMethods.xhrSetRequestHeader.call(this, XHR_HEADERS.origin, getOriginHeader());
nativeMethods.xhrSetRequestHeader.call(xhr, XHR_HEADERS.requestMarker, 'true');
nativeMethods.xhrSetRequestHeader.call(xhr, XHR_HEADERS.origin, getOriginHeader());

if (xhrSandbox.corsSupported)
nativeMethods.xhrSetRequestHeader.call(this, XHR_HEADERS.corsSupported, 'true');
nativeMethods.xhrSetRequestHeader.call(xhr, XHR_HEADERS.corsSupported, 'true');

if (this.withCredentials)
nativeMethods.xhrSetRequestHeader.call(this, XHR_HEADERS.withCredentials, 'true');
if (xhr.withCredentials)
nativeMethods.xhrSetRequestHeader.call(xhr, XHR_HEADERS.withCredentials, 'true');

nativeMethods.xhrSend.apply(this, arguments);
nativeMethods.xhrSend.apply(xhr, arguments);

// NOTE: For xhr with the sync mode
syncCookieWithClient.call(this);
emitXhrCompletedEvent.call(xhr);
syncCookieWithClient.call(xhr);
};

xmlHttpRequestProto.getResponseHeader = function (name) {
Expand Down
31 changes: 29 additions & 2 deletions test/client/fixtures/sandbox/xhr-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var settings = hammerhead.get('./settings');
var iframeSandbox = hammerhead.sandbox.iframe;
var browserUtils = hammerhead.utils.browser;
var nativeMethods = hammerhead.nativeMethods;
var xhrSandbox = hammerhead.sandbox.xhr;

QUnit.testStart(function () {
iframeSandbox.on(iframeSandbox.RUN_TASK_SCRIPT_EVENT, initIframeTestHandler);
Expand Down Expand Up @@ -43,13 +44,16 @@ test('redirect requests to proxy', function () {
});

test('createNativeXHR', function () {
window.XMLHttpRequest = function () {};
var storedXMLHttpRequest = window.XMLHttpRequest;

window.XMLHttpRequest = function () {
};

var xhr = XhrSandbox.createNativeXHR();

ok(xhr instanceof nativeMethods.XMLHttpRequest);

window.XMLHttpRequest = nativeMethods.XMLHttpRequest;
window.XMLHttpRequest = storedXMLHttpRequest;

var isWrappedFunctionRE = /return 'function is wrapped'/;

Expand Down Expand Up @@ -201,3 +205,26 @@ asyncTest('authorization headers by client should be processed (GH-1016)', funct
});
xhr.send();
});

asyncTest('our internal the onreadystatechange handler must be first (GH-1283)', function () {
var xhr = new XMLHttpRequest();
var timeout = null;
var testDone = function (eventObj) {
clearTimeout(timeout);
ok(!!eventObj);
start();
};

var readyStateChangeHandler = function (e) {
if (this.readyState === this.DONE)
e.stopImmediatePropagation();
};

xhr.addEventListener('readystatechange', readyStateChangeHandler, true);
xhr.onreadystatechange = readyStateChangeHandler;
xhrSandbox.on(xhrSandbox.XHR_COMPLETED_EVENT, testDone);
timeout = setTimeout(testDone, 2000);

xhr.open('GET', '/xhr-test/', true);
xhr.send();
});

0 comments on commit 2cb0448

Please sign in to comment.