Merge f-t to m-c
authorPhil Ringnalda <philringnalda@gmail.com>
Sun, 27 Oct 2013 10:22:27 -0700
changeset 166211 4ec6e8c7c2ceaa19dc8cfcb1708971c0ae4c37cf
parent 166201 508288a2b62cc6e6332ec008a3448dc2fff769c2 (current diff)
parent 166210 4196008456101facd333b536053918ec211e2c6a (diff)
child 166212 5a85d47b5642bd7fa1259fc82100cde9ab32f354
child 166215 809832214af3f680dafec5f53be50941b55d1c16
child 166221 0ea543f598ed3a8c0611fff8d4dfeaa0c85ddbba
child 170493 e96ae667f6696e3eb142c2b2c49d42bda04daca9
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.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 f-t to m-c
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -566,17 +566,17 @@
           <hbox id="urlbar-icons">
             <image id="page-report-button"
                    class="urlbar-icon"
                    hidden="true"
                    tooltiptext="&pageReportIcon.tooltip;"
                    onclick="gPopupBlockerObserver.onReportButtonClick(event);"/>
             <image id="star-button"
                    class="urlbar-icon"
-                   onclick="BookmarkingUI.onCommand(event);"/>
+                   onclick="if (event.button === 0) BookmarkingUI.onCommand(event);"/>
             <image id="go-button"
                    class="urlbar-icon"
                    tooltiptext="&goEndCap.tooltip;"
                    onclick="gURLBar.handleCommand(event);"/>
           </hbox>
           <toolbarbutton id="urlbar-go-button"
                          class="chromeclass-toolbar-additional"
                          onclick="gURLBar.handleCommand(event);"
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
@@ -59,31 +59,31 @@ function test() {
     is(prefs.getComplexValue("lastDir", Ci.nsIFile).path, aDisplayDir.path,
        "LastDir should be the expected display dir");
     // Check gDownloadLastDir value.
     is(gDownloadLastDir.file.path, aDisplayDir.path,
        "gDownloadLastDir should be the expected display dir");
 
     MockFilePicker.returnFiles = [aFile];
     MockFilePicker.displayDirectory = null;
-    aWin.getTargetFile(params, function() {
+    aWin.promiseTargetFile(params).then(function() {
       // File picker should start with expected display dir.
       is(MockFilePicker.displayDirectory.path, aDisplayDir.path,
          "File picker should start with browser.download.lastDir");
       // browser.download.lastDir should be modified on not private windows
       is(prefs.getComplexValue("lastDir", Ci.nsIFile).path, aLastDir.path,
          "LastDir should be the expected last dir");
       // gDownloadLastDir should be usable outside of private windows
       is(gDownloadLastDir.file.path, aGlobalLastDir.path,
          "gDownloadLastDir should be the expected global last dir");
 
       gDownloadLastDir.cleanupPrivateFile();
       aWin.close();
       aCallback();
-    });
+    }).then(null, function() { ok(false); });
   }
 
   testOnWindow(false, function(win, downloadDir) {
     testDownloadDir(win, downloadDir, file1, tmpDir, dir1, dir1, function() {
       testOnWindow(true, function(win, downloadDir) {
         testDownloadDir(win, downloadDir, file2, dir1, dir1, dir2, function() {
           testOnWindow(false, function(win, downloadDir) {
             testDownloadDir(win, downloadDir, file3, dir1, dir3, dir3, finish);
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,4 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 0.8.629
+Current extension version is: 0.8.641
 
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.629';
-PDFJS.build = 'b16b3be';
+PDFJS.version = '0.8.641';
+PDFJS.build = '19485c3';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -6570,16 +6570,21 @@ var CanvasGraphics = (function CanvasGra
       }
       assert(group.bbox, 'Bounding box is required.');
 
       // Based on the current transform figure out how big the bounding box
       // will actually be.
       var bounds = Util.getAxialAlignedBoundingBox(
                     group.bbox,
                     currentCtx.mozCurrentTransform);
+      // Clip the bounding box to the current canvas.
+      bounds = Util.intersect(bounds, [0,
+                                       0,
+                                       currentCtx.canvas.width,
+                                       currentCtx.canvas.height]);
       // Use ceil in case we're between sizes so we don't create canvas that is
       // too small and make the canvas at least 1x1 pixels.
       var drawnWidth = Math.max(Math.ceil(bounds[2] - bounds[0]), 1);
       var drawnHeight = Math.max(Math.ceil(bounds[3] - bounds[1]), 1);
 
       var scratchCanvas = CachedCanvases.getCanvas(
         'groupAt' + this.groupLevel, drawnWidth, drawnHeight, true);
       var groupCtx = scratchCanvas.context;
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.629';
-PDFJS.build = 'b16b3be';
+PDFJS.version = '0.8.641';
+PDFJS.build = '19485c3';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -218,17 +218,17 @@ limitations under the License.
           <menuitem id="contextLastPage" label="Last Page"
                     data-l10n-id="last_page"></menuitem>
           <menuitem id="contextPageRotateCw" label="Rotate Clockwise"
                     data-l10n-id="page_rotate_cw"></menuitem>
           <menuitem id="contextPageRotateCcw" label="Rotate Counter-Clockwise"
                     data-l10n-id="page_rotate_ccw"></menuitem>
         </menu>
 
-      <div id="viewerContainer">   
+        <div id="viewerContainer" tabindex="0">
           <div id="viewer"></div>
         </div>
 
         <div id="errorWrapper" hidden='true'>
           <div id="errorMessageLeft">
             <span id="errorMessage"></span>
             <button id="errorShowMore" data-l10n-id="error_more_info">
               More Information
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -1294,33 +1294,35 @@ var PDFHistory = {
 
 var SecondaryToolbar = {
   opened: false,
   previousContainerHeight: null,
   newContainerHeight: null,
 
   initialize: function secondaryToolbarInitialize(options) {
     this.toolbar = options.toolbar;
+    this.presentationMode = options.presentationMode;
     this.buttonContainer = this.toolbar.firstElementChild;
 
     // Define the toolbar buttons.
     this.toggleButton = options.toggleButton;
-    this.presentationMode = options.presentationMode;
+    this.presentationModeButton = options.presentationModeButton;
     this.openFile = options.openFile;
     this.print = options.print;
     this.download = options.download;
     this.firstPage = options.firstPage;
     this.lastPage = options.lastPage;
     this.pageRotateCw = options.pageRotateCw;
     this.pageRotateCcw = options.pageRotateCcw;
 
     // Attach the event listeners.
     var elements = [
       { element: this.toggleButton, handler: this.toggle },
-      { element: this.presentationMode, handler: this.presentationModeClick },
+      { element: this.presentationModeButton,
+        handler: this.presentationModeClick },
       { element: this.openFile, handler: this.openFileClick },
       { element: this.print, handler: this.printClick },
       { element: this.download, handler: this.downloadClick },
       { element: this.firstPage, handler: this.firstPageClick },
       { element: this.lastPage, handler: this.lastPageClick },
       { element: this.pageRotateCw, handler: this.pageRotateCwClick },
       { element: this.pageRotateCcw, handler: this.pageRotateCcwClick }
     ];
@@ -1330,17 +1332,17 @@ var SecondaryToolbar = {
       if (element) {
         element.addEventListener('click', elements[item].handler.bind(this));
       }
     }
   },
 
   // Event handling functions.
   presentationModeClick: function secondaryToolbarPresentationModeClick(evt) {
-    PresentationMode.request();
+    this.presentationMode.request();
     this.close();
   },
 
   openFileClick: function secondaryToolbarOpenFileClick(evt) {
     document.getElementById('fileInput').click();
     this.close(evt.target);
   },
 
@@ -1367,17 +1369,17 @@ var SecondaryToolbar = {
   },
 
   pageRotateCcwClick: function secondaryToolbarPageRotateCcwClick(evt) {
     PDFView.rotatePages(-90);
   },
 
   // Misc. functions for interacting with the toolbar.
   setMaxHeight: function secondaryToolbarSetMaxHeight(container) {
-    if (!container) {
+    if (!container || !this.buttonContainer) {
       return;
     }
     this.newContainerHeight = container.clientHeight;
     if (this.previousContainerHeight === this.newContainerHeight) {
       return;
     }
     this.buttonContainer.setAttribute('style',
       'max-height: ' + (this.newContainerHeight - SCROLLBAR_PADDING) + 'px;');
@@ -1405,20 +1407,16 @@ var SecondaryToolbar = {
   },
 
   toggle: function secondaryToolbarToggle() {
     if (this.opened) {
       this.close();
     } else {
       this.open();
     }
-  },
-
-  get isOpen() {
-    return this.opened;
   }
 };
 
 
 var PasswordPrompt = {
   visible: false,
   updatePassword: null,
   reason: null,
@@ -1501,16 +1499,18 @@ var PresentationMode = {
   active: false,
   args: null,
   contextMenuOpen: false,
 
   initialize: function presentationModeInitialize(options) {
     this.container = options.container;
     this.secondaryToolbar = options.secondaryToolbar;
 
+    this.viewer = this.container.firstElementChild;
+
     this.firstPage = options.firstPage;
     this.lastPage = options.lastPage;
     this.pageRotateCw = options.pageRotateCw;
     this.pageRotateCcw = options.pageRotateCcw;
 
     this.firstPage.addEventListener('click', function() {
       this.contextMenuOpen = false;
       this.secondaryToolbar.firstPageClick();
@@ -1533,17 +1533,18 @@ var PresentationMode = {
   get isFullscreen() {
     return (document.fullscreenElement ||
             document.mozFullScreen ||
             document.webkitIsFullScreen ||
             document.msFullscreenElement);
   },
 
   request: function presentationModeRequest() {
-    if (!PDFView.supportsFullscreen || this.isFullscreen) {
+    if (!PDFView.supportsFullscreen || this.isFullscreen ||
+        !this.viewer.hasChildNodes()) {
       return false;
     }
 
     if (this.container.requestFullscreen) {
       this.container.requestFullscreen();
     } else if (this.container.mozRequestFullScreen) {
       this.container.mozRequestFullScreen();
     } else if (this.container.webkitRequestFullScreen) {
@@ -1715,18 +1716,20 @@ var PDFView = {
 
     PDFFindController.initialize({
       pdfPageSource: this,
       integratedFind: this.supportsIntegratedFind
     });
 
     SecondaryToolbar.initialize({
       toolbar: document.getElementById('secondaryToolbar'),
+      presentationMode: PresentationMode,
       toggleButton: document.getElementById('secondaryToolbarToggle'),
-      presentationMode: document.getElementById('secondaryPresentationMode'),
+      presentationModeButton:
+        document.getElementById('secondaryPresentationMode'),
       openFile: document.getElementById('secondaryOpenFile'),
       print: document.getElementById('secondaryPrint'),
       download: document.getElementById('secondaryDownload'),
       firstPage: document.getElementById('firstPage'),
       lastPage: document.getElementById('lastPage'),
       pageRotateCw: document.getElementById('pageRotateCw'),
       pageRotateCcw: document.getElementById('pageRotateCcw')
     });
@@ -1963,18 +1966,18 @@ var PDFView = {
     Object.defineProperty(this, 'loadingBar', { value: bar,
                                                 enumerable: true,
                                                 configurable: true,
                                                 writable: false });
     return bar;
   },
 
   get isHorizontalScrollbarEnabled() {
-    var div = document.getElementById('viewerContainer');
-    return div.scrollWidth > div.clientWidth;
+    return (PresentationMode.active ? false :
+            (this.container.scrollWidth > this.container.clientWidth));
   },
 
   initPassiveLoading: function pdfViewInitPassiveLoading() {
     var pdfDataRangeTransport = {
       rangeListeners: [],
       progressListeners: [],
 
       addRangeListener: function PdfDataRangeTransport_addRangeListener(
@@ -3225,16 +3228,17 @@ var PageView = function pageView(contain
 
   this.getPagePoint = function pageViewGetPagePoint(x, y) {
     return this.viewport.convertToPdfPoint(x, y);
   };
 
   this.scrollIntoView = function pageViewScrollIntoView(dest) {
     if (PresentationMode.active) { // Avoid breaking presentation mode.
       dest = null;
+      PDFView.setScale(PDFView.currentScaleValue, true, true);
     }
     if (!dest) {
       scrollIntoView(div);
       return;
     }
 
     var x = 0, y = 0;
     var width = 0, height = 0, widthScale, heightScale;
@@ -4549,17 +4553,17 @@ window.addEventListener('DOMMouseScroll'
   } else if (PresentationMode.active) {
     var FIREFOX_DELTA_FACTOR = -40;
     PDFView.mouseScroll(evt.detail * FIREFOX_DELTA_FACTOR);
   }
 }, false);
 
 window.addEventListener('click', function click(evt) {
   if (!PresentationMode.active) {
-    if (SecondaryToolbar.isOpen && PDFView.container.contains(evt.target)) {
+    if (SecondaryToolbar.opened && PDFView.container.contains(evt.target)) {
       SecondaryToolbar.close();
     }
   } else if (evt.button === 0) {
     // Necessary since preventDefault() in 'mousedown' won't stop
     // the event propagation in all circumstances in presentation mode.
     evt.preventDefault();
   }
 }, false);
@@ -4644,16 +4648,19 @@ window.addEventListener('keydown', funct
     }
   }
   var controlsElement = document.getElementById('toolbar');
   while (curElement) {
     if (curElement === controlsElement && !PresentationMode.active)
       return; // ignoring if the 'toolbar' element is focused
     curElement = curElement.parentNode;
   }
+  // Workaround for issue in Firefox, that prevents scroll keys from working
+  // when elements with 'tabindex' are focused.
+  PDFView.container.blur();
 
   if (cmd === 0) { // no control key pressed at all.
     switch (evt.keyCode) {
       case 38: // up arrow
       case 33: // pg up
       case 8: // backspace
         if (!PresentationMode.active &&
             PDFView.currentScaleValue !== 'page-fit') {
@@ -4668,17 +4675,17 @@ window.addEventListener('keydown', funct
         }
         /* falls through */
       case 75: // 'k'
       case 80: // 'p'
         PDFView.page--;
         handled = true;
         break;
       case 27: // esc key
-        if (SecondaryToolbar.isOpen) {
+        if (SecondaryToolbar.opened) {
           SecondaryToolbar.close();
           handled = true;
         }
         if (!PDFView.supportsIntegratedFind && PDFFindBar.opened) {
           PDFFindBar.close();
           handled = true;
         }
         break;
--- a/browser/extensions/pdfjs/extension-files
+++ b/browser/extensions/pdfjs/extension-files
@@ -1,16 +1,16 @@
 chrome.manifest
 components/PdfRedirector.js
 components/PdfStreamConverter.js
+content/PdfJs.jsm
+content/PdfJsTelemetry.jsm
 content/build/pdf.js
 content/build/pdf.worker.js
 content/network.js
-content/PdfJs.jsm
-content/PdfJsTelemetry.jsm
 content/web/debugger.js
 content/web/images/annotation-check.svg
 content/web/images/annotation-comment.svg
 content/web/images/annotation-help.svg
 content/web/images/annotation-insert.svg
 content/web/images/annotation-key.svg
 content/web/images/annotation-newparagraph.svg
 content/web/images/annotation-note.svg
--- a/dom/system/gonk/AudioChannelManager.h
+++ b/dom/system/gonk/AudioChannelManager.h
@@ -44,18 +44,22 @@ public:
      return GetOwner();
   }
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   bool Headphones() const
   {
-    MOZ_ASSERT(mState != hal::SWITCH_STATE_UNKNOWN);
-    return mState != hal::SWITCH_STATE_OFF;
+    // Bug 929139 - Remove the assert check for SWITCH_STATE_UNKNOWN.
+    // If any devices (ex: emulator) didn't have the corresponding sys node for
+    // headset switch state then GonkSwitch will report the unknown state.
+    // So it is possible to get unknown state here.
+    return mState != hal::SWITCH_STATE_OFF &&
+           mState != hal::SWITCH_STATE_UNKNOWN;
   }
 
   bool SetVolumeControlChannel(const nsAString& aChannel);
 
   bool GetVolumeControlChannel(nsAString& aChannel);
 
   IMPL_EVENT_HANDLER(headphoneschange)
 
--- a/toolkit/components/osfile/modules/osfile_shared_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_shared_front.jsm
@@ -299,39 +299,43 @@ AbstractFile.AbstractIterator.prototype 
  * and |existing|.
  */
 AbstractFile.normalizeOpenMode = function normalizeOpenMode(mode) {
   let result = {
     read: false,
     write: false,
     trunc: false,
     create: false,
-    existing: false
+    existing: false,
+    append: true
   };
   for (let key in mode) {
-    if (!mode[key]) continue; // Only interpret true-ish keys
+    let val = !!mode[key]; // bool cast.
     switch (key) {
     case "read":
-      result.read = true;
+      result.read = val;
       break;
     case "write":
-      result.write = true;
+      result.write = val;
       break;
     case "truncate": // fallthrough
     case "trunc":
-      result.trunc = true;
-      result.write = true;
+      result.trunc = val;
+      result.write |= val;
       break;
     case "create":
-      result.create = true;
-      result.write = true;
+      result.create = val;
+      result.write |= val;
       break;
     case "existing": // fallthrough
     case "exist":
-      result.existing = true;
+      result.existing = val;
+      break;
+    case "append":
+      result.append = val;
       break;
     default:
       throw new TypeError("Mode " + key + " not understood");
     }
   }
   // Reject opposite modes
   if (result.existing && result.create) {
     throw new TypeError("Cannot specify both existing:true and create:true");
--- a/toolkit/components/osfile/modules/osfile_unix_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_unix_front.jsm
@@ -198,18 +198,21 @@
       *  be specified with |truncate| or |existing|.
       * - {bool} existing. If the file does not exist, this function
       *  fails. Cannot be specified with |create|.
       * - {bool} read If |true|, the file will be opened for
       *  reading. The file may also be opened for writing, depending
       *  on the other fields of |mode|.
       * - {bool} write If |true|, the file will be opened for
       *  writing. The file may also be opened for reading, depending
-      *  on the other fields of |mode|. If neither |truncate| nor
-      *  |create| is specified, the file is opened for appending.
+      *  on the other fields of |mode|.
+      * - {bool} append If |true|, the file will be opened for appending,
+      *  meaning the equivalent of |.setPosition(0, POS_END)| is executed
+      *  before each write. The default is |true|, i.e. opening a file for
+      *  appending. Specify |append: false| to open the file in regular mode.
       *
       * If neither |truncate|, |create| or |write| is specified, the file
       * is opened for reading.
       *
       * Note that |false|, |null| or |undefined| flags are simply ignored.
       *
       * @param {*=} options Additional options for file opening. This
       * implementation interprets the following fields:
@@ -246,22 +249,21 @@
              flags |= Const.O_TRUNC;
            } else {
              flags |= Const.O_CREAT | Const.O_TRUNC;
            }
          } else if (mode.create) {
            flags |= Const.O_CREAT | Const.O_EXCL;
          } else if (mode.read && !mode.write) {
            // flags are sufficient
-         } else /*append*/ {
-           if (mode.existing) {
-             flags |= Const.O_APPEND;
-           } else {
-             flags |= Const.O_APPEND | Const.O_CREAT;
-           }
+         } else if (!mode.existing) {
+           flags |= Const.O_CREAT;
+         }
+         if (mode.append) {
+           flags |= Const.O_APPEND;
          }
        }
        return error_or_file(UnixFile.open(path, flags, omode));
      };
 
      /**
       * Checks if a file exists
       *
@@ -543,20 +545,22 @@
        // Implement |copy| using |pump|.
        // This implementation would require some work before being able to
        // copy directories
        File.copy = function copy(sourcePath, destPath, options = {}) {
          let source, dest;
          let result;
          try {
            source = File.open(sourcePath);
+           // Need to open the output file with |append:false|, or else |splice|
+           // won't work.
            if (options.noOverwrite) {
-             dest = File.open(destPath, {create:true});
+             dest = File.open(destPath, {create:true, append:false});
            } else {
-             dest = File.open(destPath, {trunc:true});
+             dest = File.open(destPath, {trunc:true, append:false});
            }
            if (options.unixUserland) {
              result = pump_userland(source, dest, options);
            } else {
              result = pump(source, dest, options);
            }
          } catch (x) {
            if (dest) {
--- a/toolkit/components/osfile/modules/osfile_win_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_win_front.jsm
@@ -129,16 +129,21 @@
       * exceed the size of |buffer| in bytes.
       * @param {*=} options Additional options for writing. Ignored in
       * this implementation.
       *
       * @return {number} The number of bytes effectively written.
       * @throws {OS.File.Error} In case of I/O error.
       */
      File.prototype._write = function _write(buffer, nbytes, options) {
+       if (this._appendMode) {
+         // Need to manually seek on Windows, as O_APPEND is not supported.
+         // This is, of course, a race, but there is no real way around this.
+         this.setPosition(0, File.POS_END);
+       }
        // |gBytesWrittenPtr| is a pointer to |gBytesWritten|.
        throw_on_zero("write",
          WinFile.WriteFile(this.fd, buffer, nbytes, gBytesWrittenPtr, null)
        );
        return gBytesWritten.value;
      };
 
      /**
@@ -220,18 +225,21 @@
       *  be specified with |truncate| or |existing|.
       * - {bool} existing. If the file does not exist, this function
       *  fails. Cannot be specified with |create|.
       * - {bool} read If |true|, the file will be opened for
       *  reading. The file may also be opened for writing, depending
       *  on the other fields of |mode|.
       * - {bool} write If |true|, the file will be opened for
       *  writing. The file may also be opened for reading, depending
-      *  on the other fields of |mode|. If neither |truncate| nor
-      *  |create| is specified, the file is opened for appending.
+      *  on the other fields of |mode|.
+      * - {bool} append If |true|, the file will be opened for appending,
+      *  meaning the equivalent of |.setPosition(0, POS_END)| is executed
+      *  before each write. The default is |true|, i.e. opening a file for
+      *  appending. Specify |append: false| to open the file in regular mode.
       *
       * If neither |truncate|, |create| or |write| is specified, the file
       * is opened for reading.
       *
       * Note that |false|, |null| or |undefined| flags are simply ignored.
       *
       * @param {*=} options Additional options for file opening. This
       * implementation interprets the following fields:
@@ -259,25 +267,27 @@
       */
      File.open = function Win_open(path, mode = {}, options = {}) {
        let share = options.winShare !== undefined ? options.winShare : DEFAULT_SHARE;
        let security = options.winSecurity || null;
        let flags = options.winFlags !== undefined ? options.winFlags : DEFAULT_FLAGS;
        let template = options.winTemplate ? options.winTemplate._fd : null;
        let access;
        let disposition;
+
+       mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
+
        if ("winAccess" in options && "winDisposition" in options) {
          access = options.winAccess;
          disposition = options.winDisposition;
        } else if (("winAccess" in options && !("winDisposition" in options))
                  ||(!("winAccess" in options) && "winDisposition" in options)) {
          throw new TypeError("OS.File.open requires either both options " +
            "winAccess and winDisposition or neither");
        } else {
-         mode = OS.Shared.AbstractFile.normalizeOpenMode(mode);
          if (mode.read) {
            access |= Const.GENERIC_READ;
          }
          if (mode.write) {
            access |= Const.GENERIC_WRITE;
          }
          // Finally, handle create/existing/trunc
          if (mode.trunc) {
@@ -288,26 +298,28 @@
              disposition = Const.OPEN_EXISTING;
            } else {
              disposition = Const.CREATE_ALWAYS;
            }
          } else if (mode.create) {
            disposition = Const.CREATE_NEW;
          } else if (mode.read && !mode.write) {
            disposition = Const.OPEN_EXISTING;
-         } else /*append*/ {
-           if (mode.existing) {
-             disposition = Const.OPEN_EXISTING;
-           } else {
-             disposition = Const.OPEN_ALWAYS;
-           }
+         } else if (mode.existing) {
+           disposition = Const.OPEN_EXISTING;
+         } else {
+           disposition = Const.OPEN_ALWAYS;
          }
        }
+
        let file = error_or_file(WinFile.CreateFile(path,
          access, share, security, disposition, flags, template));
+
+       file._appendMode = !!mode.append;
+
        if (!(mode.trunc && mode.existing)) {
          return file;
        }
        // Now, perform manual truncation
        file.setPosition(0, File.POS_START);
        throw_on_zero("open",
          WinFile.SetEndOfFile(file.fd));
        return file;
new file mode 100644
--- /dev/null
+++ b/toolkit/components/osfile/tests/xpcshell/test_osfile_async_append.js
@@ -0,0 +1,122 @@
+"use strict";
+
+do_print("starting tests");
+
+Components.utils.import("resource://gre/modules/osfile.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+
+/**
+ * A test to check that the |append| mode flag is correctly implemented.
+ * (see bug 925865)
+ */
+
+function setup_mode(mode) {
+  // Complete mode.
+  let realMode = {
+    read: true,
+    write: true
+  };
+  for (let k in mode) {
+    realMode[k] = mode[k];
+  }
+  return realMode;
+}
+
+// Test append mode.
+function test_append(mode) {
+  let path = OS.Path.join(OS.Constants.Path.tmpDir,
+                          "test_osfile_async_append.tmp");
+
+  // Clear any left-over files from previous runs.
+  try {
+    yield OS.File.remove(path);
+  } catch (ex if ex.becauseNoSuchFile) {
+    // ignore
+  }
+
+  try {
+    mode = setup_mode(mode);
+    mode.append = true;
+    if (mode.trunc) {
+      // Pre-fill file with some data to see if |trunc| actually works.
+      yield OS.File.writeAtomic(path, new Uint8Array(500));
+    }
+    let file = yield OS.File.open(path, mode);
+    try {
+      yield file.write(new Uint8Array(1000));
+      yield file.setPosition(0, OS.File.POS_START);
+      yield file.read(100);
+      // Should be at offset 100, length 1000 now.
+      yield file.write(new Uint8Array(100));
+      // Should be at offset 1100, length 1100 now.
+      let stat = yield file.stat();
+      do_check_eq(1100, stat.size);
+    } finally {
+      yield file.close();
+    }
+  } catch(ex) {
+    try {
+      yield OS.File.remove(path);
+    } catch (ex if ex.becauseNoSuchFile) {
+      // ignore.
+    }
+  }
+}
+
+// Test no-append mode.
+function test_no_append(mode) {
+  let path = OS.Path.join(OS.Constants.Path.tmpDir,
+                          "test_osfile_async_noappend.tmp");
+
+  // Clear any left-over files from previous runs.
+  try {
+    yield OS.File.remove(path);
+  } catch (ex if ex.becauseNoSuchFile) {
+    // ignore
+  }
+
+  try {
+    mode = setup_mode(mode);
+    mode.append = false;
+    if (mode.trunc) {
+      // Pre-fill file with some data to see if |trunc| actually works.
+      yield OS.File.writeAtomic(path, new Uint8Array(500));
+    }
+    let file = yield OS.File.open(path, mode);
+    try {
+      yield file.write(new Uint8Array(1000));
+      yield file.setPosition(0, OS.File.POS_START);
+      yield file.read(100);
+      // Should be at offset 100, length 1000 now.
+      yield file.write(new Uint8Array(100));
+      // Should be at offset 200, length 1000 now.
+      let stat = yield file.stat();
+      do_check_eq(1000, stat.size);
+    } finally {
+      yield file.close();
+    }
+  } finally {
+    try {
+      yield OS.File.remove(path);
+    } catch (ex if ex.becauseNoSuchFile) {
+      // ignore.
+    }
+  }
+}
+
+let test_flags = [
+  {},
+  {create:true},
+  {trunc:true}
+];
+function run_test() {
+  do_test_pending();
+
+  for (let t of test_flags) {
+    add_task(test_append.bind(null, t));
+    add_task(test_no_append.bind(null, t));
+  }
+  add_task(do_test_finished);
+
+  run_next_test();
+}
--- a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 head =
 tail =
 
 [test_osfile_closed.js]
 [test_path.js]
 [test_osfile_async.js]
+[test_osfile_async_append.js]
 [test_osfile_async_bytes.js]
 [test_osfile_async_copy.js]
 [test_profiledir.js]
 [test_logging.js]
 [test_creationDate.js]
 [test_exception.js]
 [test_path_constants.js]
 [test_removeDir.js]
--- a/toolkit/content/contentAreaUtils.js
+++ b/toolkit/content/contentAreaUtils.js
@@ -1,16 +1,31 @@
 # -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 
 # 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/.
 
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+                                  "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadLastDir",
+                                  "resource://gre/modules/DownloadLastDir.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+                                  "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
+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");
 var ContentAreaUtils = {
 
   // this is for backwards compatibility.
   get ioService() {
     return Services.io;
   },
 
   get stringBundle() {
@@ -306,25 +321,25 @@ function internalSave(aURL, aDocument, a
       saveMode: saveMode,
       saveAsType: kSaveAsType_Complete,
       file: file
     };
 
     // Find a URI to use for determining last-downloaded-to directory
     let relatedURI = aReferrer || sourceURI;
 
-    getTargetFile(fpParams, function(aDialogCancelled) {
-      if (aDialogCancelled)
+    promiseTargetFile(fpParams, aSkipPrompt, relatedURI).then(aDialogAccepted => {
+      if (!aDialogAccepted)
         return;
 
       saveAsType = fpParams.saveAsType;
       file = fpParams.file;
 
       continueSave();
-    }, aSkipPrompt, relatedURI);
+    }).then(null, Components.utils.reportError);
   }
 
   function continueSave() {
     // XXX We depend on the following holding true in appendFiltersForContentType():
     // If we should save as a complete page, the saveAsType is kSaveAsType_Complete.
     // If we should save as text, the saveAsType is kSaveAsType_Text.
     var useSaveDocument = aDocument &&
                           (((saveMode & SAVEMODE_COMPLETE_DOM) && (saveAsType == kSaveAsType_Complete)) ||
@@ -522,122 +537,123 @@ function initFileInfo(aFI, aURL, aURLCha
 }
 
 /** 
  * Given the Filepicker Parameters (aFpP), show the file picker dialog,
  * prompting the user to confirm (or change) the fileName.
  * @param aFpP
  *        A structure (see definition in internalSave(...) method)
  *        containing all the data used within this method.
- * @param aCallback
- *        A callback function that will be called once the function finishes.
- *        The first argument passed to the function will be a boolean that,
- *        when true, indicated that the user dismissed the file picker.
  * @param aSkipPrompt
  *        If true, attempt to save the file automatically to the user's default
  *        download directory, thus skipping the explicit prompt for a file name,
  *        but only if the associated preference is set.
  *        If false, don't save the file automatically to the user's
  *        default download directory, even if the associated preference
  *        is set, but ask for the target explicitly.
  * @param aRelatedURI
  *        An nsIURI associated with the download. The last used
  *        directory of the picker is retrieved from/stored in the 
  *        Content Pref Service using this URI.
+ * @return Promise
+ * @resolve a boolean. When true, it indicates that the file picker dialog
+ *          is accepted.
  */
-function getTargetFile(aFpP, aCallback, /* optional */ aSkipPrompt, /* optional */ aRelatedURI)
+function promiseTargetFile(aFpP, /* optional */ aSkipPrompt, /* optional */ aRelatedURI)
 {
-  if (!getTargetFile.DownloadLastDir)
-    Components.utils.import("resource://gre/modules/DownloadLastDir.jsm", getTargetFile);
-  var gDownloadLastDir = new getTargetFile.DownloadLastDir(window);
+  return Task.spawn(function() {
+    let downloadLastDir = new DownloadLastDir(window);
+    let prefBranch = Services.prefs.getBranch("browser.download.");
+    let useDownloadDir = prefBranch.getBoolPref("useDownloadDir");
 
-  var prefs = Services.prefs.getBranch("browser.download.");
-  var useDownloadDir = prefs.getBoolPref("useDownloadDir");
-  const nsIFile = Components.interfaces.nsIFile;
+    if (!aSkipPrompt)
+      useDownloadDir = false;
 
-  if (!aSkipPrompt)
-    useDownloadDir = false;
-
-  // Default to the user's default downloads directory configured
-  // through download prefs.
-  var dir = Services.downloads.userDownloadsDirectory;
-  var dirExists = dir && dir.exists();
+    // Default to the user's default downloads directory configured
+    // through download prefs.
+    let dirPath = yield Downloads.getPreferredDownloadsDirectory();
+    let dirExists = yield OS.File.exists(dirPath);
+    let dir = new FileUtils.File(dirPath);
 
-  if (useDownloadDir && dirExists) {
-    dir.append(getNormalizedLeafName(aFpP.fileInfo.fileName,
-                                     aFpP.fileInfo.fileExt));
-    aFpP.file = uniqueFile(dir);
-    aCallback(false);
-    return;
-  }
+    if (useDownloadDir && dirExists) {
+      dir.append(getNormalizedLeafName(aFpP.fileInfo.fileName,
+                                       aFpP.fileInfo.fileExt));
+      aFpP.file = uniqueFile(dir);
+      throw new Task.Result(true);
+    }
 
-  // We must prompt for the file name explicitly.
-  // If we must prompt because we were asked to...
-  if (useDownloadDir) {
-    // Keep async behavior in both branches
-    Services.tm.mainThread.dispatch(function() {
-      displayPicker();
-    }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
-  } else {
-    gDownloadLastDir.getFileAsync(aRelatedURI, function getFileAsyncCB(aFile) {
-      if (aFile && aFile.exists()) {
-        dir = aFile;
-        dirExists = true;
-      }
-      displayPicker();
-    });
-  }
+    // We must prompt for the file name explicitly.
+    // If we must prompt because we were asked to...
+    let deferred = Promise.defer();
+    if (useDownloadDir) {
+      // Keep async behavior in both branches
+      Services.tm.mainThread.dispatch(function() {
+        deferred.resolve(null);
+      }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
+    } else {
+      downloadLastDir.getFileAsync(aRelatedURI, function getFileAsyncCB(aFile) {
+        deferred.resolve(aFile);
+      });
+    }
+    let file = yield deferred.promise;
+    if (file && (yield OS.File.exists(file.path))) {
+      dir = file;
+      dirExists = true;
+    }
 
-  function displayPicker() {
     if (!dirExists) {
       // Default to desktop.
-      dir = Services.dirsvc.get("Desk", nsIFile);
+      dir = Services.dirsvc.get("Desk", Components.interfaces.nsIFile);
     }
 
-    var fp = makeFilePicker();
-    var titleKey = aFpP.fpTitleKey || "SaveLinkTitle";
+    let fp = makeFilePicker();
+    let titleKey = aFpP.fpTitleKey || "SaveLinkTitle";
     fp.init(window, ContentAreaUtils.stringBundle.GetStringFromName(titleKey),
             Components.interfaces.nsIFilePicker.modeSave);
 
     fp.displayDirectory = dir;
     fp.defaultExtension = aFpP.fileInfo.fileExt;
     fp.defaultString = getNormalizedLeafName(aFpP.fileInfo.fileName,
                                              aFpP.fileInfo.fileExt);
     appendFiltersForContentType(fp, aFpP.contentType, aFpP.fileInfo.fileExt,
                                 aFpP.saveMode);
 
     // The index of the selected filter is only preserved and restored if there's
     // more than one filter in addition to "All Files".
     if (aFpP.saveMode != SAVEMODE_FILEONLY) {
       try {
-        fp.filterIndex = prefs.getIntPref("save_converter_index");
+        fp.filterIndex = prefBranch.getIntPref("save_converter_index");
       }
       catch (e) {
       }
     }
 
-    if (fp.show() == Components.interfaces.nsIFilePicker.returnCancel || !fp.file) {
-      aCallback(true);
-      return;
+    let deferComplete = Promise.defer();
+    fp.open(function(aResult) {
+      deferComplete.resolve(aResult);
+    });
+    let result = yield deferComplete.promise;
+    if (result == Components.interfaces.nsIFilePicker.returnCancel || !fp.file) {
+      throw new Task.Result(false);
     }
 
     if (aFpP.saveMode != SAVEMODE_FILEONLY)
-      prefs.setIntPref("save_converter_index", fp.filterIndex);
+      prefBranch.setIntPref("save_converter_index", fp.filterIndex);
 
     // Do not store the last save directory as a pref inside the private browsing mode
-    var directory = fp.file.parent.QueryInterface(nsIFile);
-    gDownloadLastDir.setFile(aRelatedURI, directory);
+    downloadLastDir.setFile(aRelatedURI, fp.file.parent);
 
     fp.file.leafName = validateFileName(fp.file.leafName);
 
     aFpP.saveAsType = fp.filterIndex;
     aFpP.file = fp.file;
     aFpP.fileURL = fp.fileURL;
-    aCallback(false);
-  }
+
+    throw new Task.Result(true);
+  });
 }
 
 // Since we're automatically downloading, we don't get the file picker's
 // logic to check for existing files, so we need to do that here.
 //
 // Note - this code is identical to that in
 //   mozilla/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
 // If you are updating this code, update that code too! We can't share code
--- a/toolkit/content/widgets/datetimepicker.xml
+++ b/toolkit/content/widgets/datetimepicker.xml
@@ -802,18 +802,17 @@
 
               this.yearLeadingZero = (numberFields[yi].length > 1);
               this.monthLeadingZero = (numberFields[mi].length > 1);
               this.dateLeadingZero = (numberFields[di].length > 1);
             }
 
             this.yearField = document.getAnonymousElementByAttribute(this, "anonid", yfield);
             if (!twoDigitYear)
-              this.yearField.parentNode.className =
-                "datetimepicker-input-subbox datetimepicker-year";
+              this.yearField.parentNode.classList.add("datetimepicker-input-subbox", "datetimepicker-year");
             this.monthField = document.getAnonymousElementByAttribute(this, "anonid", mfield);
             this.dateField = document.getAnonymousElementByAttribute(this, "anonid", dfield);
 
             this._fieldAMPM.parentNode.collapsed = true;
             this.yearField.size = twoDigitYear ? 2 : 4;
             this.yearField.maxLength = twoDigitYear ? 2 : 4;
           ]]>
         </body>
@@ -1173,17 +1172,17 @@
 
     <handlers>
       <handler event="click">
         <![CDATA[
           if (event.button != 0 || this.disabled || this.readOnly)
             return;
 
           var target = event.originalTarget;
-          if (target.className == "datepicker-gridlabel" &&
+          if (target.classList.contains("datepicker-gridlabel") &&
               target != this.selectedItem) {
             this.selectedItem = target;
             this._dateValue = new Date(this._displayedDate);
             if (this.attachedControl)
               this.attachedControl._setValueNoSync(this._dateValue);
             this._fireEvent("change", this);
 
             if (this.attachedControl && "open" in this.attachedControl)
--- a/toolkit/modules/Promise.jsm
+++ b/toolkit/modules/Promise.jsm
@@ -215,21 +215,21 @@ Services.obs.addObserver(function observ
     dump("On: " + date + "\n");
     dump("Full message: " + message + "\n");
     dump("See https://developer.mozilla.org/Mozilla/JavaScript_code_modules/Promise.jsm/Promise\n");
     dump("Full stack: " + (stack||"not available") + "\n");
     dump("*************************\n");
     return;
   }
   if (stack) {
-    message += " at " + stack;
+    message += "\nFull Stack: " + stack;
   }
   error.init(
-             /*message*/"A promise chain failed to handle a rejection: on " +
-               date + ", " + message,
+             /*message*/"A promise chain failed to handle a rejection.\n\n" +
+             "Date: " + date + "\nFull Message: " + message,
              /*sourceName*/ fileName,
              /*sourceLine*/ lineNumber?("" + lineNumber):0,
              /*lineNumber*/ lineNumber || 0,
              /*columnNumber*/ 0,
              /*flags*/ Ci.nsIScriptError.errorFlag,
              /*category*/ "chrome javascript");
   Services.console.logMessage(error);
 }, "promise-finalization-witness", false);