Merge fx-team to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 25 Jul 2013 21:55:41 -0400
changeset 152262 1d31650caa8f075b1bb67a968ea2273c302b1ee5
parent 152253 76fb8f815ac79fc27411848c7055d3e9a14b0e7e (current diff)
parent 152261 156f5120a9f03747ebd91f9ade0b2e03288bb2b1 (diff)
child 152285 5a60a08e6899c8c75adab892ca5e8f21284e3b84
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c.
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -467,16 +467,20 @@ pref("general.warnOnAboutConfig",       
 pref("dom.disable_window_open_feature.location",  true);
 // prevent JS from setting status messages
 pref("dom.disable_window_status_change",          true);
 // allow JS to move and resize existing windows
 pref("dom.disable_window_move_resize",            false);
 // prevent JS from monkeying with window focus, etc
 pref("dom.disable_window_flip",                   true);
 
+// Disable touch events on Desktop Firefox by default until they are properly
+// supported (bug 736048)
+pref("dom.w3c_touch_events.enabled",        0);
+
 // popups.policy 1=allow,2=reject
 pref("privacy.popups.policy",               1);
 pref("privacy.popups.usecustom",            true);
 pref("privacy.popups.showBrowserMessage",   true);
 
 pref("privacy.item.cookies",                false);
 
 pref("privacy.clearOnShutdown.history",     true);
--- a/browser/base/content/abouthome/aboutHome.js
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -289,31 +289,59 @@ function ensureSnippetsMapThen(aCallback
 }
 
 function onSearchSubmit(aEvent)
 {
   let searchTerms = document.getElementById("searchText").value;
   let searchURL = document.documentElement.getAttribute("searchEngineURL");
 
   if (searchURL && searchTerms.length > 0) {
-    const SEARCH_TOKENS = {
-      "_searchTerms_": encodeURIComponent(searchTerms)
-    }
-    for (let key in SEARCH_TOKENS) {
-      searchURL = searchURL.replace(key, SEARCH_TOKENS[key]);
-    }
-
     // Send an event that a search was performed. This was originally
     // added so Firefox Health Report could record that a search from
     // about:home had occurred.
     let engineName = document.documentElement.getAttribute("searchEngineName");
     let event = new CustomEvent("AboutHomeSearchEvent", {detail: engineName});
     document.dispatchEvent(event);
 
-    window.location.href = searchURL;
+    const SEARCH_TOKEN = "_searchTerms_";
+    let searchPostData = document.documentElement.getAttribute("searchEnginePostData");
+    if (searchPostData) {
+      // Check if a post form already exists. If so, remove it.
+      const POST_FORM_NAME = "searchFormPost";
+      let form = document.forms[POST_FORM_NAME];
+      if (form) {
+        form.parentNode.removeChild(form);
+      }
+
+      // Create a new post form.
+      form = document.body.appendChild(document.createElement("form"));
+      form.setAttribute("name", POST_FORM_NAME);
+      // Set the URL to submit the form to.
+      form.setAttribute("action", searchURL.replace(SEARCH_TOKEN, searchTerms));
+      form.setAttribute("method", "post");
+
+      // Create new <input type=hidden> elements for search param.
+      searchPostData = searchPostData.split("&");
+      for (let postVar of searchPostData) {
+        let [name, value] = postVar.split("=");
+        if (value == SEARCH_TOKEN) {
+          value = searchTerms;
+        }
+        let input = document.createElement("input");
+        input.setAttribute("type", "hidden");
+        input.setAttribute("name", name);
+        input.setAttribute("value", value);
+        form.appendChild(input);
+      }
+      // Submit the form.
+      form.submit();
+   } else {
+      searchURL = searchURL.replace(SEARCH_TOKEN, encodeURIComponent(searchTerms));
+      window.location.href = searchURL;
+    }
   }
 
   aEvent.preventDefault();
 }
 
 
 function setupSearchEngine()
 {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1035,17 +1035,16 @@ var gBrowserInit = {
     Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
     Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false);
 
     BrowserOffline.init();
     OfflineApps.init();
     IndexedDBPromptHelper.init();
     gFormSubmitObserver.init();
-    SocialUI.init();
     AddonManager.addAddonListener(AddonsMgrListener);
     WebrtcIndicator.init();
 
     // Ensure login manager is up and running.
     Services.logins;
 
     if (mustLoadSidebar) {
       let sidebar = document.getElementById("sidebar");
@@ -1096,22 +1095,17 @@ var gBrowserInit = {
     if (!gMultiProcessBrowser) {
       let NP = {};
       Cu.import("resource:///modules/NetworkPrioritizer.jsm", NP);
       NP.trackBrowserWindow(window);
     }
 
     // initialize the session-restore service (in case it's not already running)
     let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
-    ss.init(window);
-
-    // Enable the Restore Last Session command if needed
-    if (ss.canRestoreLastSession &&
-        !PrivateBrowsingUtils.isWindowPrivate(window))
-      goSetCommandEnabled("Browser:RestoreLastSession", true);
+    let ssPromise = ss.init(window);
 
     PlacesToolbarHelper.init();
 
     ctrlTab.readPref();
     gPrefService.addObserver(ctrlTab.prefName, ctrlTab, false);
 
     // Initialize the download manager some time after the app starts so that
     // auto-resume downloads begin (such as after crashing or quitting with
@@ -1164,17 +1158,16 @@ var gBrowserInit = {
     gSyncUI.init();
 #endif
 
 #ifdef MOZ_DATA_REPORTING
     gDataNotificationInfoBar.init();
 #endif
 
     gBrowserThumbnails.init();
-    TabView.init();
 
     setUrlAndSearchBarWidthForConditionalForwardButton();
     window.addEventListener("resize", function resizeHandler(event) {
       if (event.target == window)
         setUrlAndSearchBarWidthForConditionalForwardButton();
     });
 
     // Enable developer toolbar?
@@ -1279,18 +1272,29 @@ var gBrowserInit = {
 #ifdef MOZ_METRO
     gMetroPrefs.prefDomain.forEach(function(prefName) {
       gMetroPrefs.pushDesktopControlledPrefToMetro(prefName);
       Services.prefs.addObserver(prefName, gMetroPrefs, false);
     }, this);
 #endif
 #endif
 
+    ssPromise.then(() =>{
+      // Enable the Restore Last Session command if needed
+      if (ss.canRestoreLastSession &&
+          !PrivateBrowsingUtils.isWindowPrivate(window))
+        goSetCommandEnabled("Browser:RestoreLastSession", true);
+
+      TabView.init();
+      SocialUI.init();
+
+      setTimeout(function () { BrowserChromeTest.markAsReady(); }, 0);
+    });
+
     Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
-    setTimeout(function () { BrowserChromeTest.markAsReady(); }, 0);
     TelemetryTimestamps.add("delayedStartupFinished");
   },
 
   onUnload: function() {
     // In certain scenarios it's possible for unload to be fired before onload,
     // (e.g. if the window is being closed after browser.js loads but before the
     // load completes). In that case, there's nothing to do here.
     if (!this._loadHandled)
@@ -2305,16 +2309,19 @@ function BrowserOnAboutPageLoad(doc) {
       let currentVersion = Services.prefs.getIntPref("browser.rights.version");
       Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
     }
     docElt.setAttribute("snippetsVersion", AboutHomeUtils.snippetsVersion);
 
     let updateSearchEngine = function() {
       let engine = AboutHomeUtils.defaultSearchEngine;
       docElt.setAttribute("searchEngineName", engine.name);
+      docElt.setAttribute("searchEnginePostData", engine.postDataString || "");
+      // Again, keep the searchEngineURL as the last attribute, because the
+      // mutation observer in aboutHome.js is counting on that.
       docElt.setAttribute("searchEngineURL", engine.searchURL);
     };
     updateSearchEngine();
 
     // Listen for the event that's triggered when the user changes search engine.
     // At this point we simply reload about:home to reflect the change.
     Services.obs.addObserver(updateSearchEngine, "browser-search-engine-modified", false);
 
--- a/browser/base/content/test/browser_aboutHome.js
+++ b/browser/base/content/test/browser_aboutHome.js
@@ -237,28 +237,82 @@ let gTests = [
     }
     // Do a sanity check that all attributes are correctly set to begin with
     checkSearchUI(currEngine);
 
     let deferred = Promise.defer();
     promiseBrowserAttributes(gBrowser.selectedTab).then(function() {
       // Test if the update propagated
       checkSearchUI(unusedEngines[0]);
+      searchbar.currentEngine = currEngine;
       deferred.resolve();
     });
 
-    // The following cleanup function will set currentEngine back to the previous engine
+    // The following cleanup function will set currentEngine back to the previous
+    // engine if we fail to do so above.
     registerCleanupFunction(function() {
       searchbar.currentEngine = currEngine;
     });
     // Set the current search engine to an unused one
     searchbar.currentEngine = unusedEngines[0];
     searchbar.select();
     return deferred.promise;
   }
+},
+
+{
+  desc: "Check POST search engine support",
+  setup: function() {},
+  run: function()
+  {
+    let deferred = Promise.defer();
+    let currEngine = Services.search.defaultEngine;
+    let searchObserver = function search_observer(aSubject, aTopic, aData) {
+      let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
+      info("Observer: " + aData + " for " + engine.name);
+
+      if (aData != "engine-added")
+        return;
+
+      if (engine.name != "POST Search")
+        return;
+
+      Services.search.defaultEngine = engine;
+
+      registerCleanupFunction(function() {
+        Services.search.removeEngine(engine);
+        Services.search.defaultEngine = currEngine;
+      });
+
+
+      // Ready to execute the tests!
+      let needle = "Search for something awesome.";
+      let document = gBrowser.selectedTab.linkedBrowser.contentDocument;
+      let searchText = document.getElementById("searchText");
+
+      waitForLoad(function() {
+        let loadedText = gBrowser.contentDocument.body.textContent;
+        ok(loadedText, "search page loaded");
+        is(loadedText, "searchterms=" + escape(needle.replace(/\s/g, "+")),
+           "Search text should arrive correctly");
+        deferred.resolve();
+      });
+
+      searchText.value = needle;
+      searchText.focus();
+      EventUtils.synthesizeKey("VK_RETURN", {});
+    };
+    Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false);
+    registerCleanupFunction(function () {
+      Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
+    });
+    Services.search.addEngine("http://test:80/browser/browser/base/content/test/POSTSearchEngine.xml",
+                              Ci.nsISearchEngine.DATA_XML, null, false);
+    return deferred.promise;
+  }
 }
 
 ];
 
 function test()
 {
   waitForExplicitFinish();
   requestLongerTimeout(2);
@@ -437,8 +491,20 @@ function getNumberOfSearchesByDate(aEngi
 
     if (day.has(field)) {
       return day.get(field) || 0;
     }
   }
 
   return 0; // No records found.
 }
+
+function waitForLoad(cb) {
+  let browser = gBrowser.selectedBrowser;
+  browser.addEventListener("load", function listener() {
+    if (browser.currentURI.spec == "about:blank")
+      return;
+    info("Page loaded: " + browser.currentURI.spec);
+    browser.removeEventListener("load", listener, true);
+
+    cb();
+  }, true);
+}
--- a/browser/base/content/test/social/browser_social_window.js
+++ b/browser/base/content/test/social/browser_social_window.js
@@ -12,27 +12,32 @@ function resetSocial() {
   Social._provider = null;
   Social.providers = [];
   // *sob* - listeners keep getting added...
   let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
   SocialService._providerListeners.clear();
 }
 
 let createdWindows = [];
+let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
 
 function openWindowAndWaitForInit(callback) {
   // this notification tells us SocialUI.init() has been run...
   let topic = "browser-delayed-startup-finished";
   let w = OpenBrowserWindow();
   createdWindows.push(w);
   Services.obs.addObserver(function providerSet(subject, topic, data) {
     Services.obs.removeObserver(providerSet, topic);
     info(topic + " observer was notified - continuing test");
-    // executeSoon to let the browser UI observers run first
-    executeSoon(function() {callback(w)});
+    // We need to wait for the SessionStore as well, since
+    // SocialUI.init() is also waiting on it.
+    ss.init(w).then(function () {
+      executeSoon(function() {callback(w);});
+    });
+
   }, topic, false);
 }
 
 function postTestCleanup(cb) {
   for (let w of createdWindows)
     w.close();
   createdWindows = [];
   Services.prefs.clearUserPref("social.enabled");
--- a/browser/components/sessionstore/nsISessionStore.idl
+++ b/browser/components/sessionstore/nsISessionStore.idl
@@ -20,23 +20,23 @@ interface nsIDOMNode;
  * global |window| object to the API, though (or |top| from a sidebar).
  * From elsewhere you can get browser windows through the nsIWindowMediator
  * by looking for "navigator:browser" windows.
  *
  * * "Tabbrowser tabs" are all the child nodes of a browser window's
  * |gBrowser.tabContainer| such as e.g. |gBrowser.selectedTab|.
  */
 
-[scriptable, uuid(0aa5492c-15ad-4376-8eac-28895796826e)]
+[scriptable, uuid(092fa0cc-e99b-11e2-a2a3-a25b4f45d8e2)]
 interface nsISessionStore : nsISupports
 {
   /**
    * Initialize the service
    */
-  void init(in nsIDOMWindow aWindow);
+  jsval init(in nsIDOMWindow aWindow);
 
   /**
    * Is it possible to restore the previous session. Will always be false when
    * in Private Browsing mode.
    */
   attribute boolean canRestoreLastSession;
 
   /**
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -74,17 +74,17 @@ const TAB_EVENTS = [
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 // debug.js adds NS_ASSERT. cf. bug 669196
 Cu.import("resource://gre/modules/debug.js", this);
 Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", this);
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
-Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", this);
+Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
   "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
 XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
   "@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
 
 // List of docShell capabilities to (re)store. These are automatically
@@ -134,17 +134,17 @@ this.SessionStore = {
     return SessionStoreInternal.canRestoreLastSession;
   },
 
   set canRestoreLastSession(val) {
     SessionStoreInternal.canRestoreLastSession = val;
   },
 
   init: function ss_init(aWindow) {
-    SessionStoreInternal.init(aWindow);
+    return SessionStoreInternal.init(aWindow);
   },
 
   getBrowserState: function ss_getBrowserState() {
     return SessionStoreInternal.getBrowserState();
   },
 
   setBrowserState: function ss_setBrowserState(aState) {
     SessionStoreInternal.setBrowserState(aState);
@@ -574,19 +574,21 @@ let SessionStoreInternal = {
    */
   init: function ssi_init(aWindow) {
     if (!aWindow) {
       throw new Error("init() must be called with a valid window.");
     }
 
     let self = this;
     this.initService();
-    this._promiseInitialization.promise.then(
+    return this._promiseInitialization.promise.then(
       function onSuccess() {
-        self.onLoad(aWindow);
+        if (!aWindow.closed) {
+          self.onLoad(aWindow);
+        }
       }
     );
   },
 
   /**
    * Called on application shutdown, after notifications:
    * quit-application-granted, quit-application
    */
--- a/browser/components/sessionstore/src/_SessionFile.jsm
+++ b/browser/components/sessionstore/src/_SessionFile.jsm
@@ -28,17 +28,17 @@ this.EXPORTED_SYMBOLS = ["_SessionFile"]
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
-Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
+Cu.import("resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
   "resource://gre/modules/TelemetryStopwatch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
--- a/browser/components/sessionstore/src/nsSessionStartup.js
+++ b/browser/components/sessionstore/src/nsSessionStartup.js
@@ -34,17 +34,17 @@
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
-Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
+Cu.import("resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
   "resource:///modules/sessionstore/_SessionFile.jsm");
 
 const STATE_RUNNING_STR = "running";
 
 function debug(aMsg) {
   aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n");
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -31,17 +31,18 @@ function SourcesView() {
 SourcesView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the SourcesView");
 
     this.widget = new SideMenuWidget(document.getElementById("sources"), {
-      showCheckboxes: true
+      showCheckboxes: true,
+      showArrows: true
     });
     this.emptyText = L10N.getStr("noSourcesText");
     this.unavailableText = L10N.getStr("noMatchingSourcesText");
     this._blackBoxCheckboxTooltip = L10N.getStr("blackBoxCheckboxTooltip");
 
     this._commandset = document.getElementById("debuggerCommands");
     this._popupset = document.getElementById("debuggerPopupset");
     this._cmPopup = document.getElementById("sourceEditorContextMenu");
--- a/browser/devtools/profiler/sidebar.js
+++ b/browser/devtools/profiler/sidebar.js
@@ -18,17 +18,17 @@ const {
 } = require("devtools/profiler/consts");
 
 loader.lazyGetter(this, "L10N", () => new ViewHelpers.L10N(L10N_BUNDLE));
 
 function Sidebar(el) {
   EventEmitter.decorate(this);
 
   this.document = el.ownerDocument;
-  this.widget = new SideMenuWidget(el);
+  this.widget = new SideMenuWidget(el, { showArrows: true });
   this.widget.notice = L10N.getStr("profiler.sidebarNotice");
 
   this.widget.addEventListener("select", (ev) => {
     if (!ev.detail)
       return;
 
     this.emit("select", parseInt(ev.detail.value, 10));
   });
@@ -112,9 +112,9 @@ Sidebar.prototype = Heritage.extend(Widg
         return;
     }
 
     item.attachment.state = state;
     this.emit("stateChanged", item);
   }
 });
 
-module.exports = Sidebar;
\ No newline at end of file
+module.exports = Sidebar;
--- a/browser/modules/AboutHomeUtils.jsm
+++ b/browser/modules/AboutHomeUtils.jsm
@@ -20,23 +20,21 @@ this.AboutHomeUtils = {
 
   /**
    * Returns an object containing the name and searchURL of the original default
    * search engine.
    */
   get defaultSearchEngine() {
     let defaultEngine = Services.search.defaultEngine;
     let submission = defaultEngine.getSubmission("_searchTerms_", null, "homepage");
-    if (submission.postData) {
-      throw new Error("Home page does not support POST search engines.");
-    }
   
     return Object.freeze({
       name: defaultEngine.name,
-      searchURL: submission.uri.spec
+      searchURL: submission.uri.spec,
+      postDataString: submission.postDataString
     });
   },
 
   /*
    * showKnowYourRights - Determines if the user should be shown the
    * about:rights notification. The notification should *not* be shown if
    * we've already shown the current version, or if the override pref says to
    * never show it. The notification *should* be shown if it's never been seen
--- a/netwerk/base/public/nsIBrowserSearchService.idl
+++ b/netwerk/base/public/nsIBrowserSearchService.idl
@@ -2,26 +2,32 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIInputStream;
 
-[scriptable, uuid(58e4f602-a7c8-4cd1-9dca-716705e826ef)]
+[scriptable, uuid(82ec6ee8-68b5-49ef-87b7-0d5240f8a183)]
 interface nsISearchSubmission : nsISupports
 {
   /**
    * The POST data associated with a search submission, wrapped in a MIME
    * input stream. May be null.
    */
   readonly attribute nsIInputStream postData;
 
   /**
+   * The POST data associated with a search submission as an
+   * application/x-www-form-urlencoded string. May be null.
+   */
+  readonly attribute AString postDataString;
+
+  /**
    * The URI to submit a search to.
    */
   readonly attribute nsIURI uri;
 };
 
 [scriptable, uuid(ccf6aa20-10a9-4a0c-a81d-31b10ea846de)]
 interface nsISearchEngine : nsISupports
 {
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -926,37 +926,38 @@ EngineURL.prototype = {
         continue;
 
       var value = ParamSubstitution(param.value, aSearchTerms, aEngine);
 
       dataString += (i > 0 ? "&" : "") + param.name + "=" + value;
     }
 
     var postData = null;
+    let postDataString = null;
     if (this.method == "GET") {
       // GET method requests have no post data, and append the encoded
       // query string to the url...
       if (url.indexOf("?") == -1 && dataString)
         url += "?";
       url += dataString;
     } else if (this.method == "POST") {
-      // POST method requests must wrap the encoded text in a MIME
-      // stream and supply that as POSTDATA.
+      // For POST requests, specify the data as a MIME stream as well as a string.
+      postDataString = dataString;
       var stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
                          createInstance(Ci.nsIStringInputStream);
       stringStream.data = dataString;
 
       postData = Cc["@mozilla.org/network/mime-input-stream;1"].
                  createInstance(Ci.nsIMIMEInputStream);
       postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
       postData.addContentLength = true;
       postData.setData(stringStream);
     }
 
-    return new Submission(makeURI(url), postData);
+    return new Submission(makeURI(url), postData, postDataString);
   },
 
   _hasRelation: function SRC_EURL__hasRelation(aRel)
     this.rels.some(function(e) e == aRel.toLowerCase()),
 
   _initWithJSON: function SRC_EURL__initWithJSON(aJson, aEngine) {
     if (!aJson.params)
       return;
@@ -2538,17 +2539,17 @@ Engine.prototype = {
 
     var url = this._getURLOfType(aResponseType);
 
     if (!url)
       return null;
 
     if (!aData) {
       // Return a dummy submission object with our searchForm attribute
-      return new Submission(makeURI(this.searchForm), null);
+      return new Submission(makeURI(this.searchForm));
     }
 
     LOG("getSubmission: In data: \"" + aData + "\"; Purpose: \"" + aPurpose + "\"");
     var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
                        getService(Ci.nsITextToSubURI);
     var data = "";
     try {
       data = textToSubURI.ConvertAndEscape(this.queryCharset, aData);
@@ -2575,27 +2576,31 @@ Engine.prototype = {
 
   get wrappedJSObject() {
     return this;
   }
 
 };
 
 // nsISearchSubmission
-function Submission(aURI, aPostData) {
+function Submission(aURI, aPostData = null, aPostDataString = null) {
   this._uri = aURI;
   this._postData = aPostData;
+  this._postDataString = aPostDataString;
 }
 Submission.prototype = {
   get uri() {
     return this._uri;
   },
   get postData() {
     return this._postData;
   },
+  get postDataString() {
+    return this._postDataString;
+  },
   QueryInterface: function SRCH_SUBM_QI(aIID) {
     if (aIID.equals(Ci.nsISearchSubmission) ||
         aIID.equals(Ci.nsISupports))
       return this;
     throw Cr.NS_ERROR_NO_INTERFACE;
   }
 }