summaryrefslogtreecommitdiffstats
path: root/netwerk/test/browser
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/test/browser')
-rw-r--r--netwerk/test/browser/browser.ini11
-rw-r--r--netwerk/test/browser/browser_NetUtil.js92
-rw-r--r--netwerk/test/browser/browser_about_cache.js71
-rw-r--r--netwerk/test/browser/browser_child_resource.js256
-rw-r--r--netwerk/test/browser/browser_nsIFormPOSTActionChannel.js284
-rw-r--r--netwerk/test/browser/browser_post_file.js101
-rw-r--r--netwerk/test/browser/dummy.html7
7 files changed, 822 insertions, 0 deletions
diff --git a/netwerk/test/browser/browser.ini b/netwerk/test/browser/browser.ini
new file mode 100644
index 000000000..8611891fd
--- /dev/null
+++ b/netwerk/test/browser/browser.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+support-files =
+ dummy.html
+
+[browser_about_cache.js]
+[browser_NetUtil.js]
+[browser_child_resource.js]
+skip-if = e10s && debug && os == "linux" && bits == 64
+[browser_post_file.js]
+[browser_nsIFormPOSTActionChannel.js]
+skip-if = e10s # protocol handler and channel does not work in content process
diff --git a/netwerk/test/browser/browser_NetUtil.js b/netwerk/test/browser/browser_NetUtil.js
new file mode 100644
index 000000000..a6c4f2bcd
--- /dev/null
+++ b/netwerk/test/browser/browser_NetUtil.js
@@ -0,0 +1,92 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ // We overload this test to include verifying that httpd.js is
+ // importable as a testing-only JS module.
+ Components.utils.import("resource://testing-common/httpd.js", {});
+
+ nextTest();
+}
+
+function nextTest() {
+ if (tests.length)
+ executeSoon(tests.shift());
+ else
+ executeSoon(finish);
+}
+
+var tests = [
+ test_asyncFetchBadCert,
+];
+
+function test_asyncFetchBadCert() {
+ // Try a load from an untrusted cert, with errors supressed
+ NetUtil.asyncFetch({
+ uri: "https://untrusted.example.com",
+ loadUsingSystemPrincipal: true
+ }, function (aInputStream, aStatusCode, aRequest) {
+ ok(!Components.isSuccessCode(aStatusCode), "request failed");
+ ok(aRequest instanceof Ci.nsIHttpChannel, "request is an nsIHttpChannel");
+
+ // Now try again with a channel whose notificationCallbacks doesn't suprress errors
+ let channel = NetUtil.newChannel({
+ uri: "https://untrusted.example.com",
+ loadUsingSystemPrincipal: true});
+ channel.notificationCallbacks = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIProgressEventSink,
+ Ci.nsIInterfaceRequestor]),
+ getInterface: function (aIID) { return this.QueryInterface(aIID); },
+ onProgress: function () {},
+ onStatus: function () {}
+ };
+ NetUtil.asyncFetch(channel, function (aInputStream, aStatusCode, aRequest) {
+ ok(!Components.isSuccessCode(aStatusCode), "request failed");
+ ok(aRequest instanceof Ci.nsIHttpChannel, "request is an nsIHttpChannel");
+
+ // Now try a valid request
+ NetUtil.asyncFetch({
+ uri: "https://example.com",
+ loadUsingSystemPrincipal: true
+ }, function (aInputStream, aStatusCode, aRequest) {
+ info("aStatusCode for valid request: " + aStatusCode);
+ ok(Components.isSuccessCode(aStatusCode), "request succeeded");
+ ok(aRequest instanceof Ci.nsIHttpChannel, "request is an nsIHttpChannel");
+ ok(aRequest.requestSucceeded, "HTTP request succeeded");
+
+ nextTest();
+ });
+ });
+ });
+}
+
+function WindowListener(aURL, aCallback) {
+ this.callback = aCallback;
+ this.url = aURL;
+}
+WindowListener.prototype = {
+ onOpenWindow: function(aXULWindow) {
+ var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ var self = this;
+ domwindow.addEventListener("load", function() {
+ domwindow.removeEventListener("load", arguments.callee, false);
+
+ if (domwindow.document.location.href != self.url)
+ return;
+
+ // Allow other window load listeners to execute before passing to callback
+ executeSoon(function() {
+ self.callback(domwindow);
+ });
+ }, false);
+ },
+ onCloseWindow: function(aXULWindow) {},
+ onWindowTitleChange: function(aXULWindow, aNewTitle) {}
+}
diff --git a/netwerk/test/browser/browser_about_cache.js b/netwerk/test/browser/browser_about_cache.js
new file mode 100644
index 000000000..38cfa3d02
--- /dev/null
+++ b/netwerk/test/browser/browser_about_cache.js
@@ -0,0 +1,71 @@
+"use strict";
+
+/**
+ * Open a dummy page, then open about:cache and verify the opened page shows up in the cache.
+ */
+add_task(function*() {
+ const kRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
+ "https://example.com/");
+ const kTestPage = kRoot + "dummy.html";
+ // Open the dummy page to get it cached.
+ let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, kTestPage, true);
+ yield BrowserTestUtils.removeTab(tab);
+
+ tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:cache", true);
+ let expectedPageCheck = function(uri) {
+ info("Saw load for " + uri);
+ // Can't easily use searchParms and new URL() because it's an about: URI...
+ return uri.startsWith("about:cache?") && uri.includes("storage=disk");
+ };
+ let diskPageLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, expectedPageCheck);
+ yield ContentTask.spawn(tab.linkedBrowser, null, function() {
+ ok(!content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache should not have system principal");
+ let principalURI = content.document.nodePrincipal.URI;
+ let channel = content.document.docShell.currentDocumentChannel;
+ ok(!channel.loadInfo.loadingPrincipal, "Loading principal should be null.");
+ is(principalURI && principalURI.spec, content.document.location.href, "Principal matches location");
+ let links = [... content.document.querySelectorAll("a[href*=disk]")];
+ is(links.length, 1, "Should have 1 link to the disk entries");
+ links[0].click();
+ });
+ yield diskPageLoaded;
+ info("about:cache disk subpage loaded");
+
+ expectedPageCheck = function(uri) {
+ info("Saw load for " + uri);
+ return uri.startsWith("about:cache-entry") && uri.includes("dummy.html");
+ };
+ let triggeringURISpec = tab.linkedBrowser.currentURI.spec;
+ let entryLoaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, expectedPageCheck);
+ yield ContentTask.spawn(tab.linkedBrowser, kTestPage, function(kTestPage) {
+ ok(!content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache with query params should still not have system principal");
+ let principalURI = content.document.nodePrincipal.URI;
+ is(principalURI && principalURI.spec, content.document.location.href, "Principal matches location");
+ let channel = content.document.docShell.currentDocumentChannel;
+ principalURI = channel.loadInfo.triggeringPrincipal.URI;
+ is(principalURI && principalURI.spec, "about:cache", "Triggering principal matches previous location");
+ ok(!channel.loadInfo.loadingPrincipal, "Loading principal should be null.");
+ let links = [... content.document.querySelectorAll("a[href*='" + kTestPage + "']")];
+ is(links.length, 1, "Should have 1 link to the entry for " + kTestPage);
+ links[0].click();
+ });
+ yield entryLoaded;
+ info("about:cache entry loaded");
+
+
+ yield ContentTask.spawn(tab.linkedBrowser, triggeringURISpec, function(triggeringURISpec) {
+ ok(!content.document.nodePrincipal.isSystemPrincipal,
+ "about:cache-entry should also not have system principal");
+ let principalURI = content.document.nodePrincipal.URI;
+ is(principalURI && principalURI.spec, content.document.location.href, "Principal matches location");
+ let channel = content.document.docShell.currentDocumentChannel;
+ principalURI = channel.loadInfo.triggeringPrincipal.URI;
+ is(principalURI && principalURI.spec, triggeringURISpec, "Triggering principal matches previous location");
+ ok(!channel.loadInfo.loadingPrincipal, "Loading principal should be null.");
+ ok(content.document.querySelectorAll("th").length,
+ "Should have several table headers with data.");
+ });
+ yield BrowserTestUtils.removeTab(tab);
+});
diff --git a/netwerk/test/browser/browser_child_resource.js b/netwerk/test/browser/browser_child_resource.js
new file mode 100644
index 000000000..098e6bd84
--- /dev/null
+++ b/netwerk/test/browser/browser_child_resource.js
@@ -0,0 +1,256 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+// This must be loaded in the remote process for this test to be useful
+const TEST_URL = "http://example.com/browser/netwerk/test/browser/dummy.html";
+
+const expectedRemote = gMultiProcessBrowser ? "true" : "";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+const resProtocol = Cc["@mozilla.org/network/protocol;1?name=resource"]
+ .getService(Ci.nsIResProtocolHandler);
+
+function getMinidumpDirectory() {
+ var dir = Services.dirsvc.get('ProfD', Ci.nsIFile);
+ dir.append("minidumps");
+ return dir;
+}
+
+// This observer is needed so we can clean up all evidence of the crash so
+// the testrunner thinks things are peachy.
+var CrashObserver = {
+ observe: function(subject, topic, data) {
+ is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
+ ok(subject instanceof Ci.nsIPropertyBag2,
+ 'Subject implements nsIPropertyBag2.');
+ // we might see this called as the process terminates due to previous tests.
+ // We are only looking for "abnormal" exits...
+ if (!subject.hasKey("abnormal")) {
+ info("This is a normal termination and isn't the one we are looking for...");
+ return;
+ }
+
+ var dumpID;
+ if ('nsICrashReporter' in Ci) {
+ dumpID = subject.getPropertyAsAString('dumpID');
+ ok(dumpID, "dumpID is present and not an empty string");
+ }
+
+ if (dumpID) {
+ var minidumpDirectory = getMinidumpDirectory();
+ let file = minidumpDirectory.clone();
+ file.append(dumpID + '.dmp');
+ file.remove(true);
+ file = minidumpDirectory.clone();
+ file.append(dumpID + '.extra');
+ file.remove(true);
+ }
+ }
+}
+Services.obs.addObserver(CrashObserver, 'ipc:content-shutdown', false);
+
+registerCleanupFunction(() => {
+ Services.obs.removeObserver(CrashObserver, 'ipc:content-shutdown');
+});
+
+function frameScript() {
+ Components.utils.import("resource://gre/modules/Services.jsm");
+ let resProtocol = Components.classes["@mozilla.org/network/protocol;1?name=resource"]
+ .getService(Components.interfaces.nsIResProtocolHandler);
+
+ addMessageListener("Test:ResolveURI", function({ data: uri }) {
+ uri = Services.io.newURI(uri, null, null);
+ try {
+ let resolved = resProtocol.resolveURI(uri);
+ sendAsyncMessage("Test:ResolvedURI", resolved);
+ }
+ catch (e) {
+ sendAsyncMessage("Test:ResolvedURI", null);
+ }
+ });
+
+ addMessageListener("Test:Crash", function() {
+ dump("Crashing\n");
+ privateNoteIntentionalCrash();
+ Components.utils.import("resource://gre/modules/ctypes.jsm");
+ let zero = new ctypes.intptr_t(8);
+ let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
+ badptr.contents
+ });
+}
+
+function waitForEvent(obj, name, capturing, chromeEvent) {
+ info("Waiting for " + name);
+ return new Promise((resolve) => {
+ function listener(event) {
+ info("Saw " + name);
+ obj.removeEventListener(name, listener, capturing, chromeEvent);
+ resolve(event);
+ }
+
+ obj.addEventListener(name, listener, capturing, chromeEvent);
+ });
+}
+
+function resolveURI(uri) {
+ uri = Services.io.newURI(uri, null, null);
+ try {
+ return resProtocol.resolveURI(uri);
+ }
+ catch (e) {
+ return null;
+ }
+}
+
+function remoteResolveURI(uri) {
+ return new Promise((resolve) => {
+ let manager = gBrowser.selectedBrowser.messageManager;
+
+ function listener({ data: resolved }) {
+ manager.removeMessageListener("Test:ResolvedURI", listener);
+ resolve(resolved);
+ }
+
+ manager.addMessageListener("Test:ResolvedURI", listener);
+ manager.sendAsyncMessage("Test:ResolveURI", uri);
+ });
+}
+
+var loadTestTab = Task.async(function*() {
+ gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
+ let browser = gBrowser.selectedBrowser;
+ yield BrowserTestUtils.browserLoaded(browser);
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+ return browser;
+});
+
+// Restarts the child process by crashing it then reloading the tab
+var restart = Task.async(function*() {
+ let browser = gBrowser.selectedBrowser;
+ // If the tab isn't remote this would crash the main process so skip it
+ if (browser.getAttribute("remote") != "true")
+ return browser;
+
+ browser.messageManager.sendAsyncMessage("Test:Crash");
+ yield waitForEvent(browser, "AboutTabCrashedLoad", false, true);
+
+ browser.reload();
+
+ yield BrowserTestUtils.browserLoaded(browser);
+ is(browser.getAttribute("remote"), expectedRemote, "Browser should be in the right process");
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+ return browser;
+});
+
+// Sanity check that this test is going to be useful
+add_task(function*() {
+ let browser = yield loadTestTab();
+
+ // This must be loaded in the remote process for this test to be useful
+ is(browser.getAttribute("remote"), expectedRemote, "Browser should be in the right process");
+
+ let local = resolveURI("resource://gre/modules/Services.jsm");
+ let remote = yield remoteResolveURI("resource://gre/modules/Services.jsm");
+ is(local, remote, "Services.jsm should resolve in both processes");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Add a mapping, update it then remove it
+add_task(function*() {
+ let browser = yield loadTestTab();
+
+ info("Set");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/content", null, null));
+ let local = resolveURI("resource://testing/test.js");
+ let remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/content/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+ info("Change");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/skin", null, null));
+ local = resolveURI("resource://testing/test.js");
+ remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/skin/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/skin/test.js", "Should resolve in child process");
+
+ info("Clear");
+ resProtocol.setSubstitution("testing", null);
+ local = resolveURI("resource://testing/test.js");
+ remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, null, "Shouldn't resolve in main process");
+ is(remote, null, "Shouldn't resolve in child process");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Add a mapping, restart the child process then check it is still there
+add_task(function*() {
+ let browser = yield loadTestTab();
+
+ info("Set");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/content", null, null));
+ let local = resolveURI("resource://testing/test.js");
+ let remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/content/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+ yield restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/content/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+ info("Change");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/skin", null, null));
+
+ yield restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, "chrome://global/skin/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/skin/test.js", "Should resolve in child process");
+
+ info("Clear");
+ resProtocol.setSubstitution("testing", null);
+
+ yield restart();
+
+ local = resolveURI("resource://testing/test.js");
+ remote = yield remoteResolveURI("resource://testing/test.js");
+ is(local, null, "Shouldn't resolve in main process");
+ is(remote, null, "Shouldn't resolve in child process");
+
+ gBrowser.removeCurrentTab();
+});
+
+// Adding a mapping to a resource URI should work
+add_task(function*() {
+ let browser = yield loadTestTab();
+
+ info("Set");
+ resProtocol.setSubstitution("testing", Services.io.newURI("chrome://global/content", null, null));
+ resProtocol.setSubstitution("testing2", Services.io.newURI("resource://testing", null, null));
+ let local = resolveURI("resource://testing2/test.js");
+ let remote = yield remoteResolveURI("resource://testing2/test.js");
+ is(local, "chrome://global/content/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+ info("Clear");
+ resProtocol.setSubstitution("testing", null);
+ local = resolveURI("resource://testing2/test.js");
+ remote = yield remoteResolveURI("resource://testing2/test.js");
+ is(local, "chrome://global/content/test.js", "Should resolve in main process");
+ is(remote, "chrome://global/content/test.js", "Should resolve in child process");
+
+ resProtocol.setSubstitution("testing2", null);
+ local = resolveURI("resource://testing2/test.js");
+ remote = yield remoteResolveURI("resource://testing2/test.js");
+ is(local, null, "Shouldn't resolve in main process");
+ is(remote, null, "Shouldn't resolve in child process");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
new file mode 100644
index 000000000..150c4feca
--- /dev/null
+++ b/netwerk/test/browser/browser_nsIFormPOSTActionChannel.js
@@ -0,0 +1,284 @@
+/*
+ * Tests for bug 1241377: A channel with nsIFormPOSTActionChannel interface
+ * should be able to accept form POST.
+ */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+const SCHEME = "x-bug1241377";
+
+const FORM_BASE = SCHEME + "://dummy/form/";
+const NORMAL_FORM_URI = FORM_BASE + "normal.html";
+const UPLOAD_FORM_URI = FORM_BASE + "upload.html";
+const POST_FORM_URI = FORM_BASE + "post.html";
+
+const ACTION_BASE = SCHEME + "://dummy/action/";
+const NORMAL_ACTION_URI = ACTION_BASE + "normal.html";
+const UPLOAD_ACTION_URI = ACTION_BASE + "upload.html";
+const POST_ACTION_URI = ACTION_BASE + "post.html";
+
+function CustomProtocolHandler() {
+}
+CustomProtocolHandler.prototype = {
+ /** nsIProtocolHandler */
+ get scheme() {
+ return SCHEME;
+ },
+ get defaultPort() {
+ return -1;
+ },
+ get protocolFlags() {
+ return Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE;
+ },
+ newURI: function(aSpec, aOriginCharset, aBaseURI) {
+ var uri = Cc["@mozilla.org/network/standard-url;1"].
+ createInstance(Ci.nsIURI);
+ uri.spec = aSpec;
+ return uri;
+ },
+ newChannel2: function(aURI, aLoadInfo) {
+ return new CustomChannel(aURI, aLoadInfo);
+ },
+ newChannel: function(aURI) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ allowPort: function(port, scheme) {
+ return port != -1;
+ },
+
+ /** nsIFactory */
+ createInstance: function(aOuter, aIID) {
+ if (aOuter) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return this.QueryInterface(aIID);
+ },
+ lockFactory: function() {},
+
+ /** nsISupports */
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler,
+ Ci.nsIFactory]),
+ classID: Components.ID("{16d594bc-d9d8-47ae-a139-ea714dc0c35c}")
+};
+
+function CustomChannel(aURI, aLoadInfo) {
+ this.uri = aURI;
+ this.loadInfo = aLoadInfo;
+
+ this._uploadStream = null;
+
+ var interfaces = [Ci.nsIRequest, Ci.nsIChannel];
+ if (this.uri.spec == POST_ACTION_URI) {
+ interfaces.push(Ci.nsIFormPOSTActionChannel);
+ } else if (this.uri.spec == UPLOAD_ACTION_URI) {
+ interfaces.push(Ci.nsIUploadChannel);
+ }
+ this.QueryInterface = XPCOMUtils.generateQI(interfaces);
+}
+CustomChannel.prototype = {
+ /** nsIUploadChannel */
+ get uploadStream() {
+ return this._uploadStream;
+ },
+ set uploadStream(val) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ setUploadStream: function(aStream, aContentType, aContentLength) {
+ this._uploadStream = aStream;
+ },
+
+ /** nsIChannel */
+ get originalURI() {
+ return this.uri;
+ },
+ get URI() {
+ return this.uri;
+ },
+ owner: null,
+ notificationCallbacks: null,
+ get securityInfo() {
+ return null;
+ },
+ get contentType() {
+ return "text/html";
+ },
+ set contentType(val) {
+ },
+ contentCharset: "UTF-8",
+ get contentLength() {
+ return -1;
+ },
+ set contentLength(val) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ open: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ open2: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ asyncOpen: function(aListener, aContext) {
+ var data = `
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>test bug 1241377</title>
+</head>
+<body>
+`;
+
+ if (this.uri.spec.startsWith(FORM_BASE)) {
+ data += `
+<form id="form" action="${this.uri.spec.replace(FORM_BASE, ACTION_BASE)}"
+ method="post" enctype="text/plain" target="frame">
+<input type="hidden" name="foo" value="bar">
+<input type="submit">
+</form>
+
+<iframe id="frame" name="frame" width="200" height="200"></iframe>
+
+<script type="text/javascript">
+<!--
+document.getElementById('form').submit();
+//-->
+</script>
+`;
+ } else if (this.uri.spec.startsWith(ACTION_BASE)) {
+ var postData = "";
+ if (this._uploadStream) {
+ var bstream = Cc["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Ci.nsIBinaryInputStream);
+ bstream.setInputStream(this._uploadStream);
+ postData = bstream.readBytes(bstream.available());
+ }
+ data += `
+<input id="upload_stream" value="${this._uploadStream ? "yes" : "no"}">
+<input id="post_data" value="${btoa(postData)}">
+`;
+ }
+
+ data += `
+</body>
+</html>
+`;
+
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stream.setData(data, data.length);
+
+ var runnable = {
+ run: () => {
+ try {
+ aListener.onStartRequest(this, aContext);
+ } catch(e) {}
+ try {
+ aListener.onDataAvailable(this, aContext, stream, 0, stream.available());
+ } catch(e) {}
+ try {
+ aListener.onStopRequest(this, aContext, Cr.NS_OK);
+ } catch(e) {}
+ }
+ };
+ Services.tm.currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
+ },
+ asyncOpen2: function(aListener) {
+ this.asyncOpen(aListener, null);
+ },
+
+ /** nsIRequest */
+ get name() {
+ return this.uri.spec;
+ },
+ isPending: function () {
+ return false;
+ },
+ get status() {
+ return Cr.NS_OK;
+ },
+ cancel: function(status) {},
+ loadGroup: null,
+ loadFlags: Ci.nsIRequest.LOAD_NORMAL |
+ Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIRequest.LOAD_BYPASS_CACHE,
+};
+
+function frameScript() {
+ addMessageListener("Test:WaitForIFrame", function() {
+ var check = function() {
+ if (content) {
+ var frame = content.document.getElementById("frame");
+ if (frame) {
+ var upload_stream = frame.contentDocument.getElementById("upload_stream");
+ var post_data = frame.contentDocument.getElementById("post_data");
+ if (upload_stream && post_data) {
+ sendAsyncMessage("Test:IFrameLoaded", [upload_stream.value, post_data.value]);
+ return;
+ }
+ }
+ }
+
+ setTimeout(check, 100);
+ };
+
+ check();
+ });
+}
+
+function loadTestTab(uri) {
+ gBrowser.selectedTab = gBrowser.addTab(uri);
+ var browser = gBrowser.selectedBrowser;
+
+ let manager = browser.messageManager;
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+
+ return new Promise(resolve => {
+ function listener({ data: [hasUploadStream, postData] }) {
+ manager.removeMessageListener("Test:IFrameLoaded", listener);
+ resolve([hasUploadStream, atob(postData)]);
+ }
+
+ manager.addMessageListener("Test:IFrameLoaded", listener);
+ manager.sendAsyncMessage("Test:WaitForIFrame");
+ });
+}
+
+add_task(function*() {
+ var handler = new CustomProtocolHandler();
+ var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(handler.classID, "",
+ "@mozilla.org/network/protocol;1?name=" + handler.scheme,
+ handler);
+ registerCleanupFunction(function() {
+ registrar.unregisterFactory(handler.classID, handler);
+ });
+});
+
+add_task(function*() {
+ var [hasUploadStream, postData] = yield loadTestTab(NORMAL_FORM_URI);
+ is(hasUploadStream, "no", "normal action should not have uploadStream");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function*() {
+ var [hasUploadStream, postData] = yield loadTestTab(UPLOAD_FORM_URI);
+ is(hasUploadStream, "no", "upload action should not have uploadStream");
+
+ gBrowser.removeCurrentTab();
+});
+
+add_task(function*() {
+ var [hasUploadStream, postData] = yield loadTestTab(POST_FORM_URI);
+ is(hasUploadStream, "yes", "post action should have uploadStream");
+ is(postData,
+ "Content-Type: text/plain\r\n" +
+ "Content-Length: 9\r\n" +
+ "\r\n" +
+ "foo=bar\r\n", "POST data is received correctly");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/browser_post_file.js b/netwerk/test/browser/browser_post_file.js
new file mode 100644
index 000000000..6c9fd7a2f
--- /dev/null
+++ b/netwerk/test/browser/browser_post_file.js
@@ -0,0 +1,101 @@
+/*
+ * Tests for bug 1241100: Post to local file should not overwrite the file.
+ */
+
+Components.utils.import("resource://gre/modules/osfile.jsm");
+
+function* createTestFile(filename, content) {
+ let path = OS.Path.join(OS.Constants.Path.tmpDir, filename);
+ yield OS.File.writeAtomic(path, content);
+ return path;
+}
+
+function* readFile(path) {
+ var array = yield OS.File.read(path);
+ var decoder = new TextDecoder();
+ return decoder.decode(array);
+}
+
+function frameScript() {
+ addMessageListener("Test:WaitForIFrame", function() {
+ var check = function() {
+ if (content) {
+ var frame = content.document.getElementById("frame");
+ if (frame) {
+ var okBox = frame.contentDocument.getElementById("action_file_ok");
+ if (okBox) {
+ sendAsyncMessage("Test:IFrameLoaded");
+ return;
+ }
+ }
+ }
+
+ setTimeout(check, 100);
+ };
+
+ check();
+ });
+}
+
+add_task(function*() {
+ var postFilename = "post_file.html";
+ var actionFilename = "action_file.html";
+
+ var postFileContent = `
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>post file</title>
+</head>
+<body onload="document.getElementById('form').submit();">
+<form id="form" action="${actionFilename}" method="post" enctype="text/plain" target="frame">
+<input type="hidden" name="foo" value="bar">
+<input type="submit">
+</form>
+<iframe id="frame" name="frame"></iframe>
+</body>
+</html>
+`;
+
+ var actionFileContent = `
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>action file</title>
+</head>
+<body>
+<div id="action_file_ok">ok</div>
+</body>
+</html>
+`;
+
+ var postPath = yield* createTestFile(postFilename, postFileContent);
+ var actionPath = yield* createTestFile(actionFilename, actionFileContent);
+
+ var postURI = OS.Path.toFileURI(postPath);
+
+ gBrowser.selectedTab = gBrowser.addTab(postURI);
+ let browser = gBrowser.selectedBrowser;
+ browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
+ yield new Promise(resolve => {
+ let manager = browser.messageManager;
+
+ function listener() {
+ manager.removeMessageListener("Test:IFrameLoaded", listener);
+ resolve();
+ }
+
+ manager.addMessageListener("Test:IFrameLoaded", listener);
+ manager.sendAsyncMessage("Test:WaitForIFrame");
+ });
+
+ var actionFileContentAfter = yield* readFile(actionPath);
+ is(actionFileContentAfter, actionFileContent, "action file is not modified");
+
+ yield OS.File.remove(postPath);
+ yield OS.File.remove(actionPath);
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/netwerk/test/browser/dummy.html b/netwerk/test/browser/dummy.html
new file mode 100644
index 000000000..6b28a248f
--- /dev/null
+++ b/netwerk/test/browser/dummy.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+
+<html>
+<body>
+ <p>Dummy Page</p>
+</body>
+</html>