fix: wrap `onRequestFinished` to use promises
Fixes #249. This updates `browser.devtools.network.onRequestFinished` to emit an object with a promisified `getContent()` property. This brings the polyfill implementation in line with Firefox's implementation, although MDN documentation is still inaccurate at the moment. Also updates some out of date documentation with `makeCallback()` and `wrapAsyncFunction()`.
This commit is contained in:
parent
3ba72c96a9
commit
716c90bca4
|
@ -77,13 +77,17 @@ if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.
|
||||||
* promise.
|
* promise.
|
||||||
* @param {function} promise.resolve
|
* @param {function} promise.resolve
|
||||||
* The promise's resolution function.
|
* The promise's resolution function.
|
||||||
* @param {function} promise.rejection
|
* @param {function} promise.reject
|
||||||
* The promise's rejection function.
|
* The promise's rejection function.
|
||||||
* @param {object} metadata
|
* @param {object} metadata
|
||||||
* Metadata about the wrapped method which has created the callback.
|
* Metadata about the wrapped method which has created the callback.
|
||||||
* @param {integer} metadata.maxResolvedArgs
|
* @param {boolean} metadata.singleCallbackArg
|
||||||
* The maximum number of arguments which may be passed to the
|
* Whether or not the promise is resolved with only the first
|
||||||
* callback created by the wrapped async function.
|
* argument of the callback, alternatively an array of all the
|
||||||
|
* callback arguments is resolved. By default, if the callback
|
||||||
|
* function is invoked with only a single argument, that will be
|
||||||
|
* resolved to the promise, while all arguments will be resolved as
|
||||||
|
* an array if multiple are given.
|
||||||
*
|
*
|
||||||
* @returns {function}
|
* @returns {function}
|
||||||
* The generated callback function.
|
* The generated callback function.
|
||||||
|
@ -118,9 +122,13 @@ if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.
|
||||||
* The maximum number of arguments which may be passed to the
|
* The maximum number of arguments which may be passed to the
|
||||||
* function. If called with more than this number of arguments, the
|
* function. If called with more than this number of arguments, the
|
||||||
* wrapper will raise an exception.
|
* wrapper will raise an exception.
|
||||||
* @param {integer} metadata.maxResolvedArgs
|
* @param {boolean} metadata.singleCallbackArg
|
||||||
* The maximum number of arguments which may be passed to the
|
* Whether or not the promise is resolved with only the first
|
||||||
* callback created by the wrapped async function.
|
* argument of the callback, alternatively an array of all the
|
||||||
|
* callback arguments is resolved. By default, if the callback
|
||||||
|
* function is invoked with only a single argument, that will be
|
||||||
|
* resolved to the promise, while all arguments will be resolved as
|
||||||
|
* an array if multiple are given.
|
||||||
*
|
*
|
||||||
* @returns {function(object, ...*)}
|
* @returns {function(object, ...*)}
|
||||||
* The generated wrapper function.
|
* The generated wrapper function.
|
||||||
|
@ -345,6 +353,30 @@ if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onRequestFinishedWrappers = new DefaultWeakMap(listener => {
|
||||||
|
if (typeof listener !== "function") {
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps an onRequestFinished listener function so that it will return a
|
||||||
|
* `getContent()` property which returns a `Promise` rather than using a
|
||||||
|
* callback API.
|
||||||
|
*
|
||||||
|
* @param {object} req
|
||||||
|
* The HAR entry object representing the network request.
|
||||||
|
*/
|
||||||
|
return function onRequestFinished(req) {
|
||||||
|
const wrappedReq = wrapObject(req, {} /* wrappers */, {
|
||||||
|
getContent: {
|
||||||
|
minArgs: 0,
|
||||||
|
maxArgs: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
listener(wrappedReq);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Keep track if the deprecation warning has been logged at least once.
|
// Keep track if the deprecation warning has been logged at least once.
|
||||||
let loggedSendResponseDeprecationWarning = false;
|
let loggedSendResponseDeprecationWarning = false;
|
||||||
|
|
||||||
|
@ -480,6 +512,11 @@ if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.
|
||||||
};
|
};
|
||||||
|
|
||||||
const staticWrappers = {
|
const staticWrappers = {
|
||||||
|
devtools: {
|
||||||
|
network: {
|
||||||
|
onRequestFinished: wrapEvent(onRequestFinishedWrappers),
|
||||||
|
},
|
||||||
|
},
|
||||||
runtime: {
|
runtime: {
|
||||||
onMessage: wrapEvent(onMessageWrappers),
|
onMessage: wrapEvent(onMessageWrappers),
|
||||||
onMessageExternal: wrapEvent(onMessageWrappers),
|
onMessageExternal: wrapEvent(onMessageWrappers),
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {deepEqual, equal, ok} = require("chai").assert;
|
||||||
|
const sinon = require("sinon");
|
||||||
|
|
||||||
|
const {setupTestDOMWindow} = require("./setup");
|
||||||
|
|
||||||
|
describe("browser-polyfill", () => {
|
||||||
|
describe("wrapped devtools.network.onRequestFinished listener", () => {
|
||||||
|
it("does not wrap the listener if it is not a function", () => {
|
||||||
|
const fakeChrome = {
|
||||||
|
devtools: {
|
||||||
|
network: {
|
||||||
|
onRequestFinished: {
|
||||||
|
addListener: sinon.spy(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||||
|
const fakeNonFunctionListener = {fake: "non function listener"};
|
||||||
|
|
||||||
|
const browserOnRequestFinished = window.browser.devtools.network.onRequestFinished;
|
||||||
|
browserOnRequestFinished.addListener(fakeNonFunctionListener);
|
||||||
|
|
||||||
|
const fakeChromeOnRequestFinished = fakeChrome.devtools.network.onRequestFinished;
|
||||||
|
deepEqual(
|
||||||
|
fakeChromeOnRequestFinished.addListener.firstCall.args[0],
|
||||||
|
fakeNonFunctionListener,
|
||||||
|
"The non-function listener has not been wrapped"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("promisifies the result", () => {
|
||||||
|
const fakeChrome = {
|
||||||
|
devtools: {
|
||||||
|
network: {
|
||||||
|
onRequestFinished: {
|
||||||
|
addListener: sinon.spy(),
|
||||||
|
hasListener: sinon.stub(),
|
||||||
|
removeListener: sinon.spy(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||||
|
const listener = sinon.spy();
|
||||||
|
|
||||||
|
const browserOnRequestFinished = window.browser.devtools.network.onRequestFinished;
|
||||||
|
browserOnRequestFinished.addListener(listener);
|
||||||
|
|
||||||
|
const fakeChromeOnRequestFinished = fakeChrome.devtools.network.onRequestFinished;
|
||||||
|
ok(fakeChromeOnRequestFinished.addListener.calledOnce,
|
||||||
|
"devtools.network.onRequestFinished.addListener has been called once");
|
||||||
|
|
||||||
|
const wrappedListener = fakeChromeOnRequestFinished.addListener.firstCall.args[0];
|
||||||
|
wrappedListener({
|
||||||
|
getContent(cb) {
|
||||||
|
cb("<html>...</html>", "text/html; charset=utf8");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
ok(listener.calledOnce, "listener has been called once");
|
||||||
|
|
||||||
|
const req = listener.firstCall.args[0];
|
||||||
|
return req.getContent().then(([content, encodingOrMimeType]) => {
|
||||||
|
equal(content, "<html>...</html>");
|
||||||
|
// On Chrome this is the encoding ('' or 'base64') while on Firefox
|
||||||
|
// this is the MIME type of the resource.
|
||||||
|
// See: https://github.com/mozilla/webextension-polyfill/issues/249#issuecomment-740000461
|
||||||
|
equal(encodingOrMimeType, "text/html; charset=utf8");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("promisifies the result with a wrapped Request object", () => {
|
||||||
|
const fakeChrome = {
|
||||||
|
devtools: {
|
||||||
|
network: {
|
||||||
|
onRequestFinished: {
|
||||||
|
addListener: sinon.spy(),
|
||||||
|
hasListener: sinon.stub(),
|
||||||
|
removeListener: sinon.spy(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return setupTestDOMWindow(fakeChrome).then(window => {
|
||||||
|
const listener = sinon.spy();
|
||||||
|
|
||||||
|
const browserOnRequestFinished = window.browser.devtools.network.onRequestFinished;
|
||||||
|
browserOnRequestFinished.addListener(listener);
|
||||||
|
|
||||||
|
const fakeChromeOnRequestFinished = fakeChrome.devtools.network.onRequestFinished;
|
||||||
|
ok(fakeChromeOnRequestFinished.addListener.calledOnce,
|
||||||
|
"devtools.network.onRequestFinished.addListener has been called once");
|
||||||
|
|
||||||
|
const request = Object.create({
|
||||||
|
inheritedProp: true,
|
||||||
|
getContent(cb) {
|
||||||
|
cb("", "");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const wrappedListener = fakeChromeOnRequestFinished.addListener.firstCall.args[0];
|
||||||
|
wrappedListener(request);
|
||||||
|
|
||||||
|
ok(listener.calledOnce, "listener has been called once");
|
||||||
|
|
||||||
|
const req = listener.firstCall.args[0];
|
||||||
|
ok(req.inheritedProp, "Wrapped request inherited prototype properties");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue