Merge b2g-inbound to m-c
authorWes Kocher <wkocher@mozilla.com>
Tue, 12 Nov 2013 16:39:59 -0800
changeset 154647 bb502bb5ed5fe7d48f1ace7cfff35bf71c72e7fd
parent 154627 777f73da73c7a5e2b2bd7c37558fb4a4bdd9fbf6 (current diff)
parent 154646 8ee98e6dd8e91217080138c485000457bc7766e7 (diff)
child 154662 7b014f0f3b031ad57e2995d1f9ee04095c82bda5
push id25651
push userkwierso@gmail.com
push dateWed, 13 Nov 2013 00:40:26 +0000
treeherdermozilla-central@bb502bb5ed5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.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 b2g-inbound to m-c
--- a/b2g/components/B2GComponents.manifest
+++ b/b2g/components/B2GComponents.manifest
@@ -66,8 +66,12 @@ contract @mozilla.org/network/protocol/a
 # FilePicker.js
 component {436ff8f9-0acc-4b11-8ec7-e293efba3141} FilePicker.js
 contract @mozilla.org/filepicker;1 {436ff8f9-0acc-4b11-8ec7-e293efba3141}
 
 # WebappsUpdateTimer.js
 component {637b0f77-2429-49a0-915f-abf5d0db8b9a} WebappsUpdateTimer.js
 contract @mozilla.org/b2g/webapps-update-timer;1 {637b0f77-2429-49a0-915f-abf5d0db8b9a}
 category update-timer WebappsUpdateTimer @mozilla.org/b2g/webapps-update-timer;1,getService,background-update-timer,webapps.update.interval,86400
+
+# HelperAppDialog.js
+component {710322af-e6ae-4b0c-b2c9-1474a87b077e} HelperAppDialog.js
+contract @mozilla.org/helperapplauncherdialog;1 {710322af-e6ae-4b0c-b2c9-1474a87b077e}
new file mode 100644
--- /dev/null
+++ b/b2g/components/HelperAppDialog.js
@@ -0,0 +1,123 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+                                  "resource://gre/modules/Downloads.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
+// -----------------------------------------------------------------------
+// HelperApp Launcher Dialog
+//
+// For now on b2g we never prompt and just download to the default
+// location.
+//
+// -----------------------------------------------------------------------
+
+function HelperAppLauncherDialog() { }
+
+HelperAppLauncherDialog.prototype = {
+  classID: Components.ID("{710322af-e6ae-4b0c-b2c9-1474a87b077e}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
+
+  show: function(aLauncher, aContext, aReason) {
+    aLauncher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.saveToDisk;
+    aLauncher.saveToDisk(null, false);
+  },
+
+  promptForSaveToFile: function(aLauncher,
+                                aContext,
+                                aDefaultFile,
+                                aSuggestedFileExt,
+                                aForcePrompt) {
+    throw Cr.NS_ERROR_NOT_AVAILABLE;
+  },
+
+  promptForSaveToFileAsync: function(aLauncher,
+                                     aContext,
+                                     aDefaultFile,
+                                     aSuggestedFileExt,
+                                     aForcePrompt) {
+    // Retrieve the user's default download directory.
+    Task.spawn(function() {
+      let file = null;
+      try {
+        let defaultFolder = yield Downloads.getPreferredDownloadsDirectory();
+        let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+        dir.initWithPath(defaultFolder);
+        file = this.validateLeafName(dir, aDefaultFile, aSuggestedFileExt);
+      } catch(e) { }
+      aLauncher.saveDestinationAvailable(file);
+    }.bind(this)).then(null, Cu.reportError);
+  },
+
+  validateLeafName: function(aLocalFile, aLeafName, aFileExt) {
+    if (!(aLocalFile && this.isUsableDirectory(aLocalFile)))
+      return null;
+
+    // Remove any leading periods, since we don't want to save hidden files
+    // automatically.
+    aLeafName = aLeafName.replace(/^\.+/, "");
+
+    if (aLeafName == "")
+      aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");
+    aLocalFile.append(aLeafName);
+
+    this.makeFileUnique(aLocalFile);
+    return aLocalFile;
+  },
+
+  makeFileUnique: function(aLocalFile) {
+    try {
+      // Note - this code is identical to that in
+      //   toolkit/content/contentAreaUtils.js.
+      // If you are updating this code, update that code too! We can't share code
+      // here since this is called in a js component.
+      let collisionCount = 0;
+      while (aLocalFile.exists()) {
+        collisionCount++;
+        if (collisionCount == 1) {
+          // Append "(2)" before the last dot in (or at the end of) the filename
+          // special case .ext.gz etc files so we don't wind up with .tar(2).gz
+          if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i))
+            aLocalFile.leafName = aLocalFile.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&");
+          else
+            aLocalFile.leafName = aLocalFile.leafName.replace(/(\.[^\.]*)?$/, "(2)$&");
+        }
+        else {
+          // replace the last (n) in the filename with (n+1)
+          aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount+1) + ")");
+        }
+      }
+      aLocalFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
+    }
+    catch (e) {
+      dump("*** exception in makeFileUnique: " + e + "\n");
+
+      if (e.result == Cr.NS_ERROR_FILE_ACCESS_DENIED)
+        throw e;
+
+      if (aLocalFile.leafName == "" || aLocalFile.isDirectory()) {
+        aLocalFile.append("unnamed");
+        if (aLocalFile.exists())
+          aLocalFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
+      }
+    }
+  },
+
+  isUsableDirectory: function(aDirectory) {
+    return aDirectory.exists() &&
+           aDirectory.isDirectory() &&
+           aDirectory.isWritable();
+  },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HelperAppLauncherDialog]);
--- a/b2g/components/moz.build
+++ b/b2g/components/moz.build
@@ -10,16 +10,17 @@ MODULE = 'B2GComponents'
 
 EXTRA_COMPONENTS += [
     'ActivitiesGlue.js',
     'AlertsService.js',
     'B2GAboutRedirector.js',
     'ContentHandler.js',
     'ContentPermissionPrompt.js',
     'FilePicker.js',
+    'HelperAppDialog.js',
     'MailtoProtocolHandler.js',
     'PaymentGlue.js',
     'ProcessGlobal.js',
     'SmsProtocolHandler.js',
     'TelProtocolHandler.js',
     'WebappsUpdateTimer.js',
     'YoutubeProtocolHandler.js',
 ]
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "eb98f1a5ade8454427a56053c9ee2f67b1a8c9c1", 
+    "revision": "f6f928dc9106402f7101dc55f9401cd136622faf", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -390,18 +390,16 @@
 @BINPATH@/components/HttpDataUsage.manifest
 @BINPATH@/components/HttpDataUsage.js
 @BINPATH@/components/SiteSpecificUserAgent.js
 @BINPATH@/components/SiteSpecificUserAgent.manifest
 @BINPATH@/components/storage-mozStorage.js
 @BINPATH@/components/crypto-SDR.js
 @BINPATH@/components/jsconsole-clhandler.manifest
 @BINPATH@/components/jsconsole-clhandler.js
-@BINPATH@/components/nsHelperAppDlg.manifest
-@BINPATH@/components/nsHelperAppDlg.js
 @BINPATH@/components/nsDownloadManagerUI.manifest
 @BINPATH@/components/nsDownloadManagerUI.js
 @BINPATH@/components/nsSidebar.manifest
 @BINPATH@/components/nsSidebar.js
 
 ; WiFi, NetworkManager, NetworkStats
 #ifdef MOZ_WIDGET_GONK
 @BINPATH@/components/DOMWifiManager.js
@@ -769,16 +767,17 @@ bin/components/@DLL_PREFIX@nkgnomevfs@DL
 @BINPATH@/components/PaymentGlue.js
 @BINPATH@/components/YoutubeProtocolHandler.js
 @BINPATH@/components/RecoveryService.js
 @BINPATH@/components/MailtoProtocolHandler.js
 @BINPATH@/components/SmsProtocolHandler.js
 @BINPATH@/components/TelProtocolHandler.js
 @BINPATH@/components/B2GAboutRedirector.js
 @BINPATH@/components/FilePicker.js
+@BINPATH@/components/HelperAppDialog.js
 
 @BINPATH@/components/DataStore.manifest
 @BINPATH@/components/DataStoreService.js
 @BINPATH@/components/dom_datastore.xpt
 
 #ifdef MOZ_WEBSPEECH
 @BINPATH@/components/dom_webspeechsynth.xpt
 #endif
--- a/toolkit/components/jsdownloads/src/DownloadCore.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadCore.jsm
@@ -132,22 +132,22 @@ const kProgressUpdateIntervalMs = 400;
 ////////////////////////////////////////////////////////////////////////////////
 //// Download
 
 /**
  * Represents a single download, with associated state and actions.  This object
  * is transient, though it can be included in a DownloadList so that it can be
  * managed by the user interface and persisted across sessions.
  */
-function Download()
+this.Download = function ()
 {
   this._deferSucceeded = Promise.defer();
 }
 
-Download.prototype = {
+this.Download.prototype = {
   /**
    * DownloadSource object associated with this download.
    */
   source: null,
 
   /**
    * DownloadTarget object associated with this download.
    */
@@ -993,19 +993,19 @@ Download.fromSerializable = function (aS
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadSource
 
 /**
  * Represents the source of a download, for example a document or an URI.
  */
-function DownloadSource() { }
+this.DownloadSource = function () {}
 
-DownloadSource.prototype = {
+this.DownloadSource.prototype = {
   /**
    * String containing the URI for the download source.
    */
   url: null,
 
   /**
    * Indicates whether the download originated from a private window.  This
    * determines the context of the network request that is made to retrieve the
@@ -1057,17 +1057,17 @@ DownloadSource.prototype = {
  *                     window.  If omitted, the download is public.
  *          referrer: String containing the referrer URI of the download source.
  *                    Can be omitted or null if no referrer should be sent or
  *                    the download source is not HTTP.
  *        }
  *
  * @return The newly created DownloadSource object.
  */
-DownloadSource.fromSerializable = function (aSerializable) {
+this.DownloadSource.fromSerializable = function (aSerializable) {
   let source = new DownloadSource();
   if (isString(aSerializable)) {
     // Convert String objects to primitive strings at this point.
     source.url = aSerializable.toString();
   } else if (aSerializable instanceof Ci.nsIURI) {
     source.url = aSerializable.spec;
   } else {
     // Convert String objects to primitive strings at this point.
@@ -1088,19 +1088,19 @@ DownloadSource.fromSerializable = functi
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadTarget
 
 /**
  * Represents the target of a download, for example a file in the global
  * downloads directory, or a file in the system temporary directory.
  */
-function DownloadTarget() { }
+this.DownloadTarget = function () {}
 
-DownloadTarget.prototype = {
+this.DownloadTarget.prototype = {
   /**
    * String containing the path of the target file.
    */
   path: null,
 
   /**
    * String containing the path of the ".part" file containing the data
    * downloaded so far, or null to disable the use of a ".part" file to keep
@@ -1136,17 +1136,17 @@ DownloadTarget.prototype = {
  *        object with the following properties:
  *        {
  *          path: String containing the path of the target file.
  *          partFilePath: optional string containing the part file path.
  *        }
  *
  * @return The newly created DownloadTarget object.
  */
-DownloadTarget.fromSerializable = function (aSerializable) {
+this.DownloadTarget.fromSerializable = function (aSerializable) {
   let target = new DownloadTarget();
   if (isString(aSerializable)) {
     // Convert String objects to primitive strings at this point.
     target.path = aSerializable.toString();
   } else if (aSerializable instanceof Ci.nsIFile) {
     // Read the "path" property of nsIFile after checking the object type.
     target.path = aSerializable.path;
   } else {
@@ -1179,17 +1179,17 @@ DownloadTarget.fromSerializable = functi
  *                        download is a network failure or a local file failure,
  *                        based on a set of known values of the result code.
  *                        This is useful when the error is received by a
  *                        component that handles both aspects of the download.
  *          }
  *        The properties object may also contain any of the DownloadError's
  *        because properties, which will be set accordingly in the error object.
  */
-function DownloadError(aProperties)
+this.DownloadError = function (aProperties)
 {
   const NS_ERROR_MODULE_BASE_OFFSET = 0x45;
   const NS_ERROR_MODULE_NETWORK = 6;
   const NS_ERROR_MODULE_FILES = 13;
 
   // Set the error name used by the Error object prototype first.
   this.name = "DownloadError";
   this.result = aProperties.result || Cr.NS_ERROR_FAILURE;
@@ -1226,17 +1226,17 @@ function DownloadError(aProperties)
     this.becauseBlockedByReputationCheck = true;
   } else if (aProperties.becauseBlocked) {
     this.becauseBlocked = true;
   }
 
   this.stack = new Error().stack;
 }
 
-DownloadError.prototype = {
+this.DownloadError.prototype = {
   __proto__: Error.prototype,
 
   /**
    * The result code associated with this error.
    */
   result: false,
 
   /**
@@ -1269,19 +1269,19 @@ DownloadError.prototype = {
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadSaver
 
 /**
  * Template for an object that actually transfers the data for the download.
  */
-function DownloadSaver() { }
+this.DownloadSaver = function () {}
 
-DownloadSaver.prototype = {
+this.DownloadSaver.prototype = {
   /**
    * Download object for raising notifications and reading properties.
    *
    * If the tryToKeepPartialData property of the download object is false, the
    * saver should never try to keep partially downloaded data if the download
    * fails.
    */
   download: null,
@@ -1409,17 +1409,17 @@ DownloadSaver.prototype = {
  *
  * @param aSerializable
  *        Serializable representation of a DownloadSaver object.  If no initial
  *        state information for the saver object is needed, can be a string
  *        representing the class of the download operation, for example "copy".
  *
  * @return The newly created DownloadSaver object.
  */
-DownloadSaver.fromSerializable = function (aSerializable) {
+this.DownloadSaver.fromSerializable = function (aSerializable) {
   let serializable = isString(aSerializable) ? { type: aSerializable }
                                              : aSerializable;
   let saver;
   switch (serializable.type) {
     case "copy":
       saver = DownloadCopySaver.fromSerializable(serializable);
       break;
     case "legacy":
@@ -1432,19 +1432,19 @@ DownloadSaver.fromSerializable = functio
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadCopySaver
 
 /**
  * Saver object that simply copies the entire source file to the target.
  */
-function DownloadCopySaver() { }
+this.DownloadCopySaver = function () {}
 
-DownloadCopySaver.prototype = {
+this.DownloadCopySaver.prototype = {
   __proto__: DownloadSaver.prototype,
 
   /**
    * BackgroundFileSaver object currently handling the download.
    */
   _backgroundFileSaver: null,
 
   /**
@@ -1777,17 +1777,17 @@ DownloadCopySaver.prototype = {
  * Creates a new DownloadCopySaver object, with its initial state derived from
  * its serializable representation.
  *
  * @param aSerializable
  *        Serializable representation of a DownloadCopySaver object.
  *
  * @return The newly created DownloadCopySaver object.
  */
-DownloadCopySaver.fromSerializable = function (aSerializable) {
+this.DownloadCopySaver.fromSerializable = function (aSerializable) {
   let saver = new DownloadCopySaver();
   if ("entityID" in aSerializable) {
     saver.entityID = aSerializable.entityID;
   }
 
   deserializeUnknownProperties(saver, aSerializable, property =>
     property != "entityID" && property != "type");
 
@@ -1797,23 +1797,23 @@ DownloadCopySaver.fromSerializable = fun
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadLegacySaver
 
 /**
  * Saver object that integrates with the legacy nsITransfer interface.
  *
  * For more background on the process, see the DownloadLegacyTransfer object.
  */
-function DownloadLegacySaver()
+this.DownloadLegacySaver = function()
 {
   this.deferExecuted = Promise.defer();
   this.deferCanceled = Promise.defer();
 }
 
-DownloadLegacySaver.prototype = {
+this.DownloadLegacySaver.prototype = {
   __proto__: DownloadSaver.prototype,
 
   /**
    * Save the SHA-256 hash in raw bytes of the downloaded file. This may be
    * null when nsExternalHelperAppService (and thus BackgroundFileSaver) is not
    * invoked.
    */
   _sha256Hash: null,
@@ -2090,11 +2090,11 @@ DownloadLegacySaver.prototype = {
   },
 };
 
 /**
  * Returns a new DownloadLegacySaver object.  This saver type has a
  * deserializable form only when creating a new object in memory, because it
  * cannot be serialized to disk.
  */
-DownloadLegacySaver.fromSerializable = function () {
+this.DownloadLegacySaver.fromSerializable = function () {
   return new DownloadLegacySaver();
 };
--- a/toolkit/components/jsdownloads/src/DownloadImport.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadImport.jsm
@@ -48,17 +48,18 @@ const DOWNLOAD_QUEUED = 5;
 /**
  * Provides an object that has a method to import downloads
  * from the previous SQLite storage format.
  *
  * @param aList   A DownloadList where each successfully
  *                imported download will be added.
  * @param aPath   The path to the database file.
  */
-this.DownloadImport = function(aList, aPath) {
+this.DownloadImport = function (aList, aPath)
+{
   this.list = aList;
   this.path = aPath;
 }
 
 this.DownloadImport.prototype = {
   /**
    * Imports unfinished downloads from the previous SQLite storage
    * format (supporting schemas 7 and up), to the new Download object
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -34,18 +34,20 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
                                   "resource://gre/modules/DownloadUIHelper.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
+#ifdef MOZ_PLACES
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
+#endif
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/commonjs/sdk/core/promise.js");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
@@ -78,16 +80,20 @@ XPCOMUtils.defineLazyServiceGetter(this,
 /**
  * ArrayBufferView representing the bytes to be written to the "Zone.Identifier"
  * Alternate Data Stream to mark a file as coming from the Internet zone.
  */
 XPCOMUtils.defineLazyGetter(this, "gInternetZoneIdentifier", function() {
   return new TextEncoder().encode("[ZoneTransfer]\r\nZoneId=3\r\n");
 });
 
+XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
+                                   "@mozilla.org/telephony/volume-service;1",
+                                   "nsIVolumeService");
+
 const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer",
                                      "initWithCallback");
 
 /**
  * Indicates the delay between a change to the downloads data and the related
  * save operation.  This value is the result of a delicate trade-off, assuming
  * the host application uses the browser history instead of the download store
  * to save completed downloads.
@@ -124,17 +130,21 @@ const kPrefImportedFromSqlite = "browser
 this.DownloadIntegration = {
   // For testing only
   _testMode: false,
   testPromptDownloads: 0,
   dontLoadList: false,
   dontLoadObservers: false,
   dontCheckParentalControls: false,
   shouldBlockInTest: false,
+#ifdef MOZ_URL_CLASSIFIER
   dontCheckApplicationReputation: false,
+#else
+  dontCheckApplicationReputation: true,
+#endif
   shouldBlockInTestForApplicationReputation: false,
   dontOpenFileAndFolder: false,
   downloadDoneCalled: false,
   _deferTestOpenFile: null,
   _deferTestShowDir: null,
   _deferTestClearPrivateList: null,
 
   /**
@@ -226,16 +236,58 @@ this.DownloadIntegration = {
       // complete initialization of the view used for detecting changes to
       // downloads to be persisted, before other callers get a chance to modify
       // the list without being detected.
       yield new DownloadAutoSaveView(aList, this._store).initialize();
       new DownloadHistoryObserver(aList);
     }.bind(this));
   },
 
+#ifdef MOZ_WIDGET_GONK
+  /**
+    * Finds the default download directory which can be either in the
+    * internal storage or on the sdcard.
+    *
+    * @return {Promise}
+    * @resolves The downloads directory string path.
+    */
+  _getDefaultDownloadDirectory: function() {
+    return Task.spawn(function() {
+      let directoryPath;
+      let win = Services.wm.getMostRecentWindow("navigator:browser");
+      let storages = win.navigator.getDeviceStorages("sdcard");
+      let preferredStorageName;
+      // Use the first one or the default storage.
+      storages.forEach((aStorage) => {
+        if (aStorage.default || !preferredStorageName) {
+          preferredStorageName = aStorage.storageName;
+        }
+      });
+
+      // Now get the path for this storage area.
+      if (preferredStorageName) {
+        let volume = volumeService.getVolumeByName(preferredStorageName);
+        if (volume &&
+            volume.isMediaPresent &&
+            !volume.isMountLocked &&
+            !volume.isSharing) {
+          directoryPath = OS.Path.join(volume.mountPoint, "downloads");
+          yield OS.File.makeDir(directoryPath, { ignoreExisting: true });
+        }
+      }
+      if (directoryPath) {
+        throw new Task.Result(directoryPath);
+      } else {
+        throw new Components.Exception("No suitable storage for downloads.",
+                                       Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH);
+      }
+    });
+  },
+#endif
+
   /**
    * Determines if a Download object from the list of persistent downloads
    * should be saved into a file, so that it can be restored across sessions.
    *
    * This function allows filtering out downloads that the host application is
    * not interested in persisting across sessions, for example downloads that
    * finished successfully.
    *
@@ -281,24 +333,26 @@ this.DownloadIntegration = {
       // the default Downloads directory.
       let version = parseFloat(Services.sysinfo.getProperty("version"));
       if (version < 6) {
         directoryPath = yield this._createDownloadsDirectory("Pers");
       } else {
         directoryPath = this._getDirectory("DfltDwnld");
       }
 #elifdef XP_UNIX
-#ifdef ANDROID
+#ifdef MOZ_WIDGET_ANDROID
       // Android doesn't have a $HOME directory, and by default we only have
       // write access to /data/data/org.mozilla.{$APP} and /sdcard
       directoryPath = gEnvironment.get("DOWNLOADS_DIRECTORY");
       if (!directoryPath) {
         throw new Components.Exception("DOWNLOADS_DIRECTORY is not set.",
                                        Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH);
       }
+#elifdef MOZ_WIDGET_GONK
+      directoryPath = this._getDefaultDownloadDirectory();
 #else
       // For Linux, use XDG download dir, with a fallback to Home/Downloads
       // if the XDG user dirs are disabled.
       try {
         directoryPath = this._getDirectory("DfltDwnld");
       } catch(e) {
         directoryPath = yield this._createDownloadsDirectory("Home");
       }
@@ -316,16 +370,19 @@ this.DownloadIntegration = {
    * Returns the user downloads directory asynchronously.
    *
    * @return {Promise}
    * @resolves The downloads directory string path.
    */
   getPreferredDownloadsDirectory: function DI_getPreferredDownloadsDirectory() {
     return Task.spawn(function() {
       let directoryPath = null;
+#ifdef MOZ_WIDGET_GONK
+      directoryPath = this._getDefaultDownloadDirectory();
+#else
       let prefValue = 1;
 
       try {
         prefValue = Services.prefs.getIntPref("browser.download.folderList");
       } catch(e) {}
 
       switch(prefValue) {
         case 0: // Desktop
@@ -343,32 +400,35 @@ this.DownloadIntegration = {
           } catch(ex) {
             // Either the preference isn't set or the directory cannot be created.
             directoryPath = yield this.getSystemDownloadsDirectory();
           }
           break;
         default:
           directoryPath = yield this.getSystemDownloadsDirectory();
       }
+#endif
       throw new Task.Result(directoryPath);
     }.bind(this));
   },
 
   /**
    * Returns the temporary downloads directory asynchronously.
    *
    * @return {Promise}
    * @resolves The downloads directory string path.
    */
   getTemporaryDownloadsDirectory: function DI_getTemporaryDownloadsDirectory() {
     return Task.spawn(function() {
       let directoryPath = null;
 #ifdef XP_MACOSX
       directoryPath = yield this.getPreferredDownloadsDirectory();
-#elifdef ANDROID
+#elifdef MOZ_WIDGET_ANDROID
+      directoryPath = yield this.getSystemDownloadsDirectory();
+#elifdef MOZ_WIDGET_GONK
       directoryPath = yield this.getSystemDownloadsDirectory();
 #else
       // For Metro mode on Windows 8,  we want searchability for documents
       // that the user chose to open with an external application.
       if (Services.metro && Services.metro.immersive) {
         directoryPath = yield this.getSystemDownloadsDirectory();
       } else {
         directoryPath = this._getDirectory("TmpD");
@@ -948,33 +1008,34 @@ this.DownloadObserver = {
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference])
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadHistoryObserver
 
+#ifdef MOZ_PLACES
 /**
  * Registers a Places observer so that operations on download history are
  * reflected on the provided list of downloads.
  *
  * You do not need to keep a reference to this object in order to keep it alive,
  * because the history service already keeps a strong reference to it.
  *
  * @param aList
  *        DownloadList object linked to this observer.
  */
-function DownloadHistoryObserver(aList)
+this.DownloadHistoryObserver = function (aList)
 {
   this._list = aList;
   PlacesUtils.history.addObserver(this, false);
 }
 
-DownloadHistoryObserver.prototype = {
+this.DownloadHistoryObserver.prototype = {
   /**
    * DownloadList object linked to this observer.
    */
   _list: null,
 
   ////////////////////////////////////////////////////////////////////////////
   //// nsISupports
 
@@ -994,16 +1055,22 @@ DownloadHistoryObserver.prototype = {
 
   onTitleChanged: function () {},
   onBeginUpdateBatch: function () {},
   onEndUpdateBatch: function () {},
   onVisit: function () {},
   onPageChanged: function () {},
   onDeleteVisits: function () {},
 };
+#else
+/**
+ * Empty implementation when we have no Places support, for example on B2G.
+ */
+this.DownloadHistoryObserver = function (aList) {}
+#endif
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadAutoSaveView
 
 /**
  * This view can be added to a DownloadList object to trigger a save operation
  * in the given DownloadStore object when a relevant change occurs.  You should
  * call the "initialize" method in order to register the view and load the
@@ -1012,23 +1079,24 @@ DownloadHistoryObserver.prototype = {
  * You do not need to keep a reference to this object in order to keep it alive,
  * because the DownloadList object already keeps a strong reference to it.
  *
  * @param aList
  *        The DownloadList object on which the view should be registered.
  * @param aStore
  *        The DownloadStore object used for saving.
  */
-function DownloadAutoSaveView(aList, aStore) {
+this.DownloadAutoSaveView = function (aList, aStore)
+{
   this._list = aList;
   this._store = aStore;
   this._downloadsMap = new Map();
 }
 
-DownloadAutoSaveView.prototype = {
+this.DownloadAutoSaveView.prototype = {
   /**
    * DownloadList object linked to this view.
    */
   _list: null,
 
   /**
    * The DownloadStore object used for saving.
    */
--- a/toolkit/components/jsdownloads/src/DownloadList.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadList.jsm
@@ -43,22 +43,23 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadList
 
 /**
  * Represents a collection of Download objects that can be viewed and managed by
  * the user interface, and persisted across sessions.
  */
-function DownloadList() {
+this.DownloadList = function ()
+{
   this._downloads = [];
   this._views = new Set();
 }
 
-DownloadList.prototype = {
+this.DownloadList.prototype = {
   /**
    * Array of Download objects currently in the list.
    */
   _downloads: null,
 
   /**
    * Retrieves a snapshot of the downloads that are currently in the list.  The
    * returned array does not change when downloads are added or removed, though
@@ -264,26 +265,26 @@ DownloadList.prototype = {
  * underlying lists, based on their "source.isPrivate" property.  Views on this
  * list will receive notifications for both public and private downloads.
  *
  * @param aPublicList
  *        Underlying DownloadList containing public downloads.
  * @param aPrivateList
  *        Underlying DownloadList containing private downloads.
  */
-function DownloadCombinedList(aPublicList, aPrivateList)
+this.DownloadCombinedList = function (aPublicList, aPrivateList)
 {
   DownloadList.call(this);
   this._publicList = aPublicList;
   this._privateList = aPrivateList;
   aPublicList.addView(this).then(null, Cu.reportError);
   aPrivateList.addView(this).then(null, Cu.reportError);
 }
 
-DownloadCombinedList.prototype = {
+this.DownloadCombinedList.prototype = {
   __proto__: DownloadList.prototype,
 
   /**
    * Underlying DownloadList containing public downloads.
    */
   _publicList: null,
 
   /**
@@ -366,22 +367,23 @@ DownloadCombinedList.prototype = {
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadSummary
 
 /**
  * Provides an aggregated view on the contents of a DownloadList.
  */
-function DownloadSummary() {
+this.DownloadSummary = function ()
+{
   this._downloads = [];
   this._views = new Set();
 }
 
-DownloadSummary.prototype = {
+this.DownloadSummary.prototype = {
   /**
    * Array of Download objects that are currently part of the summary.
    */
   _downloads: null,
 
   /**
    * Underlying DownloadList whose contents should be summarized.
    */
--- a/toolkit/components/jsdownloads/src/DownloadStore.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadStore.jsm
@@ -66,23 +66,23 @@ XPCOMUtils.defineLazyGetter(this, "gText
  * Handles serialization of Download objects and persistence into a file, so
  * that the state of downloads can be restored across sessions.
  *
  * @param aList
  *        DownloadList object to be populated or serialized.
  * @param aPath
  *        String containing the file path where data should be saved.
  */
-function DownloadStore(aList, aPath)
+this.DownloadStore = function (aList, aPath)
 {
   this.list = aList;
   this.path = aPath;
 }
 
-DownloadStore.prototype = {
+this.DownloadStore.prototype = {
   /**
    * DownloadList object to be populated or serialized.
    */
   list: null,
 
   /**
    * String containing the file path where data should be saved.
    */
--- a/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadUIHelper.jsm
@@ -101,22 +101,27 @@ XPCOMUtils.defineLazyGetter(DownloadUIHe
 
 /**
  * Allows displaying prompts related to downloads.
  *
  * @param aParent
  *        The nsIDOMWindow to which prompts should be attached, or null to
  *        attach prompts to the most recently active window.
  */
-function DownloadPrompter(aParent)
+this.DownloadPrompter = function (aParent)
 {
+#ifdef MOZ_WIDGET_GONK
+  // On B2G there is no prompter implementation.
+  this._prompter = null;
+#else
   this._prompter = Services.ww.getNewPrompter(aParent);
+#endif
 }
 
-DownloadPrompter.prototype = {
+this.DownloadPrompter.prototype = {
   /**
    * Constants with the different type of prompts.
    */
   ON_QUIT: "prompt-on-quit",
   ON_OFFLINE: "prompt-on-offline",
   ON_LEAVE_PRIVATE_BROWSING: "prompt-on-leave-private-browsing",
 
   /**
@@ -136,16 +141,21 @@ DownloadPrompter.prototype = {
    * @resolves Boolean indicating whether the launch operation can continue.
    * @rejects JavaScript exception.
    */
   confirmLaunchExecutable: function (aPath)
   {
     const kPrefAlertOnEXEOpen = "browser.download.manager.alertOnEXEOpen";
 
     try {
+      // Always launch in case we have no prompter implementation.
+      if (!this._prompter) {
+        return Promise.resolve(true);
+      }
+
       try {
         if (!Services.prefs.getBoolPref(kPrefAlertOnEXEOpen)) {
           return Promise.resolve(true);
         }
       } catch (ex) {
         // If the preference does not exist, continue with the prompt.
       }
 
@@ -173,24 +183,25 @@ DownloadPrompter.prototype = {
    * Displays a warning message box that informs that there are active
    * downloads, and asks whether the user wants to cancel them or not.
    *
    * @param aDownloadsCount
    *        The current downloads count.
    * @param aPromptType
    *        The type of prompt notification depending on the observer.
    *
-   * @return True to cancel the downloads and continue, false to abort the
+   * @return False to cancel the downloads and continue, true to abort the
    *         operation.
    */
   confirmCancelDownloads: function DP_confirmCancelDownload(aDownloadsCount,
                                                             aPromptType)
   {
-    // If there are no active downloads, then do nothing.
-    if (aDownloadsCount <= 0) {
+    // Always continue in case we have no prompter implementation, or if there
+    // are no active downloads.
+    if (!this._prompter || aDownloadsCount <= 0) {
       return false;
     }
 
     let s = DownloadUIHelper.strings;
     let buttonFlags = (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
                       (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1);
     let okButton = aDownloadsCount > 1 ? s.cancelDownloadsOKTextMultiple(aDownloadsCount)
                                        : s.cancelDownloadsOKText;
--- a/uriloader/exthandler/gonk/nsOSHelperAppService.cpp
+++ b/uriloader/exthandler/gonk/nsOSHelperAppService.cpp
@@ -12,19 +12,20 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "nsOSHelperAppService.h"
 #include "nsMIMEInfoImpl.h"
 
-// Simplest nsIMIMEInfo implementation possible.
-// We don't need it to actually do anything.
 class nsGonkMIMEInfo : public nsMIMEInfoImpl {
+public:
+    nsGonkMIMEInfo(const nsACString& aMIMEType) : nsMIMEInfoImpl(aMIMEType) { }
+
 protected:
     virtual NS_HIDDEN_(nsresult) LoadUriInternal(nsIURI *aURI) {
         return NS_ERROR_NOT_IMPLEMENTED;
     }
 };
 
 nsOSHelperAppService::nsOSHelperAppService() : nsExternalHelperAppService()
 {
@@ -35,19 +36,19 @@ nsOSHelperAppService::~nsOSHelperAppServ
 }
 
 already_AddRefed<nsIMIMEInfo>
 nsOSHelperAppService::GetMIMEInfoFromOS(const nsACString& aMIMEType,
                                         const nsACString& aFileExt,
                                         bool* aFound)
 {
     *aFound = false;
-    // Even if we return false for aFound, we need to return a non-null
-    // nsIMIMEInfo implementation to prevent a crash in the caller.
-    nsRefPtr<nsGonkMIMEInfo> mimeInfo = new nsGonkMIMEInfo();
+    // Even if we return false for aFound, we need to return a working
+    // nsIMIMEInfo implementation that will be used by the caller.
+    nsRefPtr<nsGonkMIMEInfo> mimeInfo = new nsGonkMIMEInfo(aMIMEType);
     return mimeInfo.forget();
 }
 
 nsresult
 nsOSHelperAppService::OSProtocolHandlerExists(const char* aScheme,
                                               bool* aExists)
 {
     *aExists = false;
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -111,16 +111,20 @@
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidBridge.h"
 #endif
 
 #include "mozilla/Preferences.h"
 #include "mozilla/ipc/URIUtils.h"
 
+#ifdef MOZ_WIDGET_GONK
+#include "nsDeviceStorage.h"
+#endif
+
 using namespace mozilla;
 using namespace mozilla::ipc;
 
 // Buffer file writes in 32kb chunks
 #define BUFFERED_OUTPUT_SIZE (1024 * 32)
 
 // Download Folder location constants
 #define NS_PREF_DOWNLOAD_DIR        "browser.download.dir"
@@ -323,16 +327,42 @@ static nsresult GetDownloadDirectory(nsI
   }
 
   if (!dir) {
     // If not, we default to the OS X default download location.
     nsresult rv = NS_GetSpecialDirectory(NS_OSX_DEFAULT_DOWNLOAD_DIR,
                                          getter_AddRefs(dir));
     NS_ENSURE_SUCCESS(rv, rv);
   }
+#elif defined(MOZ_WIDGET_GONK)
+  // On Gonk, store the files on the sdcard in the downloads directory.
+  // We need to check with the volume manager which storage point is
+  // available.
+
+  // Pick the default storage in case multiple (internal and external) ones
+  // are available.
+  nsString storageName;
+  nsDOMDeviceStorage::GetDefaultStorageName(NS_LITERAL_STRING("sdcard"),
+                                            storageName);
+  NS_ENSURE_TRUE(!storageName.IsEmpty(), NS_ERROR_FAILURE);
+
+  DeviceStorageFile dsf(NS_LITERAL_STRING("sdcard"),
+                        storageName,
+                        NS_LITERAL_STRING("downloads"));
+  NS_ENSURE_TRUE(dsf.mFile, NS_ERROR_FAILURE);
+  NS_ENSURE_TRUE(dsf.IsAvailable(), NS_ERROR_FAILURE);
+
+  bool alreadyThere;
+  nsresult rv = dsf.mFile->Exists(&alreadyThere);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (!alreadyThere) {
+    rv = dsf.mFile->Create(nsIFile::DIRECTORY_TYPE, 0770);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  dir = dsf.mFile;
 #elif defined(ANDROID)
   // On mobile devices, we are avoiding exposing users to the file
   // system, and don't save downloads to temp directories
 
   // On Android we only return something if we have and SD-card
   char* downloadDir = getenv("DOWNLOADS_DIRECTORY");
   nsresult rv;
   if (downloadDir) {