Shift "commit point" for when a download will no longer accept cancels.

This CL shifts the commit point for a download to just after the download 
file release has been dispatched.  The download remains IN_PROGRESS as 
far as the outside world is concerned, but at this point it is committed to 
continue to completion.

BUG=123998
[email protected]
[email protected]


Review URL: https://chromiumcodereview.appspot.com/10950015

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@158298 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/content/browser/download/download_browsertest.cc b/content/browser/download/download_browsertest.cc
index 6fab6e25..46030b1e 100644
--- a/content/browser/download/download_browsertest.cc
+++ b/content/browser/download/download_browsertest.cc
@@ -8,10 +8,16 @@
 #include "base/file_path.h"
 #include "base/file_util.h"
 #include "base/scoped_temp_dir.h"
+#include "content/browser/download/download_file_factory.h"
+#include "content/browser/download/download_file_impl.h"
+#include "content/browser/download/download_file_manager.h"
 #include "content/browser/download/download_item_impl.h"
 #include "content/browser/download/download_manager_impl.h"
+#include "content/browser/power_save_blocker.h"
+#include "content/browser/renderer_host/resource_dispatcher_host_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/test/download_test_observer.h"
+#include "content/public/test/test_utils.h"
 #include "content/shell/shell.h"
 #include "content/shell/shell_browser_context.h"
 #include "content/shell/shell_download_manager_delegate.h"
@@ -25,9 +31,197 @@
 
 namespace {
 
-static DownloadManager* DownloadManagerForShell(Shell* shell) {
-  return BrowserContext::GetDownloadManager(
-      shell->web_contents()->GetBrowserContext());
+class DownloadFileWithDelayFactory;
+
+static DownloadManagerImpl* DownloadManagerForShell(Shell* shell) {
+  // We're in a content_browsertest; we know that the DownloadManager
+  // is a DownloadManagerImpl.
+  return static_cast<DownloadManagerImpl*>(
+      BrowserContext::GetDownloadManager(
+          shell->web_contents()->GetBrowserContext()));
+}
+
+class DownloadFileWithDelay : public DownloadFileImpl {
+ public:
+  DownloadFileWithDelay(
+      const DownloadCreateInfo* info,
+      scoped_ptr<content::ByteStreamReader> stream,
+      DownloadRequestHandleInterface* request_handle,
+      scoped_refptr<content::DownloadManager> download_manager,
+      bool calculate_hash,
+      scoped_ptr<content::PowerSaveBlocker> power_save_blocker,
+      const net::BoundNetLog& bound_net_log,
+      // |owner| is required to outlive the DownloadFileWithDelay.
+      DownloadFileWithDelayFactory* owner);
+
+  virtual ~DownloadFileWithDelay();
+
+  // Wraps DownloadFileImpl::Rename and intercepts the return callback,
+  // storing it in the factory that produced this object for later
+  // retrieval.
+  virtual void Rename(const FilePath& full_path,
+                      bool overwrite_existing_file,
+                      const RenameCompletionCallback& callback) OVERRIDE;
+
+  // Wraps DownloadFileImpl::Detach and intercepts the return callback,
+  // storing it in the factory that produced this object for later
+  // retrieval.
+  virtual void Detach(base::Closure callback) OVERRIDE;
+
+ private:
+  static void RenameCallbackWrapper(
+      DownloadFileWithDelayFactory* factory,
+      const RenameCompletionCallback& original_callback,
+      content::DownloadInterruptReason reason,
+      const FilePath& path);
+
+  static void DetachCallbackWrapper(
+      DownloadFileWithDelayFactory* factory,
+      const base::Closure& original_callback);
+
+  // May only be used on the UI thread.
+  DownloadFileWithDelayFactory* owner_;
+
+  DISALLOW_COPY_AND_ASSIGN(DownloadFileWithDelay);
+};
+
+class DownloadFileWithDelayFactory : public DownloadFileFactory {
+ public:
+  DownloadFileWithDelayFactory();
+  virtual ~DownloadFileWithDelayFactory();
+
+  // DownloadFileFactory interface.
+  virtual content::DownloadFile* CreateFile(
+      DownloadCreateInfo* info,
+      scoped_ptr<content::ByteStreamReader> stream,
+      DownloadManager* download_manager,
+      bool calculate_hash,
+      const net::BoundNetLog& bound_net_log) OVERRIDE;
+
+  // Must all be called on the UI thread.
+  void AddRenameCallback(base::Closure callback);
+  void AddDetachCallback(base::Closure callback);
+  void GetAllRenameCallbacks(std::vector<base::Closure>* results);
+  void GetAllDetachCallbacks(std::vector<base::Closure>* results);
+
+  // Do not return until either GetAllRenameCallbacks() or
+  // GetAllDetachCallbacks() will return a non-empty list.
+  void WaitForSomeCallback();
+
+ private:
+  std::vector<base::Closure> rename_callbacks_;
+  std::vector<base::Closure> detach_callbacks_;
+  bool waiting_;
+
+  DISALLOW_COPY_AND_ASSIGN(DownloadFileWithDelayFactory);
+};
+
+DownloadFileWithDelay::DownloadFileWithDelay(
+      const DownloadCreateInfo* info,
+      scoped_ptr<content::ByteStreamReader> stream,
+      DownloadRequestHandleInterface* request_handle,
+      scoped_refptr<content::DownloadManager> download_manager,
+      bool calculate_hash,
+      scoped_ptr<content::PowerSaveBlocker> power_save_blocker,
+      const net::BoundNetLog& bound_net_log,
+      DownloadFileWithDelayFactory* owner)
+    : DownloadFileImpl(info, stream.Pass(), request_handle, download_manager,
+                       calculate_hash, power_save_blocker.Pass(),
+                       bound_net_log),
+      owner_(owner) {}
+
+DownloadFileWithDelay::~DownloadFileWithDelay() {}
+
+void DownloadFileWithDelay::Rename(const FilePath& full_path,
+                      bool overwrite_existing_file,
+                      const RenameCompletionCallback& callback) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+  DownloadFileImpl::Rename(
+      full_path, overwrite_existing_file,
+      base::Bind(DownloadFileWithDelay::RenameCallbackWrapper,
+                 base::Unretained(owner_), callback));
+}
+
+void DownloadFileWithDelay::Detach(base::Closure callback) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+  DownloadFileImpl::Detach(
+      base::Bind(DownloadFileWithDelay::DetachCallbackWrapper,
+                 base::Unretained(owner_), callback));
+}
+
+// static
+void DownloadFileWithDelay::RenameCallbackWrapper(
+    DownloadFileWithDelayFactory* factory,
+    const RenameCompletionCallback& original_callback,
+    content::DownloadInterruptReason reason,
+    const FilePath& path) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  factory->AddRenameCallback(base::Bind(original_callback, reason, path));
+}
+
+// static
+void DownloadFileWithDelay::DetachCallbackWrapper(
+    DownloadFileWithDelayFactory* factory,
+    const base::Closure& original_callback) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  factory->AddDetachCallback(original_callback);
+}
+
+DownloadFileWithDelayFactory::DownloadFileWithDelayFactory()
+    : waiting_(false) {}
+DownloadFileWithDelayFactory::~DownloadFileWithDelayFactory() {}
+
+DownloadFile* DownloadFileWithDelayFactory::CreateFile(
+    DownloadCreateInfo* info,
+    scoped_ptr<content::ByteStreamReader> stream,
+    DownloadManager* download_manager,
+    bool calculate_hash,
+    const net::BoundNetLog& bound_net_log) {
+
+  return new DownloadFileWithDelay(
+      info, stream.Pass(), new DownloadRequestHandle(info->request_handle),
+      download_manager, calculate_hash,
+      scoped_ptr<content::PowerSaveBlocker>(
+          new content::PowerSaveBlocker(
+              content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
+              "Download in progress")).Pass(),
+      bound_net_log, this);
+}
+
+void DownloadFileWithDelayFactory::AddRenameCallback(base::Closure callback) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  rename_callbacks_.push_back(callback);
+  if (waiting_)
+    MessageLoopForUI::current()->Quit();
+}
+
+void DownloadFileWithDelayFactory::AddDetachCallback(base::Closure callback) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  detach_callbacks_.push_back(callback);
+  if (waiting_)
+    MessageLoopForUI::current()->Quit();
+}
+
+void DownloadFileWithDelayFactory::GetAllRenameCallbacks(
+    std::vector<base::Closure>* results) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  results->swap(rename_callbacks_);
+}
+
+void DownloadFileWithDelayFactory::GetAllDetachCallbacks(
+    std::vector<base::Closure>* results) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  results->swap(detach_callbacks_);
+}
+
+void DownloadFileWithDelayFactory::WaitForSomeCallback() {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+  if (rename_callbacks_.empty() && detach_callbacks_.empty()) {
+    waiting_ = true;
+    RunMessageLoop();
+    waiting_ = false;
+  }
 }
 
 }  // namespace
@@ -114,6 +308,11 @@
     return true;
   }
 
+  DownloadFileManager* GetDownloadFileManager() {
+    ResourceDispatcherHostImpl* rdh(ResourceDispatcherHostImpl::Get());
+    return rdh->download_file_manager();
+  }
+
  private:
   static void EnsureNoPendingDownloadJobsOnIO(bool* result) {
     if (URLRequestSlowDownloadJob::NumberOutstandingRequests())
@@ -127,18 +326,18 @@
 };
 
 IN_PROC_BROWSER_TEST_F(DownloadContentTest, DownloadCancelled) {
-  // TODO(rdsmith): Fragile code warning!  The code below relies on the
-  // DownloadTestObserverInProgress only finishing when the new download
-  // has reached the state of being entered into the history and being
-  // user-visible (that's what's required for the Remove to be valid and
-  // for the download shelf to be visible).  By the pure semantics of
-  // DownloadTestObserverInProgress, that's not guaranteed; DownloadItems
-  // are created in the IN_PROGRESS state and made known to the DownloadManager
-  // immediately, so any ModelChanged event on the DownloadManager after
-  // navigation would allow the observer to return.  However, the only
-  // ModelChanged() event the code will currently fire is in
-  // OnCreateDownloadEntryComplete, at which point the download item will
-  // be in the state we need.
+  // TODO(rdsmith): Fragile code warning!  The code below relies on
+  // the DownloadTestObserverInProgress only finishing when the new
+  // download has reached the state of being entered into the history
+  // and being user-visible (that's what's required for the Remove to
+  // be valid).  By the pure semantics of
+  // DownloadTestObserverInProgress, that's not guaranteed;
+  // DownloadItems are created in the IN_PROGRESS state and made known
+  // to the DownloadManager immediately, so any ModelChanged event on
+  // the DownloadManager after navigation would allow the observer to
+  // return.  However, the only ModelChanged() event the code will
+  // currently fire is in OnCreateDownloadEntryComplete, at which
+  // point the download item will be in the state we need.
   // The right way to fix this is to create finer grained states on the
   // DownloadItem, and wait for the state that indicates the item has been
   // entered in the history and made visible in the UI.
@@ -220,4 +419,110 @@
       file2, GetTestFilePath("download", "download-test.lib")));
 }
 
+// Try to cancel just before we release the download file, by delaying final
+// rename callback.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelAtFinalRename) {
+  // Setup new factory.
+  DownloadFileWithDelayFactory* file_factory =
+      new DownloadFileWithDelayFactory();
+  GetDownloadFileManager()->SetFileFactoryForTesting(
+      scoped_ptr<content::DownloadFileFactory>(file_factory).Pass());
+
+  // Create a download
+  FilePath file(FILE_PATH_LITERAL("download-test.lib"));
+  NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file));
+
+  // Wait until the first (intermediate file) rename and execute the callback.
+  file_factory->WaitForSomeCallback();
+  std::vector<base::Closure> callbacks;
+  file_factory->GetAllDetachCallbacks(&callbacks);
+  ASSERT_TRUE(callbacks.empty());
+  file_factory->GetAllRenameCallbacks(&callbacks);
+  ASSERT_EQ(1u, callbacks.size());
+  callbacks[0].Run();
+  callbacks.clear();
+
+  // Wait until the second (final) rename callback is posted.
+  file_factory->WaitForSomeCallback();
+  file_factory->GetAllDetachCallbacks(&callbacks);
+  ASSERT_TRUE(callbacks.empty());
+  file_factory->GetAllRenameCallbacks(&callbacks);
+  ASSERT_EQ(1u, callbacks.size());
+
+  // Cancel it.
+  std::vector<DownloadItem*> items;
+  DownloadManagerForShell(shell())->GetAllDownloads(&items);
+  ASSERT_EQ(1u, items.size());
+  items[0]->Cancel(true);
+  RunAllPendingInMessageLoop();
+
+  // Check state.
+  EXPECT_EQ(DownloadItem::CANCELLED, items[0]->GetState());
+
+  // Run final rename callback.
+  callbacks[0].Run();
+  callbacks.clear();
+
+  // Check state.
+  EXPECT_EQ(DownloadItem::CANCELLED, items[0]->GetState());
+}
+
+// Try to cancel just after we release the download file, by delaying
+// release.
+IN_PROC_BROWSER_TEST_F(DownloadContentTest, CancelAtRelease) {
+  // Setup new factory.
+  // Setup new factory.
+  DownloadFileWithDelayFactory* file_factory =
+      new DownloadFileWithDelayFactory();
+  GetDownloadFileManager()->SetFileFactoryForTesting(
+      scoped_ptr<content::DownloadFileFactory>(file_factory).Pass());
+
+  // Create a download
+  FilePath file(FILE_PATH_LITERAL("download-test.lib"));
+  NavigateToURL(shell(), URLRequestMockHTTPJob::GetMockUrl(file));
+
+  // Wait until the first (intermediate file) rename and execute the callback.
+  file_factory->WaitForSomeCallback();
+  std::vector<base::Closure> callbacks;
+  file_factory->GetAllDetachCallbacks(&callbacks);
+  ASSERT_TRUE(callbacks.empty());
+  file_factory->GetAllRenameCallbacks(&callbacks);
+  ASSERT_EQ(1u, callbacks.size());
+  callbacks[0].Run();
+  callbacks.clear();
+
+  // Wait until the second (final) rename callback is posted.
+  file_factory->WaitForSomeCallback();
+  file_factory->GetAllDetachCallbacks(&callbacks);
+  ASSERT_TRUE(callbacks.empty());
+  file_factory->GetAllRenameCallbacks(&callbacks);
+  ASSERT_EQ(1u, callbacks.size());
+
+  // Call it.
+  callbacks[0].Run();
+  callbacks.clear();
+
+  // Confirm download isn't complete yet.
+  std::vector<DownloadItem*> items;
+  DownloadManagerForShell(shell())->GetAllDownloads(&items);
+  EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());
+
+  // Cancel the download; confirm cancel fails anyway.
+  ASSERT_EQ(1u, items.size());
+  items[0]->Cancel(true);
+  EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());
+  RunAllPendingInMessageLoop();
+  EXPECT_EQ(DownloadItem::IN_PROGRESS, items[0]->GetState());
+
+  // Confirm detach callback and run it.
+  file_factory->WaitForSomeCallback();
+  file_factory->GetAllRenameCallbacks(&callbacks);
+  ASSERT_TRUE(callbacks.empty());
+  file_factory->GetAllDetachCallbacks(&callbacks);
+  ASSERT_EQ(1u, callbacks.size());
+  callbacks[0].Run();
+  callbacks.clear();
+  EXPECT_EQ(DownloadItem::COMPLETE, items[0]->GetState());
+}
+
 }  // namespace content
diff --git a/content/browser/download/download_file.h b/content/browser/download/download_file.h
index e707a26c..22d646e 100644
--- a/content/browser/download/download_file.h
+++ b/content/browser/download/download_file.h
@@ -28,15 +28,15 @@
   // DOWNLOAD_INTERRUPT_REASON_NONE and |path| the path the rename
   // was done to.  On a failed rename, |reason| will contain the
   // error.
-  typedef base::Callback<void(content::DownloadInterruptReason reason,
+  typedef base::Callback<void(DownloadInterruptReason reason,
                               const FilePath& path)> RenameCompletionCallback;
 
   virtual ~DownloadFile() {}
 
-  // If calculate_hash is true, sha256 hash will be calculated.
   // Returns DOWNLOAD_INTERRUPT_REASON_NONE on success, or a network
-  // error code on failure.
-  virtual DownloadInterruptReason Initialize() = 0;
+  // error code on failure.  Upon completion, |callback| will be
+  // called on the UI thread as per the comment above.
+  virtual content::DownloadInterruptReason Initialize() = 0;
 
   // Rename the download file to |full_path|.  If that file exists and
   // |overwrite_existing_file| is false, |full_path| will be uniquified by
@@ -48,7 +48,8 @@
                       const RenameCompletionCallback& callback) = 0;
 
   // Detach the file so it is not deleted on destruction.
-  virtual void Detach() = 0;
+  // |callback| will be called on the UI thread after detach.
+  virtual void Detach(base::Closure callback) = 0;
 
   // Abort the download and automatically close the file.
   virtual void Cancel() = 0;
diff --git a/content/browser/download/download_file_factory.cc b/content/browser/download/download_file_factory.cc
new file mode 100644
index 0000000..819137f
--- /dev/null
+++ b/content/browser/download/download_file_factory.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/download/download_file_factory.h"
+
+#include "content/browser/download/download_file_impl.h"
+#include "content/browser/power_save_blocker.h"
+
+namespace content {
+
+DownloadFileFactory::~DownloadFileFactory() {}
+
+DownloadFile* DownloadFileFactory::CreateFile(
+    DownloadCreateInfo* info,
+    scoped_ptr<content::ByteStreamReader> stream,
+    DownloadManager* download_manager,
+    bool calculate_hash,
+    const net::BoundNetLog& bound_net_log) {
+  return new DownloadFileImpl(
+      info, stream.Pass(), new DownloadRequestHandle(info->request_handle),
+      download_manager, calculate_hash,
+      scoped_ptr<content::PowerSaveBlocker>(
+          new content::PowerSaveBlocker(
+              content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
+              "Download in progress")).Pass(),
+      bound_net_log);
+}
+
+}  // namespace content
diff --git a/content/browser/download/download_file_factory.h b/content/browser/download/download_file_factory.h
new file mode 100644
index 0000000..f1f8478
--- /dev/null
+++ b/content/browser/download/download_file_factory.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+#ifndef CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_FACTORY_H_
+#define CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_FACTORY_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "content/common/content_export.h"
+#include "content/browser/download/download_create_info.h"
+#include "googleurl/src/gurl.h"
+
+namespace net {
+class BoundNetLog;
+}
+
+namespace content {
+
+struct DownloadSaveInfo;
+
+class ByteStreamReader;
+class DownloadDestinationObserver;
+class DownloadFile;
+class DownloadManager;
+
+class CONTENT_EXPORT DownloadFileFactory {
+ public:
+  virtual ~DownloadFileFactory();
+
+  virtual content::DownloadFile* CreateFile(
+      DownloadCreateInfo* info,
+      scoped_ptr<content::ByteStreamReader> stream,
+      DownloadManager* download_manager,
+      bool calculate_hash,
+      const net::BoundNetLog& bound_net_log);
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_DOWNLOAD_DOWNLOAD_FILE_FACTORY_H_
diff --git a/content/browser/download/download_file_impl.cc b/content/browser/download/download_file_impl.cc
index 6941216..7599900 100644
--- a/content/browser/download/download_file_impl.cc
+++ b/content/browser/download/download_file_impl.cc
@@ -60,17 +60,20 @@
 }
 
 content::DownloadInterruptReason DownloadFileImpl::Initialize() {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
   update_timer_.reset(new base::RepeatingTimer<DownloadFileImpl>());
-  net::Error result = file_.Initialize(default_download_directory_);
-  if (result != net::OK) {
+  net::Error net_result = file_.Initialize(default_download_directory_);
+  if (net_result != net::OK) {
     return content::ConvertNetErrorToInterruptReason(
-        result, content::DOWNLOAD_INTERRUPT_FROM_DISK);
+        net_result, content::DOWNLOAD_INTERRUPT_FROM_DISK);
   }
 
   stream_reader_->RegisterCallback(
       base::Bind(&DownloadFileImpl::StreamActive, weak_factory_.GetWeakPtr()));
 
   download_start_ = base::TimeTicks::Now();
+
   // Initial pull from the straw.
   StreamActive();
 
@@ -126,8 +129,17 @@
       base::Bind(callback, reason, new_path));
 }
 
-void DownloadFileImpl::Detach() {
+void DownloadFileImpl::Detach(base::Closure callback) {
+  // Doing the annotation here leaves a small window during
+  // which the file has the final name but hasn't been marked with the
+  // Mark Of The Web.  However, it allows anti-virus scanners on Windows
+  // to actually see the data (http://crbug.com/127999), and the Window
+  // is pretty small (round trip to the UI thread).
+  AnnotateWithSourceInformation();
+
   file_.Detach();
+
+  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback);
 }
 
 void DownloadFileImpl::Cancel() {
diff --git a/content/browser/download/download_file_impl.h b/content/browser/download/download_file_impl.h
index 0392d99..4364b09 100644
--- a/content/browser/download/download_file_impl.h
+++ b/content/browser/download/download_file_impl.h
@@ -43,7 +43,7 @@
   virtual void Rename(const FilePath& full_path,
                       bool overwrite_existing_file,
                       const RenameCompletionCallback& callback) OVERRIDE;
-  virtual void Detach() OVERRIDE;
+  virtual void Detach(base::Closure callback) OVERRIDE;
   virtual void Cancel() OVERRIDE;
   virtual void AnnotateWithSourceInformation() OVERRIDE;
   virtual FilePath FullPath() const OVERRIDE;
diff --git a/content/browser/download/download_file_manager.cc b/content/browser/download/download_file_manager.cc
index 7bebd92..7577208 100644
--- a/content/browser/download/download_file_manager.cc
+++ b/content/browser/download/download_file_manager.cc
@@ -31,43 +31,10 @@
 using content::DownloadId;
 using content::DownloadManager;
 
-namespace {
-
-class DownloadFileFactoryImpl
-    : public DownloadFileManager::DownloadFileFactory {
- public:
-  DownloadFileFactoryImpl() {}
-
-  virtual content::DownloadFile* CreateFile(
-      DownloadCreateInfo* info,
-      scoped_ptr<content::ByteStreamReader> stream,
-      DownloadManager* download_manager,
-      bool calculate_hash,
-      const net::BoundNetLog& bound_net_log) OVERRIDE;
-};
-
-DownloadFile* DownloadFileFactoryImpl::CreateFile(
-    DownloadCreateInfo* info,
-    scoped_ptr<content::ByteStreamReader> stream,
-    DownloadManager* download_manager,
-    bool calculate_hash,
-    const net::BoundNetLog& bound_net_log) {
-  return new DownloadFileImpl(
-      info, stream.Pass(), new DownloadRequestHandle(info->request_handle),
-      download_manager, calculate_hash,
-      scoped_ptr<content::PowerSaveBlocker>(
-          new content::PowerSaveBlocker(
-              content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
-              "Download in progress")).Pass(),
-      bound_net_log);
-}
-
-}  // namespace
-
-DownloadFileManager::DownloadFileManager(DownloadFileFactory* factory)
+DownloadFileManager::DownloadFileManager(content::DownloadFileFactory* factory)
     : download_file_factory_(factory) {
   if (download_file_factory_ == NULL)
-    download_file_factory_.reset(new DownloadFileFactoryImpl);
+    download_file_factory_.reset(new content::DownloadFileFactory);
 }
 
 DownloadFileManager::~DownloadFileManager() {
@@ -147,20 +114,9 @@
            << " id = " << global_id
            << " download_file = " << download_file->DebugString();
 
-  // Done here on Windows so that anti-virus scanners invoked by
-  // the attachment service actually see the data; see
-  // http://crbug.com/127999.
-  // Done here for mac because we only want to do this once; see
-  // http://crbug.com/13120 for details.
-  // Other platforms don't currently do source annotation.
-  download_file->AnnotateWithSourceInformation();
-
-  download_file->Detach();
+  download_file->Detach(callback);
 
   EraseDownload(global_id);
-
-  // Notify our caller we've let it go.
-  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback);
 }
 
 void DownloadFileManager::OnDownloadManagerShutdown(DownloadManager* manager) {
diff --git a/content/browser/download/download_file_manager.h b/content/browser/download/download_file_manager.h
index 919f1c4..c86ca2a 100644
--- a/content/browser/download/download_file_manager.h
+++ b/content/browser/download/download_file_manager.h
@@ -50,6 +50,7 @@
 #include "base/memory/scoped_ptr.h"
 #include "base/timer.h"
 #include "content/browser/download/download_file.h"
+#include "content/browser/download/download_file_factory.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/download_id.h"
 #include "content/public/browser/download_interrupt_reasons.h"
@@ -85,22 +86,10 @@
   typedef content::DownloadFile::RenameCompletionCallback
       RenameCompletionCallback;
 
-  class DownloadFileFactory {
-   public:
-    virtual ~DownloadFileFactory() {}
-
-    virtual content::DownloadFile* CreateFile(
-        DownloadCreateInfo* info,
-        scoped_ptr<content::ByteStreamReader> stream,
-        content::DownloadManager* download_manager,
-        bool calculate_hash,
-        const net::BoundNetLog& bound_net_log) = 0;
-  };
-
   // Takes ownership of the factory.
   // Passing in a NULL for |factory| will cause a default
   // |DownloadFileFactory| to be used.
-  explicit DownloadFileManager(DownloadFileFactory* factory);
+  explicit DownloadFileManager(content::DownloadFileFactory* factory);
 
   // Create a download file and record it in the download file manager.
   virtual void CreateDownloadFile(
@@ -139,11 +128,12 @@
   // Primarily for testing.
   virtual int NumberOfActiveDownloads() const;
 
-  void SetFileFactoryForTesting(scoped_ptr<DownloadFileFactory> file_factory) {
+  void SetFileFactoryForTesting(
+      scoped_ptr<content::DownloadFileFactory> file_factory) {
     download_file_factory_.reset(file_factory.release());
   }
 
-  DownloadFileFactory* GetFileFactoryForTesting() const {
+  content::DownloadFileFactory* GetFileFactoryForTesting() const {
     return download_file_factory_.get();  // Explicitly NOT a scoped_ptr.
   }
 
@@ -172,7 +162,7 @@
   // A map of all in progress downloads.  It owns the download files.
   DownloadFileMap downloads_;
 
-  scoped_ptr<DownloadFileFactory> download_file_factory_;
+  scoped_ptr<content::DownloadFileFactory> download_file_factory_;
 
   DISALLOW_COPY_AND_ASSIGN(DownloadFileManager);
 };
diff --git a/content/browser/download/download_file_manager_unittest.cc b/content/browser/download/download_file_manager_unittest.cc
index 52ce433..b5e337b 100644
--- a/content/browser/download/download_file_manager_unittest.cc
+++ b/content/browser/download/download_file_manager_unittest.cc
@@ -13,6 +13,7 @@
 #include "content/browser/download/byte_stream.h"
 #include "content/browser/download/download_create_info.h"
 #include "content/browser/download/download_interrupt_reasons_impl.h"
+#include "content/browser/download/download_file_factory.h"
 #include "content/browser/download/download_request_handle.h"
 #include "content/browser/download/mock_download_file.h"
 #include "content/public/browser/download_id.h"
@@ -48,8 +49,7 @@
   ~TestDownloadManager() {}
 };
 
-class MockDownloadFileFactory :
-    public DownloadFileManager::DownloadFileFactory {
+class MockDownloadFileFactory : public content::DownloadFileFactory {
 
  public:
   MockDownloadFileFactory() {}
@@ -184,8 +184,10 @@
 
   // Create a download item on the DFM.
   // |info| is the information needed to create a new download file.
-  // |id| is the download ID of the new download file.
   void CreateDownloadFile(scoped_ptr<DownloadCreateInfo> info) {
+    // Anything that isn't DOWNLOAD_INTERRUPT_REASON_NONE.
+    last_reason_ = content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
+
     // Mostly null out args; they'll be passed to MockDownloadFileFactory
     // to be ignored anyway.
     download_file_manager_->CreateDownloadFile(
@@ -195,8 +197,6 @@
                    // The test jig will outlive all download files.
                    base::Unretained(this)));
 
-    // Anything that isn't DOWNLOAD_INTERRUPT_REASON_NONE.
-    last_reason_ = content::DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED;
     ProcessAllPendingMessages();
     EXPECT_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, last_reason_);
   }
@@ -237,9 +237,7 @@
     MockDownloadFile* file = download_file_factory_->GetExistingFile(id);
     ASSERT_TRUE(file != NULL);
 
-    EXPECT_CALL(*file, AnnotateWithSourceInformation())
-        .WillOnce(Return());
-    EXPECT_CALL(*file, Detach())
+    EXPECT_CALL(*file, Detach(_))
         .WillOnce(Return());
     int num_downloads = download_file_manager_->NumberOfActiveDownloads();
     EXPECT_LT(0, num_downloads);
diff --git a/content/browser/download/download_item_impl.cc b/content/browser/download/download_item_impl.cc
index ea39f32a2..78d3919 100644
--- a/content/browser/download/download_item_impl.cc
+++ b/content/browser/download/download_item_impl.cc
@@ -80,22 +80,6 @@
   };
 }
 
-const char* DebugDownloadStateString(DownloadItem::DownloadState state) {
-  switch (state) {
-    case DownloadItem::IN_PROGRESS:
-      return "IN_PROGRESS";
-    case DownloadItem::COMPLETE:
-      return "COMPLETE";
-    case DownloadItem::CANCELLED:
-      return "CANCELLED";
-    case DownloadItem::INTERRUPTED:
-      return "INTERRUPTED";
-    default:
-      NOTREACHED() << "Unknown download state " << state;
-      return "unknown";
-  };
-}
-
 // Classes to null out request handle calls (for SavePage DownloadItems, which
 // may have, e.g., Cancel() called on them without it doing anything)
 // and to DCHECK on them (for history DownloadItems, which should never have
@@ -158,7 +142,7 @@
       bytes_per_sec_(0),
       last_reason_(content::DOWNLOAD_INTERRUPT_REASON_NONE),
       start_tick_(base::TimeTicks()),
-      state_(info.state),
+      state_(ExternalToInternalState(info.state)),
       danger_type_(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS),
       start_time_(info.start_time),
       end_time_(info.end_time),
@@ -178,9 +162,9 @@
       bound_net_log_(bound_net_log),
       ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
   delegate_->Attach();
-  if (IsInProgress())
-    state_ = CANCELLED;
-  if (IsComplete())
+  if (state_ == IN_PROGRESS_INTERNAL)
+    state_ = CANCELLED_INTERNAL;
+  if (state_ == COMPLETE_INTERNAL)
     all_data_saved_ = true;
   Init(false /* not actively downloading */,
        download_net_logs::SRC_HISTORY_IMPORT);
@@ -213,7 +197,7 @@
       bytes_per_sec_(0),
       last_reason_(content::DOWNLOAD_INTERRUPT_REASON_NONE),
       start_tick_(base::TimeTicks::Now()),
-      state_(IN_PROGRESS),
+      state_(IN_PROGRESS_INTERNAL),
       danger_type_(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS),
       start_time_(info.start_time),
       db_handle_(DownloadItem::kUninitializedHandle),
@@ -268,7 +252,7 @@
       bytes_per_sec_(0),
       last_reason_(content::DOWNLOAD_INTERRUPT_REASON_NONE),
       start_tick_(base::TimeTicks::Now()),
-      state_(IN_PROGRESS),
+      state_(IN_PROGRESS_INTERNAL),
       danger_type_(content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS),
       start_time_(base::Time::Now()),
       db_handle_(DownloadItem::kUninitializedHandle),
@@ -339,7 +323,7 @@
 void DownloadItemImpl::TogglePause() {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
 
-  DCHECK(IsInProgress());
+  DCHECK(state_ == IN_PROGRESS_INTERNAL || state_ == COMPLETING_INTERNAL);
   if (is_paused_)
     request_handle_->ResumeRequest();
   else
@@ -356,7 +340,7 @@
       content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN;
 
   VLOG(20) << __FUNCTION__ << "() download = " << DebugString(true);
-  if (!IsPartialDownload()) {
+  if (state_ != IN_PROGRESS_INTERNAL) {
     // Small downloads might be complete before this method has
     // a chance to run.
     return;
@@ -364,7 +348,7 @@
 
   download_stats::RecordDownloadCount(download_stats::CANCELLED_COUNT);
 
-  TransitionTo(CANCELLED);
+  TransitionTo(CANCELLED_INTERNAL);
   if (user_cancel)
     delegate_->DownloadStopped(this);
 }
@@ -410,7 +394,7 @@
 void DownloadItemImpl::OpenDownload() {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
 
-  if (IsPartialDownload()) {
+  if (state_ == IN_PROGRESS_INTERNAL) {
     // We don't honor the open_when_complete_ flag for temporary
     // downloads. Don't set it because it shows up in the UI.
     if (!IsTemporary())
@@ -418,7 +402,7 @@
     return;
   }
 
-  if (!IsComplete() || file_externally_removed_)
+  if (state_ != COMPLETE_INTERNAL || file_externally_removed_)
     return;
 
   // Ideally, we want to detect errors in opening and report them, but we
@@ -458,7 +442,7 @@
 }
 
 DownloadItem::DownloadState DownloadItemImpl::GetState() const {
-  return state_;
+  return InternalToExternalState(state_);
 }
 
 content::DownloadInterruptReason DownloadItemImpl::GetLastReason() const {
@@ -480,24 +464,24 @@
 // TODO(ahendrickson) -- Move |INTERRUPTED| from |IsCancelled()| to
 // |IsPartialDownload()|, when resuming interrupted downloads is implemented.
 bool DownloadItemImpl::IsPartialDownload() const {
-  return (state_ == IN_PROGRESS);
+  return InternalToExternalState(state_) == IN_PROGRESS;
 }
 
 bool DownloadItemImpl::IsInProgress() const {
-  return (state_ == IN_PROGRESS);
+  return InternalToExternalState(state_) == IN_PROGRESS;
 }
 
 bool DownloadItemImpl::IsCancelled() const {
-  return (state_ == CANCELLED) ||
-         (state_ == INTERRUPTED);
+  DownloadState external_state = InternalToExternalState(state_);
+  return  external_state == CANCELLED || external_state == INTERRUPTED;
 }
 
 bool DownloadItemImpl::IsInterrupted() const {
-  return (state_ == INTERRUPTED);
+  return InternalToExternalState(state_) == INTERRUPTED;
 }
 
 bool DownloadItemImpl::IsComplete() const {
-  return (state_ == COMPLETE);
+  return InternalToExternalState(state_) == COMPLETE;
 }
 
 const GURL& DownloadItemImpl::GetURL() const {
@@ -670,7 +654,7 @@
 }
 
 bool DownloadItemImpl::CanShowInFolder() {
-  return !IsCancelled() && !file_externally_removed_;
+  return state_ != CANCELLED_INTERNAL && !file_externally_removed_;
 }
 
 bool DownloadItemImpl::CanOpenDownload() {
@@ -750,7 +734,7 @@
       base::StringPrintf("{ id = %d"
                          " state = %s",
                          download_id_.local(),
-                         DebugDownloadStateString(GetState()));
+                         DebugDownloadStateString(state_));
 
   // Construct a string of the URL chain.
   std::string url_list("<none>");
@@ -834,11 +818,11 @@
   // interrupts to race with cancels.
 
   // Whatever happens, the first one to hit the UI thread wins.
-  if (!IsInProgress())
+  if (state_ != IN_PROGRESS_INTERNAL)
     return;
 
   last_reason_ = reason;
-  TransitionTo(INTERRUPTED);
+  TransitionTo(INTERRUPTED_INTERNAL);
   download_stats::RecordDownloadInterrupted(
       reason, received_bytes_, total_bytes_);
   delegate_->DownloadStopped(this);
@@ -856,7 +840,7 @@
                                       const std::string& hash_state) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
 
-  if (!IsInProgress()) {
+  if (state_ != IN_PROGRESS_INTERNAL) {
     // Ignore if we're no longer in-progress.  This can happen if we race a
     // Cancel on the UI thread with an update on the FILE thread.
     //
@@ -901,7 +885,7 @@
 
   DCHECK(all_data_saved_);
   end_time_ = base::Time::Now();
-  TransitionTo(COMPLETE);
+  TransitionTo(COMPLETE_INTERNAL);
 }
 
 void DownloadItemImpl::SetIsPersisted() {
@@ -986,6 +970,16 @@
   // space/permission/availability constraints.
   DCHECK(intermediate_path.DirName() == target_path.DirName());
 
+  if (state_ != IN_PROGRESS_INTERNAL) {
+    // If we've been cancelled or interrupted while the target was being
+    // determined, continue the cascade with a null name.
+    // The error doesn't matter as the cause of download stoppage
+    // will already have been recorded and will be retained, but we use
+    // whatever was recorded for consistency.
+    OnDownloadRenamedToIntermediateName(last_reason_, FilePath());
+    return;
+  }
+
   // Rename to intermediate name.
   // TODO(asanka): Skip this rename if AllDataSaved() is true. This avoids a
   //               spurious rename when we can just rename to the final
@@ -1025,6 +1019,9 @@
 void DownloadItemImpl::OnDownloadCompleting() {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
 
+  if (state_ != IN_PROGRESS_INTERNAL)
+    return;
+
   VLOG(20) << __FUNCTION__ << "()"
            << " needs rename = " << NeedsRename()
            << " " << DebugString(true);
@@ -1048,6 +1045,7 @@
                    delegate_->GetDownloadFileManager(), GetGlobalId(),
                    base::Bind(&DownloadItemImpl::OnDownloadFileReleased,
                               weak_ptr_factory_.GetWeakPtr())));
+    TransitionTo(COMPLETING_INTERNAL);
   }
 }
 
@@ -1056,6 +1054,12 @@
     const FilePath& full_path) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
 
+  // If a cancel or interrupt hit, we'll cancel the DownloadFile, which
+  // will result in deleting the file on the file thread.  So we don't
+  // care about the name having been changed.
+  if (state_ != IN_PROGRESS_INTERNAL)
+    return;
+
   VLOG(20) << __FUNCTION__ << "()"
            << " full_path = \"" << full_path.value() << "\""
            << " needed rename = " << NeedsRename()
@@ -1080,6 +1084,7 @@
                  delegate_->GetDownloadFileManager(), GetGlobalId(),
                  base::Bind(&DownloadItemImpl::OnDownloadFileReleased,
                             weak_ptr_factory_.GetWeakPtr())));
+  TransitionTo(COMPLETING_INTERNAL);
 }
 
 void DownloadItemImpl::OnDownloadFileReleased() {
@@ -1101,7 +1106,7 @@
 
   DCHECK(all_data_saved_);
   end_time_ = base::Time::Now();
-  TransitionTo(COMPLETE);
+  TransitionTo(COMPLETE_INTERNAL);
   delegate_->DownloadCompleted(this);
   download_stats::RecordDownloadCompleted(start_tick_, received_bytes_);
 
@@ -1143,27 +1148,33 @@
     total_bytes_ = 0;
 }
 
-void DownloadItemImpl::TransitionTo(DownloadState new_state) {
+void DownloadItemImpl::TransitionTo(DownloadInternalState new_state) {
   if (state_ == new_state)
     return;
 
-  DownloadState old_state = state_;
+  DownloadInternalState old_state = state_;
   state_ = new_state;
 
   switch (state_) {
-    case COMPLETE:
+    case COMPLETING_INTERNAL:
+      bound_net_log_.AddEvent(
+          net::NetLog::TYPE_DOWNLOAD_ITEM_COMPLETING,
+          base::Bind(&download_net_logs::ItemCompletingCallback,
+                     received_bytes_, &hash_));
+      break;
+    case COMPLETE_INTERNAL:
       bound_net_log_.AddEvent(
           net::NetLog::TYPE_DOWNLOAD_ITEM_FINISHED,
           base::Bind(&download_net_logs::ItemFinishedCallback,
-                     received_bytes_, &hash_));
+                     auto_opened_));
       break;
-    case INTERRUPTED:
+    case INTERRUPTED_INTERNAL:
       bound_net_log_.AddEvent(
           net::NetLog::TYPE_DOWNLOAD_ITEM_INTERRUPTED,
           base::Bind(&download_net_logs::ItemInterruptedCallback,
                      last_reason_, received_bytes_, &hash_state_));
       break;
-    case CANCELLED:
+    case CANCELLED_INTERNAL:
       bound_net_log_.AddEvent(
           net::NetLog::TYPE_DOWNLOAD_ITEM_CANCELED,
           base::Bind(&download_net_logs::ItemCanceledCallback,
@@ -1175,10 +1186,14 @@
 
   VLOG(20) << " " << __FUNCTION__ << "()" << " this = " << DebugString(true);
 
-  UpdateObservers();
+  // Only update observers on user visible state changes.
+  if (InternalToExternalState(old_state) != InternalToExternalState(state_))
+    UpdateObservers();
 
-  bool is_done = (state_ != IN_PROGRESS);
-  bool was_done = (old_state != IN_PROGRESS);
+  bool is_done = (state_ != IN_PROGRESS_INTERNAL &&
+                  state_ != COMPLETING_INTERNAL);
+  bool was_done = (old_state != IN_PROGRESS_INTERNAL &&
+                   old_state != COMPLETING_INTERNAL);
   if (is_done && !was_done)
     bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_ITEM_ACTIVE);
 }
@@ -1212,6 +1227,60 @@
                  &current_path_, &new_path));
 }
 
+// static
+DownloadItem::DownloadState DownloadItemImpl::InternalToExternalState(
+    DownloadInternalState internal_state) {
+  switch (internal_state) {
+    case IN_PROGRESS_INTERNAL:
+      return IN_PROGRESS;
+    case COMPLETING_INTERNAL:
+      return IN_PROGRESS;
+    case COMPLETE_INTERNAL:
+      return COMPLETE;
+    case CANCELLED_INTERNAL:
+      return CANCELLED;
+    case INTERRUPTED_INTERNAL:
+      return INTERRUPTED;
+    default:
+      NOTREACHED();
+  }
+  return MAX_DOWNLOAD_STATE;
+}
 
+// static
+DownloadItemImpl::DownloadInternalState
+DownloadItemImpl::ExternalToInternalState(
+     DownloadState external_state) {
+   switch (external_state) {
+     case IN_PROGRESS:
+       return IN_PROGRESS_INTERNAL;
+     case COMPLETE:
+       return COMPLETE_INTERNAL;
+     case CANCELLED:
+       return CANCELLED_INTERNAL;
+     case INTERRUPTED:
+       return INTERRUPTED_INTERNAL;
+     default:
+       NOTREACHED();
+   }
+   return MAX_DOWNLOAD_INTERNAL_STATE;
+ }
 
-
+const char* DownloadItemImpl::DebugDownloadStateString(
+    DownloadInternalState state) {
+  switch (state) {
+    case IN_PROGRESS_INTERNAL:
+      return "IN_PROGRESS";
+    case COMPLETING_INTERNAL:
+      return "COMPLETING";
+    case COMPLETE_INTERNAL:
+      return "COMPLETE";
+    case CANCELLED_INTERNAL:
+      return "CANCELLED";
+    case INTERRUPTED_INTERNAL:
+      return "INTERRUPTED";
+    default:
+      NOTREACHED() << "Unknown download state " << state;
+      return "unknown";
+  };
+}
diff --git a/content/browser/download/download_item_impl.h b/content/browser/download/download_item_impl.h
index ad8ed4d..93293cb 100644
--- a/content/browser/download/download_item_impl.h
+++ b/content/browser/download/download_item_impl.h
@@ -207,6 +207,34 @@
   virtual void SetDbHandle(int64 handle);
 
  private:
+  // Fine grained states of a download.
+  enum DownloadInternalState {
+    // Unless otherwise specified, state transitions are linear forward
+    // in this list.
+
+    // Includes both before and after file name determination.
+    // TODO(rdsmith): Put in state variable for file name determination.
+    IN_PROGRESS_INTERNAL,
+
+    // Between commit point (dispatch of download file release) and
+    // completed.  Embedder may be opening the file in this state.
+    COMPLETING_INTERNAL,
+
+    // After embedder has had a chance to auto-open.  User may now open
+    // or auto-open based on extension.
+    COMPLETE_INTERNAL,
+
+    // User has cancelled the download.
+    // Only incoming transition IN_PROGRESS->
+    CANCELLED_INTERNAL,
+
+    // An error has interrupted the download.
+    // Only incoming transition IN_PROGRESS->
+    INTERRUPTED_INTERNAL,
+
+    MAX_DOWNLOAD_INTERNAL_STATE,
+  };
+
   // Normal progression of a download ------------------------------------------
 
   // These are listed in approximately chronological order.  There are also
@@ -249,13 +277,22 @@
                         const std::string& final_hash);
 
   // Call to transition state; all state transitions should go through this.
-  void TransitionTo(DownloadState new_state);
+  void TransitionTo(DownloadInternalState new_state);
 
   // Set the |danger_type_| and invoke obserers if necessary.
   void SetDangerType(content::DownloadDangerType danger_type);
 
   void SetFullPath(const FilePath& new_path);
 
+  // Mapping between internal and external states.
+  static DownloadState InternalToExternalState(
+      DownloadInternalState internal_state);
+  static DownloadInternalState ExternalToInternalState(
+      DownloadState external_state);
+
+  // Debugging routines --------------------------------------------------------
+  static const char* DebugDownloadStateString(DownloadInternalState state);
+
   // The handle to the request information.  Used for operations outside the
   // download system.
   scoped_ptr<DownloadRequestHandleInterface> request_handle_;
@@ -353,7 +390,7 @@
   base::TimeTicks start_tick_;
 
   // The current state of this download.
-  DownloadState state_;
+  DownloadInternalState state_;
 
   // Current danger type for the download.
   content::DownloadDangerType danger_type_;
diff --git a/content/browser/download/download_item_impl_unittest.cc b/content/browser/download/download_item_impl_unittest.cc
index d2ce5dd..7a186af7 100644
--- a/content/browser/download/download_item_impl_unittest.cc
+++ b/content/browser/download/download_item_impl_unittest.cc
@@ -67,8 +67,7 @@
   MOCK_CONST_METHOD0(DebugString, std::string());
 };
 
-class MockDownloadFileFactory
-    : public DownloadFileManager::DownloadFileFactory {
+class MockDownloadFileFactory : public content::DownloadFileFactory {
  public:
   content::DownloadFile* CreateFile(
       DownloadCreateInfo* info,
diff --git a/content/browser/download/download_net_log_parameters.cc b/content/browser/download/download_net_log_parameters.cc
index f09cfeb..00a9d5d 100644
--- a/content/browser/download/download_net_log_parameters.cc
+++ b/content/browser/download/download_net_log_parameters.cc
@@ -106,9 +106,9 @@
   return dict;
 }
 
-base::Value* ItemFinishedCallback(int64 bytes_so_far,
-                                  const std::string* final_hash,
-                                  net::NetLog::LogLevel /* log_level */) {
+base::Value* ItemCompletingCallback(int64 bytes_so_far,
+                                    const std::string* final_hash,
+                                    net::NetLog::LogLevel /* log_level */) {
   DictionaryValue* dict = new DictionaryValue();
 
   dict->SetString("bytes_so_far", base::Int64ToString(bytes_so_far));
@@ -118,6 +118,15 @@
   return dict;
 }
 
+base::Value* ItemFinishedCallback(bool auto_opened,
+                                  net::NetLog::LogLevel /* log_level */) {
+  DictionaryValue* dict = new DictionaryValue();
+
+  dict->SetString("auto_opened", auto_opened ? "yes" : "no");
+
+  return dict;
+}
+
 base::Value* ItemCanceledCallback(int64 bytes_so_far,
                                   const std::string* hash_state,
                                   net::NetLog::LogLevel /* log_level */) {
diff --git a/content/browser/download/download_net_log_parameters.h b/content/browser/download/download_net_log_parameters.h
index f321d15..5167772 100644
--- a/content/browser/download/download_net_log_parameters.h
+++ b/content/browser/download/download_net_log_parameters.h
@@ -46,9 +46,13 @@
                                      const std::string* hash_state,
                                      net::NetLog::LogLevel log_level);
 
+// Returns NetLog parameters when a DownloadItem is completing.
+base::Value* ItemCompletingCallback(int64 bytes_so_far,
+                                    const std::string* final_hash,
+                                    net::NetLog::LogLevel log_level);
+
 // Returns NetLog parameters when a DownloadItem is finished.
-base::Value* ItemFinishedCallback(int64 bytes_so_far,
-                                  const std::string* final_hash,
+base::Value* ItemFinishedCallback(bool auto_opened,
                                   net::NetLog::LogLevel log_level);
 
 // Returns NetLog parameters when a DownloadItem is canceled.
diff --git a/content/browser/download/mock_download_file.cc b/content/browser/download/mock_download_file.cc
index d5c7781c9..233d95e0 100644
--- a/content/browser/download/mock_download_file.cc
+++ b/content/browser/download/mock_download_file.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "content/browser/download/mock_download_file.h"
+#include "testing/gmock/include/gmock/gmock.h"
 
 using ::testing::_;
 using ::testing::Return;
diff --git a/content/browser/download/mock_download_file.h b/content/browser/download/mock_download_file.h
index 34bcb54..99aa021 100644
--- a/content/browser/download/mock_download_file.h
+++ b/content/browser/download/mock_download_file.h
@@ -32,7 +32,7 @@
   MOCK_METHOD3(Rename, void(const FilePath& full_path,
                             bool overwrite_existing_file,
                             const RenameCompletionCallback& callback));
-  MOCK_METHOD0(Detach, void());
+  MOCK_METHOD1(Detach, void(base::Closure));
   MOCK_METHOD0(Cancel, void());
   MOCK_METHOD0(Finish, void());
   MOCK_METHOD0(AnnotateWithSourceInformation, void());
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
index 99f029c..1d01015 100644
--- a/content/content_browser.gypi
+++ b/content/content_browser.gypi
@@ -319,6 +319,8 @@
     'browser/download/download_create_info.cc',
     'browser/download/download_create_info.h',
     'browser/download/download_file.h',
+    'browser/download/download_file_factory.cc',
+    'browser/download/download_file_factory.h',
     'browser/download/download_file_impl.cc',
     'browser/download/download_file_impl.h',
     'browser/download/download_file_manager.cc',
diff --git a/content/public/test/test_file_error_injector.cc b/content/public/test/test_file_error_injector.cc
index 26eaf8e..dd53d14 100644
--- a/content/public/test/test_file_error_injector.cc
+++ b/content/public/test/test_file_error_injector.cc
@@ -65,12 +65,15 @@
       content::TestFileErrorInjector::FileOperationCode code,
       content::DownloadInterruptReason original_error);
 
-  // Used in place of original rename callback to intercept with
-  // ShouldReturnError.
-  void RenameErrorCallback(
-    const RenameCompletionCallback& original_callback,
-    content::DownloadInterruptReason original_error,
-    const FilePath& path_result);
+  // Determine whether to overwrite an operation with the given code
+  // with a substitute error; if returns true, |*original_error| is
+  // written with the error to use for overwriting.
+  // NOTE: This routine changes state; specifically, it increases the
+  // operations counts for the specified code.  It should only be called
+  // once per operation.
+  bool OverwriteError(
+    content::TestFileErrorInjector::FileOperationCode code,
+    content::DownloadInterruptReason* output_error);
 
   // Source URL for the file being downloaded.
   GURL source_url_;
@@ -86,6 +89,18 @@
   DestructionCallback destruction_callback_;
 };
 
+static void RenameErrorCallback(
+    const content::DownloadFile::RenameCompletionCallback original_callback,
+    content::DownloadInterruptReason overwrite_error,
+    content::DownloadInterruptReason original_error,
+    const FilePath& path_result) {
+  original_callback.Run(
+      overwrite_error,
+      overwrite_error == content::DOWNLOAD_INTERRUPT_REASON_NONE ?
+      path_result : FilePath());
+}
+
+
 DownloadFileWithErrors::DownloadFileWithErrors(
     const DownloadCreateInfo* info,
     scoped_ptr<content::ByteStreamReader> stream,
@@ -115,8 +130,8 @@
 
 content::DownloadInterruptReason DownloadFileWithErrors::Initialize() {
   return ShouldReturnError(
-          content::TestFileErrorInjector::FILE_OPERATION_INITIALIZE,
-          DownloadFileImpl::Initialize());
+      content::TestFileErrorInjector::FILE_OPERATION_INITIALIZE,
+      DownloadFileImpl::Initialize());
 }
 
 content::DownloadInterruptReason DownloadFileWithErrors::AppendDataToFile(
@@ -130,48 +145,42 @@
     const FilePath& full_path,
     bool overwrite_existing_file,
     const RenameCompletionCallback& callback) {
-  DownloadFileImpl::Rename(
-      full_path, overwrite_existing_file,
-      base::Bind(&DownloadFileWithErrors::RenameErrorCallback,
-                 // Unretained since this'll only be called from
-                 // the DownloadFileImpl slice of the same object.
-                 base::Unretained(this), callback));
+  content::DownloadInterruptReason error_to_return =
+      content::DOWNLOAD_INTERRUPT_REASON_NONE;
+  RenameCompletionCallback callback_to_use = callback;
+
+  // Replace callback if the error needs to be overwritten.
+  if (OverwriteError(
+          content::TestFileErrorInjector::FILE_OPERATION_RENAME,
+          &error_to_return)) {
+    callback_to_use = base::Bind(&RenameErrorCallback, callback,
+                                 error_to_return);
+  }
+
+  DownloadFileImpl::Rename(full_path, overwrite_existing_file, callback_to_use);
+}
+
+bool DownloadFileWithErrors::OverwriteError(
+    content::TestFileErrorInjector::FileOperationCode code,
+    content::DownloadInterruptReason* output_error) {
+  int counter = operation_counter_[code]++;
+
+  if (code != error_info_.code)
+    return false;
+
+  if (counter != error_info_.operation_instance)
+    return false;
+
+  *output_error = error_info_.error;
+  return true;
 }
 
 content::DownloadInterruptReason DownloadFileWithErrors::ShouldReturnError(
     content::TestFileErrorInjector::FileOperationCode code,
     content::DownloadInterruptReason original_error) {
-  int counter = operation_counter_[code];
-  ++operation_counter_[code];
-
-  if (code != error_info_.code)
-    return original_error;
-
-  if (counter != error_info_.operation_instance)
-    return original_error;
-
-  VLOG(1) << " " << __FUNCTION__ << "()"
-          << " url = '" << source_url_.spec() << "'"
-          << " code = " << content::TestFileErrorInjector::DebugString(code)
-          << " (" << code << ")"
-          << " counter = " << counter
-          << " original_error = "
-          << content::InterruptReasonDebugString(original_error)
-          << " (" << original_error << ")"
-          << " new error = "
-          << content::InterruptReasonDebugString(error_info_.error)
-          << " (" << error_info_.error << ")";
-
-  return error_info_.error;
-}
-
-void DownloadFileWithErrors::RenameErrorCallback(
-    const RenameCompletionCallback& original_callback,
-    content::DownloadInterruptReason original_error,
-    const FilePath& path_result) {
-  original_callback.Run(ShouldReturnError(
-      content::TestFileErrorInjector::FILE_OPERATION_RENAME,
-      original_error), path_result);
+  content::DownloadInterruptReason output_error = original_error;
+  OverwriteError(code, &output_error);
+  return output_error;
 }
 
 }  // namespace
@@ -179,8 +188,7 @@
 namespace content {
 
 // A factory for constructing DownloadFiles that inject errors.
-class DownloadFileWithErrorsFactory
-    : public DownloadFileManager::DownloadFileFactory {
+class DownloadFileWithErrorsFactory : public content::DownloadFileFactory {
  public:
 
   DownloadFileWithErrorsFactory(
@@ -298,7 +306,7 @@
   DCHECK(download_file_manager);
 
   // Convert to base class pointer, for GCC.
-  scoped_ptr<DownloadFileManager::DownloadFileFactory> plain_factory(
+  scoped_ptr<content::DownloadFileFactory> plain_factory(
       factory.release());
 
   download_file_manager->SetFileFactoryForTesting(plain_factory.Pass());
@@ -344,11 +352,11 @@
   DownloadFileManager* download_file_manager = GetDownloadFileManager();
   DCHECK(download_file_manager);
 
-  DownloadFileManager::DownloadFileFactory* file_factory =
+  content::DownloadFileFactory* file_factory =
       download_file_manager->GetFileFactoryForTesting();
 
   // Validate that we still have the same factory.
-  DCHECK_EQ(static_cast<DownloadFileManager::DownloadFileFactory*>(factory),
+  DCHECK_EQ(static_cast<content::DownloadFileFactory*>(factory),
             file_factory);
 
   // We want to replace all existing injection errors.
diff --git a/content/shell/shell_download_manager_delegate.cc b/content/shell/shell_download_manager_delegate.cc
index 5dfb492..8290ea2 100644
--- a/content/shell/shell_download_manager_delegate.cc
+++ b/content/shell/shell_download_manager_delegate.cc
@@ -111,7 +111,8 @@
   if (suppress_prompting_) {
     // Testing exit.
     callback.Run(suggested_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-                 DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, suggested_path);
+                 DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
+                 suggested_path.AddExtension(FILE_PATH_LITERAL(".crdownload")));
     return;
   }
 
diff --git a/net/base/net_log_event_type_list.h b/net/base/net_log_event_type_list.h
index 755407a..8fea0d5 100644
--- a/net/base/net_log_event_type_list.h
+++ b/net/base/net_log_event_type_list.h
@@ -1584,11 +1584,17 @@
 //   }
 EVENT_TYPE(DOWNLOAD_ITEM_RESUMED)
 
-// This event is created when a download item is finished.
+// This event is created when a download item is completing.
 //   {
 //     "bytes_so_far": <Number of bytes received>,
 //     "final_hash": <Final hash, as a hex-encoded binary string>,
 //   }
+EVENT_TYPE(DOWNLOAD_ITEM_COMPLETING)
+
+// This event is created when a download item is finished.
+//   {
+//     "auto_opened": <Whether or not the download was auto-opened>
+//   }
 EVENT_TYPE(DOWNLOAD_ITEM_FINISHED)
 
 // This event is created when a download item is canceled.