Skip to content

Commit

Permalink
fix(Observable/Ajax): mount properties to origin readystatechange fn (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn authored and jayphelps committed Oct 24, 2016
1 parent f94ceb9 commit 76a9abb
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 16 deletions.
6 changes: 6 additions & 0 deletions spec/helpers/ajax-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ export class MockXMLHttpRequest {
requestHeaders: any = {};
withCredentials: boolean = false;

onreadystatechange: (e: ProgressEvent) => any;
onerror: (e: ErrorEvent) => any;
onprogress: (e: ProgressEvent) => any;
ontimeout: (e: ProgressEvent) => any;
upload: XMLHttpRequestUpload;

constructor() {
this.previousRequest = MockXMLHttpRequest.recentRequest;
MockXMLHttpRequest.recentRequest = this;
Expand Down
162 changes: 162 additions & 0 deletions spec/observables/dom/ajax-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,4 +598,166 @@ describe('Observable.ajax', () => {
});

});

it('should work fine when XMLHttpRequest onreadystatechange property is monkey patched', function() {
Object.defineProperty(root.XMLHttpRequest.prototype, 'onreadystatechange', {
set: function (fn: (e: ProgressEvent) => any) {
const wrapFn = (ev: ProgressEvent) => {
const result = fn.call(this, ev);
if (result === false) {
ev.preventDefault();
}
};
this['_onreadystatechange'] = wrapFn;
},
get() {
return this['_onreadystatechange'];
},
configurable: true
});

Rx.Observable.ajax({
url: '/flibbertyJibbet'
})
.subscribe();

const request = MockXMLHttpRequest.mostRecent;
expect(() => {
request.onreadystatechange((<any>'onreadystatechange'));
}).not.throw();

delete root.XMLHttpRequest.prototype.onreadystatechange;
});

it('should work fine when XMLHttpRequest ontimeout property is monkey patched', function() {
Object.defineProperty(root.XMLHttpRequest.prototype, 'ontimeout', {
set: function (fn: (e: ProgressEvent) => any) {
const wrapFn = (ev: ProgressEvent) => {
const result = fn.call(this, ev);
if (result === false) {
ev.preventDefault();
}
};
this['_ontimeout'] = wrapFn;
},
get() {
return this['_ontimeout'];
},
configurable: true
});

const ajaxRequest = {
url: '/flibbertyJibbet'
};

Rx.Observable.ajax(ajaxRequest)
.subscribe();

const request = MockXMLHttpRequest.mostRecent;
try {
request.ontimeout((<any>'ontimeout'));
} catch (e) {
expect(e.message).to.equal(new Rx.AjaxTimeoutError((<any>request), ajaxRequest).message);
}
delete root.XMLHttpRequest.prototype.ontimeout;
});

it('should work fine when XMLHttpRequest onprogress property is monkey patched', function() {
Object.defineProperty(root.XMLHttpRequest.prototype, 'onprogress', {
set: function (fn: (e: ProgressEvent) => any) {
const wrapFn = (ev: ProgressEvent) => {
const result = fn.call(this, ev);
if (result === false) {
ev.preventDefault();
}
};
this['_onprogress'] = wrapFn;
},
get() {
return this['_onprogress'];
},
configurable: true
});

Object.defineProperty(root.XMLHttpRequest.prototype, 'upload', {
get() {
return true;
},
configurable: true
});

// mock for onprogress
root.XDomainRequest = true;

Rx.Observable.ajax({
url: '/flibbertyJibbet',
progressSubscriber: (<any>{
next: () => {
// noop
},
error: () => {
// noop
},
complete: () => {
// noop
}
})
})
.subscribe();

const request = MockXMLHttpRequest.mostRecent;

expect(() => {
request.onprogress((<any>'onprogress'));
}).not.throw();

delete root.XMLHttpRequest.prototype.onprogress;
delete root.XMLHttpRequest.prototype.upload;
delete root.XDomainRequest;
});

it('should work fine when XMLHttpRequest onerror property is monkey patched', function() {
Object.defineProperty(root.XMLHttpRequest.prototype, 'onerror', {
set: function (fn: (e: ProgressEvent) => any) {
const wrapFn = (ev: ProgressEvent) => {
const result = fn.call(this, ev);
if (result === false) {
ev.preventDefault();
}
};
this['_onerror'] = wrapFn;
},
get() {
return this['_onerror'];
},
configurable: true
});

Object.defineProperty(root.XMLHttpRequest.prototype, 'upload', {
get() {
return true;
},
configurable: true
});

// mock for onprogress
root.XDomainRequest = true;

Rx.Observable.ajax({
url: '/flibbertyJibbet'
})
.subscribe();

const request = MockXMLHttpRequest.mostRecent;

try {
request.onerror((<any>'onerror'));
} catch (e) {
expect(e.message).to.equal('ajax error');
}

delete root.XMLHttpRequest.prototype.onerror;
delete root.XMLHttpRequest.prototype.upload;
delete root.XDomainRequest;
});
});
36 changes: 20 additions & 16 deletions src/observable/dom/AjaxObservable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,39 +293,42 @@ export class AjaxSubscriber<T> extends Subscriber<Event> {
private setupEvents(xhr: XMLHttpRequest, request: AjaxRequest) {
const progressSubscriber = request.progressSubscriber;

xhr.ontimeout = function xhrTimeout(e) {
function xhrTimeout(e: ProgressEvent) {
const {subscriber, progressSubscriber, request } = (<any>xhrTimeout);
if (progressSubscriber) {
progressSubscriber.error(e);
}
subscriber.error(new AjaxTimeoutError(this, request)); //TODO: Make betterer.
};
(<any>xhr.ontimeout).request = request;
(<any>xhr.ontimeout).subscriber = this;
(<any>xhr.ontimeout).progressSubscriber = progressSubscriber;

xhr.ontimeout = xhrTimeout;
(<any>xhrTimeout).request = request;
(<any>xhrTimeout).subscriber = this;
(<any>xhrTimeout).progressSubscriber = progressSubscriber;
if (xhr.upload && 'withCredentials' in xhr && root.XDomainRequest) {
if (progressSubscriber) {
xhr.onprogress = function xhrProgress(e) {
let xhrProgress: (e: ProgressEvent) => void;
xhrProgress = function(e: ProgressEvent) {
const { progressSubscriber } = (<any>xhrProgress);
progressSubscriber.next(e);
};
(<any>xhr.onprogress).progressSubscriber = progressSubscriber;
xhr.onprogress = xhrProgress;
(<any>xhrProgress).progressSubscriber = progressSubscriber;
}

xhr.onerror = function xhrError(e) {
let xhrError: (e: ErrorEvent) => void;
xhrError = function(e: ErrorEvent) {
const { progressSubscriber, subscriber, request } = (<any>xhrError);
if (progressSubscriber) {
progressSubscriber.error(e);
}
subscriber.error(new AjaxError('ajax error', this, request));
};
(<any>xhr.onerror).request = request;
(<any>xhr.onerror).subscriber = this;
(<any>xhr.onerror).progressSubscriber = progressSubscriber;
xhr.onerror = xhrError;
(<any>xhrError).request = request;
(<any>xhrError).subscriber = this;
(<any>xhrError).progressSubscriber = progressSubscriber;
}

xhr.onreadystatechange = function xhrReadyStateChange(e) {
function xhrReadyStateChange(e: ProgressEvent) {
const { subscriber, progressSubscriber, request } = (<any>xhrReadyStateChange);
if (this.readyState === 4) {
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
Expand Down Expand Up @@ -354,9 +357,10 @@ export class AjaxSubscriber<T> extends Subscriber<Event> {
}
}
};
(<any>xhr.onreadystatechange).subscriber = this;
(<any>xhr.onreadystatechange).progressSubscriber = progressSubscriber;
(<any>xhr.onreadystatechange).request = request;
xhr.onreadystatechange = xhrReadyStateChange;
(<any>xhrReadyStateChange).subscriber = this;
(<any>xhrReadyStateChange).progressSubscriber = progressSubscriber;
(<any>xhrReadyStateChange).request = request;
}

unsubscribe() {
Expand Down

0 comments on commit 76a9abb

Please sign in to comment.