diff --git a/src/patch/xmlhttprequest.js b/src/patch/xmlhttprequest.js index 599866a..99a6d35 100644 --- a/src/patch/xmlhttprequest.js +++ b/src/patch/xmlhttprequest.js @@ -27,6 +27,7 @@ const Xhook = function () { let transiting = undefined; let response = undefined; var currentState = 0; + let lastProgress = null; //========================== // Private API @@ -97,11 +98,20 @@ const Xhook = function () { } }; + const createZeroProgress = function () { + return { + lengthComputable: false, + loaded: 0, + total: 0, + }; + }; + const emitFinal = function () { + const finalEvent = lastProgress ? lastProgress : createZeroProgress(); if (!hasError) { - facade.dispatchEvent("load", {}); + facade.dispatchEvent("load", finalEvent); } - facade.dispatchEvent("loadend", {}); + facade.dispatchEvent("loadend", finalEvent); if (hasError) { facade.readyState = 0; } @@ -111,11 +121,6 @@ const Xhook = function () { const emitReadyState = function (n) { while (n > currentState && currentState < 4) { facade.readyState = ++currentState; - // make fake events for libraries that actually check the type on - // the event object - if (currentState === 1) { - facade.dispatchEvent("loadstart", {}); - } if (currentState === 2) { writeHead(); } @@ -197,6 +202,11 @@ const Xhook = function () { facade.addEventListener("abort", hasErrorHandler); // progress means we're current downloading... facade.addEventListener("progress", function (event) { + lastProgress = { + lengthComputable: event.lengthComputable, + loaded: event.loaded, + total: event.total, + }; if (currentState < 3) { setReadyState(3); } else if (xhr.readyState <= 3) { @@ -222,6 +232,7 @@ const Xhook = function () { currentState = 0; hasError = false; transiting = false; + lastProgress = null; //reset request request.headers = {}; request.headerNames = {}; @@ -287,6 +298,10 @@ const Xhook = function () { xhr.setRequestHeader(header, value); } } + + //dispatch loadstart just before xhr.send() to simulate native XHR. + facade.dispatchEvent("loadstart", createZeroProgress()); + //real send! xhr.send(request.body); }; diff --git a/tests/load-events-props.spec.ts b/tests/load-events-props.spec.ts new file mode 100644 index 0000000..30f2439 --- /dev/null +++ b/tests/load-events-props.spec.ts @@ -0,0 +1,47 @@ +import { test, expect } from "@playwright/test"; + +test("loadstart/load/loadend events should contain the same properties as ProgressEvent interface", async ({ + page, +}) => { + await page.goto("http://127.0.0.1:8080/example/common.html"); + const getProperties = async (method: string) => { + return page.evaluate(async method => { + const xhr = new XMLHttpRequest(); + xhr.open(method, "example1.txt"); + const log = {}; + ["loadstart", "load", "loadend"].forEach(type => { + xhr.addEventListener(type, (e: any) => { + log[type] = { + lengthComputable: e.lengthComputable, + loaded: e.loaded, + total: e.total, + }; + }); + }); + return await new Promise((resolve, reject) => { + xhr.addEventListener("loadend", () => resolve(log)); + xhr.addEventListener("error", () => reject(new Error("XHR failed"))); + xhr.send(); + }); + }, method); + }; + + const events: any = await getProperties("GET"); + ["loadstart", "load", "loadend"].forEach(type => { + expect(events[type]).toMatchObject({ + lengthComputable: expect.any(Boolean), + loaded: expect.any(Number), + total: expect.any(Number), + }); + }); + expect(events.loadstart.loaded).toBe(0); + expect(events.loadstart.total).toBe(0); + expect(events.load.loaded).toBe(events.loadend.loaded); + expect(events.load.total).toBe(events.loadend.total); + expect(events.loadend.loaded).toBeGreaterThan(0); + expect(events.loadend.total).toBeGreaterThanOrEqual(0); + + const events2: any = await getProperties("HEAD"); + expect(events2.loadend.loaded).toBe(0); + expect(events2.loadend.total).toBe(0); +});