webextension-polyfill/test/test-runtime-onMessage.js

268 lines
11 KiB
JavaScript

"use strict";
const {deepEqual, equal, ok} = require("chai").assert;
const sinon = require("sinon");
const {setupTestDOMWindow} = require("./setup");
describe("browser-polyfill", () => {
describe("wrapped runtime.onMessage listener", () => {
it("does not wrap the listener if it is not a function", () => {
const fakeChrome = {
runtime: {
lastError: null,
onMessage: {
addListener: sinon.spy(),
hasListener: sinon.stub(),
removeListener: sinon.spy(),
},
},
};
return setupTestDOMWindow(fakeChrome).then(window => {
const fakeNonFunctionListener = {fake: "non function listener"};
window.browser.runtime.onMessage.addListener(fakeNonFunctionListener);
deepEqual(fakeChrome.runtime.onMessage.addListener.firstCall.args[0],
fakeNonFunctionListener,
"The non-function listener has not been wrapped");
});
});
it("keeps track of the listeners added", () => {
const messageListener = sinon.spy();
const fakeChromeListeners = new Set();
const fakeChrome = {
runtime: {
lastError: null,
onMessage: {
addListener: sinon.spy((listener, ...args) => {
fakeChromeListeners.add(listener);
}),
hasListener: sinon.spy(listener => fakeChromeListeners.has(listener)),
removeListener: sinon.spy(listener => {
fakeChromeListeners.delete(listener);
}),
},
},
};
return setupTestDOMWindow(fakeChrome).then(window => {
equal(window.browser.runtime.onMessage.hasListener(messageListener),
false, "Got hasListener==false before the listener has been added");
window.browser.runtime.onMessage.addListener(messageListener);
equal(window.browser.runtime.onMessage.hasListener(messageListener),
true, "Got hasListener==true once the listener has been added");
// Add the same listener again to test that it will be called with the
// same wrapped listener.
window.browser.runtime.onMessage.addListener(messageListener);
ok(fakeChrome.runtime.onMessage.addListener.calledTwice,
"addListener has been called twice");
equal(fakeChrome.runtime.onMessage.addListener.secondCall.args[0],
fakeChrome.runtime.onMessage.addListener.firstCall.args[0],
"both the addListener calls received the same wrapped listener");
// Retrieve the wrapped listener and execute it to fake a received message.
const wrappedListener = fakeChrome.runtime.onMessage.addListener.firstCall.args[0];
wrappedListener("msg", {name: "sender"}, function sendResponse() {});
ok(messageListener.calledOnce, "The listener has been called once");
// Remove the listener.
window.browser.runtime.onMessage.removeListener(messageListener);
ok(fakeChrome.runtime.onMessage.removeListener.calledOnce,
"removeListener has been called once");
equal(fakeChrome.runtime.onMessage.addListener.secondCall.args[0],
fakeChrome.runtime.onMessage.removeListener.firstCall.args[0],
"both the addListener and removeListenercalls received the same wrapped listener");
equal(fakeChrome.runtime.onMessage.hasListener(messageListener), false,
"Got hasListener==false once the listener has been removed");
});
});
it("generates different wrappers for different listeners", () => {
const fakeChromeListeners = [];
const fakeChrome = {
runtime: {
lastError: null,
onMessage: {
addListener: sinon.spy((listener, ...args) => {
fakeChromeListeners.push(listener);
}),
hasListener: sinon.spy(),
removeListener: sinon.spy(),
},
},
};
return setupTestDOMWindow(fakeChrome).then(window => {
const firstMessageListener = sinon.spy();
const secondMessageListener = sinon.spy();
window.browser.runtime.onMessage.addListener(firstMessageListener);
window.browser.runtime.onMessage.addListener(secondMessageListener);
equal(fakeChromeListeners.length, 2, "Got two wrapped listeners");
fakeChromeListeners[0]("call first wrapper");
ok(firstMessageListener.calledOnce);
equal(firstMessageListener.firstCall.args[0], "call first wrapper");
fakeChromeListeners[1]("call second wrapper");
ok(secondMessageListener.calledOnce);
equal(secondMessageListener.firstCall.args[0], "call second wrapper");
});
});
});
describe("sendResponse callback", () => {
it("ignores the sendResponse calls when the listener returns a promise", () => {
const fakeChrome = {
runtime: {
lastError: null,
onMessage: {
addListener: sinon.spy(),
},
},
};
return setupTestDOMWindow(fakeChrome).then(window => {
const listener = sinon.spy((msg, sender, sendResponse) => {
sendResponse("Ignored sendReponse callback on returned Promise");
return Promise.resolve("listener resolved value");
});
const sendResponseSpy = sinon.spy();
window.browser.runtime.onMessage.addListener(listener);
ok(fakeChrome.runtime.onMessage.addListener.calledOnce,
"runtime.onMessage.addListener should have been called once");
let wrappedListener = fakeChrome.runtime.onMessage.addListener.firstCall.args[0];
let returnedValue = wrappedListener("test message", {name: "fake sender"}, sendResponseSpy);
equal(returnedValue, true, "the wrapped listener should have returned true");
ok(listener.calledOnce, "listener has been called once");
// Wait a promise to be resolved and then check the wrapped listener behaviors.
return Promise.resolve().then(() => {
ok(sendResponseSpy.calledOnce, "sendResponse callback called once");
equal(sendResponseSpy.firstCall.args[0], "listener resolved value",
"sendResponse has been called with the expected value");
});
});
});
it("ignores asynchronous sendResponse calls if the listener does not return true", () => {
const fakeChrome = {
runtime: {
lastError: null,
onMessage: {
addListener: sinon.spy(),
},
},
};
const waitPromises = [];
return setupTestDOMWindow(fakeChrome).then(window => {
const listenerReturnsFalse = sinon.spy((msg, sender, sendResponse) => {
waitPromises.push(Promise.resolve().then(() => {
sendResponse("Ignored sendReponse callback on returned false");
}));
return false;
});
const listenerReturnsValue = sinon.spy((msg, sender, sendResponse) => {
waitPromises.push(Promise.resolve().then(() => {
sendResponse("Ignored sendReponse callback on non boolean/thenable return values");
}));
// Any return value that is not a promise should not be sent as a response,
// and any return value that is not true should make the sendResponse
// calls to be ignored.
return "Ignored return value";
});
const listenerReturnsTrue = sinon.spy((msg, sender, sendResponse) => {
waitPromises.push(Promise.resolve().then(() => {
sendResponse("expected sendResponse value");
}));
// Expect the asynchronous sendResponse call to be used to send a response
// when the listener returns true.
return true;
});
const sendResponseSpy = sinon.spy();
window.browser.runtime.onMessage.addListener(listenerReturnsFalse);
window.browser.runtime.onMessage.addListener(listenerReturnsValue);
window.browser.runtime.onMessage.addListener(listenerReturnsTrue);
equal(fakeChrome.runtime.onMessage.addListener.callCount, 3,
"runtime.onMessage.addListener should have been called 3 times");
let wrappedListenerReturnsFalse = fakeChrome.runtime.onMessage.addListener.firstCall.args[0];
let wrappedListenerReturnsValue = fakeChrome.runtime.onMessage.addListener.secondCall.args[0];
let wrappedListenerReturnsTrue = fakeChrome.runtime.onMessage.addListener.thirdCall.args[0];
let returnedValue = wrappedListenerReturnsFalse("test message", {name: "fake sender"}, sendResponseSpy);
equal(returnedValue, false, "the first wrapped listener should return false");
returnedValue = wrappedListenerReturnsValue("test message2", {name: "fake sender"}, sendResponseSpy);
equal(returnedValue, false, "the second wrapped listener should return false");
returnedValue = wrappedListenerReturnsTrue("test message3", {name: "fake sender"}, sendResponseSpy);
equal(returnedValue, true, "the third wrapped listener should return true");
ok(listenerReturnsFalse.calledOnce, "first listener has been called once");
ok(listenerReturnsValue.calledOnce, "second listener has been called once");
ok(listenerReturnsTrue.calledOnce, "third listener has been called once");
// Wait all the collected promises to be resolved and then check the wrapped listener behaviors.
return Promise.all(waitPromises).then(() => {
ok(sendResponseSpy.calledOnce, "sendResponse callback should have been called once");
equal(sendResponseSpy.firstCall.args[0], "expected sendResponse value",
"sendResponse has been called with the expected value");
});
});
});
it("resolves to undefined when no listeners reply", () => {
const fakeChrome = {
runtime: {
// This error message is defined as CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE
// in the polyfill sources and it is used to recognize when Chrome has detected that
// none of the listeners replied.
lastError: {
message: "The message port closed before a response was received.",
},
sendMessage: sinon.stub(),
},
};
fakeChrome.runtime.sendMessage.onFirstCall().callsArgWith(1, [undefined]);
return setupTestDOMWindow(fakeChrome).then(window => {
const promise = window.browser.runtime.sendMessage("some_message");
ok(fakeChrome.runtime.sendMessage.calledOnce, "sendMessage has been called once");
return promise.then(reply => {
deepEqual(reply, undefined, "sendMessage promise should be resolved to undefined");
});
});
});
});
});