910e62b5创建于 1月15日历史提交
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/devtools/protocol/devtools_mhtml_helper.h"

#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/task/thread_pool.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/mhtml_generation_params.h"
#include "storage/browser/blob/shareable_file_reference.h"

namespace content {
namespace protocol {

namespace {

void ClearFileReferenceOnIOThread(
    scoped_refptr<storage::ShareableFileReference>) {}

}  // namespace

DevToolsMHTMLHelper::DevToolsMHTMLHelper(
    const WebContents::Getter& web_contents_getter,
    std::unique_ptr<PageHandler::CaptureSnapshotCallback> callback)
    : web_contents_getter_(web_contents_getter),
      callback_(std::move(callback)) {}

DevToolsMHTMLHelper::~DevToolsMHTMLHelper() {
  if (mhtml_file_.get()) {
    GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&ClearFileReferenceOnIOThread, std::move(mhtml_file_)));
  }
}

// static
void DevToolsMHTMLHelper::Capture(
    const WebContents::Getter& web_contents_getter,
    std::unique_ptr<PageHandler::CaptureSnapshotCallback> callback) {
  scoped_refptr<DevToolsMHTMLHelper> helper =
      new DevToolsMHTMLHelper(web_contents_getter, std::move(callback));
  base::ThreadPool::PostTask(
      FROM_HERE,
      {// Requires IO.
       base::MayBlock(),

       // TaskShutdownBehavior: use SKIP_ON_SHUTDOWN so that the helper's
       // fields do not suddenly become invalid.
       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
      base::BindOnce(&DevToolsMHTMLHelper::CreateTemporaryFile, helper));
}

void DevToolsMHTMLHelper::CreateTemporaryFile() {
  if (!base::CreateTemporaryFile(&mhtml_snapshot_path_)) {
    ReportFailure("Unable to create temporary file");
    return;
  }
  GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&DevToolsMHTMLHelper::TemporaryFileCreatedOnIO, this));
}

void DevToolsMHTMLHelper::TemporaryFileCreatedOnIO() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  // Setup a ShareableFileReference so the temporary file gets deleted
  // once it is no longer used.
  mhtml_file_ = storage::ShareableFileReference::GetOrCreate(
      mhtml_snapshot_path_,
      storage::ShareableFileReference::DELETE_ON_FINAL_RELEASE,
      base::ThreadPool::CreateSequencedTaskRunner(
          {// Requires IO.
           base::MayBlock(),

           // Because we are using DELETE_ON_FINAL_RELEASE here, the
           // storage::ScopedFile inside ShareableFileReference requires
           // a shutdown blocking task runner to ensure that its deletion
           // task runs.
           base::TaskShutdownBehavior::BLOCK_SHUTDOWN})
          .get());

  GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&DevToolsMHTMLHelper::TemporaryFileCreatedOnUI, this));
}

void DevToolsMHTMLHelper::TemporaryFileCreatedOnUI() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  WebContents* web_contents = web_contents_getter_.Run();
  if (!web_contents) {
    ReportFailure("No web contents");
    return;
  }
  web_contents->GenerateMHTML(
      content::MHTMLGenerationParams(mhtml_snapshot_path_),
      base::BindOnce(&DevToolsMHTMLHelper::MHTMLGeneratedOnUI, this));
}

void DevToolsMHTMLHelper::MHTMLGeneratedOnUI(int64_t mhtml_file_size) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (mhtml_file_size <= 0 ||
      mhtml_file_size > std::numeric_limits<int>::max()) {
    ReportFailure("Failed to generate MHTML");
    return;
  }
  base::ThreadPool::PostTask(
      FROM_HERE,
      {// Requires IO.
       base::MayBlock(),

       // TaskShutdownBehavior: use SKIP_ON_SHUTDOWN so that the
       // helper's fields do not suddenly become invalid.
       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
      base::BindOnce(&DevToolsMHTMLHelper::ReadMHTML, this));
}

void DevToolsMHTMLHelper::ReadMHTML() {
  std::string buffer;
  if (!base::ReadFileToString(mhtml_snapshot_path_, &buffer)) {
    LOG(ERROR) << "Failed to read " << mhtml_snapshot_path_;
    ReportFailure("Failed to read MHTML file");
    return;
  }
  std::unique_ptr<std::string> buffer_ptr =
      std::make_unique<std::string>(buffer);
  ReportSuccess(std::move(buffer_ptr));
}

void DevToolsMHTMLHelper::ReportFailure(const std::string& message) {
  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&DevToolsMHTMLHelper::ReportFailure, this, message));
    return;
  }
  if (message.empty())
    callback_->sendFailure(Response::InternalError());
  else
    callback_->sendFailure(Response::ServerError(message));
}

void DevToolsMHTMLHelper::ReportSuccess(
    std::unique_ptr<std::string> mhtml_snapshot) {
  if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
    GetUIThreadTaskRunner({})->PostTask(
        FROM_HERE, base::BindOnce(&DevToolsMHTMLHelper::ReportSuccess, this,
                                  std::move(mhtml_snapshot)));
    return;
  }
  callback_->sendSuccess(*mhtml_snapshot);
}

}  // namespace protocol
}  // namespace content