Bug 1244816 - Add a mock Web Push server and update the tests. r?dragana
--- a/dom/push/test/mochitest.ini
+++ b/dom/push/test/mochitest.ini
@@ -3,28 +3,28 @@ subsuite = push
support-files =
worker.js
push-server.sjs
frame.html
webpush.js
lifetime_worker.js
[test_has_permissions.html]
-skip-if = os == "android" || toolkit == "gonk"
+skip-if = os == "android" || toolkit == "gonk" || !hasNode
[test_permissions.html]
-skip-if = os == "android" || toolkit == "gonk"
+skip-if = os == "android" || toolkit == "gonk" || !hasNode
[test_register.html]
-skip-if = os == "android" || toolkit == "gonk"
+skip-if = os == "android" || toolkit == "gonk" || !hasNode
[test_multiple_register.html]
-skip-if = os == "android" || toolkit == "gonk"
+skip-if = os == "android" || toolkit == "gonk" || !hasNode
[test_multiple_register_during_service_activation.html]
-skip-if = os == "android" || toolkit == "gonk"
+skip-if = os == "android" || toolkit == "gonk" || !hasNode
[test_unregister.html]
-skip-if = os == "android" || toolkit == "gonk"
+skip-if = os == "android" || toolkit == "gonk" || !hasNode
[test_multiple_register_different_scope.html]
-skip-if = os == "android" || toolkit == "gonk"
+skip-if = os == "android" || toolkit == "gonk" || !hasNode
[test_data.html]
-skip-if = os == "android" || toolkit == "gonk"
+skip-if = os == "android" || toolkit == "gonk" || !hasNode
# Disabled for too many intermittent failures (bug 1164432)
# [test_try_registering_offline_disabled.html]
# skip-if = os == "android" || toolkit == "gonk"
[test_serviceworker_lifetime.html]
-skip-if = os == "android" || toolkit == "gonk"
+skip-if = os == "android" || toolkit == "gonk" || !hasNode
--- a/dom/push/test/test_data.html
+++ b/dom/push/test/test_data.html
@@ -200,22 +200,18 @@ http://creativecommons.org/licenses/publ
reader.readAsText(message.data.blob);
});
is(text, "Hi! \ud83d\udc40", "Wrong blob data for message with emoji");
is(text, "Hi! \ud83d\udc40", "Wrong blob data for message with emoji");
// Send a blank message.
var [message] = yield Promise.all([
controlledFrame.contentWindow.waitOnPushMessage(pushSubscription),
- fetch("http://mochi.test:8888/tests/dom/push/test/push-server.sjs", {
- method: "PUT",
- headers: {
- "X-Push-Method": "POST",
- "X-Push-Server": pushSubscription.endpoint,
- },
+ fetch(pushSubscription.endpoint, {
+ method: "POST",
}),
]);
ok(!message.data, "Should exclude data for blank messages");
});
add_task(function* unsubscribe() {
controlledFrame.parentNode.removeChild(controlledFrame);
controlledFrame = null;
--- a/dom/push/test/test_register.html
+++ b/dom/push/test/test_register.html
@@ -51,26 +51,24 @@ http://creativecommons.org/licenses/publ
ok(false, "permissionState() should resolve to granted.");
return swr;
});
}
function sendPushToPushServer(pushEndpoint) {
// Work around CORS for now.
var xhr = new XMLHttpRequest();
- xhr.open('GET', "http://mochi.test:8888/tests/dom/push/test/push-server.sjs", true);
- xhr.setRequestHeader("X-Push-Method", "PUT");
- xhr.setRequestHeader("X-Push-Server", pushEndpoint);
+ xhr.open('POST', pushEndpoint, true);
xhr.onload = function(e) {
debug("xhr : " + this.status);
}
xhr.onerror = function(e) {
debug("xhr error: " + e);
}
- xhr.send("version=24601");
+ xhr.send();
}
var registration;
function start() {
return navigator.serviceWorker.register("worker.js" + "?" + (Math.random()), {scope: "."})
.then((swr) => registration = swr);
}
--- a/dom/push/test/webpush.js
+++ b/dom/push/test/webpush.js
@@ -177,30 +177,27 @@
.then(localKey => {
return Promise.all([
encrypt(localKey.privateKey, subscription.getKey("p256dh"), salt, data),
// 1337 p-256 specific haxx to get the raw value out of the spki value
webCrypto.exportKey('raw', localKey.publicKey),
]);
}).then(([payload, pubkey]) => {
var options = {
- method: 'PUT',
+ method: 'POST',
headers: {
- 'X-Push-Server': subscription.endpoint,
- // Web Push requires POST requests.
- 'X-Push-Method': 'POST',
'Encryption-Key': 'keyid=p256dh;dh=' + base64url.encode(pubkey),
Encryption: 'keyid=p256dh;salt=' + base64url.encode(salt),
'Content-Encoding': 'aesgcm128'
},
body: payload,
};
- return fetch('http://mochi.test:8888/tests/dom/push/test/push-server.sjs', options);
+ return fetch(subscription.endpoint, options);
}).then(response => {
- if (response.status / 100 !== 2) {
+ if (Math.floor(response.status / 100) !== 2) {
throw new Error('Unable to deliver message');
}
return response;
});
}
g.webpush = webpush;
}(this));
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -338,8 +338,10 @@ user_pref("browser.urlbar.suggest.search
// Turn off the location bar search suggestions opt-in. It interferes with
// tests that don't expect it to be there.
user_pref("browser.urlbar.userMadeSearchSuggestionsChoice", true);
user_pref("dom.audiochannel.mutedByDefault", false);
user_pref("webextensions.tests", true);
+
+user_pref("dom.push.serverURL", "https://localhost:%(MOZHTTP2_PORT)s/push/subscribe");
--- a/testing/xpcshell/moz-http2/moz-http2.js
+++ b/testing/xpcshell/moz-http2/moz-http2.js
@@ -8,16 +8,17 @@
var node_http2_root = '../node-http2';
if (process.env.NODE_HTTP2_ROOT) {
node_http2_root = process.env.NODE_HTTP2_ROOT;
}
var http2 = require(node_http2_root);
var fs = require('fs');
var url = require('url');
var crypto = require('crypto');
+var util = require('util');
// Hook into the decompression code to log the decompressed name-value pairs
var compression_module = node_http2_root + "/lib/protocol/compressor";
var http2_compression = require(compression_module);
var HeaderSetDecompressor = http2_compression.HeaderSetDecompressor;
var originalRead = HeaderSetDecompressor.prototype.read;
var lastDecompressor;
var decompressedPairs;
@@ -57,16 +58,143 @@ function getHttpContent(path) {
function generateContent(size) {
var content = '';
for (var i = 0; i < size; i++) {
content += '0';
}
return content;
}
+// A `String.prototype.startsWith` polyfill for older Node versions.
+function startsWith(string, prefix) {
+ return string.length >= prefix.length &&
+ string.lastIndexOf(prefix, 0) === 0;
+}
+
+// An in-memory Web Push server based on draft-ietf-webpush-protocol-02. This
+// server does not support receipts, subscription sets, or message storage.
+var webPushServer = {
+ subscriptions: {},
+
+ cryptoHeaders: [
+ "content-encoding",
+ "encryption",
+ "crypto-key",
+ "encryption-key", // Legacy.
+ ],
+
+ // Section 4.
+ subscribe: function(req, res) {
+ var subscriptionId = crypto.randomBytes(16).toString("hex");
+ res.writeHead(201, {
+ "location": util.format("https://localhost:%d/push/s/%s",
+ serverPort, subscriptionId),
+ "link": util.format('</push/p/%s>; rel="urn:ietf:params:push"',
+ subscriptionId),
+ });
+ res.end();
+ },
+
+ // Section 7.
+ getSubscription: function(req, res, subscriptionId) {
+ if (this.subscriptions[subscriptionId]) {
+ res.writeHead(409);
+ res.end();
+ return;
+ }
+ this.subscriptions[subscriptionId] = res;
+ },
+
+ // Section 8.3.
+ unsubscribe: function(req, res, subscriptionId) {
+ res.writeHead(200);
+ res.end();
+ },
+
+ // Section 6.
+ push: function(req, res, subscriptionId) {
+ this.setCORSHeaders(req, res);
+ var stream = this.subscriptions[subscriptionId];
+ if (!stream) {
+ res.statusCode = 404;
+ res.end();
+ return;
+ }
+ var messagePath = "/push/d/" + crypto.randomBytes(16).toString("hex");
+ var push = stream.push({
+ method: "GET",
+ path: messagePath,
+ // Forward encryption headers.
+ headers: this.cryptoHeaders.reduce(function(headers, name) {
+ if (name in req.headers) {
+ headers[name] = req.headers[name];
+ }
+ return headers;
+ }, {}),
+ });
+ req.pipe(push);
+ res.statusCode = 201;
+ res.setHeader("location", util.format("https://localhost:%d/%s", serverPort,
+ messagePath));
+ res.end();
+ },
+
+ // Section 7.2.
+ ack: function(req, res, messageId) {
+ res.writeHead(200);
+ res.end();
+ },
+
+ // Required for the Push mochitests.
+ setCORSHeaders: function(req, res) {
+ res.setHeader("access-control-allow-origin", req.headers.origin);
+ res.setHeader("access-control-allow-methods", "POST");
+ res.setHeader("access-control-allow-headers",
+ this.cryptoHeaders.concat("content-type").join(","));
+ res.setHeader("access-control-expose-headers", "location");
+ },
+
+ options: function(req, res) {
+ res.statusCode = 200;
+ this.setCORSHeaders(req, res);
+ res.end();
+ },
+
+ route: function(req, res, u) {
+ if (req.method === "OPTIONS") {
+ return this.options(req, res);
+ }
+ if (u.pathname === "/push/subscribe" && req.method === "POST") {
+ return this.subscribe(req, res);
+ }
+ var subscriptionPrefix = "/push/s/";
+ if (startsWith(u.pathname, subscriptionPrefix)) {
+ var subscriptionId = u.pathname.slice(subscriptionPrefix.length);
+ if (req.method === "GET") {
+ return this.getSubscription(req, res, subscriptionId);
+ }
+ if (req.method == "DELETE") {
+ return this.unsubscribe(req, res, subscriptionId);
+ }
+ }
+ var pushPrefix = "/push/p/";
+ if (startsWith(u.pathname, pushPrefix) && req.method === "POST") {
+ var subscriptionId = u.pathname.slice(pushPrefix.length);
+ return this.push(req, res, subscriptionId);
+ }
+ var messagePrefix = "/push/d/";
+ if (startsWith(u.pathname, messagePrefix) && req.method === "DELETE") {
+ var messageId = u.pathname.slice(messagePrefix.length);
+ return this.ack(req, res, messageId);
+ }
+ res.writeHead(404);
+ res.end();
+ },
+};
+
/* This takes care of responding to the multiplexed request for us */
var m = {
mp1res: null,
mp2res: null,
buf: null,
mp1start: 0,
mp2start: 0,
@@ -479,16 +607,20 @@ function handleRequest(req, res) {
}
// for use with test_altsvc.js
else if (u.pathname === "/altsvc-test") {
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Alt-Svc', 'h2=' + req.headers['x-altsvc']);
}
+ else if (startsWith(u.pathname, "/push/")) {
+ return webPushServer.route(req, res, u);
+ }
+
// for PushService tests.
else if (u.pathname === "/pushSubscriptionSuccess/subscribe") {
res.setHeader("Location",
'https://localhost:' + serverPort + '/pushSubscriptionSuccesss');
res.setHeader("Link",
'</pushEndpointSuccess>; rel="urn:ietf:params:push", ' +
'</receiptPushEndpointSuccess>; rel="urn:ietf:params:push:receipt"');
res.writeHead(201, "OK");