mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
57a88e0d3d
This makes `pushManager.subscribe()` to succeed on GeckoView when permission is already granted, and allows basic permission testing on GeckoView test runner. Differential Revision: https://phabricator.services.mozilla.com/D190961
208 lines
5.1 KiB
JavaScript
208 lines
5.1 KiB
JavaScript
/* eslint-env mozilla/chrome-script */
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* Defers one or more callbacks until the next turn of the event loop. Multiple
|
|
* callbacks are executed in order.
|
|
*
|
|
* @param {Function[]} callbacks The callbacks to execute. One callback will be
|
|
* executed per tick.
|
|
*/
|
|
function waterfall(...callbacks) {
|
|
callbacks
|
|
.reduce(
|
|
(promise, callback) =>
|
|
promise.then(() => {
|
|
callback();
|
|
}),
|
|
Promise.resolve()
|
|
)
|
|
.catch(Cu.reportError);
|
|
}
|
|
|
|
/**
|
|
* Minimal implementation of a mock WebSocket connect to be used with
|
|
* PushService. Forwards and receive messages from the implementation
|
|
* that lives in the content process.
|
|
*/
|
|
function MockWebSocketParent(originalURI) {
|
|
this._originalURI = originalURI;
|
|
}
|
|
|
|
MockWebSocketParent.prototype = {
|
|
_originalURI: null,
|
|
|
|
_listener: null,
|
|
_context: null,
|
|
|
|
QueryInterface: ChromeUtils.generateQI(["nsIWebSocketChannel"]),
|
|
|
|
get originalURI() {
|
|
return this._originalURI;
|
|
},
|
|
|
|
asyncOpen(uri, origin, originAttributes, windowId, listener, context) {
|
|
this._listener = listener;
|
|
this._context = context;
|
|
waterfall(() => this._listener.onStart(this._context));
|
|
},
|
|
|
|
sendMsg(msg) {
|
|
sendAsyncMessage("socket-client-msg", msg);
|
|
},
|
|
|
|
close() {
|
|
waterfall(() => this._listener.onStop(this._context, Cr.NS_OK));
|
|
},
|
|
|
|
serverSendMsg(msg) {
|
|
waterfall(
|
|
() => this._listener.onMessageAvailable(this._context, msg),
|
|
() => this._listener.onAcknowledge(this._context, 0)
|
|
);
|
|
},
|
|
};
|
|
|
|
var pushService = Cc["@mozilla.org/push/Service;1"].getService(
|
|
Ci.nsIPushService
|
|
).wrappedJSObject;
|
|
|
|
var mockSocket;
|
|
var serverMsgs = [];
|
|
|
|
addMessageListener("socket-setup", function () {
|
|
pushService.replaceServiceBackend({
|
|
serverURI: "wss://push.example.org/",
|
|
makeWebSocket(uri) {
|
|
mockSocket = new MockWebSocketParent(uri);
|
|
while (serverMsgs.length) {
|
|
let msg = serverMsgs.shift();
|
|
mockSocket.serverSendMsg(msg);
|
|
}
|
|
return mockSocket;
|
|
},
|
|
});
|
|
});
|
|
|
|
addMessageListener("socket-teardown", function (msg) {
|
|
pushService
|
|
.restoreServiceBackend()
|
|
.then(_ => {
|
|
serverMsgs.length = 0;
|
|
if (mockSocket) {
|
|
mockSocket.close();
|
|
mockSocket = null;
|
|
}
|
|
sendAsyncMessage("socket-server-teardown");
|
|
})
|
|
.catch(error => {
|
|
Cu.reportError(`Error restoring service backend: ${error}`);
|
|
});
|
|
});
|
|
|
|
addMessageListener("socket-server-msg", function (msg) {
|
|
if (mockSocket) {
|
|
mockSocket.serverSendMsg(msg);
|
|
} else {
|
|
serverMsgs.push(msg);
|
|
}
|
|
});
|
|
|
|
var MockService = {
|
|
requestID: 1,
|
|
resolvers: new Map(),
|
|
|
|
sendRequest(name, params) {
|
|
return new Promise((resolve, reject) => {
|
|
let id = this.requestID++;
|
|
this.resolvers.set(id, { resolve, reject });
|
|
sendAsyncMessage("service-request", {
|
|
name,
|
|
id,
|
|
// The request params from the real push service may contain a
|
|
// principal, which cannot be passed to the unprivileged
|
|
// mochitest scope, and will cause the message to be dropped if
|
|
// present. The mochitest scope fortunately does not need the
|
|
// principal, though, so set it to null before sending.
|
|
params: Object.assign({}, params, { principal: null }),
|
|
});
|
|
});
|
|
},
|
|
|
|
handleResponse(response) {
|
|
if (!this.resolvers.has(response.id)) {
|
|
Cu.reportError(`Unexpected response for request ${response.id}`);
|
|
return;
|
|
}
|
|
let resolver = this.resolvers.get(response.id);
|
|
this.resolvers.delete(response.id);
|
|
if (response.error) {
|
|
resolver.reject(response.error);
|
|
} else {
|
|
resolver.resolve(response.result);
|
|
}
|
|
},
|
|
|
|
init() {},
|
|
|
|
register(pageRecord) {
|
|
return this.sendRequest("register", pageRecord);
|
|
},
|
|
|
|
registration(pageRecord) {
|
|
return this.sendRequest("registration", pageRecord);
|
|
},
|
|
|
|
unregister(pageRecord) {
|
|
return this.sendRequest("unregister", pageRecord);
|
|
},
|
|
|
|
reportDeliveryError(messageId, reason) {
|
|
sendAsyncMessage("service-delivery-error", {
|
|
messageId,
|
|
reason,
|
|
});
|
|
},
|
|
|
|
uninit() {
|
|
return Promise.resolve();
|
|
},
|
|
};
|
|
|
|
async function replaceService(service) {
|
|
// `?.` because `service` can be null
|
|
// (either by calling this function with null, or the push module doesn't have the
|
|
// field at all e.g. in GeckoView)
|
|
// Passing null here resets it to the default implementation on desktop
|
|
// (so `.service` never becomes null there) but not for GeckoView.
|
|
// XXX(krosylight): we need to remove this deviation.
|
|
await pushService.service?.uninit();
|
|
pushService.service = service;
|
|
await pushService.service?.init();
|
|
}
|
|
|
|
addMessageListener("service-replace", function () {
|
|
replaceService(MockService)
|
|
.then(_ => {
|
|
sendAsyncMessage("service-replaced");
|
|
})
|
|
.catch(error => {
|
|
Cu.reportError(`Error replacing service: ${error}`);
|
|
});
|
|
});
|
|
|
|
addMessageListener("service-restore", function () {
|
|
replaceService(null)
|
|
.then(_ => {
|
|
sendAsyncMessage("service-restored");
|
|
})
|
|
.catch(error => {
|
|
Cu.reportError(`Error restoring service: ${error}`);
|
|
});
|
|
});
|
|
|
|
addMessageListener("service-response", function (response) {
|
|
MockService.handleResponse(response);
|
|
});
|