1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "content/browser/download/drag_download_file.h" 6 7 #include "base/bind.h" 8 #include "base/files/file.h" 9 #include "base/message_loop/message_loop.h" 10 #include "content/browser/download/download_stats.h" 11 #include "content/browser/web_contents/web_contents_impl.h" 12 #include "content/public/browser/browser_context.h" 13 #include "content/public/browser/browser_thread.h" 14 #include "content/public/browser/download_item.h" 15 #include "content/public/browser/download_save_info.h" 16 #include "content/public/browser/download_url_parameters.h" 17 18 namespace content { 19 20 namespace { 21 22 typedef base::Callback<void(bool)> OnCompleted; 23 24 } // namespace 25 26 // On windows, DragDownloadFile runs on a thread other than the UI thread. 27 // DownloadItem and DownloadManager may not be accessed on any thread other than 28 // the UI thread. DragDownloadFile may run on either the "drag" thread or the UI 29 // thread depending on the platform, but DragDownloadFileUI strictly always runs 30 // on the UI thread. On platforms where DragDownloadFile runs on the UI thread, 31 // none of the PostTasks are necessary, but it simplifies the code to do them 32 // anyway. 33 class DragDownloadFile::DragDownloadFileUI : public DownloadItem::Observer { 34 public: 35 DragDownloadFileUI(const GURL& url, 36 const Referrer& referrer, 37 const std::string& referrer_encoding, 38 WebContents* web_contents, 39 base::MessageLoop* on_completed_loop, 40 const OnCompleted& on_completed) 41 : on_completed_loop_(on_completed_loop), 42 on_completed_(on_completed), 43 url_(url), 44 referrer_(referrer), 45 referrer_encoding_(referrer_encoding), 46 web_contents_(web_contents), 47 download_item_(NULL), 48 weak_ptr_factory_(this) { 49 DCHECK(on_completed_loop_); 50 DCHECK(!on_completed_.is_null()); 51 DCHECK(web_contents_); 52 // May be called on any thread. 53 // Do not call weak_ptr_factory_.GetWeakPtr() outside the UI thread. 54 } 55 56 void InitiateDownload(base::File file, 57 const base::FilePath& file_path) { 58 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 59 DownloadManager* download_manager = 60 BrowserContext::GetDownloadManager(web_contents_->GetBrowserContext()); 61 62 RecordDownloadSource(INITIATED_BY_DRAG_N_DROP); 63 scoped_ptr<content::DownloadUrlParameters> params( 64 DownloadUrlParameters::FromWebContents(web_contents_, url_)); 65 params->set_referrer(referrer_); 66 params->set_referrer_encoding(referrer_encoding_); 67 params->set_callback(base::Bind(&DragDownloadFileUI::OnDownloadStarted, 68 weak_ptr_factory_.GetWeakPtr())); 69 params->set_file_path(file_path); 70 params->set_file(file.Pass()); // Nulls file. 71 download_manager->DownloadUrl(params.Pass()); 72 } 73 74 void Cancel() { 75 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 76 if (download_item_) 77 download_item_->Cancel(true); 78 } 79 80 void Delete() { 81 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 82 delete this; 83 } 84 85 private: 86 virtual ~DragDownloadFileUI() { 87 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 88 if (download_item_) 89 download_item_->RemoveObserver(this); 90 } 91 92 void OnDownloadStarted(DownloadItem* item, 93 DownloadInterruptReason interrupt_reason) { 94 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 95 if (!item) { 96 DCHECK_NE(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); 97 on_completed_loop_->PostTask(FROM_HERE, base::Bind(on_completed_, false)); 98 return; 99 } 100 DCHECK_EQ(DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason); 101 download_item_ = item; 102 download_item_->AddObserver(this); 103 } 104 105 // DownloadItem::Observer: 106 virtual void OnDownloadUpdated(DownloadItem* item) OVERRIDE { 107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 108 DCHECK_EQ(download_item_, item); 109 DownloadItem::DownloadState state = download_item_->GetState(); 110 if (state == DownloadItem::COMPLETE || 111 state == DownloadItem::CANCELLED || 112 state == DownloadItem::INTERRUPTED) { 113 if (!on_completed_.is_null()) { 114 on_completed_loop_->PostTask(FROM_HERE, base::Bind( 115 on_completed_, state == DownloadItem::COMPLETE)); 116 on_completed_.Reset(); 117 } 118 download_item_->RemoveObserver(this); 119 download_item_ = NULL; 120 } 121 // Ignore other states. 122 } 123 124 virtual void OnDownloadDestroyed(DownloadItem* item) OVERRIDE { 125 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 126 DCHECK_EQ(download_item_, item); 127 if (!on_completed_.is_null()) { 128 const bool is_complete = 129 download_item_->GetState() == DownloadItem::COMPLETE; 130 on_completed_loop_->PostTask(FROM_HERE, base::Bind( 131 on_completed_, is_complete)); 132 on_completed_.Reset(); 133 } 134 download_item_->RemoveObserver(this); 135 download_item_ = NULL; 136 } 137 138 base::MessageLoop* on_completed_loop_; 139 OnCompleted on_completed_; 140 GURL url_; 141 Referrer referrer_; 142 std::string referrer_encoding_; 143 WebContents* web_contents_; 144 DownloadItem* download_item_; 145 146 // Only used in the callback from DownloadManager::DownloadUrl(). 147 base::WeakPtrFactory<DragDownloadFileUI> weak_ptr_factory_; 148 149 DISALLOW_COPY_AND_ASSIGN(DragDownloadFileUI); 150 }; 151 152 DragDownloadFile::DragDownloadFile(const base::FilePath& file_path, 153 base::File file, 154 const GURL& url, 155 const Referrer& referrer, 156 const std::string& referrer_encoding, 157 WebContents* web_contents) 158 : file_path_(file_path), 159 file_(file.Pass()), 160 drag_message_loop_(base::MessageLoop::current()), 161 state_(INITIALIZED), 162 drag_ui_(NULL), 163 weak_ptr_factory_(this) { 164 drag_ui_ = new DragDownloadFileUI( 165 url, 166 referrer, 167 referrer_encoding, 168 web_contents, 169 drag_message_loop_, 170 base::Bind(&DragDownloadFile::DownloadCompleted, 171 weak_ptr_factory_.GetWeakPtr())); 172 DCHECK(!file_path_.empty()); 173 } 174 175 DragDownloadFile::~DragDownloadFile() { 176 CheckThread(); 177 178 // This is the only place that drag_ui_ can be deleted from. Post a message to 179 // the UI thread so that it calls RemoveObserver on the right thread, and so 180 // that this task will run after the InitiateDownload task runs on the UI 181 // thread. 182 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 183 &DragDownloadFileUI::Delete, base::Unretained(drag_ui_))); 184 drag_ui_ = NULL; 185 } 186 187 void DragDownloadFile::Start(ui::DownloadFileObserver* observer) { 188 CheckThread(); 189 190 if (state_ != INITIALIZED) 191 return; 192 state_ = STARTED; 193 194 DCHECK(!observer_.get()); 195 observer_ = observer; 196 DCHECK(observer_.get()); 197 198 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 199 &DragDownloadFileUI::InitiateDownload, base::Unretained(drag_ui_), 200 base::Passed(&file_), file_path_)); 201 } 202 203 bool DragDownloadFile::Wait() { 204 CheckThread(); 205 if (state_ == STARTED) 206 nested_loop_.Run(); 207 return state_ == SUCCESS; 208 } 209 210 void DragDownloadFile::Stop() { 211 CheckThread(); 212 if (drag_ui_) { 213 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind( 214 &DragDownloadFileUI::Cancel, base::Unretained(drag_ui_))); 215 } 216 } 217 218 void DragDownloadFile::DownloadCompleted(bool is_successful) { 219 CheckThread(); 220 221 state_ = is_successful ? SUCCESS : FAILURE; 222 223 if (is_successful) 224 observer_->OnDownloadCompleted(file_path_); 225 else 226 observer_->OnDownloadAborted(); 227 228 // Release the observer since we do not need it any more. 229 observer_ = NULL; 230 231 if (nested_loop_.running()) 232 nested_loop_.Quit(); 233 } 234 235 void DragDownloadFile::CheckThread() { 236 #if defined(OS_WIN) 237 DCHECK(drag_message_loop_ == base::MessageLoop::current()); 238 #else 239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 240 #endif 241 } 242 243 } // namespace content 244