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 17 namespace content { 18 19 class 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 59 MHTMLGenerationManager::Job::Job() 60 : process_id_(-1), 61 routing_id_(-1), 62 host_(NULL) { 63 } 64 65 MHTMLGenerationManager::Job::~Job() { 66 if (host_) 67 host_->RemoveObserver(this); 68 } 69 70 void 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 77 void 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 85 void MHTMLGenerationManager::Job::RenderProcessHostDestroyed( 86 RenderProcessHost* host) { 87 host_ = NULL; 88 } 89 90 MHTMLGenerationManager* MHTMLGenerationManager::GetInstance() { 91 return Singleton<MHTMLGenerationManager>::get(); 92 } 93 94 MHTMLGenerationManager::MHTMLGenerationManager() { 95 } 96 97 MHTMLGenerationManager::~MHTMLGenerationManager() { 98 STLDeleteValues(&id_to_job_); 99 } 100 101 void 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 115 void 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 133 void MHTMLGenerationManager::MHTMLGenerated(int job_id, int64 mhtml_data_size) { 134 JobFinished(job_id, mhtml_data_size); 135 } 136 137 void 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 162 void 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 194 void 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 213 void MHTMLGenerationManager::CloseFile(base::File file) { 214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 215 file.Close(); 216 } 217 218 int 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 229 void 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