[downloads] Allow download target determination to indicate an error.

Previously, the download target determination logic was only able to
indicate one of two outcomes:

  1. The embeddeer determined a valid target for the download.
  2. The user canceled the operation.

These two outcomes aren't sufficient to indicate the variety of error
conditions that the embedder might be able to detect. For example, the
embedder may choose to block the download instead of allowing it to
proceed or it may need be able to specify a more apt interrupt reason
than "canceled by user".

This change introduces the ability for the embedder to indicate a
specific download interrupt reason during download target determination.
This change is needed for followup changes that perform additional
checks during the download process.

[email protected]
BUG=334474

Change-Id: I141df838e581cd2358f99bbd95c2601bfeb8e491
Reviewed-on: https://chromium-review.googlesource.com/465526
Reviewed-by: John Abd-El-Malek <[email protected]>
Reviewed-by: David Trainor <[email protected]>
Commit-Queue: Asanka Herath <[email protected]>
Cr-Commit-Position: refs/heads/master@{#461779}
diff --git a/content/browser/download/download_item_impl.cc b/content/browser/download/download_item_impl.cc
index f6c3eaa..5c2286b 100644
--- a/content/browser/download/download_item_impl.cc
+++ b/content/browser/download/download_item_impl.cc
@@ -116,6 +116,14 @@
   download_file->Cancel();
 }
 
+// Most of the cancellation pathways behave the same whether the cancellation
+// was initiated by ther user (CANCELED) or initiated due to browser context
+// shutdown (SHUTDOWN).
+bool IsCancellation(DownloadInterruptReason reason) {
+  return reason == DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN ||
+         reason == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED;
+}
+
 }  // namespace
 
 const uint32_t DownloadItem::kInvalidId = 0;
@@ -1103,8 +1111,8 @@
   // callbacks.
   DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL);
 
-  // There must be no pending destination_error_.
-  DCHECK_EQ(destination_error_, DOWNLOAD_INTERRUPT_REASON_NONE);
+  // There must be no pending deferred_interrupt_reason_.
+  DCHECK_EQ(deferred_interrupt_reason_, DOWNLOAD_INTERRUPT_REASON_NONE);
 
   DVLOG(20) << __func__ << "() so_far=" << bytes_so_far
             << " per_sec=" << bytes_per_sec
@@ -1133,18 +1141,9 @@
   // callbacks.
   DCHECK(state_ == TARGET_PENDING_INTERNAL || state_ == IN_PROGRESS_INTERNAL);
   DVLOG(20) << __func__
-            << "() reason:" << DownloadInterruptReasonToString(reason);
+            << "() reason:" << DownloadInterruptReasonToString(reason)
+            << " this:" << DebugString(true);
 
-  // Postpone recognition of this error until after file name determination
-  // has completed and the intermediate file has been renamed to simplify
-  // resumption conditions.
-  if (state_ == TARGET_PENDING_INTERNAL) {
-    received_bytes_ = bytes_so_far;
-    hash_state_ = std::move(secure_hash);
-    hash_.clear();
-    destination_error_ = reason;
-    return;
-  }
   InterruptWithPartialState(bytes_so_far, std::move(secure_hash), reason);
   UpdateObservers();
 }
@@ -1212,7 +1211,7 @@
   download_file_ = std::move(file);
   job_ = DownloadJobFactory::CreateJob(this, std::move(req_handle),
                                        new_create_info);
-  destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE;
+  deferred_interrupt_reason_ = DOWNLOAD_INTERRUPT_REASON_NONE;
 
   if (state_ == CANCELLED_INTERNAL) {
     // The download was in the process of resuming when it was cancelled. Don't
@@ -1248,24 +1247,14 @@
             ? new_create_info.save_info->hash_state->Clone()
             : nullptr;
 
-    // Interrupted downloads also need a target path.
-    if (target_path_.empty()) {
-      received_bytes_ = offset;
-      hash_state_ = std::move(hash_state);
-      hash_.clear();
-      destination_error_ = new_create_info.result;
-      received_slices_.clear();
-      TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
-      DetermineDownloadTarget();
-      return;
-    }
-
-    // Otherwise, this was a resumption attempt which ended with an
-    // interruption. Continue with current target path.
-    TransitionTo(TARGET_RESOLVED_INTERNAL);
-    InterruptWithPartialState(
-        offset, std::move(hash_state), new_create_info.result);
-    UpdateObservers();
+    current_path_ = new_create_info.save_info->file_path;
+    received_bytes_ = offset;
+    hash_state_ = std::move(hash_state);
+    hash_.clear();
+    deferred_interrupt_reason_ = new_create_info.result;
+    received_slices_.clear();
+    TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
+    DetermineDownloadTarget();
     return;
   }
 
@@ -1306,19 +1295,15 @@
 void DownloadItemImpl::OnDownloadFileInitialized(
     DownloadInterruptReason result) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK_EQ(state_, TARGET_PENDING_INTERNAL);
+  DCHECK(state_ == TARGET_PENDING_INTERNAL ||
+         state_ == INTERRUPTED_TARGET_PENDING_INTERNAL)
+      << "Unexpected state: " << DebugDownloadStateString(state_);
+
   DVLOG(20) << __func__
             << "() result:" << DownloadInterruptReasonToString(result);
   if (result != DOWNLOAD_INTERRUPT_REASON_NONE) {
-    // Whoops. That didn't work. Proceed as an interrupted download, but reset
-    // the partial state. Currently, the partial stub cannot be recovered if the
-    // download file initialization fails.
-    received_bytes_ = 0;
-    hash_state_.reset();
-    hash_.clear();
-    destination_error_ = result;
-    received_slices_.clear();
-    TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
+    ReleaseDownloadFile(true);
+    InterruptAndDiscardPartialState(result);
   }
 
   DetermineDownloadTarget();
@@ -1338,33 +1323,41 @@
     const base::FilePath& target_path,
     TargetDisposition disposition,
     DownloadDangerType danger_type,
-    const base::FilePath& intermediate_path) {
+    const base::FilePath& intermediate_path,
+    DownloadInterruptReason interrupt_reason) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(state_ == TARGET_PENDING_INTERNAL ||
          state_ == INTERRUPTED_TARGET_PENDING_INTERNAL);
+  DVLOG(20) << __func__ << "() target_path:" << target_path.value()
+            << " intermediate_path:" << intermediate_path.value()
+            << " disposition:" << disposition << " danger_type:" << danger_type
+            << " interrupt_reason:"
+            << DownloadInterruptReasonToString(interrupt_reason)
+            << " this:" << DebugString(true);
 
-  // If the |target_path| is empty, then we consider this download to be
-  // canceled.
-  if (target_path.empty()) {
+  if (IsCancellation(interrupt_reason) || target_path.empty()) {
     Cancel(true);
     return;
   }
 
-  DVLOG(20) << __func__ << "() target_path:" << target_path.value()
-            << " disposition:" << disposition << " danger_type:" << danger_type
-            << " this:" << DebugString(true);
-
   target_path_ = target_path;
   target_disposition_ = disposition;
   SetDangerType(danger_type);
 
-  // This was an interrupted download that was looking for a filename. Now that
-  // it has one, transition to interrupted.
-  if (state_ == INTERRUPTED_TARGET_PENDING_INTERNAL) {
-    InterruptWithPartialState(
-        received_bytes_, std::move(hash_state_), destination_error_);
-    destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE;
-    UpdateObservers();
+  // There were no other pending errors, and we just failed to determined the
+  // download target.
+  if (state_ == TARGET_PENDING_INTERNAL &&
+      interrupt_reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
+    deferred_interrupt_reason_ = interrupt_reason;
+    TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
+  }
+
+  // This was an interrupted download that was looking for a filename. Resolve
+  // early without performing the intermediate rename. If there is a
+  // DownloadFile, then that should be renamed to the intermediate name before
+  // we can interrupt the download. Otherwise we may lose intermediate state.
+  if (state_ == INTERRUPTED_TARGET_PENDING_INTERNAL && !download_file_) {
+    OnTargetResolved();
     return;
   }
 
@@ -1392,7 +1385,6 @@
   //               filename. Unnecessary renames may cause bugs like
   //               http://crbug.com/74187.
   DCHECK(!is_save_package_download_);
-  DCHECK(download_file_.get());
   DownloadFile::RenameCompletionCallback callback =
       base::Bind(&DownloadItemImpl::OnDownloadRenamedToIntermediateName,
                  weak_ptr_factory_.GetWeakPtr());
@@ -1408,32 +1400,53 @@
     DownloadInterruptReason reason,
     const base::FilePath& full_path) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK_EQ(state_, TARGET_PENDING_INTERNAL);
+  DCHECK(state_ == TARGET_PENDING_INTERNAL ||
+         state_ == INTERRUPTED_TARGET_PENDING_INTERNAL);
+  DCHECK(download_file_);
   DVLOG(20) << __func__ << "() download=" << DebugString(true);
 
-  TransitionTo(TARGET_RESOLVED_INTERNAL);
-
-  // If the intermediate rename fails while there's also a destination_error_,
-  // then the former is considered the critical error since it requires
-  // discarding the partial state.
-  if (DOWNLOAD_INTERRUPT_REASON_NONE != reason) {
+  if (DOWNLOAD_INTERRUPT_REASON_NONE == reason) {
+    SetFullPath(full_path);
+  } else {
     // TODO(asanka): Even though the rename failed, it may still be possible to
     // recover the partial state from the 'before' name.
-    InterruptAndDiscardPartialState(reason);
+    deferred_interrupt_reason_ = reason;
+    TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
+  }
+  OnTargetResolved();
+}
+
+void DownloadItemImpl::OnTargetResolved() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  DVLOG(20) << __func__ << "() download=" << DebugString(true);
+  DCHECK((state_ == TARGET_PENDING_INTERNAL &&
+          deferred_interrupt_reason_ == DOWNLOAD_INTERRUPT_REASON_NONE) ||
+         (state_ == INTERRUPTED_TARGET_PENDING_INTERNAL &&
+          deferred_interrupt_reason_ != DOWNLOAD_INTERRUPT_REASON_NONE))
+      << " deferred_interrupt_reason_:"
+      << DownloadInterruptReasonToString(deferred_interrupt_reason_)
+      << " this:" << DebugString(true);
+
+  // This transition is here to ensure that the DownloadItemImpl state machine
+  // doesn't transition to INTERRUPTED or IN_PROGRESS from
+  // TARGET_PENDING_INTERNAL directly. Doing so without passing through
+  // OnTargetResolved() can result in an externally visible state where the
+  // download is interrupted but doesn't have a target path associated with it.
+  //
+  // While not terrible, this complicates the DownloadItem<->Observer
+  // relationship since an observer that needs a target path in order to respond
+  // properly to an interruption will need to wait for another OnDownloadUpdated
+  // notification.  This requirement currently affects all of our UIs.
+  TransitionTo(TARGET_RESOLVED_INTERNAL);
+
+  if (DOWNLOAD_INTERRUPT_REASON_NONE != deferred_interrupt_reason_) {
+    InterruptWithPartialState(received_bytes_, std::move(hash_state_),
+                              deferred_interrupt_reason_);
+    deferred_interrupt_reason_ = DOWNLOAD_INTERRUPT_REASON_NONE;
     UpdateObservers();
     return;
   }
 
-  if (DOWNLOAD_INTERRUPT_REASON_NONE != destination_error_) {
-    SetFullPath(full_path);
-    InterruptWithPartialState(
-        received_bytes_, std::move(hash_state_), destination_error_);
-    destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE;
-    UpdateObservers();
-    return;
-  }
-
-  SetFullPath(full_path);
   TransitionTo(IN_PROGRESS_INTERNAL);
   // TODO(asanka): Calling UpdateObservers() prior to MaybeCompleteDownload() is
   // not safe. The download could be in an underminate state after invoking
@@ -1639,9 +1652,28 @@
       NOTREACHED();
       return;
 
-    case INTERRUPTED_TARGET_PENDING_INTERNAL:
-    case IN_PROGRESS_INTERNAL:
     case TARGET_PENDING_INTERNAL:
+    case INTERRUPTED_TARGET_PENDING_INTERNAL:
+      // Postpone recognition of this error until after file name determination
+      // has completed and the intermediate file has been renamed to simplify
+      // resumption conditions. The target determination logic is much simpler
+      // if the state of the download remains constant until that stage
+      // completes.
+      //
+      // current_path_ may be empty because it is possible for DownloadItem to
+      // receive a DestinationError prior to the download file initialization
+      // complete callback.
+      if (!IsCancellation(reason)) {
+        UpdateProgress(bytes_so_far, 0);
+        SetHashState(std::move(hash_state));
+        deferred_interrupt_reason_ = reason;
+        TransitionTo(INTERRUPTED_TARGET_PENDING_INTERNAL);
+        return;
+      }
+    // else - Fallthrough for cancellation handling which is equivalent to the
+    // IN_PROGRESS state.
+
+    case IN_PROGRESS_INTERNAL:
     case TARGET_RESOLVED_INTERNAL:
       // last_reason_ needs to be set for GetResumeMode() to work.
       last_reason_ = reason;
@@ -1658,8 +1690,7 @@
       DCHECK(!download_file_);
       // The first non-cancel interrupt reason wins in cases where multiple
       // things go wrong.
-      if (reason != DOWNLOAD_INTERRUPT_REASON_USER_CANCELED &&
-          reason != DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN)
+      if (!IsCancellation(reason))
         return;
 
       last_reason_ = reason;
@@ -1700,8 +1731,7 @@
   if (job_)
     job_->Cancel(false);
 
-  if (reason == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED ||
-      reason == DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN) {
+  if (IsCancellation(reason)) {
     if (IsDangerous()) {
       RecordDangerousDownloadDiscard(
           reason == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
@@ -1713,6 +1743,7 @@
     RecordDownloadCount(CANCELLED_COUNT);
     if (job_ && job_->UsesParallelRequests())
       RecordParallelDownloadCount(CANCELLED_COUNT);
+    DCHECK_EQ(last_reason_, reason);
     TransitionTo(CANCELLED_INTERNAL);
     return;
   }
@@ -1722,6 +1753,10 @@
   if (!GetWebContents())
     RecordDownloadCount(INTERRUPTED_WITHOUT_WEBCONTENTS);
 
+  // TODO(asanka): This is not good. We can transition to interrupted from
+  // target-pending, which is something we don't want to do. Perhaps we should
+  // explicitly transition to target-resolved prior to switching to interrupted.
+  DCHECK_EQ(last_reason_, reason);
   TransitionTo(INTERRUPTED_INTERNAL);
   AutoResumeIfValid();
 }
@@ -1762,8 +1797,9 @@
         // Will be deleted at end of task execution.
         base::Bind(&DownloadFileCancel, base::Passed(&download_file_)));
     // Avoid attempting to reuse the intermediate file by clearing out
-    // current_path_.
+    // current_path_ and received slices.
     current_path_.clear();
+    received_slices_.clear();
   } else {
     BrowserThread::PostTask(
         BrowserThread::FILE,
@@ -1833,7 +1869,12 @@
 
     case TARGET_PENDING_INTERNAL:
     case TARGET_RESOLVED_INTERNAL:
+      break;
+
     case INTERRUPTED_TARGET_PENDING_INTERNAL:
+      DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, deferred_interrupt_reason_)
+          << "Interrupt reason must be set prior to transitioning into "
+             "TARGET_PENDING";
       break;
 
     case IN_PROGRESS_INTERNAL:
@@ -1868,6 +1909,9 @@
       break;
 
     case INTERRUPTED_INTERNAL:
+      DCHECK(!download_file_)
+          << "Download file must be released prior to interruption.";
+      DCHECK_NE(last_reason_, DOWNLOAD_INTERRUPT_REASON_NONE);
       net_log_.AddEvent(net::NetLogEventType::DOWNLOAD_ITEM_INTERRUPTED,
                               base::Bind(&ItemInterruptedNetLogCallback,
                                          last_reason_, received_bytes_));
@@ -1977,6 +2021,7 @@
   ResumeMode mode = GetResumeMode();
   if (mode == RESUME_MODE_IMMEDIATE_RESTART ||
       mode == RESUME_MODE_USER_RESTART) {
+    DCHECK(current_path_.empty());
     received_bytes_ = 0;
     last_modified_time_.clear();
     etag_.clear();
@@ -2118,7 +2163,7 @@
              to == TARGET_RESOLVED_INTERNAL || to == CANCELLED_INTERNAL;
 
     case INTERRUPTED_TARGET_PENDING_INTERNAL:
-      return to == INTERRUPTED_INTERNAL || to == CANCELLED_INTERNAL;
+      return to == TARGET_RESOLVED_INTERNAL || to == CANCELLED_INTERNAL;
 
     case TARGET_RESOLVED_INTERNAL:
       return to == IN_PROGRESS_INTERNAL || to == INTERRUPTED_INTERNAL ||
@@ -2179,7 +2224,7 @@
       return "RESUMING";
     case MAX_DOWNLOAD_INTERNAL_STATE:
       break;
-  };
+  }
   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 3db73335f..f7915d7 100644
--- a/content/browser/download/download_item_impl.h
+++ b/content/browser/download/download_item_impl.h
@@ -425,11 +425,14 @@
       const base::FilePath& target_path,
       TargetDisposition disposition,
       DownloadDangerType danger_type,
-      const base::FilePath& intermediate_path);
+      const base::FilePath& intermediate_path,
+      DownloadInterruptReason interrupt_reason);
 
   void OnDownloadRenamedToIntermediateName(
       DownloadInterruptReason reason, const base::FilePath& full_path);
 
+  void OnTargetResolved();
+
   // If all pre-requisites have been met, complete download processing, i.e. do
   // internal cleanup, file rename, and potentially auto-open.  (Dangerous
   // downloads still may block on user acceptance after this point.)
@@ -637,10 +640,11 @@
   // Did the delegate delay calling Complete on this download?
   bool delegate_delayed_complete_ = false;
 
-  // Error return from DestinationError.  Stored separately from
-  // last_reason_ so that we can avoid handling destination errors until
-  // after file name determination has occurred.
-  DownloadInterruptReason destination_error_ = DOWNLOAD_INTERRUPT_REASON_NONE;
+  // Error return from DestinationError or received at Start().  Stored
+  // separately from last_reason_ so that we can avoid handling destination
+  // errors until after file name determination has occurred.
+  DownloadInterruptReason deferred_interrupt_reason_ =
+      DOWNLOAD_INTERRUPT_REASON_NONE;
 
   // The following fields describe the current state of the download file.
 
diff --git a/content/browser/download/download_item_impl_delegate.cc b/content/browser/download/download_item_impl_delegate.cc
index 96be706..2e3367a 100644
--- a/content/browser/download/download_item_impl_delegate.cc
+++ b/content/browser/download/download_item_impl_delegate.cc
@@ -31,10 +31,9 @@
     DownloadItemImpl* download, const DownloadTargetCallback& callback) {
   // TODO(rdsmith/asanka): Do something useful if forced file path is null.
   base::FilePath target_path(download->GetForcedFilePath());
-  callback.Run(target_path,
-               DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-               target_path);
+  callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, target_path,
+               DOWNLOAD_INTERRUPT_REASON_NONE);
 }
 
 bool DownloadItemImplDelegate::ShouldCompleteDownload(
diff --git a/content/browser/download/download_item_impl_delegate.h b/content/browser/download/download_item_impl_delegate.h
index 5c380598..fd193ac4 100644
--- a/content/browser/download/download_item_impl_delegate.h
+++ b/content/browser/download/download_item_impl_delegate.h
@@ -13,6 +13,7 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/download_danger_type.h"
 #include "content/public/browser/download_item.h"
+#include "content/public/browser/download_manager_delegate.h"
 #include "content/public/browser/download_url_parameters.h"
 
 namespace content {
@@ -25,13 +26,6 @@
 // be left unimplemented.
 class CONTENT_EXPORT DownloadItemImplDelegate {
  public:
-  typedef base::Callback<void(
-      const base::FilePath&,            // Target path
-      DownloadItem::TargetDisposition,  // overwrite/uniquify target
-      DownloadDangerType,
-      const base::FilePath&             // Intermediate file path
-                              )> DownloadTargetCallback;
-
   // The boolean argument indicates whether or not the download was
   // actually opened.
   typedef base::Callback<void(bool)> ShouldOpenDownloadCallback;
diff --git a/content/browser/download/download_item_impl_unittest.cc b/content/browser/download/download_item_impl_unittest.cc
index a81c744..0ac8b83 100644
--- a/content/browser/download/download_item_impl_unittest.cc
+++ b/content/browser/download/download_item_impl_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <stdint.h>
 
+#include <deque>
 #include <iterator>
 #include <map>
 #include <memory>
@@ -13,6 +14,7 @@
 #include <utility>
 
 #include "base/callback.h"
+#include "base/callback_helpers.h"
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
 #include "base/message_loop/message_loop.h"
@@ -38,6 +40,8 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 using ::testing::DoAll;
+using ::testing::Invoke;
+using ::testing::InvokeWithoutArgs;
 using ::testing::NiceMock;
 using ::testing::Property;
 using ::testing::Return;
@@ -285,19 +289,13 @@
     return download;
   }
 
-  // Add DownloadFile to DownloadItem. Set |callback| to nullptr if a download
-  // target determination is not expected.
-  MockDownloadFile* CallDownloadItemStart(
-      DownloadItemImpl* item,
-      DownloadItemImplDelegate::DownloadTargetCallback* callback) {
+  // Add DownloadFile to DownloadItem.
+  MockDownloadFile* CallDownloadItemStart(DownloadItemImpl* item,
+                                          DownloadTargetCallback* callback) {
     MockDownloadFile* mock_download_file = nullptr;
     std::unique_ptr<DownloadFile> download_file;
-    if (callback) {
-      EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _))
-          .WillOnce(SaveArg<1>(callback));
-    } else {
-      EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _)).Times(0);
-    }
+    EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _))
+        .WillOnce(SaveArg<1>(callback));
 
     // Only create a DownloadFile if the request was successful.
     if (create_info_->result == DOWNLOAD_INTERRUPT_REASON_NONE) {
@@ -309,8 +307,8 @@
           .WillRepeatedly(ReturnRefOfCopy(base::FilePath()));
     }
 
-    std::unique_ptr<MockRequestHandle> request_handle(
-        new NiceMock<MockRequestHandle>);
+    std::unique_ptr<MockRequestHandle> request_handle =
+        base::MakeUnique<NiceMock<MockRequestHandle>>();
     item->Start(std::move(download_file), std::move(request_handle),
                 *create_info_);
     RunAllPendingInMessageLoops();
@@ -335,7 +333,7 @@
                                          DownloadDangerType danger_type) {
     EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
     EXPECT_TRUE(item->GetTargetFilePath().empty());
-    DownloadItemImplDelegate::DownloadTargetCallback callback;
+    DownloadTargetCallback callback;
     MockDownloadFile* download_file = CallDownloadItemStart(item, &callback);
     base::FilePath target_path(kDummyTargetPath);
     base::FilePath intermediate_path(kDummyIntermediatePath);
@@ -343,7 +341,8 @@
         .WillOnce(ScheduleRenameAndUniquifyCallback(
             DOWNLOAD_INTERRUPT_REASON_NONE, intermediate_path));
     callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-                 danger_type, intermediate_path);
+                 danger_type, intermediate_path,
+                 DOWNLOAD_INTERRUPT_REASON_NONE);
     RunAllPendingInMessageLoops();
     return download_file;
   }
@@ -433,7 +432,7 @@
 
 TEST_F(DownloadItemTest, NotificationAfterCancel) {
   DownloadItemImpl* user_cancel = CreateDownloadItem();
-  DownloadItemImplDelegate::DownloadTargetCallback target_callback;
+  DownloadTargetCallback target_callback;
   MockDownloadFile* download_file =
       CallDownloadItemStart(user_cancel, &target_callback);
   EXPECT_CALL(*download_file, Cancel());
@@ -476,8 +475,7 @@
   EXPECT_CALL(*download_file, Cancel());
   TestDownloadItemObserver observer(item);
 
-  EXPECT_CALL(*mock_delegate(), MockResumeInterruptedDownload(_,_))
-      .Times(0);
+  EXPECT_CALL(*mock_delegate(), MockResumeInterruptedDownload(_, _)).Times(0);
 
   item->DestinationObserverAsWeakPtr()->DestinationError(
       DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, 0,
@@ -495,7 +493,7 @@
 
 TEST_F(DownloadItemTest, NotificationAfterRemove) {
   DownloadItemImpl* item = CreateDownloadItem();
-  DownloadItemImplDelegate::DownloadTargetCallback target_callback;
+  DownloadTargetCallback target_callback;
   MockDownloadFile* download_file =
       CallDownloadItemStart(item, &target_callback);
   EXPECT_CALL(*download_file, Cancel());
@@ -563,7 +561,7 @@
 // not before.
 TEST_F(DownloadItemTest, NotificationAfterOnDownloadTargetDetermined) {
   DownloadItemImpl* item = CreateDownloadItem();
-  DownloadItemImplDelegate::DownloadTargetCallback callback;
+  DownloadTargetCallback callback;
   MockDownloadFile* download_file = CallDownloadItemStart(item, &callback);
   TestDownloadItemObserver observer(item);
   base::FilePath target_path(kDummyTargetPath);
@@ -577,7 +575,8 @@
   // Currently, a notification would be generated if the danger type is anything
   // other than NOT_DANGEROUS.
   callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path,
+               DOWNLOAD_INTERRUPT_REASON_NONE);
   EXPECT_FALSE(observer.CheckAndResetDownloadUpdated());
   RunAllPendingInMessageLoops();
   EXPECT_TRUE(observer.CheckAndResetDownloadUpdated());
@@ -613,19 +612,32 @@
 }
 
 // Test that a download is resumed automatcially after a continuable interrupt.
-TEST_F(DownloadItemTest, ContinueAfterInterrupted) {
+TEST_F(DownloadItemTest, AutomaticResumption_Continue) {
   DownloadItemImpl* item = CreateDownloadItem();
   TestDownloadItemObserver observer(item);
   MockDownloadFile* download_file =
       DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
 
-  // Interrupt the download, using a continuable interrupt.
+  // Interrupt the download using a continuable interrupt after writing a single
+  // byte. An intermediate file with data shouldn't be discarding after a
+  // continuable interrupt.
+
+  // The DownloadFile should be detached without discarding.
   EXPECT_CALL(*download_file, FullPath())
       .WillOnce(ReturnRefOfCopy(base::FilePath()));
   EXPECT_CALL(*download_file, Detach());
-  EXPECT_CALL(*mock_delegate(), MockResumeInterruptedDownload(_, _)).Times(1);
+
+  // Resumption attempt should pass the intermediate file along.
+  EXPECT_CALL(*mock_delegate(),
+              MockResumeInterruptedDownload(
+                  AllOf(Property(&DownloadUrlParameters::file_path,
+                                 Property(&base::FilePath::value,
+                                          kDummyIntermediatePath)),
+                        Property(&DownloadUrlParameters::offset, 1)),
+                  _));
+
   item->DestinationObserverAsWeakPtr()->DestinationError(
-      DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, 0,
+      DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, 1,
       std::unique_ptr<crypto::SecureHash>());
   ASSERT_TRUE(observer.CheckAndResetDownloadUpdated());
   // Since the download is resumed automatically, the interrupt count doesn't
@@ -644,9 +656,41 @@
   CleanupItem(item, nullptr, DownloadItem::IN_PROGRESS);
 }
 
-// Test that automatic resumption doesn't happen after a non-continuable
-// interrupt.
-TEST_F(DownloadItemTest, RestartAfterInterrupted) {
+// Automatic resumption should restart and discard the intermediate file if the
+// interrupt reason requires it.
+TEST_F(DownloadItemTest, AutomaticResumption_Restart) {
+  DownloadItemImpl* item = CreateDownloadItem();
+  TestDownloadItemObserver observer(item);
+  MockDownloadFile* download_file =
+      DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS);
+
+  // Interrupt the download, using a restartable interrupt.
+  EXPECT_CALL(*download_file, Cancel());
+  EXPECT_EQ(kDummyIntermediatePath, item->GetFullPath().value());
+
+  // Resumption attempt should have discarded intermediate file.
+  EXPECT_CALL(*mock_delegate(),
+              MockResumeInterruptedDownload(
+                  Property(&DownloadUrlParameters::file_path,
+                           Property(&base::FilePath::empty, true)),
+                  _));
+
+  item->DestinationObserverAsWeakPtr()->DestinationError(
+      DOWNLOAD_INTERRUPT_REASON_SERVER_NO_RANGE, 1,
+      std::unique_ptr<crypto::SecureHash>());
+  ASSERT_TRUE(observer.CheckAndResetDownloadUpdated());
+
+  // Since the download is resumed automatically, the interrupt count doesn't
+  // increase.
+  ASSERT_EQ(0, observer.interrupt_count());
+  RunAllPendingInMessageLoops();
+
+  CleanupItem(item, nullptr, DownloadItem::IN_PROGRESS);
+}
+
+// Test that automatic resumption doesn't happen after an interrupt that
+// requires user action to resolve.
+TEST_F(DownloadItemTest, AutomaticResumption_NeedsUserAction) {
   DownloadItemImpl* item = CreateDownloadItem();
   TestDownloadItemObserver observer(item);
   MockDownloadFile* download_file =
@@ -655,7 +699,7 @@
   // Interrupt the download, using a restartable interrupt.
   EXPECT_CALL(*download_file, Cancel());
   item->DestinationObserverAsWeakPtr()->DestinationError(
-      DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, 0,
+      DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, 1,
       std::unique_ptr<crypto::SecureHash>());
   ASSERT_TRUE(observer.CheckAndResetDownloadUpdated());
   // Should not try to auto-resume.
@@ -695,59 +739,72 @@
   CleanupItem(item, nullptr, DownloadItem::INTERRUPTED);
 }
 
-TEST_F(DownloadItemTest, LimitRestartsAfterInterrupted) {
+TEST_F(DownloadItemTest, AutomaticResumption_AttemptLimit) {
   DownloadItemImpl* item = CreateDownloadItem();
   base::WeakPtr<DownloadDestinationObserver> as_observer(
       item->DestinationObserverAsWeakPtr());
   TestDownloadItemObserver observer(item);
-  MockDownloadFile* mock_download_file(nullptr);
-  std::unique_ptr<DownloadFile> download_file;
-  MockRequestHandle* mock_request_handle(nullptr);
-  std::unique_ptr<DownloadRequestHandleInterface> request_handle;
-  DownloadItemImplDelegate::DownloadTargetCallback callback;
+  MockDownloadFile* mock_download_file_ref = nullptr;
+  std::unique_ptr<MockDownloadFile> mock_download_file;
+  std::unique_ptr<MockRequestHandle> mock_request_handle;
+  DownloadTargetCallback callback;
 
   EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _))
       .WillRepeatedly(SaveArg<1>(&callback));
-  EXPECT_CALL(*mock_delegate(), MockResumeInterruptedDownload(_, _))
+
+  // All attempts at resumption should pass along the intermediate file.
+  EXPECT_CALL(*mock_delegate(),
+              MockResumeInterruptedDownload(
+                  AllOf(Property(&DownloadUrlParameters::file_path,
+                                 Property(&base::FilePath::value,
+                                          kDummyIntermediatePath)),
+                        Property(&DownloadUrlParameters::offset, 1)),
+                  _))
       .Times(DownloadItemImpl::kMaxAutoResumeAttempts);
   for (int i = 0; i < (DownloadItemImpl::kMaxAutoResumeAttempts + 1); ++i) {
     SCOPED_TRACE(::testing::Message() << "Iteration " << i);
 
-    mock_download_file = new NiceMock<MockDownloadFile>;
-    download_file.reset(mock_download_file);
-    mock_request_handle = new NiceMock<MockRequestHandle>;
-    request_handle.reset(mock_request_handle);
+    mock_download_file = base::MakeUnique<NiceMock<MockDownloadFile>>();
+    mock_download_file_ref = mock_download_file.get();
+    mock_request_handle = base::MakeUnique<NiceMock<MockRequestHandle>>();
 
-    ON_CALL(*mock_download_file, FullPath())
+    ON_CALL(*mock_download_file_ref, FullPath())
         .WillByDefault(ReturnRefOfCopy(base::FilePath()));
 
     // Copied key parts of DoIntermediateRename & CallDownloadItemStart
     // to allow for holding onto the request handle.
-    item->Start(std::move(download_file), std::move(request_handle),
+    item->Start(std::move(mock_download_file), std::move(mock_request_handle),
                 *create_info());
     RunAllPendingInMessageLoops();
 
     base::FilePath target_path(kDummyTargetPath);
     base::FilePath intermediate_path(kDummyIntermediatePath);
-    if (i == 0) {
-      // RenameAndUniquify is only called the first time. In all the subsequent
-      // iterations, the intermediate file already has the correct name, hence
-      // no rename is necessary.
-      EXPECT_CALL(*mock_download_file, RenameAndUniquify(intermediate_path, _))
-          .WillOnce(ScheduleRenameAndUniquifyCallback(
-              DOWNLOAD_INTERRUPT_REASON_NONE, intermediate_path));
-    }
-    callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-                 DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+
+    // Target of RenameAndUniquify is always the intermediate path.
+    ON_CALL(*mock_download_file_ref, RenameAndUniquify(_, _))
+        .WillByDefault(ScheduleRenameAndUniquifyCallback(
+            DOWNLOAD_INTERRUPT_REASON_NONE, intermediate_path));
+
+    // RenameAndUniquify is only called the first time. In all the subsequent
+    // iterations, the intermediate file already has the correct name, hence no
+    // rename is necessary.
+    EXPECT_CALL(*mock_download_file_ref, RenameAndUniquify(_, _)).Times(i == 0);
+
+    ASSERT_FALSE(callback.is_null());
+    base::ResetAndReturn(&callback).Run(
+        target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+        DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path,
+        DOWNLOAD_INTERRUPT_REASON_NONE);
     RunAllPendingInMessageLoops();
 
     // Use a continuable interrupt.
+    EXPECT_CALL(*mock_download_file_ref, Cancel()).Times(0);
     item->DestinationObserverAsWeakPtr()->DestinationError(
-        DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, 0,
+        DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, 1,
         std::unique_ptr<crypto::SecureHash>());
 
     RunAllPendingInMessageLoops();
-    ::testing::Mock::VerifyAndClearExpectations(mock_download_file);
+    ::testing::Mock::VerifyAndClearExpectations(mock_download_file_ref);
   }
 
   EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState());
@@ -779,10 +836,16 @@
   EXPECT_EQ(kFirstURL, item->GetURL().spec());
   EXPECT_EQ(kMimeType, item->GetMimeType());
 
-  EXPECT_CALL(*mock_delegate(), MockResumeInterruptedDownload(_, _));
+  EXPECT_CALL(*mock_delegate(),
+              MockResumeInterruptedDownload(
+                  AllOf(Property(&DownloadUrlParameters::file_path,
+                                 Property(&base::FilePath::value,
+                                          kDummyIntermediatePath)),
+                        Property(&DownloadUrlParameters::offset, 1)),
+                  _));
   EXPECT_CALL(*download_file, Detach());
   item->DestinationObserverAsWeakPtr()->DestinationError(
-      DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, 0,
+      DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, 1,
       std::unique_ptr<crypto::SecureHash>());
   RunAllPendingInMessageLoops();
   EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
@@ -801,10 +864,20 @@
   create_info()->url_chain.push_back(GURL(kSecondURL));
   create_info()->mime_type = kSecondMimeType;
   create_info()->result = DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED;
+  create_info()->save_info->file_path = base::FilePath(kDummyIntermediatePath);
+  create_info()->save_info->offset = 1;
 
-  // The following ends up calling DownloadItem::Start(), but shouldn't result
-  // in an origin update.
-  download_file = CallDownloadItemStart(item, nullptr);
+  // Calling Start() with a response indicating failure shouldn't cause a target
+  // update, nor should it result in discarding the intermediate file.
+  DownloadTargetCallback target_callback;
+  download_file = CallDownloadItemStart(item, &target_callback);
+  ASSERT_FALSE(target_callback.is_null());
+  target_callback.Run(base::FilePath(kDummyTargetPath),
+                      DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+                      DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
+                      base::FilePath(kDummyIntermediatePath),
+                      DOWNLOAD_INTERRUPT_REASON_NONE);
+  RunAllPendingInMessageLoops();
 
   EXPECT_EQ(kContentDisposition, item->GetContentDisposition());
   EXPECT_EQ(kFirstETag, item->GetETag());
@@ -813,6 +886,8 @@
   EXPECT_EQ(kMimeType, item->GetMimeType());
   EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState());
   EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, item->GetLastReason());
+  EXPECT_EQ(kDummyIntermediatePath, item->GetFullPath().value());
+  EXPECT_EQ(1, item->GetReceivedBytes());
 }
 
 // If the download resumption request succeeds, the origin state should be
@@ -854,7 +929,7 @@
   create_info()->url_chain.push_back(GURL(kSecondURL));
   create_info()->mime_type = kSecondMimeType;
 
-  DownloadItemImplDelegate::DownloadTargetCallback target_callback;
+  DownloadTargetCallback target_callback;
   download_file = CallDownloadItemStart(item, &target_callback);
 
   EXPECT_EQ(kSecondContentDisposition, item->GetContentDisposition());
@@ -867,7 +942,7 @@
 }
 
 // Test that resumption uses the final URL in a URL chain when resuming.
-TEST_F(DownloadItemTest, ResumeUsingFinalURL) {
+TEST_F(DownloadItemTest, ResumeUsesFinalURL) {
   create_info()->save_info->prompt_for_save_location = false;
   create_info()->url_chain.clear();
   create_info()->url_chain.push_back(GURL("http://example.com/a"));
@@ -889,7 +964,7 @@
                                     _))
       .Times(1);
   item->DestinationObserverAsWeakPtr()->DestinationError(
-      DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, 0,
+      DOWNLOAD_INTERRUPT_REASON_FILE_TRANSIENT_ERROR, 1,
       std::unique_ptr<crypto::SecureHash>());
 
   // Test expectations verify that ResumeInterruptedDownload() is called (by way
@@ -905,7 +980,7 @@
 
 TEST_F(DownloadItemTest, DisplayName) {
   DownloadItemImpl* item = CreateDownloadItem();
-  DownloadItemImplDelegate::DownloadTargetCallback callback;
+  DownloadTargetCallback callback;
   MockDownloadFile* download_file = CallDownloadItemStart(item, &callback);
   base::FilePath target_path(
       base::FilePath(kDummyTargetPath).AppendASCII("foo.bar"));
@@ -916,7 +991,8 @@
       .WillOnce(ScheduleRenameAndUniquifyCallback(
           DOWNLOAD_INTERRUPT_REASON_NONE, intermediate_path));
   callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path,
+               DOWNLOAD_INTERRUPT_REASON_NONE);
   RunAllPendingInMessageLoops();
   EXPECT_EQ(FILE_PATH_LITERAL("foo.bar"),
             item->GetFileNameToReportUser().value());
@@ -946,8 +1022,10 @@
 // file initialization failing.
 TEST_F(DownloadItemTest, InitDownloadFileFails) {
   DownloadItemImpl* item = CreateDownloadItem();
-  std::unique_ptr<MockDownloadFile> file(new MockDownloadFile());
-  std::unique_ptr<MockRequestHandle> request_handle(new MockRequestHandle());
+  std::unique_ptr<MockDownloadFile> file = base::MakeUnique<MockDownloadFile>();
+  std::unique_ptr<MockRequestHandle> request_handle =
+      base::MakeUnique<MockRequestHandle>();
+
   EXPECT_CALL(*file, Cancel());
   EXPECT_CALL(*request_handle, CancelRequest());
   EXPECT_CALL(*file, Initialize(_))
@@ -955,7 +1033,7 @@
           DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED));
 
   base::RunLoop start_download_loop;
-  DownloadItemImplDelegate::DownloadTargetCallback download_target_callback;
+  DownloadTargetCallback download_target_callback;
   EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _))
       .WillOnce(DoAll(SaveArg<1>(&download_target_callback),
                       ScheduleClosure(start_download_loop.QuitClosure())));
@@ -966,12 +1044,15 @@
   download_target_callback.Run(base::FilePath(kDummyTargetPath),
                                DownloadItem::TARGET_DISPOSITION_OVERWRITE,
                                DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-                               base::FilePath(kDummyIntermediatePath));
+                               base::FilePath(kDummyIntermediatePath),
+                               DOWNLOAD_INTERRUPT_REASON_NONE);
   RunAllPendingInMessageLoops();
 
   EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState());
   EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_FILE_ACCESS_DENIED,
             item->GetLastReason());
+  EXPECT_FALSE(item->GetTargetFilePath().empty());
+  EXPECT_TRUE(item->GetFullPath().empty());
 }
 
 // Handling of downloads initiated via a failed request. In this case, Start()
@@ -984,7 +1065,7 @@
   // failed downloads.
   std::unique_ptr<DownloadFile> null_download_file;
   std::unique_ptr<DownloadRequestHandleInterface> null_request_handle;
-  DownloadItemImplDelegate::DownloadTargetCallback download_target_callback;
+  DownloadTargetCallback download_target_callback;
   EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _))
       .WillOnce(SaveArg<1>(&download_target_callback));
   item->Start(std::move(null_download_file), std::move(null_request_handle),
@@ -999,7 +1080,8 @@
   base::FilePath target_path(FILE_PATH_LITERAL("foo"));
   download_target_callback.Run(target_path,
                                DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-                               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, target_path);
+                               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, target_path,
+                               DOWNLOAD_INTERRUPT_REASON_NONE);
   RunAllPendingInMessageLoops();
 
   EXPECT_EQ(target_path, item->GetTargetFilePath());
@@ -1009,7 +1091,7 @@
 // Test that the delegate is invoked after the download file is renamed.
 TEST_F(DownloadItemTest, CallbackAfterRename) {
   DownloadItemImpl* item = CreateDownloadItem();
-  DownloadItemImplDelegate::DownloadTargetCallback callback;
+  DownloadTargetCallback callback;
   MockDownloadFile* download_file = CallDownloadItemStart(item, &callback);
   base::FilePath final_path(
       base::FilePath(kDummyTargetPath).AppendASCII("foo.bar"));
@@ -1021,7 +1103,8 @@
           DOWNLOAD_INTERRUPT_REASON_NONE, new_intermediate_path));
 
   callback.Run(final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path,
+               DOWNLOAD_INTERRUPT_REASON_NONE);
   RunAllPendingInMessageLoops();
   // All the callbacks should have happened by now.
   ::testing::Mock::VerifyAndClearExpectations(download_file);
@@ -1046,7 +1129,7 @@
 // download item is in an interrupted state.
 TEST_F(DownloadItemTest, CallbackAfterInterruptedRename) {
   DownloadItemImpl* item = CreateDownloadItem();
-  DownloadItemImplDelegate::DownloadTargetCallback callback;
+  DownloadTargetCallback callback;
   MockDownloadFile* download_file = CallDownloadItemStart(item, &callback);
   base::FilePath final_path(
       base::FilePath(kDummyTargetPath).AppendASCII("foo.bar"));
@@ -1060,7 +1143,8 @@
       .Times(1);
 
   callback.Run(final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path,
+               DOWNLOAD_INTERRUPT_REASON_NONE);
   RunAllPendingInMessageLoops();
   // All the callbacks should have happened by now.
   ::testing::Mock::VerifyAndClearExpectations(download_file);
@@ -1093,7 +1177,7 @@
 // the download to be marked as interrupted until after the intermediate rename.
 TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Restart) {
   DownloadItemImpl* item = CreateDownloadItem();
-  DownloadItemImplDelegate::DownloadTargetCallback callback;
+  DownloadTargetCallback callback;
   MockDownloadFile* download_file = CallDownloadItemStart(item, &callback);
   item->DestinationObserverAsWeakPtr()->DestinationError(
       DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, 0,
@@ -1112,7 +1196,8 @@
       .Times(1);
 
   callback.Run(final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path,
+               DOWNLOAD_INTERRUPT_REASON_NONE);
   RunAllPendingInMessageLoops();
   // All the callbacks should have happened by now.
   ::testing::Mock::VerifyAndClearExpectations(download_file);
@@ -1127,10 +1212,13 @@
 // the intermediate rename succeeds.
 TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Continue) {
   DownloadItemImpl* item = CreateDownloadItem();
-  DownloadItemImplDelegate::DownloadTargetCallback callback;
+  DownloadTargetCallback callback;
   MockDownloadFile* download_file = CallDownloadItemStart(item, &callback);
+
+  // Write some data and interrupt with NETWORK_FAILED. The download shouldn't
+  // transition to INTERRUPTED until the destination callback has been invoked.
   item->DestinationObserverAsWeakPtr()->DestinationError(
-      DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, 0,
+      DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, 1,
       std::unique_ptr<crypto::SecureHash>());
   ASSERT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
 
@@ -1147,7 +1235,8 @@
   EXPECT_CALL(*download_file, Detach());
 
   callback.Run(final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path,
+               DOWNLOAD_INTERRUPT_REASON_NONE);
   RunAllPendingInMessageLoops();
   // All the callbacks should have happened by now.
   ::testing::Mock::VerifyAndClearExpectations(download_file);
@@ -1161,7 +1250,7 @@
 // be set to the file error and the intermediate path should be empty.
 TEST_F(DownloadItemTest, InterruptedBeforeIntermediateRename_Failed) {
   DownloadItemImpl* item = CreateDownloadItem();
-  DownloadItemImplDelegate::DownloadTargetCallback callback;
+  DownloadTargetCallback callback;
   MockDownloadFile* download_file = CallDownloadItemStart(item, &callback);
   item->DestinationObserverAsWeakPtr()->DestinationError(
       DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, 0,
@@ -1180,7 +1269,8 @@
       .Times(1);
 
   callback.Run(final_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path);
+               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, intermediate_path,
+               DOWNLOAD_INTERRUPT_REASON_NONE);
   RunAllPendingInMessageLoops();
   // All the callbacks should have happened by now.
   ::testing::Mock::VerifyAndClearExpectations(download_file);
@@ -1193,7 +1283,7 @@
 
 TEST_F(DownloadItemTest, Canceled) {
   DownloadItemImpl* item = CreateDownloadItem();
-  DownloadItemImplDelegate::DownloadTargetCallback target_callback;
+  DownloadTargetCallback target_callback;
   MockDownloadFile* download_file =
       CallDownloadItemStart(item, &target_callback);
 
@@ -1203,6 +1293,32 @@
   EXPECT_EQ(DownloadItem::CANCELLED, item->GetState());
 }
 
+TEST_F(DownloadItemTest, DownloadTargetDetermined_Cancel) {
+  DownloadItemImpl* item = CreateDownloadItem();
+  DownloadTargetCallback callback;
+  MockDownloadFile* download_file = CallDownloadItemStart(item, &callback);
+
+  EXPECT_CALL(*download_file, Cancel());
+  callback.Run(base::FilePath(FILE_PATH_LITERAL("foo")),
+               DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
+               base::FilePath(FILE_PATH_LITERAL("bar")),
+               DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);
+  EXPECT_EQ(DownloadItem::CANCELLED, item->GetState());
+}
+
+TEST_F(DownloadItemTest, DownloadTargetDetermined_CancelWithEmptyName) {
+  DownloadItemImpl* item = CreateDownloadItem();
+  DownloadTargetCallback callback;
+  MockDownloadFile* download_file = CallDownloadItemStart(item, &callback);
+
+  EXPECT_CALL(*download_file, Cancel());
+  callback.Run(base::FilePath(), DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+               DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, base::FilePath(),
+               DOWNLOAD_INTERRUPT_REASON_NONE);
+  EXPECT_EQ(DownloadItem::CANCELLED, item->GetState());
+}
+
 TEST_F(DownloadItemTest, FileRemoved) {
   DownloadItemImpl* item = CreateDownloadItem();
 
@@ -1263,8 +1379,8 @@
   hash->Update(kTestData1, sizeof(kTestData1));
 
   EXPECT_CALL(*download_file, Detach());
-  as_observer->DestinationError(
-      DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, 0, std::move(hash));
+  as_observer->DestinationError(DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, 1,
+                                std::move(hash));
   mock_delegate()->VerifyAndClearExpectations();
   EXPECT_TRUE(observer.CheckAndResetDownloadUpdated());
   EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState());
@@ -1273,6 +1389,7 @@
       std::string(std::begin(kHashOfTestData1), std::end(kHashOfTestData1)),
       item->GetHash());
 }
+
 TEST_F(DownloadItemTest, DestinationError_RestartRequired) {
   DownloadItemImpl* item = CreateDownloadItem();
   MockDownloadFile* download_file =
@@ -1290,8 +1407,8 @@
   hash->Update(kTestData1, sizeof(kTestData1));
 
   EXPECT_CALL(*download_file, Cancel());
-  as_observer->DestinationError(
-      DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, 0, std::move(hash));
+  as_observer->DestinationError(DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, 1,
+                                std::move(hash));
   mock_delegate()->VerifyAndClearExpectations();
   EXPECT_TRUE(observer.CheckAndResetDownloadUpdated());
   EXPECT_EQ(DownloadItem::INTERRUPTED, item->GetState());
@@ -1643,7 +1760,7 @@
   CleanupItem(item, download_file, DownloadItem::IN_PROGRESS);
 }
 
-TEST_F(DownloadItemTest, StealInterruptedDangerousDownload) {
+TEST_F(DownloadItemTest, StealInterruptedContinuableDangerousDownload) {
   base::FilePath returned_path;
   DownloadItemImpl* item = CreateDownloadItem();
   MockDownloadFile* download_file =
@@ -1653,7 +1770,7 @@
   EXPECT_CALL(*download_file, FullPath()).WillOnce(ReturnRefOfCopy(full_path));
   EXPECT_CALL(*download_file, Detach());
   item->DestinationObserverAsWeakPtr()->DestinationError(
-      DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, 0,
+      DOWNLOAD_INTERRUPT_REASON_NETWORK_FAILED, 1,
       std::unique_ptr<crypto::SecureHash>());
   ASSERT_TRUE(item->IsDangerous());
 
@@ -1668,14 +1785,14 @@
   EXPECT_EQ(full_path, returned_path);
 }
 
-TEST_F(DownloadItemTest, StealInterruptedNonResumableDangerousDownload) {
+TEST_F(DownloadItemTest, StealInterruptedNonContinuableDangerousDownload) {
   base::FilePath returned_path;
   DownloadItemImpl* item = CreateDownloadItem();
   MockDownloadFile* download_file =
       DoIntermediateRename(item, DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE);
   EXPECT_CALL(*download_file, Cancel());
   item->DestinationObserverAsWeakPtr()->DestinationError(
-      DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, 0,
+      DOWNLOAD_INTERRUPT_REASON_FILE_FAILED, 1,
       std::unique_ptr<crypto::SecureHash>());
   ASSERT_TRUE(item->IsDangerous());
 
@@ -1946,7 +2063,7 @@
   RunAllPendingInMessageLoops();
 
   base::RunLoop initialize_completion_loop;
-  DownloadItemImplDelegate::DownloadTargetCallback target_callback;
+  DownloadTargetCallback target_callback;
   EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(_, _))
       .WillOnce(
           DoAll(SaveArg<1>(&target_callback),
@@ -1962,7 +2079,8 @@
                        destination_observer);
   target_callback.Run(base::FilePath(),
                       DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-                      DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, base::FilePath());
+                      DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, base::FilePath(),
+                      DOWNLOAD_INTERRUPT_REASON_NONE);
   EXPECT_EQ(DownloadItem::CANCELLED, item_->GetState());
   RunAllPendingInMessageLoops();
 }
@@ -1998,7 +2116,7 @@
   RunAllPendingInMessageLoops();
 
   base::RunLoop initialize_completion_loop;
-  DownloadItemImplDelegate::DownloadTargetCallback target_callback;
+  DownloadTargetCallback target_callback;
   EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(_, _))
       .WillOnce(
           DoAll(SaveArg<1>(&target_callback),
@@ -2015,7 +2133,8 @@
   target_callback.Run(base::FilePath(kDummyTargetPath),
                       DownloadItem::TARGET_DISPOSITION_OVERWRITE,
                       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-                      base::FilePath(kDummyIntermediatePath));
+                      base::FilePath(kDummyIntermediatePath),
+                      DOWNLOAD_INTERRUPT_REASON_NONE);
 
   intermediate_rename_loop.Run();
   ASSERT_FALSE(intermediate_rename_callback.is_null());
@@ -2067,7 +2186,7 @@
   RunAllPendingInMessageLoops();
 
   base::RunLoop initialize_completion_loop;
-  DownloadItemImplDelegate::DownloadTargetCallback target_callback;
+  DownloadTargetCallback target_callback;
   EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(_, _))
       .WillOnce(
           DoAll(SaveArg<1>(&target_callback),
@@ -2084,7 +2203,8 @@
   target_callback.Run(base::FilePath(kDummyTargetPath),
                       DownloadItem::TARGET_DISPOSITION_OVERWRITE,
                       DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-                      base::FilePath(kDummyIntermediatePath));
+                      base::FilePath(kDummyIntermediatePath),
+                      DOWNLOAD_INTERRUPT_REASON_NONE);
 
   intermediate_rename_loop.Run();
   ASSERT_FALSE(intermediate_rename_callback.is_null());
diff --git a/content/browser/download/download_manager_impl.cc b/content/browser/download/download_manager_impl.cc
index 4f1aa6a..ff64590 100644
--- a/content/browser/download/download_manager_impl.cc
+++ b/content/browser/download/download_manager_impl.cc
@@ -226,10 +226,9 @@
   if (!delegate_ || !delegate_->DetermineDownloadTarget(item, callback)) {
     base::FilePath target_path = item->GetForcedFilePath();
     // TODO(asanka): Determine a useful path if |target_path| is empty.
-    callback.Run(target_path,
-                 DownloadItem::TARGET_DISPOSITION_OVERWRITE,
-                 DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
-                 target_path);
+    callback.Run(target_path, DownloadItem::TARGET_DISPOSITION_OVERWRITE,
+                 DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, target_path,
+                 DOWNLOAD_INTERRUPT_REASON_NONE);
   }
 }
 
diff --git a/content/browser/download/download_manager_impl_unittest.cc b/content/browser/download/download_manager_impl_unittest.cc
index 85a57bbd..0a03984 100644
--- a/content/browser/download/download_manager_impl_unittest.cc
+++ b/content/browser/download/download_manager_impl_unittest.cc
@@ -7,6 +7,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <map>
 #include <memory>
 #include <set>
 #include <string>
@@ -292,7 +293,7 @@
   MOCK_METHOD2(MockCreateFile,
                MockDownloadFile*(const DownloadSaveInfo&, ByteStreamReader*));
 
-  virtual DownloadFile* CreateFile(
+  DownloadFile* CreateFile(
       std::unique_ptr<DownloadSaveInfo> save_info,
       const base::FilePath& default_download_directory,
       std::unique_ptr<ByteStreamReader> byte_stream,
@@ -380,10 +381,12 @@
 
   DownloadManagerTest()
       : callback_called_(false),
+        target_disposition_(DownloadItem::TARGET_DISPOSITION_OVERWRITE),
+        danger_type_(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS),
+        interrupt_reason_(DOWNLOAD_INTERRUPT_REASON_NONE),
         ui_thread_(BrowserThread::UI, &message_loop_),
         file_thread_(BrowserThread::FILE, &message_loop_),
-        next_download_id_(0) {
-  }
+        next_download_id_(0) {}
 
   // We tear down everything in TearDown().
   ~DownloadManagerTest() override {}
@@ -486,12 +489,14 @@
       const base::FilePath& target_path,
       DownloadItem::TargetDisposition disposition,
       DownloadDangerType danger_type,
-      const base::FilePath& intermediate_path) {
+      const base::FilePath& intermediate_path,
+      DownloadInterruptReason interrupt_reason) {
     callback_called_ = true;
     target_path_ = target_path;
     target_disposition_ = disposition;
     danger_type_ = danger_type;
     intermediate_path_ = intermediate_path;
+    interrupt_reason_ = interrupt_reason;
   }
 
   void DetermineDownloadTarget(DownloadItemImpl* item) {
@@ -512,6 +517,7 @@
   DownloadItem::TargetDisposition target_disposition_;
   DownloadDangerType danger_type_;
   base::FilePath intermediate_path_;
+  DownloadInterruptReason interrupt_reason_;
 
   std::vector<GURL> download_urls_;
 
@@ -585,13 +591,13 @@
       .WillOnce(ReturnRef(path));
 
   // Confirm that the callback was called with the right values in this case.
-  callback_called_ = false;
   DetermineDownloadTarget(&item);
   EXPECT_TRUE(callback_called_);
   EXPECT_EQ(path, target_path_);
   EXPECT_EQ(DownloadItem::TARGET_DISPOSITION_OVERWRITE, target_disposition_);
   EXPECT_EQ(DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, danger_type_);
   EXPECT_EQ(path, intermediate_path_);
+  EXPECT_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason_);
 }
 
 TEST_F(DownloadManagerTest, GetDownloadByGuid) {
diff --git a/content/browser/download/mock_download_item_impl.h b/content/browser/download/mock_download_item_impl.h
index f7aa52e..f0bedb0 100644
--- a/content/browser/download/mock_download_item_impl.h
+++ b/content/browser/download/mock_download_item_impl.h
@@ -26,11 +26,12 @@
                        const DownloadItem::ReceivedSlices& received_slices);
   ~MockDownloadItemImpl() override;
 
-  MOCK_METHOD4(OnDownloadTargetDetermined,
+  MOCK_METHOD5(OnDownloadTargetDetermined,
                void(const base::FilePath&,
                     TargetDisposition,
                     DownloadDangerType,
-                    const base::FilePath&));
+                    const base::FilePath&,
+                    DownloadInterruptReason));
   MOCK_METHOD1(AddObserver, void(DownloadItem::Observer*));
   MOCK_METHOD1(RemoveObserver, void(DownloadItem::Observer*));
   MOCK_METHOD0(UpdateObservers, void());