Bug 548667 - Submit crash reports via FormData. r=ted
authorJosh Matthews <josh@joshmatthews.net>
Wed, 20 Jul 2011 17:51:55 -0700
changeset 73110 5cabd0a023737e8c2e935b713daba6b19fb90af0
parent 73109 e25da7cc7c638799d715c94e2fd009679d786fdf
child 73111 03e942f652c1944de37a6ceedde8d9ca6f7ff276
push id676
push userjosh@joshmatthews.net
push dateThu, 21 Jul 2011 00:56:00 +0000
treeherdermozilla-inbound@03e942f652c1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersted
bugs548667
milestone8.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
Bug 548667 - Submit crash reports via FormData. r=ted
browser/base/content/browser.js
mobile/chrome/content/browser.js
toolkit/crashreporter/CrashSubmit.jsm
toolkit/crashreporter/content/crash-submit-form.xhtml
toolkit/crashreporter/content/crashes.js
toolkit/crashreporter/jar.mn
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6792,19 +6792,19 @@ var gPluginHandler = {
   managePlugins: function (aEvent) {
     BrowserOpenAddonsMgr("addons://list/plugin");
   },
 
   // Callback for user clicking "submit a report" link
   submitReport : function(pluginDumpID, browserDumpID) {
     // The crash reporter wants a DOM element it can append an IFRAME to,
     // which it uses to submit a form. Let's just give it gBrowser.
-    this.CrashSubmit.submit(pluginDumpID, gBrowser, null, null);
+    this.CrashSubmit.submit(pluginDumpID, null, null);
     if (browserDumpID)
-      this.CrashSubmit.submit(browserDumpID, gBrowser, null, null);
+      this.CrashSubmit.submit(browserDumpID, null, null);
   },
 
   // Callback for user clicking a "reload page" link
   reloadPage: function (browser) {
     browser.reload();
   },
 
   // Callback for user clicking the help icon
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -2522,17 +2522,17 @@ var ContentCrashObserver = {
           // use 0x02 | 0x10 to open file for appending.
           foStream.init(extra, 0x02 |  0x10, 0666, 0); 
           let data = "URL=" + crashedURL + "\n";
           foStream.write(data, data.length);
           foStream.close();
         } catch (x) {
           dump (x);
         }
-        self.CrashSubmit.submit(dumpID, Elements.stack, null, null);
+        self.CrashSubmit.submit(dumpID, null, null);
       }
     }, 0, this);
   }
 };
 
 var MemoryObserver = {
   observe: function mo_observe(aSubject, aTopic, aData) {
     function gc() {
--- a/toolkit/crashreporter/CrashSubmit.jsm
+++ b/toolkit/crashreporter/CrashSubmit.jsm
@@ -182,20 +182,18 @@ function writeSubmittedReport(crashID, v
      data += "\n" + strings.reporturl.replace("%s", viewURL);
 
   os.writeString(data);
   os.close();
   fstream.close();
 }
 
 // the Submitter class represents an individual submission.
-function Submitter(id, element, submitSuccess, submitError, noThrottle) {
+function Submitter(id, submitSuccess, submitError, noThrottle) {
   this.id = id;
-  this.element = element;
-  this.document = element.ownerDocument;
   this.successCallback = submitSuccess;
   this.errorCallback = submitError;
   this.noThrottle = noThrottle;
 }
 
 Submitter.prototype = {
   submitSuccess: function Submitter_submitSuccess(ret)
   {
@@ -218,101 +216,68 @@ Submitter.prototype = {
     }
 
     this.notifyStatus(SUCCESS, ret);
     this.cleanup();
   },
 
   cleanup: function Submitter_cleanup() {
     // drop some references just to be nice
-    this.element = null;
-    this.document = null;
     this.successCallback = null;
     this.errorCallback = null;
     this.iframe = null;
     this.dump = null;
     this.extra = null;
     // remove this object from the list of active submissions
     let idx = CrashSubmit._activeSubmissions.indexOf(this);
     if (idx != -1)
       CrashSubmit._activeSubmissions.splice(idx, 1);
   },
 
   submitForm: function Submitter_submitForm()
   {
     let reportData = parseKeyValuePairsFromFile(this.extra);
-    let form = this.iframe.contentDocument.forms[0];
-    if ('ServerURL' in reportData) {
-      form.action = reportData.ServerURL;
-      delete reportData.ServerURL;
-    }
-    else {
+    if (!('ServerURL' in reportData)) {
       return false;
     }
+
+    let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+              .createInstance(Ci.nsIXMLHttpRequest);
+    xhr.open("POST", reportData.ServerURL, true);
+    delete reportData.ServerURL;
+
+    let formData = Cc["@mozilla.org/files/formdata;1"]
+                   .createInstance(Ci.nsIDOMFormData);
     // add the other data
     for (let [name, value] in Iterator(reportData)) {
-      addFormEntry(this.iframe.contentDocument, form, name, value);
+      formData.append(name, value);
     }
     if (this.noThrottle) {
       // tell the server not to throttle this, since it was manually submitted
-      addFormEntry(this.iframe.contentDocument, form, "Throttleable", "0");
+      formData.append("Throttleable", "0");
     }
     // add the minidump
-    this.iframe.contentDocument.getElementById('minidump').value
-      = this.dump.path;
-    this.iframe.docShell.QueryInterface(Ci.nsIWebProgress);
-    this.iframe.docShell.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
-    form.submit();
+    formData.append("upload_file_minidump", File(this.dump.path));
+    let self = this;
+    xhr.onreadystatechange = function (aEvt) {
+      if (xhr.readyState == 4) {
+        if (xhr.status != 200) {
+          self.notifyStatus(FAILED);
+          self.cleanup();
+        } else {
+          let ret = parseKeyValuePairs(xhr.responseText);
+          self.submitSuccess(ret);
+        }
+      }
+    };
+
+    xhr.send(formData);
     return true;
   },
 
-  // web progress listener
-  QueryInterface: function(aIID)
-  {
-    if (aIID.equals(Ci.nsIWebProgressListener) ||
-        aIID.equals(Ci.nsISupportsWeakReference) ||
-        aIID.equals(Ci.nsISupports))
-      return this;
-    throw Components.results.NS_NOINTERFACE;
-  },
-
-  onStateChange: function(aWebProgress, aRequest, aFlag, aStatus)
-  {
-    if(aFlag & STATE_STOP) {
-      this.iframe.docShell.QueryInterface(Ci.nsIWebProgress);
-      this.iframe.docShell.removeProgressListener(this);
-
-      // check general request status first
-      if (!Components.isSuccessCode(aStatus)) {
-        this.element.removeChild(this.iframe);
-        this.notifyStatus(FAILED);
-        this.cleanup();
-        return 0;
-      }
-      // check HTTP status
-      if (aRequest instanceof Ci.nsIHttpChannel &&
-          aRequest.responseStatus != 200) {
-        this.element.removeChild(this.iframe);
-        this.notifyStatus(FAILED);
-        this.cleanup();
-        return 0;
-      }
-
-      var ret = parseKeyValuePairs(this.iframe.contentDocument.documentElement.textContent);
-      this.element.removeChild(this.iframe);
-      this.submitSuccess(ret);
-    }
-    return 0;
-  },
-
-  onLocationChange: function(aProgress, aRequest, aURI) {return 0;},
-  onProgressChange: function() {return 0;},
-  onStatusChange: function() {return 0;},
-  onSecurityChange: function() {return 0;},
-
   notifyStatus: function Submitter_notify(status, ret)
   {
     let propBag = Cc["@mozilla.org/hash-property-bag;1"].
                   createInstance(Ci.nsIWritablePropertyBag2);
     propBag.setPropertyAsAString("minidumpID", this.id);
     if (status == SUCCESS) {
       propBag.setPropertyAsAString("serverCrashID", ret.CrashID);
     }
@@ -341,51 +306,34 @@ Submitter.prototype = {
       this.cleanup();
       return false;
     }
 
     this.notifyStatus(SUBMITTING);
 
     this.dump = dump;
     this.extra = extra;
-    let iframe = this.document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "iframe");
-    iframe.setAttribute("type", "content");
-    iframe.style.width = 0;
-    iframe.style.minWidth = 0;
 
-    let self = this;
-    function loadHandler() {
-      if (iframe.contentWindow.location == "about:blank")
-        return;
-      iframe.removeEventListener("load", loadHandler, true);
-      if (!self.submitForm()) {
-        self.notifyStatus(FAILED);
-        self.cleanup();
-      }
+    if (!this.submitForm()) {
+       this.notifyStatus(FAILED);
+       this.cleanup();
+       return false;
     }
-
-    iframe.addEventListener("load", loadHandler, true);
-    this.element.appendChild(iframe);
-    this.iframe = iframe;
-    iframe.webNavigation.loadURI("chrome://global/content/crash-submit-form.xhtml", 0, null, null, null);
     return true;
   }
 };
 
 //===================================
 // External API goes here
 let CrashSubmit = {
   /**
    * Submit the crash report named id.dmp from the "pending" directory.
    *
    * @param id
    *        Filename (minus .dmp extension) of the minidump to submit.
-   * @param element
-   *        A DOM element to which an iframe can be appended as a child,
-   *        used for form submission.
    * @param submitSuccess
    *        A function that will be called if the report is submitted
    *        successfully with two parameters: the id that was passed
    *        to this function, and an object containing the key/value
    *        data returned from the server in its properties.
    * @param submitError
    *        A function that will be called with one parameter if the
    *        report fails to submit: the id that was passed to this
@@ -396,21 +344,20 @@ let CrashSubmit = {
    *        it should be processed right away. This should be set
    *        when the report is being submitted and the user expects
    *        to see the results immediately.
    *
    * @return true if the submission began successfully, or false if
    *         it failed for some reason. (If the dump file does not
    *         exist, for example.)
    */
-  submit: function CrashSubmit_submit(id, element, submitSuccess, submitError,
+  submit: function CrashSubmit_submit(id, submitSuccess, submitError,
                                       noThrottle)
   {
     let submitter = new Submitter(id,
-                                  element,
                                   submitSuccess,
                                   submitError,
                                   noThrottle);
     CrashSubmit._activeSubmissions.push(submitter);
     return submitter.submit();
   },
 
   // List of currently active submit objects
deleted file mode 100644
--- a/toolkit/crashreporter/content/crash-submit-form.xhtml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
-    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml">
-<body>
-<form method="POST" enctype="multipart/form-data" action="">
-<input type="file" name="upload_file_minidump" id="minidump"/>
-</form>
-</body></html>
--- a/toolkit/crashreporter/content/crashes.js
+++ b/toolkit/crashreporter/content/crashes.js
@@ -72,17 +72,17 @@ function submitError(dumpid) {
   let event = document.createEvent("Events");
   event.initEvent("CrashSubmitFailed", true, false);
   document.dispatchEvent(event);
 }
 
 function submitPendingReport(event) {
   var link = event.target;
   var id = link.firstChild.textContent;
-  if (CrashSubmit.submit(id, document.body, submitSuccess, submitError, true))
+  if (CrashSubmit.submit(id, submitSuccess, submitError, true))
     link.className = "submitting";
   event.preventDefault();
   return false;
 }
 
 function findInsertionPoint(reports, date) {
   if (reports.length == 0)
     return 0;
--- a/toolkit/crashreporter/jar.mn
+++ b/toolkit/crashreporter/jar.mn
@@ -1,4 +1,3 @@
 toolkit.jar:
   content/global/crashes.xhtml            (content/crashes.xhtml)
   content/global/crashes.js               (content/crashes.js)
-  content/global/crash-submit-form.xhtml  (content/crash-submit-form.xhtml)