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/mhtml_generation_manager.h" 6 7#include "base/bind.h" 8#include "base/files/file.h" 9#include "base/stl_util.h" 10#include "content/browser/renderer_host/render_view_host_impl.h" 11#include "content/public/browser/browser_thread.h" 12#include "content/public/browser/render_process_host.h" 13#include "content/public/browser/render_process_host_observer.h" 14#include "content/public/browser/web_contents.h" 15#include "content/common/view_messages.h" 16 17namespace content { 18 19class MHTMLGenerationManager::Job : public RenderProcessHostObserver { 20 public: 21 Job(); 22 virtual ~Job(); 23 24 void SetWebContents(WebContents* web_contents); 25 26 base::File browser_file() { return browser_file_.Pass(); } 27 void set_browser_file(base::File file) { browser_file_ = file.Pass(); } 28 29 int process_id() { return process_id_; } 30 int routing_id() { return routing_id_; } 31 32 GenerateMHTMLCallback callback() { return callback_; } 33 void set_callback(GenerateMHTMLCallback callback) { callback_ = callback; } 34 35 // RenderProcessHostObserver: 36 virtual void RenderProcessExited(RenderProcessHost* host, 37 base::ProcessHandle handle, 38 base::TerminationStatus status, 39 int exit_code) OVERRIDE; 40 virtual void RenderProcessHostDestroyed(RenderProcessHost* host) OVERRIDE; 41 42 43 private: 44 // The handle to the file the MHTML is saved to for the browser process. 45 base::File browser_file_; 46 47 // The IDs mapping to a specific contents. 48 int process_id_; 49 int routing_id_; 50 51 // The callback to call once generation is complete. 52 GenerateMHTMLCallback callback_; 53 54 // The RenderProcessHost being observed, or NULL if none is. 55 RenderProcessHost* host_; 56 DISALLOW_COPY_AND_ASSIGN(Job); 57}; 58 59MHTMLGenerationManager::Job::Job() 60 : process_id_(-1), 61 routing_id_(-1), 62 host_(NULL) { 63} 64 65MHTMLGenerationManager::Job::~Job() { 66 if (host_) 67 host_->RemoveObserver(this); 68} 69 70void MHTMLGenerationManager::Job::SetWebContents(WebContents* web_contents) { 71 process_id_ = web_contents->GetRenderProcessHost()->GetID(); 72 routing_id_ = web_contents->GetRenderViewHost()->GetRoutingID(); 73 host_ = web_contents->GetRenderProcessHost(); 74 host_->AddObserver(this); 75} 76 77void MHTMLGenerationManager::Job::RenderProcessExited( 78 RenderProcessHost* host, 79 base::ProcessHandle handle, 80 base::TerminationStatus status, 81 int exit_code) { 82 MHTMLGenerationManager::GetInstance()->RenderProcessExited(this); 83} 84 85void MHTMLGenerationManager::Job::RenderProcessHostDestroyed( 86 RenderProcessHost* host) { 87 host_ = NULL; 88} 89 90MHTMLGenerationManager* MHTMLGenerationManager::GetInstance() { 91 return Singleton<MHTMLGenerationManager>::get(); 92} 93 94MHTMLGenerationManager::MHTMLGenerationManager() { 95} 96 97MHTMLGenerationManager::~MHTMLGenerationManager() { 98 STLDeleteValues(&id_to_job_); 99} 100 101void MHTMLGenerationManager::SaveMHTML(WebContents* web_contents, 102 const base::FilePath& file, 103 const GenerateMHTMLCallback& callback) { 104 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 105 106 int job_id = NewJob(web_contents, callback); 107 108 base::ProcessHandle renderer_process = 109 web_contents->GetRenderProcessHost()->GetHandle(); 110 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 111 base::Bind(&MHTMLGenerationManager::CreateFile, base::Unretained(this), 112 job_id, file, renderer_process)); 113} 114 115void MHTMLGenerationManager::StreamMHTML( 116 WebContents* web_contents, 117 base::File browser_file, 118 const GenerateMHTMLCallback& callback) { 119 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 120 121 int job_id = NewJob(web_contents, callback); 122 123 base::ProcessHandle renderer_process = 124 web_contents->GetRenderProcessHost()->GetHandle(); 125 IPC::PlatformFileForTransit renderer_file = 126 IPC::GetFileHandleForProcess(browser_file.GetPlatformFile(), 127 renderer_process, false); 128 129 FileAvailable(job_id, browser_file.Pass(), renderer_file); 130} 131 132 133void MHTMLGenerationManager::MHTMLGenerated(int job_id, int64 mhtml_data_size) { 134 JobFinished(job_id, mhtml_data_size); 135} 136 137void MHTMLGenerationManager::CreateFile( 138 int job_id, const base::FilePath& file_path, 139 base::ProcessHandle renderer_process) { 140 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 141 base::File browser_file( 142 file_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); 143 if (!browser_file.IsValid()) { 144 LOG(ERROR) << "Failed to create file to save MHTML at: " << 145 file_path.value(); 146 } 147 148 IPC::PlatformFileForTransit renderer_file = 149 IPC::GetFileHandleForProcess(browser_file.GetPlatformFile(), 150 renderer_process, false); 151 152 BrowserThread::PostTask( 153 BrowserThread::UI, 154 FROM_HERE, 155 base::Bind(&MHTMLGenerationManager::FileAvailable, 156 base::Unretained(this), 157 job_id, 158 base::Passed(&browser_file), 159 renderer_file)); 160} 161 162void MHTMLGenerationManager::FileAvailable( 163 int job_id, 164 base::File browser_file, 165 IPC::PlatformFileForTransit renderer_file) { 166 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 167 if (!browser_file.IsValid()) { 168 LOG(ERROR) << "Failed to create file"; 169 JobFinished(job_id, -1); 170 return; 171 } 172 173 IDToJobMap::iterator iter = id_to_job_.find(job_id); 174 if (iter == id_to_job_.end()) { 175 NOTREACHED(); 176 return; 177 } 178 179 Job* job = iter->second; 180 job->set_browser_file(browser_file.Pass()); 181 182 RenderViewHost* rvh = RenderViewHost::FromID( 183 job->process_id(), job->routing_id()); 184 if (!rvh) { 185 // The contents went away. 186 JobFinished(job_id, -1); 187 return; 188 } 189 190 rvh->Send(new ViewMsg_SavePageAsMHTML(rvh->GetRoutingID(), job_id, 191 renderer_file)); 192} 193 194void MHTMLGenerationManager::JobFinished(int job_id, int64 file_size) { 195 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 196 IDToJobMap::iterator iter = id_to_job_.find(job_id); 197 if (iter == id_to_job_.end()) { 198 NOTREACHED(); 199 return; 200 } 201 202 Job* job = iter->second; 203 job->callback().Run(file_size); 204 205 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 206 base::Bind(&MHTMLGenerationManager::CloseFile, base::Unretained(this), 207 base::Passed(job->browser_file()))); 208 209 id_to_job_.erase(job_id); 210 delete job; 211} 212 213void MHTMLGenerationManager::CloseFile(base::File file) { 214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 215 file.Close(); 216} 217 218int MHTMLGenerationManager::NewJob(WebContents* web_contents, 219 const GenerateMHTMLCallback& callback) { 220 static int id_counter = 0; 221 int job_id = id_counter++; 222 Job* job = new Job(); 223 id_to_job_[job_id] = job; 224 job->SetWebContents(web_contents); 225 job->set_callback(callback); 226 return job_id; 227} 228 229void MHTMLGenerationManager::RenderProcessExited(Job* job) { 230 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 231 for (IDToJobMap::iterator it = id_to_job_.begin(); it != id_to_job_.end(); 232 ++it) { 233 if (it->second == job) { 234 JobFinished(it->first, -1); 235 return; 236 } 237 } 238 NOTREACHED(); 239} 240 241} // namespace content 242