Home | History | Annotate | Download | only in download
      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