summaryrefslogtreecommitdiffstats
path: root/devtools/shared/network-observer/test/browser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /devtools/shared/network-observer/test/browser
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/shared/network-observer/test/browser')
-rw-r--r--devtools/shared/network-observer/test/browser/browser.toml33
-rw-r--r--devtools/shared/network-observer/test/browser/browser_networkobserver.js72
-rw-r--r--devtools/shared/network-observer/test/browser/browser_networkobserver_auth_listener.js386
-rw-r--r--devtools/shared/network-observer/test/browser/browser_networkobserver_invalid_constructor.js49
-rw-r--r--devtools/shared/network-observer/test/browser/browser_networkobserver_override.js179
-rw-r--r--devtools/shared/network-observer/test/browser/browser_networkobserver_serviceworker.js113
-rw-r--r--devtools/shared/network-observer/test/browser/doc_network-observer-missing-service-worker.html32
-rw-r--r--devtools/shared/network-observer/test/browser/doc_network-observer.html49
-rw-r--r--devtools/shared/network-observer/test/browser/gzipped.sjs44
-rw-r--r--devtools/shared/network-observer/test/browser/head.js123
-rw-r--r--devtools/shared/network-observer/test/browser/override.html1
-rw-r--r--devtools/shared/network-observer/test/browser/override.js2
-rw-r--r--devtools/shared/network-observer/test/browser/serviceworker.js23
-rw-r--r--devtools/shared/network-observer/test/browser/sjs_network-auth-listener-test-server.sjs31
-rw-r--r--devtools/shared/network-observer/test/browser/sjs_network-observer-test-server.sjs196
15 files changed, 1333 insertions, 0 deletions
diff --git a/devtools/shared/network-observer/test/browser/browser.toml b/devtools/shared/network-observer/test/browser/browser.toml
new file mode 100644
index 0000000000..3b3b44aaae
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/browser.toml
@@ -0,0 +1,33 @@
+[DEFAULT]
+tags = "devtools"
+subsuite = "devtools"
+support-files = [
+ "head.js",
+ "doc_network-observer-missing-service-worker.html",
+ "doc_network-observer.html",
+ "gzipped.sjs",
+ "override.html",
+ "override.js",
+ "serviceworker.js",
+ "sjs_network-auth-listener-test-server.sjs",
+ "sjs_network-observer-test-server.sjs",
+]
+
+["browser_networkobserver.js"]
+skip-if = [
+ "http3", # Bug 1829298
+ "http2",
+]
+
+["browser_networkobserver_auth_listener.js"]
+skip-if = [
+ "debug", # Disabled for frequent leaks in Bug 1873571.
+ "asan",
+]
+
+["browser_networkobserver_invalid_constructor.js"]
+
+["browser_networkobserver_override.js"]
+
+["browser_networkobserver_serviceworker.js"]
+fail-if = ["true"] # Disabled until Bug 1267119 and Bug 1246289
diff --git a/devtools/shared/network-observer/test/browser/browser_networkobserver.js b/devtools/shared/network-observer/test/browser/browser_networkobserver.js
new file mode 100644
index 0000000000..8f81ef6f86
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/browser_networkobserver.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URL = URL_ROOT + "doc_network-observer.html";
+const REQUEST_URL =
+ URL_ROOT + `sjs_network-observer-test-server.sjs?sts=200&fmt=html`;
+
+// Check that the NetworkObserver can detect basic requests and calls the
+// onNetworkEvent callback when expected.
+add_task(async function testSingleRequest() {
+ await addTab(TEST_URL);
+
+ const onNetworkEvents = waitForNetworkEvents(REQUEST_URL, 1);
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [REQUEST_URL], _url => {
+ content.wrappedJSObject.fetch(_url);
+ });
+
+ const events = await onNetworkEvents;
+ is(events.length, 1, "Received the expected number of network events");
+});
+
+add_task(async function testMultipleRequests() {
+ await addTab(TEST_URL);
+ const EXPECTED_REQUESTS_COUNT = 5;
+
+ const onNetworkEvents = waitForNetworkEvents(
+ REQUEST_URL,
+ EXPECTED_REQUESTS_COUNT
+ );
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [REQUEST_URL, EXPECTED_REQUESTS_COUNT],
+ (_url, _count) => {
+ for (let i = 0; i < _count; i++) {
+ content.wrappedJSObject.fetch(_url);
+ }
+ }
+ );
+
+ const events = await onNetworkEvents;
+ is(
+ events.length,
+ EXPECTED_REQUESTS_COUNT,
+ "Received the expected number of network events"
+ );
+});
+
+add_task(async function testOnNetworkEventArguments() {
+ await addTab(TEST_URL);
+
+ const onNetworkEvent = new Promise(resolve => {
+ const networkObserver = new NetworkObserver({
+ ignoreChannelFunction: () => false,
+ onNetworkEvent: (...args) => {
+ resolve(args);
+ return createNetworkEventOwner();
+ },
+ });
+ registerCleanupFunction(() => networkObserver.destroy());
+ });
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [REQUEST_URL], _url => {
+ content.wrappedJSObject.fetch(_url);
+ });
+
+ const args = await onNetworkEvent;
+ is(args.length, 2, "Received two arguments");
+ is(typeof args[0], "object", "First argument is an object");
+ ok(args[1] instanceof Ci.nsIChannel, "Second argument is a channel");
+});
diff --git a/devtools/shared/network-observer/test/browser/browser_networkobserver_auth_listener.js b/devtools/shared/network-observer/test/browser/browser_networkobserver_auth_listener.js
new file mode 100644
index 0000000000..e3492c10ad
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/browser_networkobserver_auth_listener.js
@@ -0,0 +1,386 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+const TEST_URL = URL_ROOT + "doc_network-observer.html";
+const AUTH_URL = URL_ROOT + `sjs_network-auth-listener-test-server.sjs`;
+
+// Correct credentials for sjs_network-auth-listener-test-server.sjs.
+const USERNAME = "guest";
+const PASSWORD = "guest";
+const BAD_PASSWORD = "bad";
+
+// NetworkEventOwner which will cancel all auth prompt requests.
+class AuthCancellingOwner extends NetworkEventOwner {
+ hasAuthPrompt = false;
+
+ onAuthPrompt(authDetails, authCallbacks) {
+ this.hasAuthPrompt = true;
+ authCallbacks.cancelAuthPrompt();
+ }
+}
+
+// NetworkEventOwner which will forward all auth prompt requests to the browser.
+class AuthForwardingOwner extends NetworkEventOwner {
+ hasAuthPrompt = false;
+
+ onAuthPrompt(authDetails, authCallbacks) {
+ this.hasAuthPrompt = true;
+ authCallbacks.forwardAuthPrompt();
+ }
+}
+
+// NetworkEventOwner which will answer provided credentials to auth prompts.
+class AuthCredentialsProvidingOwner extends NetworkEventOwner {
+ hasAuthPrompt = false;
+
+ constructor(channel, username, password) {
+ super();
+
+ this.channel = channel;
+ this.username = username;
+ this.password = password;
+ }
+
+ async onAuthPrompt(authDetails, authCallbacks) {
+ this.hasAuthPrompt = true;
+
+ // Providing credentials immediately can lead to intermittent failures.
+ // TODO: Investigate and remove.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 100));
+
+ await authCallbacks.provideAuthCredentials(this.username, this.password);
+ }
+
+ addResponseContent(content) {
+ super.addResponseContent();
+ this.responseContent = content.text;
+ }
+}
+
+add_task(async function testAuthRequestWithoutListener() {
+ cleanupAuthManager();
+ const tab = await addTab(TEST_URL);
+
+ const events = [];
+ const networkObserver = new NetworkObserver({
+ ignoreChannelFunction: channel => channel.URI.spec !== AUTH_URL,
+ onNetworkEvent: event => {
+ const owner = new AuthForwardingOwner();
+ events.push(owner);
+ return owner;
+ },
+ });
+ registerCleanupFunction(() => networkObserver.destroy());
+
+ const onAuthPrompt = waitForAuthPrompt(tab);
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [AUTH_URL], _url => {
+ content.wrappedJSObject.fetch(_url);
+ });
+
+ info("Wait for a network event to be created");
+ await BrowserTestUtils.waitForCondition(() => events.length >= 1);
+ is(events.length, 1, "Received the expected number of network events");
+
+ info("Wait for the auth prompt to be displayed");
+ await onAuthPrompt;
+ Assert.equal(
+ getTabAuthPrompts(tab).length,
+ 1,
+ "The auth prompt was not blocked by the network observer"
+ );
+
+ // The event owner should have been called for ResponseStart and EventTimings
+ assertEventOwner(events[0], {
+ hasResponseStart: true,
+ hasEventTimings: true,
+ hasServerTimings: true,
+ });
+
+ networkObserver.destroy();
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function testAuthRequestWithForwardingListener() {
+ cleanupAuthManager();
+ const tab = await addTab(TEST_URL);
+
+ const events = [];
+ const networkObserver = new NetworkObserver({
+ ignoreChannelFunction: channel => channel.URI.spec !== AUTH_URL,
+ onNetworkEvent: event => {
+ info("waitForNetworkEvents received a new event");
+ const owner = new AuthForwardingOwner();
+ events.push(owner);
+ return owner;
+ },
+ });
+ registerCleanupFunction(() => networkObserver.destroy());
+
+ info("Enable the auth prompt listener for this network observer");
+ networkObserver.setAuthPromptListenerEnabled(true);
+
+ const onAuthPrompt = waitForAuthPrompt(tab);
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [AUTH_URL], _url => {
+ content.wrappedJSObject.fetch(_url);
+ });
+
+ info("Wait for a network event to be received");
+ await BrowserTestUtils.waitForCondition(() => events.length >= 1);
+ is(events.length, 1, "Received the expected number of network events");
+
+ // The auth prompt should still be displayed since the network event owner
+ // forwards the auth notification immediately.
+ info("Wait for the auth prompt to be displayed");
+ await onAuthPrompt;
+ Assert.equal(
+ getTabAuthPrompts(tab).length,
+ 1,
+ "The auth prompt was not blocked by the network observer"
+ );
+
+ // The event owner should have been called for ResponseStart, EventTimings and
+ // AuthPrompt
+ assertEventOwner(events[0], {
+ hasResponseStart: true,
+ hasEventTimings: true,
+ hasAuthPrompt: true,
+ hasServerTimings: true,
+ });
+
+ networkObserver.destroy();
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function testAuthRequestWithCancellingListener() {
+ cleanupAuthManager();
+ const tab = await addTab(TEST_URL);
+
+ const events = [];
+ const networkObserver = new NetworkObserver({
+ ignoreChannelFunction: channel => channel.URI.spec !== AUTH_URL,
+ onNetworkEvent: event => {
+ const owner = new AuthCancellingOwner();
+ events.push(owner);
+ return owner;
+ },
+ });
+ registerCleanupFunction(() => networkObserver.destroy());
+
+ info("Enable the auth prompt listener for this network observer");
+ networkObserver.setAuthPromptListenerEnabled(true);
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [AUTH_URL], _url => {
+ content.wrappedJSObject.fetch(_url);
+ });
+
+ info("Wait for a network event to be received");
+ await BrowserTestUtils.waitForCondition(() => events.length >= 1);
+ is(events.length, 1, "Received the expected number of network events");
+
+ await BrowserTestUtils.waitForCondition(
+ () => events[0].hasResponseContent && events[0].hasSecurityInfo
+ );
+
+ // The auth prompt should not be displayed since the authentication was
+ // cancelled.
+ ok(
+ !getTabAuthPrompts(tab).length,
+ "The auth prompt was cancelled by the network event owner"
+ );
+
+ assertEventOwner(events[0], {
+ hasResponseStart: true,
+ hasResponseContent: true,
+ hasEventTimings: true,
+ hasServerTimings: true,
+ hasAuthPrompt: true,
+ hasSecurityInfo: true,
+ });
+
+ networkObserver.destroy();
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function testAuthRequestWithWrongCredentialsListener() {
+ cleanupAuthManager();
+ const tab = await addTab(TEST_URL);
+
+ const events = [];
+ const networkObserver = new NetworkObserver({
+ ignoreChannelFunction: channel => channel.URI.spec !== AUTH_URL,
+ onNetworkEvent: (event, channel) => {
+ const owner = new AuthCredentialsProvidingOwner(
+ channel,
+ USERNAME,
+ BAD_PASSWORD
+ );
+ events.push(owner);
+ return owner;
+ },
+ });
+ registerCleanupFunction(() => networkObserver.destroy());
+
+ info("Enable the auth prompt listener for this network observer");
+ networkObserver.setAuthPromptListenerEnabled(true);
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [AUTH_URL], _url => {
+ content.wrappedJSObject.fetch(_url);
+ });
+
+ info("Wait for all network events to be received");
+ await BrowserTestUtils.waitForCondition(() => events.length >= 1);
+ is(events.length, 1, "Received the expected number of network events");
+
+ // Wait for authPrompt to be handled
+ await BrowserTestUtils.waitForCondition(() => events[0].hasAuthPrompt);
+
+ // The auth prompt should not be displayed since the authentication was
+ // fulfilled.
+ ok(
+ !getTabAuthPrompts(tab).length,
+ "The auth prompt was handled by the network event owner"
+ );
+
+ assertEventOwner(events[0], {
+ hasAuthPrompt: true,
+ hasResponseStart: true,
+ hasEventTimings: true,
+ hasServerTimings: true,
+ });
+
+ networkObserver.destroy();
+ gBrowser.removeTab(tab);
+});
+
+add_task(async function testAuthRequestWithCredentialsListener() {
+ cleanupAuthManager();
+ const tab = await addTab(TEST_URL);
+
+ const events = [];
+ const networkObserver = new NetworkObserver({
+ ignoreChannelFunction: channel => channel.URI.spec !== AUTH_URL,
+ onNetworkEvent: (event, channel) => {
+ const owner = new AuthCredentialsProvidingOwner(
+ channel,
+ USERNAME,
+ PASSWORD
+ );
+ events.push(owner);
+ return owner;
+ },
+ });
+ registerCleanupFunction(() => networkObserver.destroy());
+
+ info("Enable the auth prompt listener for this network observer");
+ networkObserver.setAuthPromptListenerEnabled(true);
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [AUTH_URL], _url => {
+ content.wrappedJSObject.fetch(_url);
+ });
+
+ // TODO: At the moment, providing credentials will result in additional
+ // network events collected by the NetworkObserver, whereas we would expect
+ // to keep the same event.
+ // For successful auth prompts, we receive an additional event.
+ // The last event will contain the responseContent flag.
+ info("Wait for all network events to be received");
+ await BrowserTestUtils.waitForCondition(() => events.length >= 2);
+ is(events.length, 2, "Received the expected number of network events");
+
+ // Since the auth prompt was canceled we should also receive the security
+ // information and the response content.
+ await BrowserTestUtils.waitForCondition(
+ () => events[1].hasResponseContent && events[1].hasSecurityInfo
+ );
+
+ // The auth prompt should not be displayed since the authentication was
+ // fulfilled.
+ ok(
+ !getTabAuthPrompts(tab).length,
+ "The auth prompt was handled by the network event owner"
+ );
+
+ assertEventOwner(events[1], {
+ hasResponseStart: true,
+ hasEventTimings: true,
+ hasSecurityInfo: true,
+ hasServerTimings: true,
+ hasResponseContent: true,
+ });
+
+ is(events[1].responseContent, "success", "Auth prompt was successful");
+
+ networkObserver.destroy();
+ gBrowser.removeTab(tab);
+});
+
+function assertEventOwner(event, expectedFlags) {
+ is(
+ event.hasResponseStart,
+ !!expectedFlags.hasResponseStart,
+ "network event has the expected ResponseStart flag"
+ );
+ is(
+ event.hasEventTimings,
+ !!expectedFlags.hasEventTimings,
+ "network event has the expected EventTimings flag"
+ );
+ is(
+ event.hasAuthPrompt,
+ !!expectedFlags.hasAuthPrompt,
+ "network event has the expected AuthPrompt flag"
+ );
+ is(
+ event.hasResponseCache,
+ !!expectedFlags.hasResponseCache,
+ "network event has the expected ResponseCache flag"
+ );
+ is(
+ event.hasResponseContent,
+ !!expectedFlags.hasResponseContent,
+ "network event has the expected ResponseContent flag"
+ );
+ is(
+ event.hasSecurityInfo,
+ !!expectedFlags.hasSecurityInfo,
+ "network event has the expected SecurityInfo flag"
+ );
+ is(
+ event.hasServerTimings,
+ !!expectedFlags.hasServerTimings,
+ "network event has the expected ServerTimings flag"
+ );
+}
+
+function getTabAuthPrompts(tab) {
+ const tabDialogBox = gBrowser.getTabDialogBox(tab.linkedBrowser);
+ return tabDialogBox
+ .getTabDialogManager()
+ ._dialogs.filter(
+ d => d.frameContentWindow?.Dialog.args.promptType == "promptUserAndPass"
+ );
+}
+
+function waitForAuthPrompt(tab) {
+ return PromptTestUtils.waitForPrompt(tab.linkedBrowser, {
+ modalType: Services.prompt.MODAL_TYPE_TAB,
+ promptType: "promptUserAndPass",
+ });
+}
+
+// Cleanup potentially stored credentials before running any test.
+function cleanupAuthManager() {
+ const authManager = SpecialPowers.Cc[
+ "@mozilla.org/network/http-auth-manager;1"
+ ].getService(SpecialPowers.Ci.nsIHttpAuthManager);
+ authManager.clearAll();
+}
diff --git a/devtools/shared/network-observer/test/browser/browser_networkobserver_invalid_constructor.js b/devtools/shared/network-observer/test/browser/browser_networkobserver_invalid_constructor.js
new file mode 100644
index 0000000000..76a93d938a
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/browser_networkobserver_invalid_constructor.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check that the NetworkObserver constructor validates its arguments.
+add_task(async function testInvalidConstructorArguments() {
+ Assert.throws(
+ () => new NetworkObserver(),
+ /Expected "ignoreChannelFunction" to be a function, got undefined/,
+ "NetworkObserver constructor should throw if no argument was provided"
+ );
+
+ Assert.throws(
+ () => new NetworkObserver({}),
+ /Expected "ignoreChannelFunction" to be a function, got undefined/,
+ "NetworkObserver constructor should throw if ignoreChannelFunction was not provided"
+ );
+
+ const invalidValues = [null, true, false, 12, "str", ["arr"], { obj: "obj" }];
+ for (const invalidValue of invalidValues) {
+ Assert.throws(
+ () => new NetworkObserver({ ignoreChannelFunction: invalidValue }),
+ /Expected "ignoreChannelFunction" to be a function, got/,
+ `NetworkObserver constructor should throw if a(n) ${typeof invalidValue} was provided for ignoreChannelFunction`
+ );
+ }
+
+ const EMPTY_FN = () => {};
+ Assert.throws(
+ () => new NetworkObserver({ ignoreChannelFunction: EMPTY_FN }),
+ /Expected "onNetworkEvent" to be a function, got undefined/,
+ "NetworkObserver constructor should throw if onNetworkEvent was not provided"
+ );
+
+ // Now we will pass a function for `ignoreChannelFunction`, and will do the
+ // same tests for onNetworkEvent
+ for (const invalidValue of invalidValues) {
+ Assert.throws(
+ () =>
+ new NetworkObserver({
+ ignoreChannelFunction: EMPTY_FN,
+ onNetworkEvent: invalidValue,
+ }),
+ /Expected "onNetworkEvent" to be a function, got/,
+ `NetworkObserver constructor should throw if a(n) ${typeof invalidValue} was provided for onNetworkEvent`
+ );
+ }
+});
diff --git a/devtools/shared/network-observer/test/browser/browser_networkobserver_override.js b/devtools/shared/network-observer/test/browser/browser_networkobserver_override.js
new file mode 100644
index 0000000000..3b00c4b2e9
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/browser_networkobserver_override.js
@@ -0,0 +1,179 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URL = URL_ROOT + "doc_network-observer.html";
+const REQUEST_URL =
+ URL_ROOT + `sjs_network-observer-test-server.sjs?sts=200&fmt=html`;
+const GZIPPED_REQUEST_URL = URL_ROOT + `gzipped.sjs`;
+const OVERRIDE_FILENAME = "override.js";
+const OVERRIDE_HTML_FILENAME = "override.html";
+
+add_task(async function testLocalOverride() {
+ await addTab(TEST_URL);
+
+ let eventsCount = 0;
+ const networkObserver = new NetworkObserver({
+ ignoreChannelFunction: channel => channel.URI.spec !== REQUEST_URL,
+ onNetworkEvent: event => {
+ info("received a network event");
+ eventsCount++;
+ return createNetworkEventOwner(event);
+ },
+ });
+
+ const overrideFile = getChromeDir(getResolvedURI(gTestPath));
+ overrideFile.append(OVERRIDE_FILENAME);
+ info(" override " + REQUEST_URL + " to " + overrideFile.path + "\n");
+ networkObserver.override(REQUEST_URL, overrideFile.path);
+
+ info("Assert that request and cached request are overriden");
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [REQUEST_URL],
+ async _url => {
+ const request = await content.wrappedJSObject.fetch(_url);
+ const requestcontent = await request.text();
+ is(
+ requestcontent,
+ `"use strict";\ndocument.title = "evaluated";\n`,
+ "the request content has been overriden"
+ );
+ const secondRequest = await content.wrappedJSObject.fetch(_url);
+ const secondRequestcontent = await secondRequest.text();
+ is(
+ secondRequestcontent,
+ `"use strict";\ndocument.title = "evaluated";\n`,
+ "the cached request content has been overriden"
+ );
+ }
+ );
+
+ info("Assert that JS scripts can be overriden");
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [REQUEST_URL],
+ async _url => {
+ const script = await content.document.createElement("script");
+ const onLoad = new Promise(resolve =>
+ script.addEventListener("load", resolve, { once: true })
+ );
+ script.src = _url;
+ content.document.body.appendChild(script);
+ await onLoad;
+ is(
+ content.document.title,
+ "evaluated",
+ "The <script> tag content has been overriden and correctly evaluated"
+ );
+ }
+ );
+
+ await BrowserTestUtils.waitForCondition(() => eventsCount >= 1);
+
+ networkObserver.destroy();
+});
+
+add_task(async function testHtmlFileOverride() {
+ let eventsCount = 0;
+ const networkObserver = new NetworkObserver({
+ ignoreChannelFunction: channel => channel.URI.spec !== TEST_URL,
+ onNetworkEvent: event => {
+ info("received a network event");
+ eventsCount++;
+ return createNetworkEventOwner(event);
+ },
+ });
+
+ const overrideFile = getChromeDir(getResolvedURI(gTestPath));
+ overrideFile.append(OVERRIDE_HTML_FILENAME);
+ info(" override " + TEST_URL + " to " + overrideFile.path + "\n");
+ networkObserver.override(TEST_URL, overrideFile.path);
+
+ await addTab(TEST_URL);
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [TEST_URL],
+ async pageUrl => {
+ is(
+ content.document.documentElement.outerHTML,
+ "<html><head></head><body>Overriden!\n</body></html>",
+ "The content of the HTML has been overriden"
+ );
+ // For now, all overriden request have their location changed to an internal data: URI
+ // Bug xxx aims at keeping the original URI.
+ todo_is(
+ content.location.href,
+ pageUrl,
+ "The location of the page is still the original one"
+ );
+ }
+ );
+ await BrowserTestUtils.waitForCondition(() => eventsCount >= 1);
+ networkObserver.destroy();
+});
+
+// Exact same test, but with a gzipped request, which requires very special treatment
+add_task(async function testLocalOverrideGzipped() {
+ await addTab(TEST_URL);
+
+ let eventsCount = 0;
+ const networkObserver = new NetworkObserver({
+ ignoreChannelFunction: channel => channel.URI.spec !== GZIPPED_REQUEST_URL,
+ onNetworkEvent: event => {
+ info("received a network event");
+ eventsCount++;
+ return createNetworkEventOwner(event);
+ },
+ });
+
+ const overrideFile = getChromeDir(getResolvedURI(gTestPath));
+ overrideFile.append(OVERRIDE_FILENAME);
+ info(" override " + GZIPPED_REQUEST_URL + " to " + overrideFile.path + "\n");
+ networkObserver.override(GZIPPED_REQUEST_URL, overrideFile.path);
+
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [GZIPPED_REQUEST_URL],
+ async _url => {
+ const request = await content.wrappedJSObject.fetch(_url);
+ const requestcontent = await request.text();
+ is(
+ requestcontent,
+ `"use strict";\ndocument.title = "evaluated";\n`,
+ "the request content has been overriden"
+ );
+ const secondRequest = await content.wrappedJSObject.fetch(_url);
+ const secondRequestcontent = await secondRequest.text();
+ is(
+ secondRequestcontent,
+ `"use strict";\ndocument.title = "evaluated";\n`,
+ "the cached request content has been overriden"
+ );
+ }
+ );
+
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [GZIPPED_REQUEST_URL],
+ async _url => {
+ const script = await content.document.createElement("script");
+ const onLoad = new Promise(resolve =>
+ script.addEventListener("load", resolve, { once: true })
+ );
+ script.src = _url;
+ content.document.body.appendChild(script);
+ await onLoad;
+ is(
+ content.document.title,
+ "evaluated",
+ "The <script> tag content has been overriden and correctly evaluated"
+ );
+ }
+ );
+
+ await BrowserTestUtils.waitForCondition(() => eventsCount >= 1);
+
+ networkObserver.destroy();
+});
diff --git a/devtools/shared/network-observer/test/browser/browser_networkobserver_serviceworker.js b/devtools/shared/network-observer/test/browser/browser_networkobserver_serviceworker.js
new file mode 100644
index 0000000000..1680dc6005
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/browser_networkobserver_serviceworker.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that all the expected service worker requests are received
+// by the network observer.
+add_task(async function testServiceWorkerSuccessRequests() {
+ await addTab(URL_ROOT + "doc_network-observer.html");
+
+ const REQUEST_URL =
+ URL_ROOT + `sjs_network-observer-test-server.sjs?sts=200&fmt=`;
+
+ const EXPECTED_REQUESTS = [
+ // The main service worker script request
+ `https://example.com/browser/devtools/shared/network-observer/test/browser/serviceworker.js`,
+ // The requests intercepted by the service worker
+ REQUEST_URL + "js",
+ REQUEST_URL + "css",
+ // The request initiated by the service worker
+ REQUEST_URL + "json",
+ ];
+
+ const onNetworkEvents = waitForNetworkEvents(null, 4);
+
+ info("Register the service worker and send requests...");
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [REQUEST_URL],
+ async url => {
+ await content.wrappedJSObject.registerServiceWorker();
+ content.wrappedJSObject.fetch(url + "js");
+ content.wrappedJSObject.fetch(url + "css");
+ }
+ );
+ const events = await onNetworkEvents;
+
+ is(events.length, 4, "Received the expected number of network events");
+ for (const { options, channel } of events) {
+ info(`Assert the info for the request from ${channel.URI.spec}`);
+ ok(
+ EXPECTED_REQUESTS.includes(channel.URI.spec),
+ `The request for ${channel.URI.spec} is an expected service worker request`
+ );
+ Assert.notStrictEqual(
+ channel.loadInfo.browsingContextID,
+ 0,
+ `The request for ${channel.URI.spec} has a Browsing Context ID of ${channel.loadInfo.browsingContextID}`
+ );
+ // The main service worker script request is not from the service worker
+ if (channel.URI.spec.includes("serviceworker.js")) {
+ ok(
+ !options.fromServiceWorker,
+ `The request for ${channel.URI.spec} is not from the service worker\n`
+ );
+ } else {
+ ok(
+ options.fromServiceWorker,
+ `The request for ${channel.URI.spec} is from the service worker\n`
+ );
+ }
+ }
+
+ info("Unregistering the service worker...");
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ await content.wrappedJSObject.unregisterServiceWorker();
+ });
+});
+
+// Tests that the expected failed service worker request is received by the network observer.
+add_task(async function testServiceWorkerFailedRequests() {
+ await addTab(URL_ROOT + "doc_network-observer-missing-service-worker.html");
+
+ const REQUEST_URL =
+ URL_ROOT + `sjs_network-observer-test-server.sjs?sts=200&fmt=js`;
+
+ const EXPECTED_REQUESTS = [
+ // The main service worker script request which should be missing
+ "https://example.com/browser/devtools/shared/network-observer/test/browser/serviceworker-missing.js",
+ // A notrmal request
+ "https://example.com/browser/devtools/shared/network-observer/test/browser/sjs_network-observer-test-server.sjs?sts=200&fmt=js",
+ ];
+
+ const onNetworkEvents = waitForNetworkEvents(null, 2);
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [REQUEST_URL],
+ async url => {
+ await content.wrappedJSObject.registerServiceWorker();
+ content.wrappedJSObject.fetch(url);
+ }
+ );
+
+ const events = await onNetworkEvents;
+ is(events.length, 2, "Received the expected number of network events");
+
+ for (const { options, channel } of events) {
+ info(`Assert the info for the request from ${channel.URI.spec}`);
+ ok(
+ EXPECTED_REQUESTS.includes(channel.URI.spec),
+ `The request for ${channel.URI.spec} is an expected request`
+ );
+ Assert.notStrictEqual(
+ channel.loadInfo.browsingContextID,
+ 0,
+ `The request for ${channel.URI.spec} has a Browsing Context ID of ${channel.loadInfo.browsingContextID}`
+ );
+ ok(
+ !options.fromServiceWorker,
+ `The request for ${channel.URI.spec} is not from the service worker\n`
+ );
+ }
+});
diff --git a/devtools/shared/network-observer/test/browser/doc_network-observer-missing-service-worker.html b/devtools/shared/network-observer/test/browser/doc_network-observer-missing-service-worker.html
new file mode 100644
index 0000000000..396e51677c
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/doc_network-observer-missing-service-worker.html
@@ -0,0 +1,32 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Observer missing service worker test page</title>
+ </head>
+
+ <body>
+ <p>Network Observer test page</p>
+ <script type="text/javascript">
+ /* exported registerServiceWorker */
+ "use strict";
+
+ function registerServiceWorker() {
+ const sw = navigator.serviceWorker;
+ // NOTE: This service worker file does not exist which enables testing
+ // that a 404 requests is received.
+ return sw.register("serviceworker-missing.js")
+ .then(registration => {
+ throw new Error("The Service Worker file should not exist");
+ }).catch(err => {
+ console.log("Registration failed as expected");
+ });
+ }
+ </script>
+ </body>
+</html>
diff --git a/devtools/shared/network-observer/test/browser/doc_network-observer.html b/devtools/shared/network-observer/test/browser/doc_network-observer.html
new file mode 100644
index 0000000000..2ca400e0ae
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/doc_network-observer.html
@@ -0,0 +1,49 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+ <meta http-equiv="Pragma" content="no-cache" />
+ <meta http-equiv="Expires" content="0" />
+ <title>Network Observer test page</title>
+ </head>
+ <body>
+ <p>Network Observer test page</p>
+ <script type="text/javascript">
+ /* exported registerServiceWorker, unregisterServiceWorker */
+ "use strict";
+
+ let swRegistration;
+
+ function registerServiceWorker() {
+ const sw = navigator.serviceWorker;
+ return sw.register("serviceworker.js")
+ .then(registration => {
+ swRegistration = registration;
+ console.log("Registered, scope is:", registration.scope);
+ return sw.ready;
+ }).then(() => {
+ // wait until the page is controlled
+ return new Promise(resolve => {
+ if (sw.controller) {
+ resolve();
+ } else {
+ sw.addEventListener("controllerchange", function () {
+ resolve();
+ }, { once: true });
+ }
+ });
+ }).catch(err => {
+ console.error("Registration failed");
+ });
+ }
+
+ function unregisterServiceWorker() {
+ return swRegistration.unregister();
+ }
+ </script>
+ </body>
+</html>
diff --git a/devtools/shared/network-observer/test/browser/gzipped.sjs b/devtools/shared/network-observer/test/browser/gzipped.sjs
new file mode 100644
index 0000000000..09d0b249b1
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/gzipped.sjs
@@ -0,0 +1,44 @@
+"use strict";
+
+function gzipCompressString(string, obs) {
+ const scs = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ const listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance(
+ Ci.nsIStreamLoader
+ );
+ listener.init(obs);
+ const converter = scs.asyncConvertData(
+ "uncompressed",
+ "gzip",
+ listener,
+ null
+ );
+ const stringStream = Cc[
+ "@mozilla.org/io/string-input-stream;1"
+ ].createInstance(Ci.nsIStringInputStream);
+ stringStream.data = string;
+ converter.onStartRequest(null, null);
+ converter.onDataAvailable(null, stringStream, 0, string.length);
+ converter.onStopRequest(null, null, null);
+}
+
+const ORIGINAL_JS_CONTENT = `console.log("original javascript content");`;
+
+function handleRequest(request, response) {
+ response.processAsync();
+
+ // Generate data
+ response.setHeader("Content-Type", "application/javascript", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+
+ const observer = {
+ onStreamComplete(loader, context, status, length, result) {
+ const buffer = String.fromCharCode.apply(this, result);
+ response.setHeader("Content-Length", "" + buffer.length, false);
+ response.write(buffer);
+ response.finish();
+ },
+ };
+ gzipCompressString(ORIGINAL_JS_CONTENT, observer);
+}
diff --git a/devtools/shared/network-observer/test/browser/head.js b/devtools/shared/network-observer/test/browser/head.js
new file mode 100644
index 0000000000..deb7becff6
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/head.js
@@ -0,0 +1,123 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ NetworkObserver:
+ "resource://devtools/shared/network-observer/NetworkObserver.sys.mjs",
+});
+
+const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
+const CHROME_URL_ROOT = TEST_DIR + "/";
+const URL_ROOT = CHROME_URL_ROOT.replace(
+ "chrome://mochitests/content/",
+ "https://example.com/"
+);
+
+/**
+ * Load the provided url in an existing browser.
+ * Returns a promise which will resolve when the page is loaded.
+ *
+ * @param {Browser} browser
+ * The browser element where the URL should be loaded.
+ * @param {String} url
+ * The URL to load in the new tab
+ */
+async function loadURL(browser, url) {
+ const loaded = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.startLoadingURIString(browser, url);
+ return loaded;
+}
+
+/**
+ * Create a new foreground tab loading the provided url.
+ * Returns a promise which will resolve when the page is loaded.
+ *
+ * @param {String} url
+ * The URL to load in the new tab
+ */
+async function addTab(url) {
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ registerCleanupFunction(() => {
+ gBrowser.removeTab(tab);
+ });
+ return tab;
+}
+
+/**
+ * Base network event owner class implementing all mandatory callbacks and
+ * keeping track of which callbacks have been called.
+ */
+class NetworkEventOwner {
+ hasEventTimings = false;
+ hasResponseCache = false;
+ hasResponseContent = false;
+ hasResponseStart = false;
+ hasSecurityInfo = false;
+ hasServerTimings = false;
+
+ addEventTimings() {
+ this.hasEventTimings = true;
+ }
+ addResponseCache() {
+ this.hasResponseCache = true;
+ }
+ addResponseContent() {
+ this.hasResponseContent = true;
+ }
+ addResponseStart() {
+ this.hasResponseStart = true;
+ }
+ addSecurityInfo() {
+ this.hasSecurityInfo = true;
+ }
+ addServerTimings() {
+ this.hasServerTimings = true;
+ }
+ addServiceWorkerTimings() {
+ this.hasServiceWorkerTimings = true;
+ }
+}
+
+/**
+ * Create a simple network event owner, with mock implementations of all
+ * the expected APIs for a NetworkEventOwner.
+ */
+function createNetworkEventOwner(event) {
+ return new NetworkEventOwner();
+}
+
+/**
+ * Wait for network events matching the provided URL, until the count reaches
+ * the provided expected count.
+ *
+ * @param {string|null} expectedUrl
+ * The URL which should be monitored by the NetworkObserver.If set to null watch for
+ * all requests
+ * @param {number} expectedRequestsCount
+ * How many different events (requests) are expected.
+ * @returns {Promise}
+ * A promise which will resolve with an array of network event owners, when
+ * the expected event count is reached.
+ */
+async function waitForNetworkEvents(expectedUrl = null, expectedRequestsCount) {
+ const events = [];
+ const networkObserver = new NetworkObserver({
+ ignoreChannelFunction: channel =>
+ expectedUrl ? channel.URI.spec !== expectedUrl : false,
+ onNetworkEvent: () => {
+ info("waitForNetworkEvents received a new event");
+ const owner = createNetworkEventOwner();
+ events.push(owner);
+ return owner;
+ },
+ });
+ registerCleanupFunction(() => networkObserver.destroy());
+
+ info("Wait until the events count reaches " + expectedRequestsCount);
+ await BrowserTestUtils.waitForCondition(
+ () => events.length >= expectedRequestsCount
+ );
+ return events;
+}
diff --git a/devtools/shared/network-observer/test/browser/override.html b/devtools/shared/network-observer/test/browser/override.html
new file mode 100644
index 0000000000..0e3878e313
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/override.html
@@ -0,0 +1 @@
+<html><head></head><body>Overriden!</body></html>
diff --git a/devtools/shared/network-observer/test/browser/override.js b/devtools/shared/network-observer/test/browser/override.js
new file mode 100644
index 0000000000..7b000fcd0f
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/override.js
@@ -0,0 +1,2 @@
+"use strict";
+document.title = "evaluated";
diff --git a/devtools/shared/network-observer/test/browser/serviceworker.js b/devtools/shared/network-observer/test/browser/serviceworker.js
new file mode 100644
index 0000000000..3389581fb0
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/serviceworker.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+self.addEventListener("activate", async event => {
+ (
+ await fetch(
+ "https://example.com/browser/devtools/shared/network-observer/test/browser/sjs_network-observer-test-server.sjs?sts=200&fmt=json"
+ )
+ )
+ .json()
+ .then(() => console.log("json downloaded"));
+ // start controlling the already loaded page
+ event.waitUntil(self.clients.claim());
+});
+
+self.addEventListener("fetch", event => {
+ const response = new Response("Service worker response", {
+ statusText: "OK",
+ });
+ event.respondWith(response);
+});
diff --git a/devtools/shared/network-observer/test/browser/sjs_network-auth-listener-test-server.sjs b/devtools/shared/network-observer/test/browser/sjs_network-auth-listener-test-server.sjs
new file mode 100644
index 0000000000..028a26ebfe
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/sjs_network-auth-listener-test-server.sjs
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function handleRequest(request, response) {
+ let body;
+
+ // Expect guest/guest as correct credentials, but `btoa` is unavailable in sjs
+ // "Z3Vlc3Q6Z3Vlc3Q=" == btoa("guest:guest")
+ const expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
+
+ // correct login credentials provided
+ if (
+ request.hasHeader("Authorization") &&
+ request.getHeader("Authorization") == expectedHeader
+ ) {
+ response.setStatusLine(request.httpVersion, 200, "OK, authorized");
+ response.setHeader("Content-Type", "text", false);
+
+ body = "success";
+ } else {
+ // incorrect credentials
+ response.setStatusLine(request.httpVersion, 401, "Unauthorized");
+ response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
+ response.setHeader("Content-Type", "text", false);
+
+ body = "failed";
+ }
+ response.bodyOutputStream.write(body, body.length);
+}
diff --git a/devtools/shared/network-observer/test/browser/sjs_network-observer-test-server.sjs b/devtools/shared/network-observer/test/browser/sjs_network-observer-test-server.sjs
new file mode 100644
index 0000000000..b0947cadd1
--- /dev/null
+++ b/devtools/shared/network-observer/test/browser/sjs_network-observer-test-server.sjs
@@ -0,0 +1,196 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Simple server which can handle several response types and states.
+// Trimmed down from devtools/client/netmonitor/test/sjs_content-type-test-server.sjs
+// Additional features can be ported if needed.
+function handleRequest(request, response) {
+ response.processAsync();
+
+ const params = request.queryString.split("&");
+ const format = (params.filter(s => s.includes("fmt="))[0] || "").split(
+ "="
+ )[1];
+ const status =
+ (params.filter(s => s.includes("sts="))[0] || "").split("=")[1] || 200;
+
+ const cacheExpire = 60; // seconds
+
+ function setCacheHeaders() {
+ if (status != 304) {
+ response.setHeader(
+ "Cache-Control",
+ "no-cache, no-store, must-revalidate"
+ );
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Expires", "0");
+ return;
+ }
+
+ response.setHeader("Expires", Date(Date.now() + cacheExpire * 1000), false);
+ }
+
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+
+ timer.initWithCallback(
+ // eslint-disable-next-line complexity
+ () => {
+ // to avoid garbage collection
+ timer = null;
+ switch (format) {
+ case "txt": {
+ response.setStatusLine(request.httpVersion, status, "DA DA DA");
+ response.setHeader("Content-Type", "text/plain", false);
+ setCacheHeaders();
+
+ function convertToUtf8(str) {
+ return String.fromCharCode(...new TextEncoder().encode(str));
+ }
+
+ // This script must be evaluated as UTF-8 for this to write out the
+ // bytes of the string in UTF-8. If it's evaluated as Latin-1, the
+ // written bytes will be the result of UTF-8-encoding this string
+ // *twice*.
+ const data = "Братан, ты вообще качаешься?";
+ const stringOfUtf8Bytes = convertToUtf8(data);
+ response.write(stringOfUtf8Bytes);
+
+ response.finish();
+ break;
+ }
+ case "xml": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/xml; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("<label value='greeting'>Hello XML!</label>");
+ response.finish();
+ break;
+ }
+ case "html": {
+ const content = (
+ params.filter(s => s.includes("res="))[0] || ""
+ ).split("=")[1];
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ setCacheHeaders();
+ response.write(content || "<p>Hello HTML!</p>");
+ response.finish();
+ break;
+ }
+ case "xhtml": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader(
+ "Content-Type",
+ "application/xhtml+xml; charset=utf-8",
+ false
+ );
+ setCacheHeaders();
+ response.write("<p>Hello XHTML!</p>");
+ response.finish();
+ break;
+ }
+ case "html-long": {
+ const str = new Array(102400 /* 100 KB in bytes */).join(".");
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("<p>" + str + "</p>");
+ response.finish();
+ break;
+ }
+ case "css": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "text/css; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("body:pre { content: 'Hello CSS!' }");
+ response.finish();
+ break;
+ }
+ case "js": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader(
+ "Content-Type",
+ "application/javascript; charset=utf-8",
+ false
+ );
+ setCacheHeaders();
+ response.write("function() { return 'Hello JS!'; }");
+ response.finish();
+ break;
+ }
+ case "json": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader(
+ "Content-Type",
+ "application/json; charset=utf-8",
+ false
+ );
+ setCacheHeaders();
+ response.write('{ "greeting": "Hello JSON!" }');
+ response.finish();
+ break;
+ }
+
+ case "font": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "font/woff", false);
+ setCacheHeaders();
+ response.finish();
+ break;
+ }
+ case "image": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+ setCacheHeaders();
+ response.finish();
+ break;
+ }
+ case "application-ogg": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "application/ogg", false);
+ setCacheHeaders();
+ response.finish();
+ break;
+ }
+ case "audio": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "audio/ogg", false);
+ setCacheHeaders();
+ response.finish();
+ break;
+ }
+ case "video": {
+ response.setStatusLine(request.httpVersion, status, "OK");
+ response.setHeader("Content-Type", "video/webm", false);
+ setCacheHeaders();
+ response.finish();
+ break;
+ }
+ case "ws": {
+ response.setStatusLine(
+ request.httpVersion,
+ 101,
+ "Switching Protocols"
+ );
+ response.setHeader("Connection", "upgrade", false);
+ response.setHeader("Upgrade", "websocket", false);
+ setCacheHeaders();
+ response.finish();
+ break;
+ }
+ default: {
+ response.setStatusLine(request.httpVersion, 404, "Not Found");
+ response.setHeader("Content-Type", "text/html; charset=utf-8", false);
+ setCacheHeaders();
+ response.write("<blink>Not Found</blink>");
+ response.finish();
+ break;
+ }
+ }
+ },
+ 10,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ ); // Make sure this request takes a few ms.
+}