Merge inbound to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 04 Jun 2013 20:45:05 -0400
changeset 133960 22cb668fd727b762288af41593fe682c61d2d396
parent 133936 43c0123f158b7f6006cf92cf1318abf30494ea8c (current diff)
parent 133959 4b3ac2b997246e8ef414e22f85f8cd4cf54b9b5e (diff)
child 133967 345e4c957e8294935c4299df7b8ebde41d8198c4
child 134019 0ee6e6d5918e51f306b7f57b712d9d6e46e1f852
child 134069 2c12dc46fcd27050b0f6d5ccd84218bddc4e9dec
push id24778
push userryanvm@gmail.com
push dateWed, 05 Jun 2013 00:44:56 +0000
treeherdermozilla-central@22cb668fd727 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone24.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 inbound to m-c.
js/src/tests/update-test402.sh
testing/mozbase/mozinstall/README.md
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -534,21 +534,98 @@
       ]]></handler>
     </handlers>
   </binding>
 
   <binding id="searchbar-textbox"
       extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
     <implementation implements="nsIObserver">
       <constructor><![CDATA[
+        const kXULNS =
+          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
         if (document.getBindingParent(this).parentNode.parentNode.localName ==
             "toolbarpaletteitem")
           return;
 
-        this.initialize();
+        // Initialize fields
+        this._stringBundle = document.getBindingParent(this)._stringBundle;
+        this._prefBranch =
+                  Components.classes["@mozilla.org/preferences-service;1"]
+                            .getService(Components.interfaces.nsIPrefBranch);
+        this._suggestEnabled =
+            this._prefBranch.getBoolPref("browser.search.suggest.enabled");
+
+        if (this._prefBranch.getBoolPref("browser.urlbar.clickSelectsAll"))
+          this.setAttribute("clickSelectsAll", true);
+
+        // Add items to context menu and attach controller to handle them
+        var textBox = document.getAnonymousElementByAttribute(this,
+                                              "anonid", "textbox-input-box");
+        var cxmenu = document.getAnonymousElementByAttribute(textBox,
+                                          "anonid", "input-box-contextmenu");
+        var pasteAndSearch;
+        cxmenu.addEventListener("popupshowing", function() {
+          if (!pasteAndSearch)
+            return;
+          var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
+          var enabled = controller.isCommandEnabled("cmd_paste");
+          if (enabled)
+            pasteAndSearch.removeAttribute("disabled");
+          else
+            pasteAndSearch.setAttribute("disabled", "true");
+        }, false);
+
+        var element, label, akey;
+
+        element = document.createElementNS(kXULNS, "menuseparator");
+        cxmenu.appendChild(element);
+
+        var insertLocation = cxmenu.firstChild;
+        while (insertLocation.nextSibling &&
+               insertLocation.getAttribute("cmd") != "cmd_paste")
+          insertLocation = insertLocation.nextSibling;
+        if (insertLocation) {
+          element = document.createElementNS(kXULNS, "menuitem");
+          label = this._stringBundle.getString("cmd_pasteAndSearch");
+          element.setAttribute("label", label);
+          element.setAttribute("anonid", "paste-and-search");
+          element.setAttribute("oncommand",
+              "BrowserSearch.searchBar.select(); goDoCommand('cmd_paste'); BrowserSearch.searchBar.handleSearchCommand();");
+          cxmenu.insertBefore(element, insertLocation.nextSibling);
+          pasteAndSearch = element;
+        }
+
+        element = document.createElementNS(kXULNS, "menuitem");
+        label = this._stringBundle.getString("cmd_clearHistory");
+        akey = this._stringBundle.getString("cmd_clearHistory_accesskey");
+        element.setAttribute("label", label);
+        element.setAttribute("accesskey", akey);
+        element.setAttribute("cmd", "cmd_clearhistory");
+        cxmenu.appendChild(element);
+
+        element = document.createElementNS(kXULNS, "menuitem");
+        label = this._stringBundle.getString("cmd_showSuggestions");
+        akey = this._stringBundle.getString("cmd_showSuggestions_accesskey");
+        element.setAttribute("anonid", "toggle-suggest-item");
+        element.setAttribute("label", label);
+        element.setAttribute("accesskey", akey);
+        element.setAttribute("cmd", "cmd_togglesuggest");
+        element.setAttribute("type", "checkbox");
+        element.setAttribute("checked", this._suggestEnabled);
+        element.setAttribute("autocheck", "false");
+        this._suggestMenuItem = element;
+        cxmenu.appendChild(element);
+
+        this.controllers.appendController(this.searchbarController);
+
+        // Add observer for suggest preference
+        var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                            .getService(Components.interfaces.nsIPrefBranch);
+        prefs.addObserver("browser.search.suggest.enabled", this, false);
       ]]></constructor>
 
       <destructor><![CDATA[
           var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                               .getService(Components.interfaces.nsIPrefBranch);
           prefs.removeObserver("browser.search.suggest.enabled", this);
 
         // Because XBL and the customize toolbar code interacts poorly,
@@ -558,98 +635,16 @@
         } catch (ex) { }
       ]]></destructor>
 
       <field name="_stringBundle"/>
       <field name="_prefBranch"/>
       <field name="_suggestMenuItem"/>
       <field name="_suggestEnabled"/>
 
-      <method name="initialize">
-        <body><![CDATA[
-          const kXULNS =
-            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-          // Initialize fields
-          this._stringBundle = document.getBindingParent(this)._stringBundle;
-          this._prefBranch =
-                    Components.classes["@mozilla.org/preferences-service;1"]
-                              .getService(Components.interfaces.nsIPrefBranch);
-          this._suggestEnabled =
-              this._prefBranch.getBoolPref("browser.search.suggest.enabled");
-
-          if (this._prefBranch.getBoolPref("browser.urlbar.clickSelectsAll"))
-            this.setAttribute("clickSelectsAll", true);
-
-          // Add items to context menu and attach controller to handle them
-          var textBox = document.getAnonymousElementByAttribute(this,
-                                                "anonid", "textbox-input-box");
-          var cxmenu = document.getAnonymousElementByAttribute(textBox,
-                                            "anonid", "input-box-contextmenu");
-          var pasteAndSearch;
-          cxmenu.addEventListener("popupshowing", function() {
-            if (!pasteAndSearch)
-              return;
-            var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
-            var enabled = controller.isCommandEnabled("cmd_paste");
-            if (enabled)
-              pasteAndSearch.removeAttribute("disabled");
-            else
-              pasteAndSearch.setAttribute("disabled", "true");
-          }, false);
-
-          var element, label, akey;
-
-          element = document.createElementNS(kXULNS, "menuseparator");
-          cxmenu.appendChild(element);
-
-          var insertLocation = cxmenu.firstChild;
-          while (insertLocation.nextSibling &&
-                 insertLocation.getAttribute("cmd") != "cmd_paste")
-            insertLocation = insertLocation.nextSibling;
-          if (insertLocation) {
-            element = document.createElementNS(kXULNS, "menuitem");
-            label = this._stringBundle.getString("cmd_pasteAndSearch");
-            element.setAttribute("label", label);
-            element.setAttribute("anonid", "paste-and-search");
-            element.setAttribute("oncommand",
-                "BrowserSearch.searchBar.select(); goDoCommand('cmd_paste'); BrowserSearch.searchBar.handleSearchCommand();");
-            cxmenu.insertBefore(element, insertLocation.nextSibling);
-            pasteAndSearch = element;
-          }
-
-          element = document.createElementNS(kXULNS, "menuitem");
-          label = this._stringBundle.getString("cmd_clearHistory");
-          akey = this._stringBundle.getString("cmd_clearHistory_accesskey");
-          element.setAttribute("label", label);
-          element.setAttribute("accesskey", akey);
-          element.setAttribute("cmd", "cmd_clearhistory");
-          cxmenu.appendChild(element);
-
-          element = document.createElementNS(kXULNS, "menuitem");
-          label = this._stringBundle.getString("cmd_showSuggestions");
-          akey = this._stringBundle.getString("cmd_showSuggestions_accesskey");
-          element.setAttribute("anonid", "toggle-suggest-item");
-          element.setAttribute("label", label);
-          element.setAttribute("accesskey", akey);
-          element.setAttribute("cmd", "cmd_togglesuggest");
-          element.setAttribute("type", "checkbox");
-          element.setAttribute("checked", this._suggestEnabled);
-          element.setAttribute("autocheck", "false");
-          this._suggestMenuItem = element;
-          cxmenu.appendChild(element);
-
-          this.controllers.appendController(this.searchbarController);
-
-          // Add observer for suggest preference
-          var prefs = Components.classes["@mozilla.org/preferences-service;1"]
-                              .getService(Components.interfaces.nsIPrefBranch);
-          prefs.addObserver("browser.search.suggest.enabled", this, false);
-        ]]></body>
-      </method>
-
       <!--
         This overrides the searchParam property in autocomplete.xml.  We're
         hijacking this property as a vehicle for delivering the privacy
         information about the window into the guts of nsSearchSuggestions.
 
         Note that the setter is the same as the parent.  We were not sure whether
         we can override just the getter.  If that proves to be the case, the setter
         can be removed.
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -184,16 +184,22 @@ WebGLContext::WebGLContext()
     mContextRestorer = do_CreateInstance("@mozilla.org/timer;1");
     mContextStatus = ContextStable;
     mContextLostErrorSet = false;
     mLoseContextOnHeapMinimize = false;
     mCanLoseContextInForeground = true;
 
     mAlreadyGeneratedWarnings = 0;
     mAlreadyWarnedAboutFakeVertexAttrib0 = false;
+    mMaxWarnings = Preferences::GetInt("webgl.max-warnings-per-context", 32);
+    if (mMaxWarnings < -1)
+    {
+        GenerateWarning("webgl.max-warnings-per-context size is too large (seems like a negative value wrapped)");
+        mMaxWarnings = 0;
+    }
 
     mLastUseIndex = 0;
 
     mMinInUseAttribArrayLengthCached = false;
     mMinInUseAttribArrayLength = 0;
 
     mIsScreenCleared = false;
 
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -1129,20 +1129,25 @@ protected:
     ContextStatus mContextStatus;
     bool mContextLostErrorSet;
 
     // Used for some hardware (particularly Tegra 2 and 4) that likes to
     // be Flushed while doing hundreds of draw calls.
     int mDrawCallsSinceLastFlush;
 
     int mAlreadyGeneratedWarnings;
+    int mMaxWarnings;
     bool mAlreadyWarnedAboutFakeVertexAttrib0;
 
     bool ShouldGenerateWarnings() const {
-        return mAlreadyGeneratedWarnings < 32;
+        if (mMaxWarnings == -1) {
+            return true;
+        }
+
+        return mAlreadyGeneratedWarnings < mMaxWarnings;
     }
 
     uint64_t mLastUseIndex;
 
     void LoseOldestWebGLContextIfLimitExceeded();
     void UpdateLastUseIndex();
 
     template <typename WebGLObjectType>
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -231,16 +231,19 @@ CompositorOGL::CompositorOGL(nsIWidget *
   , mWidgetSize(-1, -1)
   , mSurfaceSize(aSurfaceWidth, aSurfaceHeight)
   , mHasBGRA(0)
   , mUseExternalSurfaceSize(aUseExternalSurfaceSize)
   , mFrameInProgress(false)
   , mDestroyed(false)
 {
   MOZ_COUNT_CTOR(CompositorOGL);
+  mTextures[0] = 0;
+  mTextures[1] = 0;
+  mTextures[2] = 0;
   sBackend = LAYERS_OPENGL;
 }
 
 CompositorOGL::~CompositorOGL()
 {
   MOZ_COUNT_DTOR(CompositorOGL);
   Destroy();
 }
@@ -274,19 +277,39 @@ CompositorOGL::AddPrograms(ShaderProgram
       mPrograms[aType].mVariations[maskType] = new ShaderProgramOGL(this->gl(),
         ProgramProfileOGL::GetProfileFor(aType, static_cast<MaskType>(maskType)));
     } else {
       mPrograms[aType].mVariations[maskType] = nullptr;
     }
   }
 }
 
+GLuint
+CompositorOGL::GetTemporaryTexture(GLenum aTextureUnit)
+{
+  if (!mTextures[aTextureUnit - LOCAL_GL_TEXTURE0]) {
+    gl()->MakeCurrent();
+    gl()->fGenTextures(1, &mTextures[aTextureUnit - LOCAL_GL_TEXTURE0]);
+  }
+  return mTextures[aTextureUnit - LOCAL_GL_TEXTURE0];
+}
+
 void
 CompositorOGL::Destroy()
 {
+  if (gl()) {
+    gl()->MakeCurrent();
+    gl()->fDeleteTextures(3, mTextures);
+    mTextures[0] = 0;
+    mTextures[1] = 0;
+    mTextures[2] = 0;
+  } else {
+    MOZ_ASSERT(!mTextures[0] && !mTextures[1] && !mTextures[2]);
+  }
+
   if (!mDestroyed) {
     mDestroyed = true;
     CleanupResources();
   }
 }
 
 void
 CompositorOGL::CleanupResources()
--- a/gfx/layers/opengl/CompositorOGL.h
+++ b/gfx/layers/opengl/CompositorOGL.h
@@ -129,16 +129,23 @@ public:
   }
 
   GLContext* gl() const { return mGLContext; }
   gl::ShaderProgramType GetFBOLayerProgramType() const {
     return mFBOTextureTarget == LOCAL_GL_TEXTURE_RECTANGLE_ARB ?
            gl::RGBARectLayerProgramType : gl::RGBALayerProgramType;
   }
 
+  /**
+   * The compositor provides with temporary textures for use with direct
+   * textruing like gralloc texture.
+   * Doing so lets us use gralloc the way it has been designed to be used
+   * (see https://wiki.mozilla.org/Platform/GFX/Gralloc)
+   */
+  GLuint GetTemporaryTexture(GLenum aUnit);
 private:
   /** 
    * Context target, nullptr when drawing directly to our swap chain.
    */
   nsRefPtr<gfxContext> mTarget;
 
   /** Widget associated with this compositor */
   nsIWidget *mWidget;
@@ -314,16 +321,19 @@ private:
   /**
    * Records the passed frame timestamp and returns the current estimated FPS.
    */
   double AddFrameAndGetFps(const TimeStamp& timestamp);
 
   bool mDestroyed;
 
   nsAutoPtr<FPSState> mFPS;
+  // Textures used for direct texturing of buffers like gralloc.
+  // The index of the texture in this array must correspond to the texture unit.
+  GLuint mTextures[3];
   static bool sDrawFPS;
   static bool sFrameCounter;
 };
 
 }
 }
 
 #endif /* MOZILLA_GFX_COMPOSITOROGL_H */
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -695,38 +695,39 @@ TextureTargetForAndroidPixelFormat(andro
       // we'll take down the compositor process and thus the phone. This seems
       // like undesirable behaviour. We'd rather have a subtle artifact.
       MOZ_ASSERT(false, "Unknown Android pixel format.");
       return LOCAL_GL_TEXTURE_EXTERNAL;
     }
   }
 }
 
+GrallocTextureHostOGL::GrallocTextureHostOGL()
+: mCompositor(nullptr)
+, mTextureTarget(0)
+, mEGLImage(0)
+{
+}
+
 void GrallocTextureHostOGL::SetCompositor(Compositor* aCompositor)
 {
   CompositorOGL* glCompositor = static_cast<CompositorOGL*>(aCompositor);
-  if (mGL && !glCompositor) {
+  if (mCompositor && !glCompositor) {
     DeleteTextures();
   }
-  mGL = glCompositor ? glCompositor->gl() : nullptr;
+  mCompositor = glCompositor;
 }
 
 void
 GrallocTextureHostOGL::DeleteTextures()
 {
-  if (mGLTexture || mEGLImage) {
-    mGL->MakeCurrent();
-    if (mGLTexture) {
-      mGL->fDeleteTextures(1, &mGLTexture);
-      mGLTexture = 0;
-    }
-    if (mEGLImage) {
-      mGL->DestroyEGLImage(mEGLImage);
-      mEGLImage = 0;
-    }
+  if (mEGLImage) {
+    gl()->MakeCurrent();
+    gl()->DestroyEGLImage(mEGLImage);
+    mEGLImage = 0;
   }
 }
 
 // only used for hacky fix in gecko 23 for bug 862324
 static void
 RegisterTextureHostAtGrallocBufferActor(TextureHost* aTextureHost, const SurfaceDescriptor& aSurfaceDescriptor)
 {
   if (IsSurfaceDescriptorValid(aSurfaceDescriptor)) {
@@ -762,30 +763,53 @@ GrallocTextureHostOGL::SwapTexturesImpl(
   DeleteTextures();
 
   // only done for hacky fix in gecko 23 for bug 862324.
   // Doing this in SetBuffer is not enough, as ImageHostBuffered::SwapTextures can
   // change the value of *mBuffer without calling SetBuffer again.
   RegisterTextureHostAtGrallocBufferActor(this, aImage);
 }
 
+gl::GLContext*
+GrallocTextureHostOGL::gl() const
+{
+  return mCompositor ? mCompositor->gl() : nullptr;
+}
+
 void GrallocTextureHostOGL::BindTexture(GLenum aTextureUnit)
 {
-  MOZ_ASSERT(mGLTexture);
+  /*
+   * The job of this function is to ensure that the texture is tied to the
+   * android::GraphicBuffer, so that texturing will source the GraphicBuffer.
+   *
+   * To this effect we create an EGLImage wrapping this GraphicBuffer,
+   * using CreateEGLImageForNativeBuffer, and then we tie this EGLImage to our
+   * texture using fEGLImageTargetTexture2D.
+   *
+   * We try to avoid re-creating the EGLImage everytime, by keeping it around
+   * as the mEGLImage member of this class.
+   */
+  MOZ_ASSERT(gl());
+  gl()->MakeCurrent();
 
-  mGL->MakeCurrent();
-  mGL->fActiveTexture(aTextureUnit);
-  mGL->fBindTexture(mTextureTarget, mGLTexture);
-  mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
+  GLuint tex = mCompositor->GetTemporaryTexture(aTextureUnit);
+
+  gl()->fActiveTexture(aTextureUnit);
+  gl()->fBindTexture(mTextureTarget, tex);
+  if (!mEGLImage) {
+    mEGLImage = gl()->CreateEGLImageForNativeBuffer(mGraphicBuffer->getNativeBuffer());
+  }
+  gl()->fEGLImageTargetTexture2D(mTextureTarget, mEGLImage);
+  gl()->fActiveTexture(LOCAL_GL_TEXTURE0);
 }
 
 bool
 GrallocTextureHostOGL::IsValid() const
 {
-  return !!mGL && !!mGraphicBuffer.get();
+  return !!gl() && !!mGraphicBuffer.get();
 }
 
 GrallocTextureHostOGL::~GrallocTextureHostOGL()
 {
   DeleteTextures();
 
   // only done for hacky fix in gecko 23 for bug 862324.
   if (mBuffer) {
@@ -793,75 +817,24 @@ GrallocTextureHostOGL::~GrallocTextureHo
     // pointer to us.
     RegisterTextureHostAtGrallocBufferActor(nullptr, *mBuffer);
   }
 }
 
 bool
 GrallocTextureHostOGL::Lock()
 {
-  if (!IsValid()) {
-    return false;
-  }
-  /*
-   * The job of this function is to ensure that the texture is tied to the
-   * android::GraphicBuffer, so that texturing will source the GraphicBuffer.
-   *
-   * To this effect we create an EGLImage wrapping this GraphicBuffer,
-   * using CreateEGLImageForNativeBuffer, and then we tie this EGLImage to our
-   * texture using fEGLImageTargetTexture2D.
-   *
-   * We try to avoid re-creating the EGLImage everytime, by keeping it around
-   * as the mEGLImage member of this class.
-   */
-  MOZ_ASSERT(mGraphicBuffer.get());
-
-  mGL->MakeCurrent();
-
-  if (!mGLTexture) {
-    mGL->fGenTextures(1, &mGLTexture);
-  }
-  mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
-  mGL->fBindTexture(mTextureTarget, mGLTexture);
-  if (!mEGLImage) {
-    mEGLImage = mGL->CreateEGLImageForNativeBuffer(mGraphicBuffer->getNativeBuffer());
-  }
-  mGL->fEGLImageTargetTexture2D(mTextureTarget, mEGLImage);
-  return true;
+  // Lock/Unlock is done internally when binding the gralloc buffer to a gl texture
+  return IsValid();
 }
 
 void
 GrallocTextureHostOGL::Unlock()
 {
-  /*
-   * The job of this function is to ensure that we release any read lock placed on
-   * our android::GraphicBuffer by any drawing code that sourced it via this TextureHost.
-   *
-   * Indeed, as soon as we draw with a texture that's tied to a android::GraphicBuffer,
-   * the GL may place read locks on it. We must ensure that we release them early enough,
-   * i.e. before the next time that we will try to acquire a write lock on the same buffer,
-   * because read and write locks on gralloc buffers are mutually exclusive.
-   */
-  if (mGL->Renderer() == GLContext::RendererAdrenoTM205) {
-    /* XXX This is working around a driver bug exhibited on at least the
-     * Geeksphone Peak, where retargeting to a different EGL image is very
-     * slow. See Bug 869696.
-     */
-    if (mGLTexture) {
-      mGL->MakeCurrent();
-      mGL->fDeleteTextures(1, &mGLTexture);
-      mGLTexture = 0;
-    }
-    return;
-  }
-
-  mGL->MakeCurrent();
-  mGL->fActiveTexture(LOCAL_GL_TEXTURE0);
-  mGL->fBindTexture(mTextureTarget, mGLTexture);
-  mGL->fEGLImageTargetTexture2D(mTextureTarget, mGL->GetNullEGLImage());
+  // Lock/Unlock is done internally when binding the gralloc buffer to a gl texture
 }
 
 gfx::SurfaceFormat
 GrallocTextureHostOGL::GetFormat() const
 {
   return mFormat;
 }
 
@@ -872,17 +845,17 @@ GrallocTextureHostOGL::SetBuffer(Surface
   mBuffer = aBuffer;
   mDeAllocator = aAllocator;
 
   // only done for hacky fix in gecko 23 for bug 862324.
   // Doing this in SwapTextures is not enough, as the crash could occur right after SetBuffer.
   RegisterTextureHostAtGrallocBufferActor(this, *mBuffer);
 }
 
-#endif
+#endif // MOZ_WIDGET_GONK
 
 already_AddRefed<gfxImageSurface>
 TextureImageTextureHostOGL::GetAsSurface() {
   nsRefPtr<gfxImageSurface> surf = IsValid() ?
     mGL->GetTexImage(mTexture->GetTextureID(),
                      false,
                      mTexture->GetShaderProgramType())
     : nullptr;
@@ -927,19 +900,29 @@ TiledTextureHostOGL::GetAsSurface() {
                      GetShaderProgram())
     : nullptr;
   return surf.forget();
 }
 
 #ifdef MOZ_WIDGET_GONK
 already_AddRefed<gfxImageSurface>
 GrallocTextureHostOGL::GetAsSurface() {
-  nsRefPtr<gfxImageSurface> surf = IsValid() && mGLTexture ?
-    mGL->GetTexImage(mGLTexture,
-                     false,
-                     GetShaderProgram())
+  gl()->MakeCurrent();
+
+  GLuint tex = mCompositor->GetTemporaryTexture(LOCAL_GL_TEXTURE0);
+  gl()->fActiveTexture(LOCAL_GL_TEXTURE0);
+  gl()->fBindTexture(mTextureTarget, tex);
+  if (!mEGLImage) {
+    mEGLImage = gl()->CreateEGLImageForNativeBuffer(mGraphicBuffer->getNativeBuffer());
+  }
+  gl()->fEGLImageTargetTexture2D(mTextureTarget, mEGLImage);
+
+  nsRefPtr<gfxImageSurface> surf = IsValid() ?
+    gl()->GetTexImage(tex,
+                      false,
+                      GetShaderProgram())
     : nullptr;
   return surf.forget();
 }
 #endif
 
 } // namespace
 } // namespace
--- a/gfx/layers/opengl/TextureHostOGL.h
+++ b/gfx/layers/opengl/TextureHostOGL.h
@@ -16,16 +16,17 @@
 #ifdef MOZ_WIDGET_GONK
 #include <ui/GraphicBuffer.h>
 #endif
 
 namespace mozilla {
 namespace layers {
 
 class TextureImageTextureHostOGL;
+class CompositorOGL;
 
 /*
  * TextureHost implementations for the OpenGL backend.
  *
  * Note that it is important to becareful about the ownership model with
  * the OpenGL backend, due to some widget limitation on Linux: before
  * the nsBaseWidget associated with our OpenGL context has been completely
  * deleted, every resource belonging to the OpenGL context MUST have been
@@ -563,33 +564,22 @@ private:
 // which automatically gets gralloc when it can, in which case the compositor sees that the
 // SurfaceDescriptor is gralloc, and decides to use a GrallocTextureHostOGL to do direct texturing,
 // saving the cost of a texture upload.
 class GrallocTextureHostOGL
   : public TextureHost
   , public TextureSourceOGL
 {
 public:
-  GrallocTextureHostOGL()
-    : mGL(nullptr)
-    , mTextureTarget(0)
-    , mGLTexture(0)
-    , mEGLImage(0)
-  {
-  }
+  GrallocTextureHostOGL();
 
   ~GrallocTextureHostOGL();
 
   virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE;
 
-  virtual GLuint GetTextureHandle()
-  {
-    return mGLTexture;
-  }
-
   virtual void UpdateImpl(const SurfaceDescriptor& aImage,
                           nsIntRegion* aRegion = nullptr,
                           nsIntPoint* aOffset = nullptr) MOZ_OVERRIDE;
   virtual void SwapTexturesImpl(const SurfaceDescriptor& aImage,
                           nsIntRegion* aRegion = nullptr) MOZ_OVERRIDE;
   virtual bool Lock() MOZ_OVERRIDE;
   virtual void Unlock() MOZ_OVERRIDE;
 
@@ -647,22 +637,23 @@ public:
       mBuffer = nullptr;
     }
 
     mGraphicBuffer = nullptr;
     DeleteTextures();
   }
 
 private:
+  gl::GLContext* gl() const;
+
   void DeleteTextures();
 
-  RefPtr<gl::GLContext> mGL;
+  RefPtr<CompositorOGL> mCompositor;
   android::sp<android::GraphicBuffer> mGraphicBuffer;
   GLenum mTextureTarget;
-  GLuint mGLTexture;
   EGLImage mEGLImage;
 };
 #endif
 
 } // namespace
 } // namespace
 
 #endif /* MOZILLA_GFX_TEXTUREOGL_H */
--- a/hal/Makefile.in
+++ b/hal/Makefile.in
@@ -12,24 +12,30 @@ VPATH       = \
   $(srcdir)/gonk \
   $(srcdir)/fallback \
   $(srcdir)/sandbox \
   $(srcdir)/linux \
   $(srcdir)/windows \
   $(srcdir)/cocoa \
   $(NULL)
 
+relativesrcdir   = @relativesrcdir@
+
 include $(DEPTH)/config/autoconf.mk
 
 LIBRARY_NAME   = hal_s
 FORCE_STATIC_LIB = 1
 LIBXUL_LIBRARY = 1
 EXPORT_LIBRARY = 1
 FAIL_ON_WARNINGS = 1
 
+MOCHITEST_BROWSER_FILES += \
+	tests/browser_alarms.js \
+	$(NULL)
+
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 CMMSRCS += \
   CocoaSensor.mm \
   smslib.mm \
   $(NULL)
 endif
 
 ifdef MOZ_GAMEPAD
--- a/hal/fallback/FallbackAlarm.cpp
+++ b/hal/fallback/FallbackAlarm.cpp
@@ -1,28 +1,80 @@
 /* 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/. */
 
 #include "Hal.h"
 
+#include <algorithm>
+
+#include <nsITimer.h>
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "nsThreadUtils.h"
+
 namespace mozilla {
 namespace hal_impl {
 
+static void
+TimerCallbackFunc(nsITimer *aTimer, void *aClosure)
+{
+  hal::NotifyAlarmFired();
+}
+
+static StaticRefPtr<nsITimer> sTimer;
+
 bool
 EnableAlarm()
 {
-  return false;
+  static bool initialized = false;
+  if (!initialized) {
+    initialized = true;
+    ClearOnShutdown(&sTimer);
+  }
+
+  nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
+  sTimer = timer;
+  MOZ_ASSERT(sTimer);
+  return true;
 }
 
 void
 DisableAlarm()
 {
+  /*
+   * DisableAlarm() may be called after sTimer has been set to null by
+   * ClearOnShutdown().
+   */
+  if (sTimer) {
+    sTimer->Cancel();
+  }
 }
 
 bool
 SetAlarm(int32_t aSeconds, int32_t aNanoseconds)
 {
-  return false;
+  if (!sTimer) {
+    HAL_LOG(("We should have enabled the alarm"));
+    MOZ_ASSERT(false);
+    return false;
+  }
+
+  // Do the math to convert aSeconds and aNanoseconds into milliseconds since
+  // the epoch.
+  int64_t milliseconds = static_cast<int64_t>(aSeconds) * 1000 +
+                         static_cast<int64_t>(aNanoseconds) / 1000000;
+
+  // nsITimer expects relative milliseconds.
+  int64_t relMilliseconds = milliseconds - PR_Now() / 1000;
+
+  // If the alarm time is in the past relative to PR_Now(),
+  // we choose to immediately fire the alarm. Passing 0 means nsITimer will
+  // queue a timeout event immediately.
+  sTimer->InitWithFuncCallback(TimerCallbackFunc, nullptr,
+                               clamped<int64_t>(relMilliseconds, 0, INT32_MAX),
+                               nsITimer::TYPE_ONE_SHOT);
+  return true;
 }
 
 } // hal_impl
 } // namespace mozilla
--- a/hal/moz.build
+++ b/hal/moz.build
@@ -14,16 +14,17 @@ EXPORTS.mozilla += [
     'Hal.h',
     'HalImpl.h',
     'HalSandbox.h',
     'HalSensor.h',
     'HalTypes.h',
     'HalWakeLock.h',
 ]
 
+TEST_DIRS += ['tests']
 CPP_SOURCES += [
     'Hal.cpp',
     'HalWakeLock.cpp',
     'SandboxHal.cpp',
     'WindowIdentifier.cpp',
 ]
 
 if CONFIG['MOZ_GAMEPAD_BACKEND'] == 'stub':
new file mode 100644
--- /dev/null
+++ b/hal/tests/Makefile.in
@@ -0,0 +1,18 @@
+# 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/.
+
+DEPTH		= @DEPTH@
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir  = @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MOCHITEST_BROWSER_FILES = \
+  browser_alarms.js \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
+
new file mode 100644
--- /dev/null
+++ b/hal/tests/browser_alarms.js
@@ -0,0 +1,187 @@
+XPCOMUtils.defineLazyModuleGetter(this, "AlarmService",
+                                  "resource://gre/modules/AlarmService.jsm");
+
+/*
+ * Tests for Bug 867868 and related Alarm API bugs.
+ *
+ * NOTE: These tests pass the alarm time to AlarmService as a number and not as
+ * a date. See bug 810973 about Date truncating milliseconds. AlarmService does
+ * not officially allow a integer, but nor does it disallow it. Of course this
+ * test will break if AlarmService adds a type check, hence this note.
+ * FIXME: when bug 810973 is fixed.
+ */
+
+function add_alarm_future(cb) {
+  let alarmId = undefined;
+  AlarmService.add({
+    date: Date.now() + 143,
+    ignoreTimezone: true
+  },
+  function onAlarmFired(aAlarm) {
+    ok(alarmId === aAlarm.id, "Future alarm fired successfully.");
+    cb();
+  },
+  function onSuccess(aAlarmId) {
+    alarmId = aAlarmId;
+  },
+  function onError(error) {
+    ok(false, "Unexpected error adding future alarm " + error);
+    cb();
+  });
+}
+
+function add_alarm_present(cb) {
+  let self = this;
+  let alarmId = undefined;
+  AlarmService.add({
+    date: Date.now(),
+    ignoreTimezone: true
+  },
+  function onAlarmFired(aAlarm) {
+    ok(alarmId === aAlarm.id, "Present alarm fired successfully.");
+    cb();
+  },
+  function onSuccess(aAlarmId) {
+    alarmId = aAlarmId;
+  }, function onError(error) {
+    ok(false, "Unexpected error adding alarm for current time " + error);
+    cb();
+  });
+}
+
+function add_alarm_past(cb) {
+  let self = this;
+  let alarmId = undefined;
+  AlarmService.add({
+    date: Date.now() - 5,
+    ignoreTimezone: true
+  },
+  function onAlarmFired(aAlarm) {
+    ok(alarmId === aAlarm.id, "Past alarm fired successfully.");
+    cb();
+  },
+  function onSuccess(aAlarmId) {
+    alarmId = aAlarmId;
+  },
+  function onError(error) {
+    ok(false, "Unexpected error adding alarm for time in the past " + error);
+    cb();
+  });
+}
+
+function trigger_all_alarms(cb) {
+  let n = 10;
+  let counter = 0;
+  let date = Date.now() + 57;
+  function onAlarmFired() {
+    counter++;
+    info("trigger_all_alarms count " + counter);
+    if (counter == n) {
+      ok(true, "All " + n + " alarms set to a particular time fired.");
+      cb();
+    }
+  }
+
+  for (let i = 0; i < n; i++) {
+    AlarmService.add(
+      {
+        date: date,
+        ignoreTimezone: true
+      },
+      onAlarmFired
+    );
+  }
+}
+
+function multiple_handlers(cb) {
+  let d = Date.now() + 100;
+  let called = 0;
+
+  function done() {
+    if (called == 2) {
+      ok(true, "Two alarms for the same time fired.");
+      cb();
+    }
+  }
+
+  function handler1() {
+    called++;
+    done();
+  }
+
+  function handler2() {
+    called++;
+    done();
+  }
+
+  AlarmService.add(
+    {
+      date: d,
+      ignoreTimezone: true
+    },
+    handler1
+  );
+  AlarmService.add(
+    {
+      date: d,
+      ignoreTimezone: true
+    },
+    handler2
+  );
+}
+
+function same_time_alarms(cb) {
+  var fired = 0;
+  var delay = Date.now() + 100;
+
+  function check() {
+    fired++;
+    if (fired == 4) {
+      ok(true, "All alarms set for the same time fired.");
+      cb();
+    }
+  }
+
+  function addImmediateAlarm() {
+    fired++;
+    AlarmService.add({
+      date: delay,
+      ignoreTimezone: true
+    }, check);
+  }
+
+  AlarmService.add({
+    date: delay,
+    ignoreTimezone: true
+  }, addImmediateAlarm);
+
+  AlarmService.add({
+    date: delay,
+    ignoreTimezone: true
+  }, addImmediateAlarm);
+}
+
+function test() {
+  var tests = [
+    add_alarm_future,
+    add_alarm_present,
+    add_alarm_past,
+    trigger_all_alarms,
+    multiple_handlers,
+    same_time_alarms
+  ]
+
+  var testIndex = -1;
+  function nextTest() {
+    testIndex++;
+    if (testIndex >= tests.length)
+      return;
+
+    waitForExplicitFinish();
+    tests[testIndex](function() {
+      finish();
+      nextTest();
+    });
+  }
+  nextTest();
+}
new file mode 100644
--- /dev/null
+++ b/hal/tests/moz.build
@@ -0,0 +1,3 @@
+# 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/.
--- a/image/src/imgRequestProxy.cpp
+++ b/image/src/imgRequestProxy.cpp
@@ -34,17 +34,16 @@ class ProxyBehaviour
 {
  public:
   virtual ~ProxyBehaviour() {}
 
   virtual mozilla::image::Image* GetImage() const = 0;
   virtual imgStatusTracker& GetStatusTracker() const = 0;
   virtual imgRequest* GetOwner() const = 0;
   virtual void SetOwner(imgRequest* aOwner) = 0;
-  virtual bool IsStatic() = 0;
 };
 
 class RequestBehaviour : public ProxyBehaviour
 {
  public:
   RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {}
 
   virtual mozilla::image::Image* GetImage() const MOZ_OVERRIDE;
@@ -59,57 +58,45 @@ class RequestBehaviour : public ProxyBeh
 
     if (mOwner) {
       mOwnerHasImage = !!aOwner->GetStatusTracker().GetImage();
     } else {
       mOwnerHasImage = false;
     }
   }
 
-  virtual bool IsStatic() { return false; }
-
  private:
   // We maintain the following invariant:
   // The proxy is registered at most with a single imgRequest as an observer,
   // and whenever it is, mOwner points to that object. This helps ensure that
   // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
   // from whatever request it was registered with (if any). This, in turn,
   // means that imgRequest::mObservers will not have any stale pointers in it.
   nsRefPtr<imgRequest> mOwner;
 
   bool mOwnerHasImage;
 };
 
 mozilla::image::Image*
 RequestBehaviour::GetImage() const
 {
-  if (mOwnerHasImage && !mOwner) {
-    NS_WARNING("If mOwnerHasImage is true mOwner must be true");
-    MOZ_CRASH();
-  }
-  
   if (!mOwnerHasImage)
     return nullptr;
   return GetStatusTracker().GetImage();
 }
 
 imgStatusTracker&
 RequestBehaviour::GetStatusTracker() const
 {
   // NOTE: It's possible that our mOwner has an Image that it didn't notify
   // us about, if we were Canceled before its Image was constructed.
   // (Canceling removes us as an observer, so mOwner has no way to notify us).
   // That's why this method uses mOwner->GetStatusTracker() instead of just
   // mOwner->mStatusTracker -- we might have a null mImage and yet have an
   // mOwner with a non-null mImage (and a null mStatusTracker pointer).
-  if (mOwnerHasImage && !mOwner) {
-    NS_WARNING("If mOwnerHasImage is true mOwner must be true");
-    MOZ_CRASH();
-  }
-  
   return mOwner->GetStatusTracker();
 }
 
 NS_IMPL_ADDREF(imgRequestProxy)
 NS_IMPL_RELEASE(imgRequestProxy)
 
 NS_INTERFACE_MAP_BEGIN(imgRequestProxy)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, imgIRequest)
@@ -173,21 +160,16 @@ nsresult imgRequestProxy::Init(imgReques
                                imgINotificationObserver* aObserver)
 {
   NS_PRECONDITION(!GetOwner() && !mListener, "imgRequestProxy is already initialized");
 
   LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequestProxy::Init", "request", aOwner);
 
   NS_ABORT_IF_FALSE(mAnimationConsumers == 0, "Cannot have animation before Init");
 
-  if (!mBehaviour->IsStatic() && !aOwner) {
-    NS_WARNING("Non-static imgRequestProxies should be initialized with an owner");
-    MOZ_CRASH();
-  }
-  
   mBehaviour->SetOwner(aOwner);
   mListener = aObserver;
   // Make sure to addref mListener before the AddProxy call below, since
   // that call might well want to release it if the imgRequest has
   // already seen OnStopRequest.
   if (mListener) {
     mListenerIsStrongRef = true;
     NS_ADDREF(mListener);
@@ -226,21 +208,16 @@ nsresult imgRequestProxy::ChangeOwner(im
   bool wasDecoded = false;
   if (GetImage() &&
       (GetStatusTracker().GetImageStatus() & imgIRequest::STATUS_FRAME_COMPLETE)) {
     wasDecoded = true;
   }
 
   GetOwner()->RemoveProxy(this, NS_IMAGELIB_CHANGING_OWNER);
 
-  if (!mBehaviour->IsStatic() && !aNewOwner) {
-    NS_WARNING("Non-static imgRequestProxies should be only changed to a non-null owner");
-    MOZ_CRASH();
-  }
-
   mBehaviour->SetOwner(aNewOwner);
 
   // If we were locked, apply the locks here
   for (uint32_t i = 0; i < oldLockCount; i++)
     LockImage();
 
   // If we had animation requests, restore them here. Note that we
   // do this *after* RemoveProxy, which clears out animation consumers
@@ -580,20 +557,16 @@ imgRequestProxy* NewStaticProxy(imgReque
   return new imgRequestProxyStatic(aThis->GetImage(), currentPrincipal);
 }
 
 NS_IMETHODIMP imgRequestProxy::Clone(imgINotificationObserver* aObserver,
                                      imgIRequest** aClone)
 {
   nsresult result;
   imgRequestProxy* proxy;
-  if (mBehaviour->IsStatic()) {
-    NS_WARNING("Calling non-static imgRequestProxy::Clone with static mBehaviour");
-    MOZ_CRASH();
-  }
   result = Clone(aObserver, &proxy);
   *aClone = proxy;
   return result;
 }
 
 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
                                 imgRequestProxy** aClone)
 {
@@ -1044,18 +1017,16 @@ public:
   virtual imgRequest* GetOwner() const MOZ_OVERRIDE {
     return nullptr;
   }
 
   virtual void SetOwner(imgRequest* aOwner) MOZ_OVERRIDE {
     MOZ_ASSERT(!aOwner, "We shouldn't be giving static requests a non-null owner.");
   }
 
-  virtual bool IsStatic() { return true; }
-
 private:
   // Our image. We have to hold a strong reference here, because that's normally
   // the job of the underlying request.
   nsRefPtr<mozilla::image::Image> mImage;
 };
 
 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
                                              nsIPrincipal* aPrincipal)
@@ -1069,22 +1040,14 @@ NS_IMETHODIMP imgRequestProxyStatic::Get
   if (!mPrincipal)
     return NS_ERROR_FAILURE;
 
   NS_ADDREF(*aPrincipal = mPrincipal);
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
+nsresult
 imgRequestProxyStatic::Clone(imgINotificationObserver* aObserver,
-                             imgIRequest** aClone)
+                             imgRequestProxy** aClone)
 {
-  nsresult result;
-  imgRequestProxy* proxy;
-  if (!mBehaviour->IsStatic()) {
-    NS_WARNING("Calling static imgRequestProxy::Clone with non-static mBehaviour");
-    MOZ_CRASH();
-  }
-  result = PerformClone(aObserver, NewStaticProxy, &proxy);
-  *aClone = proxy;
-  return result;
+  return PerformClone(aObserver, NewStaticProxy, aClone);
 }
--- a/image/src/imgRequestProxy.h
+++ b/image/src/imgRequestProxy.h
@@ -99,17 +99,17 @@ public:
   void SetHasImage();
 
   // Removes all animation consumers that were created with
   // IncrementAnimationConsumers. This is necessary since we need
   // to do it before the proxy itself is destroyed. See
   // imgRequest::RemoveProxy
   void ClearAnimationConsumers();
 
-  nsresult Clone(imgINotificationObserver* aObserver, imgRequestProxy** aClone);
+  virtual nsresult Clone(imgINotificationObserver* aObserver, imgRequestProxy** aClone);
   nsresult GetStaticRequest(imgRequestProxy** aReturn);
 
 protected:
   friend class imgStatusTracker;
   friend class imgStatusNotifyRunnable;
   friend class imgRequestNotifyRunnable;
 
   class imgCancelRunnable;
@@ -226,18 +226,20 @@ class imgRequestProxyStatic : public img
 {
 
 public:
   imgRequestProxyStatic(mozilla::image::Image* aImage,
                         nsIPrincipal* aPrincipal);
 
   NS_IMETHOD GetImagePrincipal(nsIPrincipal** aPrincipal) MOZ_OVERRIDE;
 
-  NS_IMETHOD Clone(imgINotificationObserver* aObserver,
-                   imgIRequest** aClone) MOZ_OVERRIDE;
+  using imgRequestProxy::Clone;
+
+  virtual nsresult Clone(imgINotificationObserver* aObserver,
+                         imgRequestProxy** aClone) MOZ_OVERRIDE;
 
 protected:
   friend imgRequestProxy* NewStaticProxy(imgRequestProxy*);
 
   // Our principal. We have to cache it, rather than accessing the underlying
   // request on-demand, because static proxies don't have an underlying request.
   nsCOMPtr<nsIPrincipal> mPrincipal;
 };
old mode 100755
new mode 100644
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -292,36 +292,36 @@ WrapperFactory::PrepareForWrapping(JSCon
 
     // This public WrapNativeToJSVal API enters the compartment of 'wrapScope'
     // so we don't have to.
     RootedValue v(cx);
     nsresult rv =
         nsXPConnect::XPConnect()->WrapNativeToJSVal(cx, wrapScope, wn->Native(), nullptr,
                                                     &NS_GET_IID(nsISupports), false,
                                                     v.address(), getter_AddRefs(holder));
-    if (NS_SUCCEEDED(rv)) {
-        obj = JSVAL_TO_OBJECT(v);
-        NS_ASSERTION(IS_WN_WRAPPER(obj), "bad object");
+    NS_ENSURE_SUCCESS(rv, nullptr);
+
+    obj = JSVAL_TO_OBJECT(v);
+    NS_ASSERTION(IS_WN_WRAPPER(obj), "bad object");
 
-        // Because the underlying native didn't have a PreCreate hook, we had
-        // to a new (or possibly pre-existing) XPCWN in our compartment.
-        // This could be a problem for chrome code that passes XPCOM objects
-        // across compartments, because the effects of QI would disappear across
-        // compartments.
-        //
-        // So whenever we pull an XPCWN across compartments in this manner, we
-        // give the destination object the union of the two native sets. We try
-        // to do this cleverly in the common case to avoid too much overhead.
-        XPCWrappedNative *newwn = static_cast<XPCWrappedNative *>(xpc_GetJSPrivate(obj));
-        XPCNativeSet *unionSet = XPCNativeSet::GetNewOrUsed(ccx, newwn->GetSet(),
-                                                            wn->GetSet(), false);
-        if (!unionSet)
-            return nullptr;
-        newwn->SetSet(unionSet);
-    }
+    // Because the underlying native didn't have a PreCreate hook, we had
+    // to a new (or possibly pre-existing) XPCWN in our compartment.
+    // This could be a problem for chrome code that passes XPCOM objects
+    // across compartments, because the effects of QI would disappear across
+    // compartments.
+    //
+    // So whenever we pull an XPCWN across compartments in this manner, we
+    // give the destination object the union of the two native sets. We try
+    // to do this cleverly in the common case to avoid too much overhead.
+    XPCWrappedNative *newwn = static_cast<XPCWrappedNative *>(xpc_GetJSPrivate(obj));
+    XPCNativeSet *unionSet = XPCNativeSet::GetNewOrUsed(ccx, newwn->GetSet(),
+                                                        wn->GetSet(), false);
+    if (!unionSet)
+        return nullptr;
+    newwn->SetSet(unionSet);
 
     return DoubleWrap(cx, obj, flags);
 }
 
 #ifdef DEBUG
 static void
 DEBUG_CheckUnwrapSafety(HandleObject obj, js::Wrapper *handler,
                         JSCompartment *origin, JSCompartment *target)
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -44,17 +44,17 @@
 
     <uses-feature android:name="android.hardware.location" android:required="false"/>
     <uses-feature android:name="android.hardware.location.gps" android:required="false"/>
     <uses-feature android:name="android.hardware.touchscreen"/>
 
 #ifdef MOZ_ANDROID_BEAM
     <!-- Android Beam support -->
     <uses-permission android:name="android.permission.NFC"/>
-    <uses-feature android:name="android.hardware.nfc"/>
+    <uses-feature android:name="android.hardware.nfc" android:required="false"/>
 #endif
 
 #ifdef MOZ_WEBRTC
     <uses-permission android:name="android.permission.RECORD_AUDIO"/>
     <uses-feature android:name="android.hardware.audio.low_latency" android:required="false"/>
     <uses-feature android:name="android.hardware.camera.any" android:required="false"/>
     <uses-feature android:name="android.hardware.microphone" android:required="false"/>
 #endif
--- a/mobile/android/locales/jar.mn
+++ b/mobile/android/locales/jar.mn
@@ -56,11 +56,13 @@ relativesrcdir toolkit/locales:
 % override chrome://passwordmgr/locale/passwordmgr.properties chrome://browser/locale/overrides/passwordmgr/passwordmgr.properties
 % override chrome://global/locale/search/search.properties chrome://browser/locale/overrides/search/search.properties
 % override chrome://mozapps/locale/update/updates.properties chrome://browser/locale/overrides/update/updates.properties
 
 # overrides for dom l10n, also for en-US
 relativesrcdir dom/locales:
   locale/@AB_CD@/browser/overrides/charsetTitles.properties    (%chrome/charsetTitles.properties)
   locale/@AB_CD@/browser/overrides/global.dtd                  (%chrome/global.dtd)
+  locale/@AB_CD@/browser/overrides/AccessFu.properties         (%chrome/accessibility/AccessFu.properties)
 
 % override chrome://global/locale/charsetTitles.properties chrome://browser/locale/overrides/charsetTitles.properties
 % override chrome://global/locale/global.dtd chrome://browser/locale/overrides/global.dtd
+% override chrome://global/locale/AccessFu.properties chrome://browser/locale/overrides/AccessFu.properties
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3957,16 +3957,17 @@ pref("webgl.prefer-native-gl", false);
 pref("webgl.min_capability_mode", false);
 pref("webgl.disable-extensions", false);
 pref("webgl.msaa-force", false);
 pref("webgl.prefer-16bpp", false);
 pref("webgl.default-no-alpha", false);
 pref("webgl.force-layers-readback", false);
 pref("webgl.lose-context-on-heap-minimize", false);
 pref("webgl.can-lose-context-in-foreground", true);
+pref("webgl.max-warnings-per-context", 32);
 
 // Stagefright prefs
 pref("stagefright.force-enabled", false);
 pref("stagefright.disabled", false);
 
 #ifdef XP_WIN
 // The default TCP send window on Windows is too small, and autotuning only occurs on receive
 pref("network.tcp.sendbuffer", 131072);
--- a/testing/marionette/client/marionette/geckoinstance.py
+++ b/testing/marionette/client/marionette/geckoinstance.py
@@ -33,17 +33,17 @@ class GeckoInstance(object):
             profile_args["path_from"] = profile_path
 
         self.gecko_log = os.path.abspath('gecko.log')
         if os.access(self.gecko_log, os.F_OK):
             os.remove(self.gecko_log)
         self.runner = runner_class.create(
             binary=self.bin,
             profile_args=profile_args,
-            cmdargs=['-no-remote'],
+            cmdargs=['-no-remote', '-marionette'],
             kp_kwargs={
                 'processOutputLine': [NullOutput()],
                 'logfile': self.gecko_log})
         self.runner.start()
 
     def close(self):
         self.runner.stop()
         self.runner.cleanup()
--- a/testing/marionette/marionette-listener.js
+++ b/testing/marionette/marionette-listener.js
@@ -889,21 +889,21 @@ function actionChain(msg) {
  * @param type represents the type of event, touch represents the current touch,touches are all pending touches
  */
 function emitMultiEvents(type, touch, touches) {
   let target = touch.target;
   let doc = target.ownerDocument;
   let win = doc.defaultView;
   // touches that are in the same document
   let documentTouches = doc.createTouchList(touches.filter(function(t) {
-    return t.target.ownerDocument === doc;
+    return ((t.target.ownerDocument === doc) && (type != 'touchcancel'));
   }));
   // touches on the same target
   let targetTouches = doc.createTouchList(touches.filter(function(t) {
-    return t.target === target;
+    return ((t.target === target) && ((type != 'touchcancel') || (type != 'touchend')));
   }));
   // Create changed touches
   let changedTouches = doc.createTouchList(touch);
   // Create the event object
   let event = curWindow.document.createEvent('TouchEvent');
   event.initTouchEvent(type,
                        true,
                        true,
--- a/toolkit/components/downloads/nsDownloadManager.cpp
+++ b/toolkit/components/downloads/nsDownloadManager.cpp
@@ -2519,16 +2519,23 @@ nsDownload::nsDownload() : mDownloadStat
                            mAutoResume(DONT_RESUME)
 {
 }
 
 nsDownload::~nsDownload()
 {
 }
 
+NS_IMETHODIMP nsDownload::SetSha256Hash(const nsACString& aHash) {
+  MOZ_ASSERT(NS_IsMainThread(), "Must call SetSha256Hash on main thread");
+  // This will be used later to query the application reputation service.
+  mHash = aHash;
+  return NS_OK;
+}
+
 #ifdef MOZ_ENABLE_GIO
 static void gio_set_metadata_done(GObject *source_obj, GAsyncResult *res, gpointer user_data)
 {
   GError *err = NULL;
   g_file_set_attributes_finish(G_FILE(source_obj), res, NULL, &err);
   if (err) {
 #ifdef DEBUG
     NS_DebugBreak(NS_DEBUG_WARNING, "Set file metadata failed: ", err->message, __FILE__, __LINE__);
@@ -2581,17 +2588,16 @@ nsDownload::SetState(DownloadState aStat
       // If we failed, then fall through to 'download finished'
       if (NS_SUCCEEDED(rv))
         break;
       mDownloadState = aState = nsIDownloadManager::DOWNLOAD_FINISHED;
     }
 #endif
     case nsIDownloadManager::DOWNLOAD_FINISHED:
     {
-      // Do what exthandler would have done if necessary
       nsresult rv = ExecuteDesiredAction();
       if (NS_FAILED(rv)) {
         // We've failed to execute the desired action.  As a result, we should
         // fail the download so the user can try again.
         (void)FailDownload(rv, nullptr);
         return rv;
       }
 
@@ -2923,16 +2929,18 @@ nsDownload::OnStatusChange(nsIWebProgres
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDownload::OnStateChange(nsIWebProgress *aWebProgress,
                           nsIRequest *aRequest, uint32_t aStateFlags,
                           nsresult aStatus)
 {
+  MOZ_ASSERT(NS_IsMainThread(), "Must call OnStateChange in main thread");
+
   // We don't want to lose access to our member variables
   nsRefPtr<nsDownload> kungFuDeathGrip = this;
 
   // Check if we're starting a request; the NETWORK flag is necessary to not
   // pick up the START of *each* file but only for the whole request
   if ((aStateFlags & STATE_START) && (aStateFlags & STATE_IS_NETWORK)) {
     nsresult rv;
     nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
@@ -3182,20 +3190,22 @@ nsDownload::Finalize()
 
   // Make sure we do not automatically resume
   mAutoResume = DONT_RESUME;
 }
 
 nsresult
 nsDownload::ExecuteDesiredAction()
 {
-  // If we have a temp file and we have resumed, we have to do what the
-  // external helper app service would have done.
-  if (!mTempFile || !WasResumed())
+  // nsExternalHelperAppHandler is the only caller of AddDownload that sets a
+  // tempfile parameter. In this case, execute the desired action according to
+  // the saved mime info.
+  if (!mTempFile) {
     return NS_OK;
+  }
 
   // We need to bail if for some reason the temp file got removed
   bool fileExists;
   if (NS_FAILED(mTempFile->Exists(&fileExists)) || !fileExists)
     return NS_ERROR_FILE_NOT_FOUND;
 
   // Assume an unknown action is save to disk
   nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk;
--- a/toolkit/components/downloads/nsDownloadManager.h
+++ b/toolkit/components/downloads/nsDownloadManager.h
@@ -416,12 +416,17 @@ private:
    * the download manager or restoring from a crash.
    *
    * DONT_RESUME: Don't automatically resume the download
    * AUTO_RESUME: Automaically resume the download
    */
   enum AutoResume { DONT_RESUME, AUTO_RESUME };
   AutoResume mAutoResume;
 
+  /**
+   * Stores the SHA-256 hash associated with the downloaded file.
+   */
+  nsAutoCString mHash;
+
   friend class nsDownloadManager;
 };
 
 #endif
--- a/toolkit/components/downloads/nsDownloadProxy.h
+++ b/toolkit/components/downloads/nsDownloadProxy.h
@@ -11,16 +11,21 @@
 #include "nsIPrefService.h"
 #include "nsIMIMEInfo.h"
 #include "nsIFileURL.h"
 #include "nsIDownloadManagerUI.h"
 
 #define PREF_BDM_SHOWWHENSTARTING "browser.download.manager.showWhenStarting"
 #define PREF_BDM_FOCUSWHENSTARTING "browser.download.manager.focusWhenStarting"
 
+// This class only exists because nsDownload cannot inherit from nsITransfer
+// directly. The reason for this is that nsDownloadManager (incorrectly) keeps
+// an nsCOMArray of nsDownloads, and nsCOMArray is only intended for use with
+// abstract classes. Using a concrete class that multiply inherits from classes
+// deriving from nsISupports will throw ambiguous base class errors.
 class nsDownloadProxy : public nsITransfer
 {
 public:
 
   nsDownloadProxy() { }
   virtual ~nsDownloadProxy() { }
 
   NS_DECL_ISUPPORTS
@@ -138,16 +143,22 @@ public:
 
   NS_IMETHODIMP OnSecurityChange(nsIWebProgress *aWebProgress,
                                  nsIRequest *aRequest, uint32_t aState)
   {
     NS_ENSURE_TRUE(mInner, NS_ERROR_NOT_INITIALIZED);
     return mInner->OnSecurityChange(aWebProgress, aRequest, aState);
   }
 
+  NS_IMETHODIMP SetSha256Hash(const nsACString& aHash)
+  {
+    NS_ENSURE_TRUE(mInner, NS_ERROR_NOT_INITIALIZED);
+    return mInner->SetSha256Hash(aHash);
+  }
+
 private:
   nsCOMPtr<nsIDownload> mInner;
 };
 
 NS_IMPL_ISUPPORTS3(nsDownloadProxy, nsITransfer,
                    nsIWebProgressListener, nsIWebProgressListener2)
 
 #endif
--- a/toolkit/components/downloads/nsIDownloadManager.idl
+++ b/toolkit/components/downloads/nsIDownloadManager.idl
@@ -109,18 +109,22 @@ interface nsIDownloadManager : nsISuppor
    *                  including MIME type and helper app when appropriate.
    *                  This parameter is optional.
    *
    * @param startTime Time when the download started
    *
    * @param aTempFile The location of a temporary file; i.e. a file in which
    *                  the received data will be stored, but which is not
    *                  equal to the target file. (will be moved to the real
-   *                  target by the caller, when the download is finished)
-   *                  May be null.
+   *                  target by the DownloadManager, when the download is
+   *                  finished). This will be null for all callers except for
+   *                  nsExternalHelperAppHandler. Addons should generally pass
+   *                  null for aTempFile. This will be moved to the real target
+   *                  by the download manager when the download is finished,
+   *                  and the action indicated by aMIMEInfo will be executed.
    *
    * @param aCancelable An object that can be used to abort the download.
    *                    Must not be null.
    *
    * @param aIsPrivate Used to determine the privacy status of the new download.
    *                   If true, the download is stored in a manner that leaves
    *                   no permanent trace outside of the current private session.
    *
--- a/toolkit/content/tests/browser/common/mockTransfer.js
+++ b/toolkit/content/tests/browser/common/mockTransfer.js
@@ -47,17 +47,18 @@ MockTransfer.prototype = {
   },
   onSecurityChange: function () {},
 
   /* nsIWebProgressListener2 */
   onProgressChange64: function () {},
   onRefreshAttempted: function () {},
 
   /* nsITransfer */
-  init: function () {}
+  init: function() {},
+  setSha256Hash: function() {}
 };
 
 // Create an instance of a MockObjectRegisterer whose methods can be used to
 // temporarily replace the default "@mozilla.org/transfer;1" object factory with
 // one that provides the mock implementation above. To activate the mock object
 // factory, call the "register" method. Starting from that moment, all the
 // transfer objects that are requested will be mock objects, until the
 // "unregister" method is called.
--- a/uriloader/base/nsITransfer.idl
+++ b/uriloader/base/nsITransfer.idl
@@ -5,17 +5,17 @@
 
 #include "nsIWebProgressListener2.idl"
 
 interface nsIURI;
 interface nsICancelable;
 interface nsIMIMEInfo;
 interface nsIFile;
 
-[scriptable, uuid(08b0b78c-6d7d-4d97-86c9-be5a695813c9)]
+[scriptable, uuid(b1c81100-9d66-11e2-9e96-0800200c9a66)]
 interface nsITransfer : nsIWebProgressListener2 {
 
     /**
      * Initializes the transfer with certain properties.  This function must
      * be called prior to accessing any properties on this interface.
      *
      * @param aSource The source URI of the transfer. Must not be null.
      *
@@ -52,16 +52,24 @@ interface nsITransfer : nsIWebProgressLi
     void init(in nsIURI aSource,
               in nsIURI aTarget,
               in AString aDisplayName,
               in nsIMIMEInfo aMIMEInfo,
               in PRTime startTime,
               in nsIFile aTempFile,
               in nsICancelable aCancelable,
               in boolean aIsPrivate);
+
+    /*
+     * Used to notify the transfer object of the hash of the downloaded file.
+     * Must be called on the main thread, only after the download has finished
+     * successfully.
+     * @param aHash The SHA-256 hash in raw bytes of the downloaded file.
+     */
+    void setSha256Hash(in ACString aHash);
 };
 
 %{C++
 /**
  * A component with this contract ID will be created each time a download is
  * started, and nsITransfer::Init will be called on it and an observer will be set.
  *
  * Notifications of the download progress will happen via
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -136,18 +136,18 @@ enum {
 #ifdef PR_LOGGING
 PRLogModuleInfo* nsExternalHelperAppService::mLog = nullptr;
 #endif
 
 // Using level 3 here because the OSHelperAppServices use a log level
 // of PR_LOG_DEBUG (4), and we want less detailed output here
 // Using 3 instead of PR_LOG_WARN because we don't output warnings
 #undef LOG
-#define LOG(args) PR_LOG(mLog, 3, args)
-#define LOG_ENABLED() PR_LOG_TEST(mLog, 3)
+#define LOG(args) PR_LOG(nsExternalHelperAppService::mLog, 3, args)
+#define LOG_ENABLED() PR_LOG_TEST(nsExternalHelperAppService::mLog, 3)
 
 static const char NEVER_ASK_FOR_SAVE_TO_DISK_PREF[] =
   "browser.helperApps.neverAsk.saveToDisk";
 static const char NEVER_ASK_FOR_OPEN_FILE_PREF[] =
   "browser.helperApps.neverAsk.openFile";
 
 // Helper functions for Content-Disposition headers
 
@@ -1106,24 +1106,23 @@ nsExternalAppHandler::nsExternalAppHandl
                                            uint32_t aReason, bool aForceSave)
 : mMimeInfo(aMIMEInfo)
 , mWindowContext(aWindowContext)
 , mWindowToClose(nullptr)
 , mSuggestedFileName(aSuggestedFilename)
 , mForceSave(aForceSave)
 , mCanceled(false)
 , mShouldCloseWindow(false)
-, mReceivedDispositionInfo(false)
 , mStopRequestIssued(false)
-, mProgressListenerInitialized(false)
 , mReason(aReason)
 , mContentLength(-1)
 , mProgress(0)
 , mSaver(nullptr)
-, mKeepRequestAlive(false)
+, mDialogProgressListener(nullptr)
+, mTransfer(nullptr)
 , mRequest(nullptr)
 , mExtProtSvc(aExtProtSvc)
 {
 
   // make sure the extention includes the '.'
   if (!aTempFileExtension.IsEmpty() && aTempFileExtension.First() != '.')
     mTempFileExtension = PRUnichar('.');
   AppendUTF8toUTF16(aTempFileExtension, mTempFileExtension);
@@ -1139,48 +1138,33 @@ nsExternalAppHandler::nsExternalAppHandl
     PRUnichar(0x202c), // Pop Directional Formatting
     PRUnichar(0x202d), // Left-to-Right Override
     PRUnichar(0x202e)  // Right-to-Left Override
   };
   for (uint32_t i = 0; i < ArrayLength(unsafeBidiCharacters); ++i) {
     mSuggestedFileName.ReplaceChar(unsafeBidiCharacters[i], '_');
     mTempFileExtension.ReplaceChar(unsafeBidiCharacters[i], '_');
   }
-  
+
   // Make sure extension is correct.
   EnsureSuggestedFileName();
 
   mBufferSize = Preferences::GetUint("network.buffer.cache.size", 4096);
 }
 
 nsExternalAppHandler::~nsExternalAppHandler()
 {
   MOZ_ASSERT(!mSaver, "Saver should hold a reference to us until deleted");
 }
 
 NS_IMETHODIMP nsExternalAppHandler::SetWebProgressListener(nsIWebProgressListener2 * aWebProgressListener)
-{ 
-  // this call back means we've successfully brought up the 
-  // progress window so set the appropriate flag, even though
-  // aWebProgressListener might be null
-  
-  if (mReceivedDispositionInfo)
-    mProgressListenerInitialized = true;
-
-  // Go ahead and register the progress listener....
-  mWebProgressListener = aWebProgressListener;
-
-  // while we were bringing up the progress dialog, we actually finished processing the
-  // url. If that's the case then mStopRequestIssued will be true. We need to execute the
-  // operation since we are actually done now.
-  if (mStopRequestIssued && aWebProgressListener)
-  {
-    return ExecuteDesiredAction();
-  }
-
+{
+  // This is always called by nsHelperDlg.js. Go ahead and register the
+  // progress listener. At this point, we don't have mTransfer.
+  mDialogProgressListener = aWebProgressListener;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsExternalAppHandler::GetTargetFile(nsIFile** aTarget)
 {
   if (mFinalFileDestination)
     *aTarget = mFinalFileDestination;
   else
@@ -1208,23 +1192,16 @@ NS_IMETHODIMP nsExternalAppHandler::GetT
 }
 
 NS_IMETHODIMP nsExternalAppHandler::GetContentLength(int64_t *aContentLength)
 {
   *aContentLength = mContentLength;
   return NS_OK;
 }
 
-NS_IMETHODIMP nsExternalAppHandler::CloseProgressWindow()
-{
-  // release extra state...
-  mWebProgressListener = nullptr;
-  return NS_OK;
-}
-
 void nsExternalAppHandler::RetargetLoadNotifications(nsIRequest *request)
 {
   // we are going to run the downloading of the helper app in our own little docloader / load group context. 
   // so go ahead and force the creation of a load group and doc loader for us to use...
   nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
   if (!aChannel)
     return;
 
@@ -1395,16 +1372,20 @@ nsresult nsExternalAppHandler::SetUpTemp
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mSaver->SetObserver(this);
   if (NS_FAILED(rv)) {
     mSaver = nullptr;
     return rv;
   }
 
+  rv = mSaver->EnableSha256();
+  NS_ENSURE_SUCCESS(rv, rv);
+  LOG(("Enabled hashing"));
+
   rv = mSaver->SetTarget(mTempFile, false);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return rv;
 }
 
 NS_IMETHODIMP nsExternalAppHandler::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
 {
@@ -1589,35 +1570,28 @@ NS_IMETHODIMP nsExternalAppHandler::OnSt
   // before this is irrelevant; override it
   if (mForceSave) {
     alwaysAsk = false;
     action = nsIMIMEInfo::saveToDisk;
   }
   
   if (alwaysAsk)
   {
-    // do this first! make sure we don't try to take an action until the user tells us what they want to do
-    // with it...
-    mReceivedDispositionInfo = false; 
-    mKeepRequestAlive = true;
-
     // invoke the dialog!!!!! use mWindowContext as the window context parameter for the dialog request
     mDialog = do_CreateInstance( NS_HELPERAPPLAUNCHERDLG_CONTRACTID, &rv );
     NS_ENSURE_SUCCESS(rv, rv);
 
     // this will create a reference cycle (the dialog holds a reference to us as
-    // nsIHelperAppLauncher), which will be broken in Cancel or
-    // CreateProgressListener.
+    // nsIHelperAppLauncher), which will be broken in Cancel or CreateTransfer.
     rv = mDialog->Show( this, mWindowContext, mReason );
 
     // what do we do if the dialog failed? I guess we should call Cancel and abort the load....
   }
   else
   {
-    mReceivedDispositionInfo = true; // no need to wait for a response from the user
 
     // We need to do the save/open immediately, then.
 #ifdef XP_WIN
     /* We need to see whether the file we've got here could be
      * executable.  If it could, we had better not try to open it!
      * We can skip this check, though, if we have a setting to open in a
      * helper app.
      * This code mirrors the code in
@@ -1652,18 +1626,18 @@ NS_IMETHODIMP nsExternalAppHandler::OnSt
     {
         rv = SaveToDisk(nullptr, false);
     }
   }
 
   return NS_OK;
 }
 
-// Convert error info into proper message text and send OnStatusChange notification
-// to the web progress listener.
+// Convert error info into proper message text and send OnStatusChange
+// notification to the dialog progress listener or nsITransfer implementation.
 void nsExternalAppHandler::SendStatusChange(ErrorType type, nsresult rv, nsIRequest *aRequest, const nsAFlatString &path)
 {
     nsAutoString msgId;
     switch(rv)
     {
     case NS_ERROR_OUT_OF_MEMORY:
         // No memory
         msgId.AssignLiteral("noMemory");
@@ -1719,36 +1693,38 @@ void nsExternalAppHandler::SendStatusCha
         case kLaunchError:
           msgId.AssignLiteral("launchError");
           break;
         }
         break;
     }
     PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
         ("Error: %s, type=%i, listener=0x%p, rv=0x%08X\n",
-         NS_LossyConvertUTF16toASCII(msgId).get(), type, mWebProgressListener.get(), rv));
+         NS_LossyConvertUTF16toASCII(msgId).get(), type, mDialogProgressListener.get(), rv));
     PR_LOG(nsExternalHelperAppService::mLog, PR_LOG_ERROR,
         ("       path='%s'\n", NS_ConvertUTF16toUTF8(path).get()));
 
     // Get properties file bundle and extract status string.
     nsCOMPtr<nsIStringBundleService> stringService =
         mozilla::services::GetStringBundleService();
     if (stringService)
     {
         nsCOMPtr<nsIStringBundle> bundle;
         if (NS_SUCCEEDED(stringService->CreateBundle("chrome://global/locale/nsWebBrowserPersist.properties", getter_AddRefs(bundle))))
         {
             nsXPIDLString msgText;
             const PRUnichar *strings[] = { path.get() };
             if(NS_SUCCEEDED(bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText))))
             {
-              if (mWebProgressListener)
+              if (mDialogProgressListener)
               {
                 // We have a listener, let it handle the error.
-                mWebProgressListener->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
+                mDialogProgressListener->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
+              } else if (mTransfer) {
+                mTransfer->OnStatusChange(nullptr, (type == kReadError) ? aRequest : nullptr, rv, msgText);
               }
               else
               if (XRE_GetProcessType() == GeckoProcessType_Default) {
                 // We don't have a listener.  Simply show the alert ourselves.
                 nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mWindowContext));
                 nsXPIDLString title;
                 bundle->FormatStringFromName(NS_LITERAL_STRING("title").get(),
                                              strings,
@@ -1779,21 +1755,20 @@ nsExternalAppHandler::OnDataAvailable(ns
   {
     mProgress += count;
 
     nsCOMPtr<nsIStreamListener> saver = do_QueryInterface(mSaver);
     rv = saver->OnDataAvailable(request, aCtxt, inStr, sourceOffset, count);
     if (NS_SUCCEEDED(rv))
     {
       // Send progress notification.
-      if (mWebProgressListener)
-      {
-        mWebProgressListener->OnProgressChange64(nullptr, request, mProgress,
-                                                 mContentLength, mProgress,
-                                                 mContentLength);
+      if (mTransfer) {
+        mTransfer->OnProgressChange64(nullptr, request, mProgress,
+                                      mContentLength, mProgress,
+                                      mContentLength);
       }
     }
     else
     {
       // An error occurred, notify listener.
       nsAutoString tempFilePath;
       if (mTempFile)
         mTempFile->GetPath(tempFilePath);
@@ -1801,24 +1776,21 @@ nsExternalAppHandler::OnDataAvailable(ns
 
       // Cancel the download.
       Cancel(rv);
     }
   }
   return rv;
 }
 
-NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt, 
+NS_IMETHODIMP nsExternalAppHandler::OnStopRequest(nsIRequest *request, nsISupports *aCtxt,
                                                   nsresult aStatus)
 {
   mStopRequestIssued = true;
 
-  if (!mKeepRequestAlive)
-    mRequest = nullptr;
-
   // Cancel if the request did not complete successfully.
   if (!mCanceled && NS_FAILED(aStatus))
   {
     // Send error notification.
     nsAutoString tempFilePath;
     if (mTempFile)
       mTempFile->GetPath(tempFilePath);
     SendStatusChange( kReadError, aStatus, request, tempFilePath );
@@ -1839,80 +1811,74 @@ nsExternalAppHandler::OnTargetChange(nsI
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsExternalAppHandler::OnSaveComplete(nsIBackgroundFileSaver *aSaver,
                                      nsresult aStatus)
 {
-  // Free the reference that the saver keeps on us.
-  mSaver = nullptr;
-
   if (mCanceled)
     return NS_OK;
 
+  // Save the hash
+  nsresult rv = mSaver->GetSha256Hash(mHash);
+  // Free the reference that the saver keeps on us, even if we couldn't get the
+  // hash.
+  mSaver = nullptr;
+
   if (NS_FAILED(aStatus)) {
     nsAutoString path;
     mTempFile->GetPath(path);
     SendStatusChange(kWriteError, aStatus, nullptr, path);
     if (!mCanceled)
       Cancel(aStatus);
     return NS_OK;
   }
 
-  // Do what the user asked for
-  ExecuteDesiredAction();
-
-  // This nsITransfer object holds a reference to us (we are its observer), so
-  // we need to release the reference to break a reference cycle (and therefore
-  // to prevent leaking)
-  mWebProgressListener = nullptr;
+  // Notify the transfer object that we are done if the user has chosen an
+  // action. If the user hasn't chosen an action and InitializeDownload hasn't
+  // been called, the progress listener (nsITransfer) will be notified in
+  // CreateTransfer.
+  if (mTransfer) {
+    rv = NotifyTransfer();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   return NS_OK;
 }
 
-nsresult nsExternalAppHandler::ExecuteDesiredAction()
+nsresult nsExternalAppHandler::NotifyTransfer()
 {
-  nsresult rv = NS_OK;
-  if (mProgressListenerInitialized && !mCanceled)
-  {
-    rv = MoveFile(mFinalFileDestination);
-    if (NS_SUCCEEDED(rv))
-    {
-      nsHandlerInfoAction action = nsIMIMEInfo::saveToDisk;
-      mMimeInfo->GetPreferredAction(&action);
-      if (action == nsIMIMEInfo::useHelperApp ||
-          action == nsIMIMEInfo::useSystemDefault)
-      {
-        rv = OpenWithApplication();
-      }
-      else if(action == nsIMIMEInfo::saveToDisk)
-      {
-        mExtProtSvc->FixFilePermissions(mFinalFileDestination);
-      }
-    }
+  MOZ_ASSERT(NS_IsMainThread(), "Must notify on main thread");
+  MOZ_ASSERT(!mCanceled, "Can't notify if canceled or action "
+             "hasn't been chosen");
+  MOZ_ASSERT(mTransfer, "We must have an nsITransfer");
+
+  LOG(("Notifying progress listener"));
+
+  nsresult rv = mTransfer->SetSha256Hash(mHash);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-    // Notify dialog that download is complete.
-    // By waiting till this point, it ensures that the progress dialog doesn't indicate
-    // success until we're really done.
-    if(mWebProgressListener)
-    {
-      if (!mCanceled)
-      {
-        mWebProgressListener->OnProgressChange64(nullptr, nullptr, mProgress, mContentLength, mProgress, mContentLength);
-      }
-      mWebProgressListener->OnStateChange(nullptr, nullptr,
-        nsIWebProgressListener::STATE_STOP |
-        nsIWebProgressListener::STATE_IS_REQUEST |
-        nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
-    }
-  }
+  rv = mTransfer->OnProgressChange64(nullptr, nullptr, mProgress,
+    mContentLength, mProgress, mContentLength);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  return rv;
+  rv = mTransfer->OnStateChange(nullptr, nullptr,
+    nsIWebProgressListener::STATE_STOP |
+    nsIWebProgressListener::STATE_IS_REQUEST |
+    nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // This nsITransfer object holds a reference to us (we are its observer), so
+  // we need to release the reference to break a reference cycle (and therefore
+  // to prevent leaking)
+  mTransfer = nullptr;
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP nsExternalAppHandler::GetMIMEInfo(nsIMIMEInfo ** aMIMEInfo)
 {
   *aMIMEInfo = mMimeInfo;
   NS_ADDREF(*aMIMEInfo);
   return NS_OK;
 }
@@ -1932,24 +1898,24 @@ NS_IMETHODIMP nsExternalAppHandler::GetS
 }
 
 nsresult nsExternalAppHandler::InitializeDownload(nsITransfer* aTransfer)
 {
   nsresult rv;
   
   nsCOMPtr<nsIURI> target;
   rv = NS_NewFileURI(getter_AddRefs(target), mFinalFileDestination);
-  if (NS_FAILED(rv)) return rv;
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
 
   rv = aTransfer->Init(mSourceUrl, target, EmptyString(),
                        mMimeInfo, mTimeDownloadStarted, mTempFile, this,
                        channel && NS_UsePrivateBrowsing(channel));
-  if (NS_FAILED(rv)) return rv;
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // Now let's add the download to history
   nsCOMPtr<nsIDownloadHistory> dh(do_GetService(NS_DOWNLOADHISTORY_CONTRACTID));
   if (dh) {
     nsCOMPtr<nsIURI> referrer;
     nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
     if (channel) {
       NS_GetReferrerFromChannel(channel, getter_AddRefs(referrer));
@@ -1958,41 +1924,48 @@ nsresult nsExternalAppHandler::Initializ
     if (channel && !NS_UsePrivateBrowsing(channel)) {
       dh->AddDownload(mSourceUrl, referrer, mTimeDownloadStarted, target);
     }
   }
 
   return rv;
 }
 
-nsresult nsExternalAppHandler::CreateProgressListener()
+nsresult nsExternalAppHandler::CreateTransfer()
 {
-  // we are back from the helper app dialog (where the user chooses to save or open), but we aren't
-  // done processing the load. in this case, throw up a progress dialog so the user can see what's going on...
+  // We are back from the helper app dialog (where the user chooses to save or
+  // open), but we aren't done processing the load. in this case, throw up a
+  // progress dialog so the user can see what's going on.
   // Also, release our reference to mDialog. We don't need it anymore, and we
   // need to break the reference cycle.
   mDialog = nullptr;
+  if (!mDialogProgressListener) {
+    NS_WARNING("The dialog should nullify the dialog progress listener");
+  }
   nsresult rv;
-  
-  nsCOMPtr<nsITransfer> tr = do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
-  if (NS_SUCCEEDED(rv))
-    InitializeDownload(tr);
+
+  // We must be able to create an nsITransfer object. If not, it doesn't matter
+  // much that we can't launch the helper application or save to disk.
+  mTransfer = do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = InitializeDownload(mTransfer);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  if (tr)
-    tr->OnStateChange(nullptr, mRequest, nsIWebProgressListener::STATE_START |
-      nsIWebProgressListener::STATE_IS_REQUEST |
-      nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
+  rv = mTransfer->OnStateChange(nullptr, mRequest,
+    nsIWebProgressListener::STATE_START |
+    nsIWebProgressListener::STATE_IS_REQUEST |
+    nsIWebProgressListener::STATE_IS_NETWORK, NS_OK);
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  // note we might not have a listener here if the QI() failed, or if
-  // there is no nsITransfer object, but we still call
-  // SetWebProgressListener() to make sure our progress state is sane
-  // NOTE: This will set up a reference cycle (this nsITransfer has us set up as
-  // its observer). This cycle will be broken in Cancel, CloseProgressWindow or
-  // OnStopRequest.
-  SetWebProgressListener(tr);
+  // While we were bringing up the progress dialog, we actually finished
+  // processing the url. If that's the case then mStopRequestIssued will be
+  // true and OnSaveComplete has been called.
+  if (mStopRequestIssued && !mSaver) {
+    return NotifyTransfer();
+  }
 
   mRequest = nullptr;
 
   return rv;
 }
 
 nsresult nsExternalAppHandler::SaveDestinationAvailable(nsIFile * aFile)
 {
@@ -2044,88 +2017,26 @@ void nsExternalAppHandler::RequestSaveDe
                                            aDefaultFile.get(),
                                            aFileExtension.get(),
                                            mForceSave);
   } else {
     SaveDestinationAvailable(rv == NS_OK ? fileToUse : nullptr);
   }
 }
 
-nsresult nsExternalAppHandler::MoveFile(nsIFile * aNewFileLocation)
-{
-  nsresult rv = NS_OK;
-  NS_ASSERTION(mStopRequestIssued, "uhoh, how did we get here if we aren't done getting data?");
- 
-  // if the on stop request was actually issued then it's now time to actually perform the file move....
-  if (mStopRequestIssued && aNewFileLocation)
-  {
-    // Unfortunately, MoveTo will fail if a file already exists at the user specified location....
-    // but the user has told us, this is where they want the file! (when we threw up the save to file dialog,
-    // it told them the file already exists and do they wish to over write it. So it should be okay to delete
-    // fileToUse if it already exists.
-    bool equalToTempFile = false;
-    bool filetoUseAlreadyExists = false;
-    aNewFileLocation->Equals(mTempFile, &equalToTempFile);
-    aNewFileLocation->Exists(&filetoUseAlreadyExists);
-    if (filetoUseAlreadyExists && !equalToTempFile)
-      aNewFileLocation->Remove(false);
-
-     // extract the new leaf name from the file location
-     nsAutoString fileName;
-     aNewFileLocation->GetLeafName(fileName);
-     nsCOMPtr<nsIFile> directoryLocation;
-     rv = aNewFileLocation->GetParent(getter_AddRefs(directoryLocation));
-     if (directoryLocation)
-     {
-       rv = mTempFile->MoveTo(directoryLocation, fileName);
-     }
-     if (NS_FAILED(rv))
-     {
-       // Send error notification.        
-       nsAutoString path;
-       aNewFileLocation->GetPath(path);
-       SendStatusChange(kWriteError, rv, nullptr, path);
-       Cancel(rv); // Cancel (and clean up temp file).
-     }
-#if defined(XP_OS2)
-     else
-     {
-       // tag the file with its source URI
-       nsCOMPtr<nsILocalFileOS2> localFileOS2 = do_QueryInterface(aNewFileLocation);
-       if (localFileOS2)
-       {
-         nsAutoCString url;
-         mSourceUrl->GetSpec(url);
-         localFileOS2->SetFileSource(url);
-       }
-     }
-#endif
-  }
-
-  return rv;
-}
-
 // SaveToDisk should only be called by the helper app dialog which allows
-// the user to say launch with application or save to disk. It doesn't actually 
-// perform the save, it just prompts for the destination file name. The actual save
-// won't happen until we are done downloading the content and are sure we've 
-// shown a progress dialog. This was done to simplify the 
-// logic that was showing up in this method. Internal callers who actually want
-// to preform the save should call ::MoveFile
-
+// the user to say launch with application or save to disk. It doesn't actually
+// perform the save, it just prompts for the destination file name.
 NS_IMETHODIMP nsExternalAppHandler::SaveToDisk(nsIFile * aNewFileLocation, bool aRememberThisPreference)
 {
   if (mCanceled)
     return NS_OK;
 
   mMimeInfo->SetPreferredAction(nsIMIMEInfo::saveToDisk);
 
-  // The helper app dialog has told us what to do.
-  mReceivedDispositionInfo = true;
-
   if (!aNewFileLocation) {
     if (mSuggestedFileName.IsEmpty())
       RequestSaveDestination(mTempLeafName, mTempFileExtension);
     else
     {
       nsAutoString fileExt;
       int32_t pos = mSuggestedFileName.RFindChar('.');
       if (pos >= 0)
@@ -2138,136 +2049,78 @@ NS_IMETHODIMP nsExternalAppHandler::Save
   } else {
     ContinueSave(aNewFileLocation);
   }
 
   return NS_OK;
 }
 nsresult nsExternalAppHandler::ContinueSave(nsIFile * aNewFileLocation)
 {
+  if (mCanceled)
+    return NS_OK;
+
   NS_PRECONDITION(aNewFileLocation, "Must be called with a non-null file");
 
   nsresult rv = NS_OK;
   nsCOMPtr<nsIFile> fileToUse = do_QueryInterface(aNewFileLocation);
   mFinalFileDestination = do_QueryInterface(fileToUse);
 
   // Move what we have in the final directory, but append .part
-  // to it, to indicate that it's unfinished.
-  // do not do that if we're already done
-  if (mFinalFileDestination && !mStopRequestIssued)
+  // to it, to indicate that it's unfinished.  Do not call SetTarget on the
+  // saver if we are done (Finish has been called) but OnSaverComplete has not
+  // been called.
+  if (mFinalFileDestination && mSaver && !mStopRequestIssued)
   {
     nsCOMPtr<nsIFile> movedFile;
     mFinalFileDestination->Clone(getter_AddRefs(movedFile));
     if (movedFile) {
       // Get the old leaf name and append .part to it
       nsAutoString name;
       mFinalFileDestination->GetLeafName(name);
       name.AppendLiteral(".part");
       movedFile->SetLeafName(name);
 
-      if (mSaver)
-      {
-        rv = mSaver->SetTarget(movedFile, true);
-        if (NS_FAILED(rv)) {
-          nsAutoString path;
-          mTempFile->GetPath(path);
-          SendStatusChange(kWriteError, rv, nullptr, path);
-          Cancel(rv);
-          return NS_OK;
-        }
+      rv = mSaver->SetTarget(movedFile, true);
+      if (NS_FAILED(rv)) {
+        nsAutoString path;
+        mTempFile->GetPath(path);
+        SendStatusChange(kWriteError, rv, nullptr, path);
+        Cancel(rv);
+        return NS_OK;
       }
 
       mTempFile = movedFile;
     }
   }
 
-  if (!mProgressListenerInitialized)
-    CreateProgressListener();
+  // The helper app dialog has told us what to do and we have a final file
+  // destination.
+  CreateTransfer();
 
   // now that the user has chosen the file location to save to, it's okay to fire the refresh tag
   // if there is one. We don't want to do this before the save as dialog goes away because this dialog
   // is modal and we do bad things if you try to load a web page in the underlying window while a modal
-  // dialog is still up. 
+  // dialog is still up.
   ProcessAnyRefreshTags();
 
   return NS_OK;
 }
 
 
-nsresult nsExternalAppHandler::OpenWithApplication()
-{
-  nsresult rv = NS_OK;
-  if (mCanceled)
-    return NS_OK;
-  
-  // we only should have gotten here if the on stop request had been fired already.
-
-  NS_ASSERTION(mStopRequestIssued, "uhoh, how did we get here if we aren't done getting data?");
-  // if a stop request was already issued then proceed with launching the application.
-  if (mStopRequestIssued)
-  {
-
-    // Note for the default value:
-    // Mac users have been very verbal about temp files being deleted on
-    // app exit - they don't like it - but we'll continue to do this on
-    // other platforms for now.
-    bool deleteTempFileOnExit =
-      Preferences::GetBool("browser.helperApps.deleteTempFileOnExit",
-#if !defined(XP_MACOSX)
-                           true);
-#else
-                           false);
-#endif
-
-    // See whether the channel has been opened in private browsing mode
-    NS_ASSERTION(mRequest, "This should never be called with a null request");
-    nsCOMPtr<nsIChannel> channel = do_QueryInterface(mRequest);
-    bool inPrivateBrowsing = channel && NS_UsePrivateBrowsing(channel);
-
-    // make the tmp file readonly so users won't edit it and lose the changes
-    // only if we're going to delete the file
-    if (deleteTempFileOnExit || inPrivateBrowsing)
-      mFinalFileDestination->SetPermissions(0400);
-
-    rv = mMimeInfo->LaunchWithFile(mFinalFileDestination);
-    if (NS_FAILED(rv))
-    {
-      // Send error notification.
-      nsAutoString path;
-      mFinalFileDestination->GetPath(path);
-      SendStatusChange(kLaunchError, rv, nullptr, path);
-      Cancel(rv); // Cancel, and clean up temp file.
-    }
-    // Always schedule files to be deleted at the end of the private browsing
-    // mode, regardless of the value of the pref.
-    else if (deleteTempFileOnExit) {
-      mExtProtSvc->DeleteTemporaryFileOnExit(mFinalFileDestination);
-    }
-    else if (inPrivateBrowsing) {
-      mExtProtSvc->DeleteTemporaryPrivateFileWhenPossible(mFinalFileDestination);
-    }
-  }
-
-  return rv;
-}
-
-// LaunchWithApplication should only be called by the helper app dialog which allows
-// the user to say launch with application or save to disk. It doesn't actually 
-// perform launch with application. That won't happen until we are done downloading
-// the content and are sure we've shown a progress dialog. This was done to simplify the 
-// logic that was showing up in this method. 
+// LaunchWithApplication should only be called by the helper app dialog which
+// allows the user to say launch with application or save to disk. It doesn't
+// actually perform launch with application.
 NS_IMETHODIMP nsExternalAppHandler::LaunchWithApplication(nsIFile * aApplication, bool aRememberThisPreference)
 {
   if (mCanceled)
     return NS_OK;
 
   // user has chosen to launch using an application, fire any refresh tags now...
   ProcessAnyRefreshTags(); 
   
-  mReceivedDispositionInfo = true; 
   if (mMimeInfo && aApplication) {
     PlatformLocalHandlerApp_t *handlerApp =
       new PlatformLocalHandlerApp_t(EmptyString(), aApplication);
     mMimeInfo->SetPreferredApplicationHandler(handlerApp);
   }
 
   // Now check if the file is local, in which case we won't bother with saving
   // it to a temporary directory and just launch it from where it is
@@ -2287,22 +2140,23 @@ NS_IMETHODIMP nsExternalAppHandler::Laun
     nsAutoString path;
     if (file)
       file->GetPath(path);
     // If we get here, an error happened
     SendStatusChange(kLaunchError, rv, nullptr, path);
     return rv;
   }
 
-  // Now that the user has elected to launch the downloaded file with a helper app, we're justified in
-  // removing the 'salted' name.  We'll rename to what was specified in mSuggestedFileName after the
-  // download is done prior to launching the helper app.  So that any existing file of that name won't
-  // be overwritten we call CreateUnique() before calling MoveFile().  Also note that we use the same
-  // directory as originally downloaded to so that MoveFile() just does an in place rename.
-
+  // Now that the user has elected to launch the downloaded file with a helper
+  // app, we're justified in removing the 'salted' name.  We'll rename to what
+  // was specified in mSuggestedFileName after the download is done prior to
+  // launching the helper app.  So that any existing file of that name won't be
+  // overwritten we call CreateUnique().  Also note that we use the same
+  // directory as originally downloaded so nsDownload can rename in place
+  // later.
   nsCOMPtr<nsIFile> fileToUse;
   (void) GetDownloadDirectory(getter_AddRefs(fileToUse));
 
   if (mSuggestedFileName.IsEmpty())
   {
     // Keep using the leafname of the temp file, since we're just starting a helper
     mSuggestedFileName = mTempLeafName;
   }
@@ -2313,18 +2167,17 @@ NS_IMETHODIMP nsExternalAppHandler::Laun
   fileToUse->Append(mSuggestedFileName);  
 #endif
 
   nsresult rv = fileToUse->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
   if(NS_SUCCEEDED(rv))
   {
     mFinalFileDestination = do_QueryInterface(fileToUse);
     // launch the progress window now that the user has picked the desired action.
-    if (!mProgressListenerInitialized)
-      CreateProgressListener();
+    CreateTransfer();
   }
   else
   {
     // Cancel the download and report an error.  We do not want to end up in
     // a state where it appears that we have a normal download that is
     // pointing to a file that we did not actually create.
     nsAutoString path;
     mTempFile->GetPath(path);
@@ -2335,28 +2188,31 @@ NS_IMETHODIMP nsExternalAppHandler::Laun
 }
 
 NS_IMETHODIMP nsExternalAppHandler::Cancel(nsresult aReason)
 {
   NS_ENSURE_ARG(NS_FAILED(aReason));
   // XXX should not ignore the reason
 
   mCanceled = true;
-  if (mSaver)
+  if (mSaver) {
     mSaver->Finish(aReason);
+    mSaver = nullptr;
+  }
 
   // Break our reference cycle with the helper app dialog (set up in
   // OnStartRequest)
   mDialog = nullptr;
 
   mRequest = nullptr;
 
   // Release the listener, to break the reference cycle with it (we are the
   // observer of the listener).
-  mWebProgressListener = nullptr;
+  mDialogProgressListener = nullptr;
+  mTransfer = nullptr;
 
   return NS_OK;
 }
 
 void nsExternalAppHandler::ProcessAnyRefreshTags()
 {
    // one last thing, try to see if the original window context supports a refresh interface...
    // Sometimes, when you download content that requires an external handler, there is
--- a/uriloader/exthandler/nsExternalHelperAppService.h
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
@@ -277,22 +277,19 @@ protected:
   /**
    * This is set based on whether the channel indicates that a new window
    * was opened specifically for this download.  If so, then we
    * close it.
    */
   bool mShouldCloseWindow;
 
   /**
-   * have we received information from the user about how they want to
-   * dispose of this content
+   * True if a stop request has been issued.
    */
-  bool mReceivedDispositionInfo;
   bool mStopRequestIssued; 
-  bool mProgressListenerInitialized;
 
   bool mIsFileChannel;
 
   /**
    * One of the REASON_ constants from nsIHelperAppLauncherDialog. Indicates the
    * reason the dialog was shown (unknown content type, server requested it,
    * etc).
    */
@@ -319,34 +316,37 @@ protected:
   /**
    * This object handles saving the data received from the network to a
    * temporary location first, and then move the file to its final location,
    * doing all the input/output on a background thread.
    */
   nsCOMPtr<nsIBackgroundFileSaver> mSaver;
 
   /**
+   * Stores the SHA-256 hash associated with the file that we downloaded.
+   */
+  nsAutoCString mHash;
+
+  /**
    * Creates the temporary file for the download and an output stream for it.
    * Upon successful return, both mTempFile and mSaver will be valid.
    */
   nsresult SetUpTempFile(nsIChannel * aChannel);
   /**
    * When we download a helper app, we are going to retarget all load
    * notifications into our own docloader and load group instead of
    * using the window which initiated the load....RetargetLoadNotifications
    * contains that information...
    */
   void RetargetLoadNotifications(nsIRequest *request); 
   /**
-   * If the user tells us how they want to dispose of the content and
-   * we still haven't finished downloading while they were deciding,
-   * then create a progress listener of some kind so they know
-   * what's going on...
+   * Once the user tells us how they want to dispose of the content
+   * create an nsITransfer so they know what's going on...
    */
-  nsresult CreateProgressListener();
+  nsresult CreateTransfer();
 
 
   /* 
    * The following two functions are part of the split of SaveToDisk
    * to make it async, and works as following:
    *
    *    SaveToDisk    ------->   RequestSaveDestination
    *                                     .
@@ -375,33 +375,21 @@ protected:
    * After we're done prompting the user for any information, if the original
    * channel had a refresh url associated with it (which might point to a
    * "thank you for downloading" kind of page, then process that....It is safe
    * to invoke this method multiple times. We'll clear mOriginalChannel after
    * it's called and this ensures we won't call it again....
    */
   void ProcessAnyRefreshTags();
 
-  /** 
-   * An internal method used to actually move the temp file to the final
-   * destination once we done receiving data AND have showed the progress dialog
+  /**
+   * Notify our nsITransfer object that we are done with the download.
    */
-  nsresult MoveFile(nsIFile * aNewFileLocation);
-  /**
-   * An internal method used to actually launch a helper app given the temp file
-   * once we are done receiving data AND have showed the progress dialog.
-   * Uses the application specified in the mime info.
-   */
-  nsresult OpenWithApplication();
-  
-  /**
-   * Helper routine which peaks at the mime action specified by mMimeInfo
-   * and calls either MoveFile or OpenWithApplication
-   */
-  nsresult ExecuteDesiredAction();
+  nsresult NotifyTransfer();
+
   /**
    * Helper routine that searches a pref string for a given mime type
    */
   bool GetNeverAskFlagFromPref(const char * prefName, const char * aContentType);
 
   /**
    * Initialize an nsITransfer object for use as a progress object
    */
@@ -422,17 +410,27 @@ protected:
 
   /**
    * Closes the window context if it does not have a refresh header
    * and it never displayed content before the external helper app
    * service was invoked.
    */
   nsresult MaybeCloseWindow();
 
-  nsCOMPtr<nsIWebProgressListener2> mWebProgressListener;
+  /**
+   * Set in nsHelperDlgApp.js. This is always null after the user has chosen an
+   * action.
+   */
+  nsCOMPtr<nsIWebProgressListener2> mDialogProgressListener;
+  /**
+   * Set once the user has chosen an action. This is null after the download
+   * has been canceled or completes.
+   */
+  nsCOMPtr<nsITransfer> mTransfer;
+
   nsCOMPtr<nsIChannel> mOriginalChannel; /**< in the case of a redirect, this will be the pre-redirect channel. */
   nsCOMPtr<nsIHelperAppLauncherDialog> mDialog;
 
   /**
    * Keep request alive in case when helper non-modal dialog shown.
    * Thus in OnStopRequest the mRequest will not be set to null (it will be set to null further).
    */
   bool mKeepRequestAlive;
--- a/uriloader/exthandler/nsIExternalHelperAppService.idl
+++ b/uriloader/exthandler/nsIExternalHelperAppService.idl
@@ -91,24 +91,26 @@ interface nsIHelperAppLauncher : nsICanc
   readonly attribute nsIURI source;
 
   /**
    * The suggested name for this file
    */
   readonly attribute AString suggestedFileName;
 
   /**
-   * Called when we want to just save the content to a particular file.
-   * NOTE: This will release the reference to the nsIHelperAppLauncherDialog.
-   * @param aNewFileLocation Location where the content should be saved
+   * Saves the final destination of the file. Does not actually perform the
+   * save.
+   * NOTE: This will release the reference to the
+   * nsIHelperAppLauncherDialog.
    */
   void saveToDisk(in nsIFile aNewFileLocation, in boolean aRememberThisPreference);
 
   /**
-   * Use aApplication to launch with this content.
+   * Remembers that aApplication should be used to launch this content. Does
+   * not actually launch the application.
    * NOTE: This will release the reference to the nsIHelperAppLauncherDialog.
    * @param aApplication nsIFile corresponding to the location of the application to use.
    * @param aRememberThisPreference TRUE if we should remember this choice.
    */
   void launchWithApplication(in nsIFile aApplication, in boolean aRememberThisPreference);
 
   /**
    * Callback invoked by nsIHelperAppLauncherDialog::promptForSaveToFileAsync
@@ -121,22 +123,16 @@ interface nsIHelperAppLauncher : nsICanc
    * The following methods are used by the progress dialog to get or set
    * information on the current helper app launcher download.
    * This reference will be released when the download is finished (after the
    * listener receives the STATE_STOP notification).
    */
   void setWebProgressListener(in nsIWebProgressListener2 aWebProgressListener);
 
   /**
-   * when the stand alone progress window actually closes, it calls this method
-   * so we can release any local state...
-   */
-  void closeProgressWindow();
-
-  /**
    * The file we are saving to
    */
   readonly attribute nsIFile targetFile;
 
   /**
    * The executable-ness of the target file
    */
   readonly attribute boolean targetFileIsExecutable;