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