blob: b60dcce29894181fb39766eb4101cef8f0ddfce7 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_SERVICES_PRINT_COMPOSITOR_PRINT_COMPOSITOR_IMPL_H_
#define COMPONENTS_SERVICES_PRINT_COMPOSITOR_PRINT_COMPOSITOR_IMPL_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/gtest_prod_util.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_refptr.h"
#include "build/build_config.h"
#include "components/enterprise/buildflags/buildflags.h"
#include "components/services/print_compositor/public/cpp/print_service_mojo_types.h"
#include "components/services/print_compositor/public/mojom/print_compositor.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "printing/buildflags/buildflags.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkStream.h"
#include "third_party/skia/include/core/SkTypeface.h"
#include "ui/accessibility/ax_tree_update.h"
#if BUILDFLAG(ENTERPRISE_WATERMARK)
#include "components/enterprise/watermarking/mojom/watermark.mojom-forward.h" // nogncheck
#endif
class SkDocument;
struct SkDocumentPage;
namespace base {
class SingleThreadTaskRunner;
}
namespace discardable_memory {
class ClientDiscardableSharedMemoryManager;
}
namespace printing {
#if BUILDFLAG(IS_WIN)
class ScopedXPSInitializer;
#endif
class PrintCompositorImpl : public mojom::PrintCompositor {
public:
// Creates an instance with an optional Mojo receiver (may be null) and
// optional initialization of the runtime environment necessary for
// compositing operations. `io_task_runner` is used for shared memory
// management, if and only if there is a receiver, which may not be the case
// in unit tests. In practice, `initialize_environment` is only false in unit
// tests.
PrintCompositorImpl(
mojo::PendingReceiver<mojom::PrintCompositor> receiver,
bool initialize_environment,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
PrintCompositorImpl(const PrintCompositorImpl&) = delete;
PrintCompositorImpl& operator=(const PrintCompositorImpl&) = delete;
~PrintCompositorImpl() override;
// mojom::PrintCompositor
void NotifyUnavailableSubframe(uint64_t frame_guid) override;
void AddSubframeContent(
uint64_t frame_guid,
base::ReadOnlySharedMemoryRegion serialized_content,
const ContentToFrameMap& subframe_content_map) override;
void SetAccessibilityTree(
const ui::AXTreeUpdate& accessibility_tree) override;
void CompositePage(
uint64_t frame_guid,
base::ReadOnlySharedMemoryRegion serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PrintCompositor::CompositePageCallback callback) override;
void CompositeDocument(
uint64_t frame_guid,
base::ReadOnlySharedMemoryRegion serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PrintCompositor::DocumentType document_type,
mojom::PrintCompositor::CompositeDocumentCallback callback) override;
void PrepareToCompositeDocument(
mojom::PrintCompositor::DocumentType document_type,
mojom::PrintCompositor::PrepareToCompositeDocumentCallback callback)
override;
void FinishDocumentComposition(
uint32_t page_count,
mojom::PrintCompositor::FinishDocumentCompositionCallback callback)
override;
void SetWebContentsURL(const GURL& url) override;
void SetUserAgent(const std::string& user_agent) override;
void SetGenerateDocumentOutline(
mojom::GenerateDocumentOutline generate_document_outline) override;
void SetTitle(const std::string& title) override;
#if BUILDFLAG(ENTERPRISE_WATERMARK)
void SetWatermarkBlock(
watermark::mojom::WatermarkBlockPtr watermark_block) override;
#endif
protected:
// This is the uniform underlying type for both
// mojom::PrintCompositor::CompositePageCallback and
// mojom::PrintCompositor::CompositeDocumentCallback.
using CompositePagesCallback =
base::OnceCallback<void(PrintCompositor::Status,
base::ReadOnlySharedMemoryRegion)>;
using PrepareForDocumentCompositionCallback =
base::OnceCallback<void(PrintCompositor::Status)>;
using FinishDocumentCompositionCallback =
base::OnceCallback<void(PrintCompositor::Status,
base::ReadOnlySharedMemoryRegion)>;
// The core function for content composition and conversion to a PDF file,
// and possibly also into a full document PDF/XPS file.
// Make this function virtual so tests can override it.
virtual mojom::PrintCompositor::Status CompositePages(
base::span<const uint8_t> serialized_content,
const ContentToFrameMap& subframe_content_map,
base::ReadOnlySharedMemoryRegion* region,
mojom::PrintCompositor::DocumentType document_type);
// Make these functions virtual so tests can override them.
virtual void DrawPage(SkDocument* doc, const SkDocumentPage& page);
virtual void FulfillRequest(
base::span<const uint8_t> serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PrintCompositor::DocumentType document_type,
CompositePagesCallback callback);
virtual void FinishDocumentRequest(
FinishDocumentCompositionCallback callback);
#if BUILDFLAG(ENTERPRISE_WATERMARK)
// Accessor for watermark block for tests
const watermark::mojom::WatermarkBlockPtr& watermark_block_for_testing()
const;
#endif
private:
FRIEND_TEST_ALL_PREFIXES(PrintCompositorImplTest, IsReadyToComposite);
FRIEND_TEST_ALL_PREFIXES(PrintCompositorImplTest, MultiLayerDependency);
FRIEND_TEST_ALL_PREFIXES(PrintCompositorImplTest, DependencyLoop);
friend class MockCompletionPrintCompositorImpl;
// The map needed during content deserialization. It stores the mapping
// between content id and its actual content.
using PictureDeserializationContext =
base::flat_map<uint32_t, sk_sp<SkPicture>>;
using TypefaceDeserializationContext =
base::flat_map<uint32_t, sk_sp<SkTypeface>>;
using ImageDeserializationContext = base::flat_map<uint32_t, sk_sp<SkImage>>;
// Base structure to store a frame's content and its subframe
// content information.
struct FrameContentInfo {
FrameContentInfo(base::span<const uint8_t> content,
const ContentToFrameMap& map);
FrameContentInfo();
~FrameContentInfo();
// Serialized SkPicture content of this frame.
std::vector<uint8_t> serialized_content;
// Frame content after composition with subframe content.
sk_sp<SkPicture> content;
// Subframe content id and its corresponding frame guid.
ContentToFrameMap subframe_content_map;
// Typefaces used within scope of this frame.
TypefaceDeserializationContext typefaces;
// Images used within scope of this frame.
ImageDeserializationContext images;
};
// Other than content, it also stores the status during frame composition.
struct FrameInfo : public FrameContentInfo {
using FrameContentInfo::FrameContentInfo;
// The following fields are used for storing composition status.
// Set to true when this frame's |serialized_content| is composed with
// subframe content and the final result is stored in |content|.
bool composited = false;
};
// Stores the mapping between frame's global unique ids and their
// corresponding frame information.
using FrameMap = base::flat_map<uint64_t, std::unique_ptr<FrameInfo>>;
// Stores the page or document's request information.
struct RequestInfo : public FrameContentInfo {
RequestInfo(base::span<const uint8_t> content,
const ContentToFrameMap& content_info,
const base::flat_set<uint64_t>& pending_subframes,
mojom::PrintCompositor::DocumentType document_type,
CompositePagesCallback callback);
~RequestInfo();
// All pending frame ids whose content is not available but needed
// for composition.
base::flat_set<uint64_t> pending_subframes;
mojom::PrintCompositor::DocumentType document_type;
CompositePagesCallback callback;
};
// Stores the concurrent document composition information.
//
// While PrintCompositorImpl is creating a document for every page it is
// compositing, it can reuse the same page info to concurrently create the
// full document with all pages. Only used when PrepareToCompositeDocument()
// gets called.
struct DocumentInfo {
explicit DocumentInfo(mojom::PrintCompositor::DocumentType document_type);
~DocumentInfo();
SkDynamicMemoryWStream compositor_stream;
sk_sp<SkDocument> doc;
mojom::PrintCompositor::DocumentType document_type;
uint32_t pages_written = 0;
uint32_t page_count = 0;
FinishDocumentCompositionCallback callback;
};
// Check whether any request is waiting for the specific subframe, if so,
// update its dependecy with the subframe's pending child frames.
void UpdateRequestsWithSubframeInfo(
uint64_t frame_guid,
const std::vector<uint64_t>& pending_subframes);
// Check whether the frame with a list of subframe content is ready to
// composite. If not, return all unavailable frames' ids in
// |pending_subframes|.
bool IsReadyToComposite(uint64_t frame_guid,
const ContentToFrameMap& subframe_content_map,
base::flat_set<uint64_t>* pending_subframes) const;
// Recursively check all the subframes in |subframe_content_map| and put those
// not ready in |pending_subframes|.
void CheckFramesForReadiness(const ContentToFrameMap& subframe_content_map,
base::flat_set<uint64_t>* pending_subframes,
base::flat_set<uint64_t>* visited) const;
// The internal implementation for handling page and documentation composition
// requests.
void HandleCompositionRequest(
uint64_t frame_guid,
base::ReadOnlySharedMemoryRegion serialized_content,
const ContentToFrameMap& subframe_content_ids,
mojom::PrintCompositor::DocumentType document_type,
CompositePagesCallback callback);
void HandleDocumentCompletionRequest();
// Composite the content of a subframe.
void CompositeSubframe(FrameInfo* frame_info);
PictureDeserializationContext GetPictureDeserializationContext(
const ContentToFrameMap& subframe_content_map);
mojo::Receiver<mojom::PrintCompositor> receiver_{this};
#if BUILDFLAG(IS_WIN)
std::unique_ptr<ScopedXPSInitializer> xps_initializer_;
#endif
const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
scoped_refptr<discardable_memory::ClientDiscardableSharedMemoryManager>
discardable_shared_memory_manager_;
// The creator of this service.
// Currently contains the service creator's user agent string if given,
// otherwise just use string "Chromium".
std::string creator_ = "Chromium";
// Keep track of all frames' information indexed by frame id.
FrameMap frame_info_map_;
// Context for dealing with all typefaces encountered across multiple pages.
TypefaceDeserializationContext typefaces_;
// Context for dealing with all images encountered across multiple pages.
ImageDeserializationContext images_;
std::vector<std::unique_ptr<RequestInfo>> requests_;
std::unique_ptr<DocumentInfo> doc_info_;
// If present, the accessibility tree for the document needed to
// export a tagged (accessible) PDF.
ui::AXTreeUpdate accessibility_tree_;
// How (or if) to generate a document outline.
mojom::GenerateDocumentOutline generate_document_outline_ =
mojom::GenerateDocumentOutline::kNone;
// The title of the document.
std::string title_;
#if BUILDFLAG(ENTERPRISE_WATERMARK)
// The watermark block. The special value `nullptr` indicates that there is no
// watermark.
watermark::mojom::WatermarkBlockPtr watermark_block_;
#endif
};
#if BUILDFLAG(ENTERPRISE_WATERMARK)
// Draw the watermark specified by `watermark_block` using the provided canvas
// and its size. Exposed for testing.
void DrawEnterpriseWatermark(
SkCanvas* canvas,
SkSize size,
const watermark::mojom::WatermarkBlockPtr& watermark_block);
#endif
} // namespace printing
#endif // COMPONENTS_SERVICES_PRINT_COMPOSITOR_PRINT_COMPOSITOR_IMPL_H_