0

Add printing support to composite into XPS document

Update the Print Compositor to support generating an XPS object model
document for Windows.

The Print Compositor service now needs to access XPS system libraries,
which are done via the calls made to Skia.  These calls fail with
access denied errors with the existing Print Compositor sandboxing
scheme.

Bug: 40100562
Change-Id: Ic9faa463ec9f864e806b442e197f1b1d46ffe163
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1984629
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Amy Huang <akhuang@google.com>
Commit-Queue: Alan Screen <awscreen@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1283365}
This commit is contained in:
Alan Screen
2024-04-05 20:37:01 +00:00
committed by Chromium LUCI CQ
parent c2239f840b
commit 84b404b26a
13 changed files with 143 additions and 23 deletions

@ -425,6 +425,7 @@ config("delayloads") {
"/DELAYLOAD:dxgi.dll",
"/DELAYLOAD:dxva2.dll",
"/DELAYLOAD:esent.dll",
"/DELAYLOAD:fontsub.dll",
"/DELAYLOAD:gdi32.dll",
"/DELAYLOAD:hid.dll",
"/DELAYLOAD:imagehlp.dll",

@ -108,6 +108,7 @@
#if BUILDFLAG(IS_WIN)
#include "printing/printing_utils.h"
#include "sandbox/policy/switches.h"
#endif
namespace printing {
@ -2206,6 +2207,16 @@ class PrintCompositorDocumentDataTypeBrowserTest
PrintBrowserTest::SetUp();
}
// TODO(crbug.com/40100562): Tests that generate XPS currently only succeed
// if the test is run with sandboxing disabled. Remove this once sandboxing
// changes for XPS are applied to the PrintCompositor service.
void SetUpCommandLine(base::CommandLine* command_line) override {
PrintBrowserTest::SetUpCommandLine(command_line);
if (GetParam() == DocumentDataType::kXps) {
command_line->AppendSwitch(sandbox::policy::switches::kNoSandbox);
}
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
@ -2228,10 +2239,8 @@ IN_PROC_BROWSER_TEST_P(PrintCompositorDocumentDataTypeBrowserTest,
"window.print();");
print_preview_observer.WaitUntilPreviewIsReady();
// TODO(crbug.com/40100562): Update to expect XPS data once Print Compositor
// is updated for generating XPS.
EXPECT_THAT(print_preview_observer.last_document_composite_data_type(),
testing::Optional(DocumentDataType::kPdf));
testing::Optional(GetParam()));
}
#endif // BUILDFLAG(IS_WIN)

@ -7,11 +7,17 @@
#include <utility>
#include "base/containers/contains.h"
#include "base/dcheck_is_on.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/singleton.h"
#include "build/build_config.h"
#include "printing/print_job_constants.h"
#include "printing/printing_utils.h"
#if BUILDFLAG(IS_WIN)
#include "chrome/browser/printing/xps_features.h"
#endif
// PrintPreviewDataStore stores data for preview workflow and preview printing
// workflow.
//
@ -55,7 +61,9 @@ class PrintPreviewDataStore {
return;
DCHECK(data);
DCHECK(printing::LooksLikePdf(*data));
#if DCHECK_IS_ON()
DCHECK(IsValidData(index, *data));
#endif
page_data_map_[index] = std::move(data);
}
@ -69,6 +77,27 @@ class PrintPreviewDataStore {
using PreviewPageDataMap =
std::map<int, scoped_refptr<base::RefCountedMemory>>;
#if DCHECK_IS_ON()
bool IsValidData(int index, base::span<const uint8_t> data) const {
#if BUILDFLAG(IS_WIN)
// Do not have access here whether this print document is from a modifiable
// source or not, so next best restriction is if some kind of XPS data
// generation is to be expected.
if (index == printing::COMPLETE_PREVIEW_DOCUMENT_INDEX &&
printing::IsXpsPrintCapabilityRequired()) {
// A valid Windows document could be PDF or XPS.
printing::DocumentDataType data_type =
printing::DetermineDocumentDataType(data);
return data_type == printing::DocumentDataType::kPdf ||
data_type == printing::DocumentDataType::kXps;
}
#endif
// Non-Windows and all individual pages are only ever supposed to be PDF.
return printing::LooksLikePdf(data);
}
#endif // DCHECK_IS_ON()
static bool IsInvalidIndex(int index) {
return (index != printing::COMPLETE_PREVIEW_DOCUMENT_INDEX &&
index < printing::FIRST_PAGE_INDEX);

@ -234,7 +234,8 @@ void PrintCompositeClient::PrepareToCompositeDocument(
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!GetIsDocumentConcurrentlyComposited(document_cookie));
auto* compositor = CreateCompositeRequest(document_cookie, render_frame_host);
auto* compositor =
CreateCompositeRequest(document_cookie, render_frame_host, document_type);
is_doc_concurrently_composited_ = true;
compositor->PrepareToCompositeDocument(
document_type,
@ -274,7 +275,8 @@ void PrintCompositeClient::CompositeDocument(
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!GetIsDocumentConcurrentlyComposited(document_cookie));
auto* compositor = CreateCompositeRequest(document_cookie, render_frame_host);
auto* compositor =
CreateCompositeRequest(document_cookie, render_frame_host, document_type);
compositor->SetAccessibilityTree(accessibility_tree);
for (auto& requested : requested_subframes_) {
@ -344,7 +346,8 @@ bool PrintCompositeClient::GetIsDocumentConcurrentlyComposited(
mojom::PrintCompositor* PrintCompositeClient::CreateCompositeRequest(
int cookie,
content::RenderFrameHost* initiator_frame) {
content::RenderFrameHost* initiator_frame,
mojom::PrintCompositor::DocumentType document_type) {
DCHECK(initiator_frame);
if (document_cookie_ != 0) {

@ -133,7 +133,8 @@ class PrintCompositeClient
// Returns the created composite request.
mojom::PrintCompositor* CreateCompositeRequest(
int cookie,
content::RenderFrameHost* initiator_frame);
content::RenderFrameHost* initiator_frame,
mojom::PrintCompositor::DocumentType document_type);
// Remove the existing composite request.
void RemoveCompositeRequest(int cookie);

@ -21,7 +21,10 @@ static_library("print_compositor") {
]
if (is_win) {
deps += [ "//content/public/child" ]
deps += [
"//content/public/child",
"//printing:printing_base",
]
}
public_deps = [

@ -6,6 +6,7 @@ include_rules = [
"+content/public/common",
"+content/public/utility",
"+mojo/public/cpp",
"+printing/backend",
"+printing/buildflags",
"+printing/common",
"+skia",

@ -32,6 +32,7 @@
#if BUILDFLAG(IS_WIN)
#include "content/public/child/dwrite_font_proxy_init_win.h"
#include "printing/backend/win_helper.h" // nogncheck
#elif BUILDFLAG(IS_APPLE)
#include "third_party/blink/public/platform/platform.h"
#include "third_party/skia/include/core/SkFontMgr.h"
@ -44,6 +45,29 @@ using MojoDiscardableSharedMemoryManager =
namespace printing {
namespace {
sk_sp<SkDocument> MakeDocument(
const std::string& creator,
const std::string& title,
ui::AXTreeUpdate* accessibility_tree,
GeneratePdfDocumentOutline generate_document_outline,
mojom::PrintCompositor::DocumentType document_type,
SkWStream& stream) {
#if BUILDFLAG(IS_WIN)
if (document_type == mojom::PrintCompositor::DocumentType::kXPS) {
return MakeXpsDocument(&stream);
}
#endif
CHECK_EQ(document_type, mojom::PrintCompositor::DocumentType::kPDF);
return MakePdfDocument(
creator, title,
accessibility_tree ? *accessibility_tree : ui::AXTreeUpdate(),
generate_document_outline, &stream);
}
} // namespace
PrintCompositorImpl::PrintCompositorImpl(
mojo::PendingReceiver<mojom::PrintCompositor> receiver,
bool initialize_environment,
@ -184,6 +208,11 @@ void PrintCompositorImpl::PrepareToCompositeDocument(
mojom::PrintCompositor::DocumentType document_type,
mojom::PrintCompositor::PrepareToCompositeDocumentCallback callback) {
DCHECK(!docinfo_);
#if BUILDFLAG(IS_WIN)
if (document_type == mojom::PrintCompositor::DocumentType::kXPS) {
xps_initializer_ = std::make_unique<ScopedXPSInitializer>();
}
#endif
docinfo_ = std::make_unique<DocumentInfo>(document_type);
std::move(callback).Run(mojom::PrintCompositor::Status::kSuccess);
}
@ -197,9 +226,10 @@ void PrintCompositorImpl::FinishDocumentComposition(
docinfo_->callback = std::move(callback);
if (!docinfo_->doc) {
docinfo_->doc = MakePdfDocument(creator_, title_, accessibility_tree_,
GeneratePdfDocumentOutline::kNone,
&docinfo_->compositor_stream);
docinfo_->doc =
MakeDocument(creator_, title_, &accessibility_tree_,
GeneratePdfDocumentOutline::kNone, docinfo_->document_type,
docinfo_->compositor_stream);
}
HandleDocumentCompletionRequest();
@ -366,9 +396,9 @@ mojom::PrintCompositor::Status PrintCompositorImpl::CompositePages(
// document composition is not in effect, i.e. when handling
// CompositeDocumentToPdf() call.
SkDynamicMemoryWStream wstream;
sk_sp<SkDocument> doc = MakePdfDocument(
creator_, title_, docinfo_ ? ui::AXTreeUpdate() : accessibility_tree_,
GeneratePdfDocumentOutline::kNone, &wstream);
sk_sp<SkDocument> doc =
MakeDocument(creator_, title_, docinfo_ ? nullptr : &accessibility_tree_,
GeneratePdfDocumentOutline::kNone, document_type, wstream);
for (const auto& page : pages) {
TRACE_EVENT0("print", "PrintCompositorImpl::CompositePages draw page");
@ -378,11 +408,10 @@ mojom::PrintCompositor::Status PrintCompositorImpl::CompositePages(
if (docinfo_) {
// Create full document if needed.
if (!docinfo_->doc) {
// TODO(crbug.com/1008222) Make use of `document_type` parameter once
// `MakeXpsDocument()` is available.
docinfo_->doc = MakePdfDocument(creator_, title_, accessibility_tree_,
GeneratePdfDocumentOutline::kNone,
&docinfo_->compositor_stream);
docinfo_->doc =
MakeDocument(creator_, title_, &accessibility_tree_,
GeneratePdfDocumentOutline::kNone,
docinfo_->document_type, docinfo_->compositor_stream);
}
// Collect this page into full document.

@ -41,6 +41,10 @@ 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
@ -239,6 +243,10 @@ class PrintCompositorImpl : public mojom::PrintCompositor {
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_;

@ -162,6 +162,8 @@ component("metafile") {
"emf_win.cc",
"emf_win.h",
]
libs = [ "xpsprint.lib" ]
}
}

@ -10,6 +10,7 @@
#include "base/check.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "printing/buildflags/buildflags.h"
#include "skia/ext/font_utils.h"
#include "third_party/skia/include/codec/SkPngDecoder.h"
@ -29,6 +30,20 @@
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_update.h"
#if BUILDFLAG(IS_WIN)
// XpsObjectModel.h indirectly includes <wincrypt.h> which is
// incompatible with Chromium's OpenSSL. By including wincrypt_shim.h
// first, problems are avoided.
// clang-format off
#include "base/win/wincrypt_shim.h"
#include <XpsObjectModel.h>
#include <objbase.h>
// clang-format on
#include "third_party/skia/include/docs/SkXPSDocument.h"
#endif // BUILDFLAG(IS_WIN)
namespace {
// Table 333 in PDF 32000-1:2008 spec, section 14.8.4.2
@ -250,6 +265,21 @@ sk_sp<SkDocument> MakePdfDocument(
return SkPDF::MakeDocument(stream, metadata);
}
#if BUILDFLAG(IS_WIN)
sk_sp<SkDocument> MakeXpsDocument(SkWStream* stream) {
IXpsOMObjectFactory* factory = nullptr;
HRESULT hr = CoCreateInstance(CLSID_XpsOMObjectFactory, nullptr,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory));
if (FAILED(hr) || !factory) {
DLOG(ERROR) << "Unable to create XPS object factory: "
<< logging::SystemErrorCodeToString(hr);
return nullptr;
}
return SkXPS::MakeDocument(stream, factory);
}
#endif
sk_sp<SkData> SerializeOopPicture(SkPicture* pic, void* ctx) {
const auto* context = reinterpret_cast<const ContentToProxyTokenMap*>(ctx);
uint32_t pic_id = pic->uniqueID();

@ -6,11 +6,13 @@
#define PRINTING_COMMON_METAFILE_UTILS_H_
#include <stdint.h>
#include <string_view>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "third_party/skia/include/core/SkDocument.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSerialProcs.h"
@ -48,6 +50,10 @@ sk_sp<SkDocument> MakePdfDocument(
GeneratePdfDocumentOutline generate_document_outline,
SkWStream* stream);
#if BUILDFLAG(IS_WIN)
sk_sp<SkDocument> MakeXpsDocument(SkWStream* stream);
#endif
SkSerialProcs SerializationProcs(PictureSerializationContext* picture_ctx,
TypefaceSerializationContext* typeface_ctx);

@ -208,9 +208,7 @@ bool MetafileSkia::FinishDocument() {
break;
#if BUILDFLAG(IS_WIN)
case mojom::SkiaDocumentType::kXPS:
// TODO(crbug.com/1008222) Update to use MakeXpsDocument() once it is
// available.
NOTIMPLEMENTED();
doc = MakeXpsDocument(&stream);
break;
#endif
case mojom::SkiaDocumentType::kMSKP: