chromium/android_webview/browser/aw_print_manager.cc

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "android_webview/browser/aw_print_manager.h"

#include <utility>

#include "base/file_descriptor_posix.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted_memory.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "components/printing/browser/print_manager_utils.h"
#include "components/printing/common/print.mojom.h"
#include "components/printing/common/print_params.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "printing/print_job_constants.h"
#include "printing/print_settings.h"

namespace android_webview {

namespace {

uint32_t SaveDataToFd(int fd,
                      uint32_t page_count,
                      scoped_refptr<base::RefCountedSharedMemoryMapping> data) {
  bool result = fd > base::kInvalidFd &&
                base::IsValueInRangeForNumericType<int>(data->size());
  if (result)
    result = base::WriteFileDescriptor(fd, *data);
  return result ? page_count : 0;
}

}  // namespace

AwPrintManager::AwPrintManager(content::WebContents* contents)
    : PrintManager(contents),
      content::WebContentsUserData<AwPrintManager>(*contents) {}

AwPrintManager::~AwPrintManager() = default;

// static
void AwPrintManager::BindPrintManagerHost(
    mojo::PendingAssociatedReceiver<printing::mojom::PrintManagerHost> receiver,
    content::RenderFrameHost* rfh) {
  auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
  if (!web_contents)
    return;
  auto* print_manager = AwPrintManager::FromWebContents(web_contents);
  if (!print_manager)
    return;
  print_manager->BindReceiver(std::move(receiver), rfh);
}

void AwPrintManager::PdfWritingDone(int page_count) {
  pdf_writing_done_callback().Run(page_count);
  // Invalidate the file descriptor so it doesn't get reused.
  fd_ = -1;
}

bool AwPrintManager::PrintNow() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto* rfh = web_contents()->GetPrimaryMainFrame();
  if (!rfh->IsRenderFrameLive())
    return false;
  GetPrintRenderFrame(rfh)->PrintRequestedPages();
  return true;
}

void AwPrintManager::GetDefaultPrintSettings(
    GetDefaultPrintSettingsCallback callback) {
  // Unlike PrintViewManagerBase, we do process this in UI thread.
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto params = printing::mojom::PrintParams::New();
  printing::RenderParamsFromPrintSettings(*settings_, params.get());
  params->document_cookie = cookie();
  if (!printing::PrintMsgPrintParamsIsValid(*params)) {
    std::move(callback).Run(nullptr);
    return;
  }

  std::move(callback).Run(std::move(params));
}

void AwPrintManager::UpdateParam(
    std::unique_ptr<printing::PrintSettings> settings,
    int file_descriptor,
    PrintManager::PdfWritingDoneCallback callback) {
  DCHECK(settings);
  DCHECK(callback);
  settings_ = std::move(settings);
  fd_ = file_descriptor;
  set_pdf_writing_done_callback(std::move(callback));
  set_cookie(printing::PrintSettings::NewCookie());
}

void AwPrintManager::ScriptedPrint(
    printing::mojom::ScriptedPrintParamsPtr scripted_params,
    ScriptedPrintCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (scripted_params->is_scripted &&
      GetCurrentTargetFrame()->IsNestedWithinFencedFrame()) {
    DLOG(ERROR) << "Unexpected message received. Script Print is not allowed"
                   " in a fenced frame.";
    std::move(callback).Run(nullptr);
    return;
  }

  auto params = printing::mojom::PrintPagesParams::New();
  params->params = printing::mojom::PrintParams::New();
  printing::RenderParamsFromPrintSettings(*settings_, params->params.get());
  params->params->document_cookie = scripted_params->cookie;
  params->pages = settings_->ranges();

  if (!printing::PrintMsgPrintParamsIsValid(*params->params)) {
    std::move(callback).Run(nullptr);
    return;
  }

  std::move(callback).Run(std::move(params));
}

void AwPrintManager::DidPrintDocument(
    printing::mojom::DidPrintDocumentParamsPtr params,
    DidPrintDocumentCallback callback) {
  if (params->document_cookie != cookie()) {
    std::move(callback).Run(false);
    return;
  }

  const printing::mojom::DidPrintContentParams& content = *params->content;
  if (!content.metafile_data_region.IsValid()) {
    NOTREACHED() << "invalid memory handle";
  }

  auto data = base::RefCountedSharedMemoryMapping::CreateFromWholeRegion(
      content.metafile_data_region);
  if (!data) {
    NOTREACHED() << "couldn't map";
  }

  if (number_pages() > printing::kMaxPageCount) {
    web_contents()->Stop();
    PdfWritingDone(0);
    std::move(callback).Run(false);
    return;
  }

  DCHECK(pdf_writing_done_callback());
  base::ThreadPool::CreateTaskRunner(
      {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
       base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})
      ->PostTaskAndReplyWithResult(
          FROM_HERE, base::BindOnce(&SaveDataToFd, fd_, number_pages(), data),
          base::BindOnce(&AwPrintManager::OnDidPrintDocumentWritingDone,
                         pdf_writing_done_callback(), std::move(callback)));
}

// static
void AwPrintManager::OnDidPrintDocumentWritingDone(
    const PdfWritingDoneCallback& callback,
    DidPrintDocumentCallback did_print_document_cb,
    uint32_t page_count) {
  DCHECK_LE(page_count, printing::kMaxPageCount);
  if (callback)
    callback.Run(base::checked_cast<int>(page_count));
  std::move(did_print_document_cb).Run(true);
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(AwPrintManager);

}  // namespace android_webview