// Copyright 2018 The Chromium Authors. All rights reserved.
// 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/bind.h"
#include "base/task/post_task.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 {

constexpr base::TaskTraits kBlockingSkippableTraits = {
    // 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};

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

}  // namespace

DevToolsMHTMLHelper::DevToolsMHTMLHelper(
    base::WeakPtr<PageHandler> page_handler,
    std::unique_ptr<PageHandler::CaptureSnapshotCallback> callback)
    : page_handler_(page_handler), callback_(std::move(callback)) {}

DevToolsMHTMLHelper::~DevToolsMHTMLHelper() {
  if (mhtml_file_.get()) {
    base::PostTaskWithTraits(
        FROM_HERE, {BrowserThread::IO},
        base::BindOnce(&ClearFileReferenceOnIOThread, std::move(mhtml_file_)));
  }
}

// static
void DevToolsMHTMLHelper::Capture(
    base::WeakPtr<PageHandler> page_handler,
    std::unique_ptr<PageHandler::CaptureSnapshotCallback> callback) {
  scoped_refptr<DevToolsMHTMLHelper> helper =
      new DevToolsMHTMLHelper(page_handler, std::move(callback));
  base::PostTaskWithTraits(
      FROM_HERE, kBlockingSkippableTraits,
      base::BindOnce(&DevToolsMHTMLHelper::CreateTemporaryFile, helper));
}

void DevToolsMHTMLHelper::CreateTemporaryFile() {
  if (!base::CreateTemporaryFile(&mhtml_snapshot_path_)) {
    ReportFailure("Unable to create temporary file");
    return;
  }
  base::PostTaskWithTraits(
      FROM_HERE, {BrowserThread::IO},
      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::CreateSequencedTaskRunnerWithTraits(
          {// 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());

  base::PostTaskWithTraits(
      FROM_HERE, {BrowserThread::UI},
      base::BindOnce(&DevToolsMHTMLHelper::TemporaryFileCreatedOnUI, this));
}

void DevToolsMHTMLHelper::TemporaryFileCreatedOnUI() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (!page_handler_) {
    ReportFailure("");
    return;
  }
  WebContentsImpl* web_contents = page_handler_->GetWebContents();
  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::PostTaskWithTraits(
      FROM_HERE, kBlockingSkippableTraits,
      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)) {
    base::PostTaskWithTraits(
        FROM_HERE, {BrowserThread::UI},
        base::BindOnce(&DevToolsMHTMLHelper::ReportFailure, this, message));
    return;
  }
  if (message.empty())
    callback_->sendFailure(Response::InternalError());
  else
    callback_->sendFailure(Response::Error(message));
}

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

}  // namespace protocol
}  // namespace content
