diff options
Diffstat (limited to 'chromium/components/services/pdf_compositor')
21 files changed, 1709 insertions, 0 deletions
diff --git a/chromium/components/services/pdf_compositor/BUILD.gn b/chromium/components/services/pdf_compositor/BUILD.gn new file mode 100644 index 00000000000..32b899386c2 --- /dev/null +++ b/chromium/components/services/pdf_compositor/BUILD.gn @@ -0,0 +1,75 @@ +# Copyright 2017 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. + +import("//printing/buildflags/buildflags.gni") +import("//services/service_manager/public/cpp/service.gni") +import("//services/service_manager/public/service_manifest.gni") +import("//services/service_manager/public/tools/test/service_test.gni") + +static_library("pdf_compositor") { + sources = [ + "pdf_compositor_impl.cc", + "pdf_compositor_impl.h", + "pdf_compositor_service.cc", + "pdf_compositor_service.h", + ] + + deps = [ + "//base", + "//components/crash/core/common:crash_key", + "//components/discardable_memory/client", + "//components/discardable_memory/public/interfaces", + "//content/public/child", + "//content/public/common:service_names", + "//content/public/utility", + "//printing/common", + "//services/ui/public/interfaces", + "//skia", + "//third_party/blink/public:blink_headers", + ] + + public_deps = [ + "//components/services/pdf_compositor/public/cpp:utils", + "//components/services/pdf_compositor/public/interfaces", + "//services/service_manager/public/cpp", + ] +} + +service_manifest("pdf_compositor_manifest") { + name = "pdf_compositor" + source = "pdf_compositor_manifest.json" +} + +if (enable_basic_printing) { + source_set("unit_tests") { + testonly = true + sources = [ + "pdf_compositor_impl_unittest.cc", + "pdf_compositor_service_unittest.cc", + ] + + include_dirs = [ + "//skia/config", + "//testing/gmock/include", + "//third_party/skia/include/core", + ] + deps = [ + ":pdf_compositor", + "//base/test:test_support", + "//cc/paint:paint", + "//components/crash/core/common:crash_key", + "//components/services/pdf_compositor/public/interfaces", + "//services/service_manager/public/cpp:service_test_support", + "//skia", + "//testing/gmock", + "//testing/gtest", + ] + } + + service_manifest("pdf_compositor_service_unittest_manifest") { + name = "pdf_compositor_service_unittest" + source = "pdf_compositor_service_unittest_manifest.json" + packaged_services = [ ":pdf_compositor_manifest" ] + } +} diff --git a/chromium/components/services/pdf_compositor/DEPS b/chromium/components/services/pdf_compositor/DEPS new file mode 100644 index 00000000000..952a2798ac8 --- /dev/null +++ b/chromium/components/services/pdf_compositor/DEPS @@ -0,0 +1,17 @@ +include_rules = [ + "+cc/paint", + "+components/crash/core/common/crash_key.h", + "+components/discardable_memory/client", + "+content/public/child", # Windows direct write proxy access. + "+content/public/common", + "+content/public/utility", + "+mojo/public/cpp", + "+printing/common", + "+services/service_manager/public/cpp", + "+services/service_manager/public/mojom", + "+services/ui/public/interfaces/constants.mojom.h", # UI service name. + "+skia", + "+third_party/skia", + "+third_party/blink/public/platform", # Test web sandbox support. + "+ui/base/ui_base_features.h", # UI features. +] diff --git a/chromium/components/services/pdf_compositor/OWNERS b/chromium/components/services/pdf_compositor/OWNERS new file mode 100644 index 00000000000..3fb8863170f --- /dev/null +++ b/chromium/components/services/pdf_compositor/OWNERS @@ -0,0 +1,7 @@ +file://printing/OWNERS + +per-file pdf_compositor_manifest.json=set noparent +per-file pdf_compositor_manifest.json=file://ipc/SECURITY_OWNERS + +per-file pdf_compositor_service_unittest_manifest.json=set noparent +per-file pdf_compositor_service_unittest_manifest.json=file://ipc/SECURITY_OWNERS diff --git a/chromium/components/services/pdf_compositor/README.md b/chromium/components/services/pdf_compositor/README.md new file mode 100644 index 00000000000..6ce9c170a85 --- /dev/null +++ b/chromium/components/services/pdf_compositor/README.md @@ -0,0 +1,4 @@ +The pdf_compositor service should composite multiple raw pictures from different +frames into a complete one, then converts it into a pdf file within an isolated +sandboxed process. Currently, it has no compositing functionality, just convert +a set of raw pictures into a pdf file within the sandboxed process. diff --git a/chromium/components/services/pdf_compositor/pdf_compositor_impl.cc b/chromium/components/services/pdf_compositor/pdf_compositor_impl.cc new file mode 100644 index 00000000000..072a78b90ec --- /dev/null +++ b/chromium/components/services/pdf_compositor/pdf_compositor_impl.cc @@ -0,0 +1,323 @@ +// Copyright 2017 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 "components/services/pdf_compositor/pdf_compositor_impl.h" + +#include <algorithm> +#include <tuple> +#include <utility> + +#include "base/logging.h" +#include "base/memory/shared_memory_handle.h" +#include "base/stl_util.h" +#include "components/crash/core/common/crash_key.h" +#include "components/services/pdf_compositor/public/cpp/pdf_service_mojo_types.h" +#include "components/services/pdf_compositor/public/cpp/pdf_service_mojo_utils.h" +#include "mojo/public/cpp/system/platform_handle.h" +#include "printing/common/pdf_metafile_utils.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkDocument.h" +#include "third_party/skia/include/core/SkSerialProcs.h" +#include "third_party/skia/src/utils/SkMultiPictureDocument.h" + +namespace printing { + +PdfCompositorImpl::PdfCompositorImpl( + const std::string& creator, + std::unique_ptr<service_manager::ServiceContextRef> service_ref) + : service_ref_(std::move(service_ref)), creator_(creator) {} + +PdfCompositorImpl::~PdfCompositorImpl() = default; + +void PdfCompositorImpl::NotifyUnavailableSubframe(uint64_t frame_guid) { + // Add this frame into the map. + DCHECK(!base::ContainsKey(frame_info_map_, frame_guid)); + auto& frame_info = + frame_info_map_.emplace(frame_guid, std::make_unique<FrameInfo>()) + .first->second; + frame_info->composited = true; + // Set content to be nullptr so it will be replaced by an empty picture during + // deserialization of its parent. + frame_info->content = nullptr; + + // Update the requests in case any of them might be waiting for this frame. + UpdateRequestsWithSubframeInfo(frame_guid, std::vector<uint64_t>()); +} + +void PdfCompositorImpl::AddSubframeContent( + uint64_t frame_guid, + mojo::ScopedSharedBufferHandle serialized_content, + const ContentToFrameMap& subframe_content_map) { + // Add this frame and its serialized content. + DCHECK(!base::ContainsKey(frame_info_map_, frame_guid)); + auto& frame_info = + frame_info_map_.emplace(frame_guid, std::make_unique<FrameInfo>()) + .first->second; + frame_info->serialized_content = + GetShmFromMojoHandle(std::move(serialized_content)); + + // Copy the subframe content information. + frame_info->subframe_content_map = subframe_content_map; + + // If there is no request, we do nothing more. + // Otherwise, we need to check whether any request actually waits on this + // frame content. + if (requests_.empty()) + return; + + // Get the pending list which is a list of subframes this frame needs + // but are still unavailable. + std::vector<uint64_t> pending_subframes; + for (auto& subframe_content : subframe_content_map) { + auto subframe_guid = subframe_content.second; + if (!base::ContainsKey(frame_info_map_, subframe_guid)) + pending_subframes.push_back(subframe_guid); + } + + // Update the requests in case any of them is waiting for this frame. + UpdateRequestsWithSubframeInfo(frame_guid, pending_subframes); +} + +void PdfCompositorImpl::CompositePageToPdf( + uint64_t frame_guid, + uint32_t page_num, + mojo::ScopedSharedBufferHandle serialized_content, + const ContentToFrameMap& subframe_content_map, + mojom::PdfCompositor::CompositePageToPdfCallback callback) { + HandleCompositionRequest(frame_guid, page_num, std::move(serialized_content), + subframe_content_map, std::move(callback)); +} + +void PdfCompositorImpl::CompositeDocumentToPdf( + uint64_t frame_guid, + mojo::ScopedSharedBufferHandle serialized_content, + const ContentToFrameMap& subframe_content_map, + mojom::PdfCompositor::CompositeDocumentToPdfCallback callback) { + HandleCompositionRequest(frame_guid, base::nullopt, + std::move(serialized_content), subframe_content_map, + std::move(callback)); +} + +void PdfCompositorImpl::SetWebContentsURL(const GURL& url) { + // Record the most recent url we tried to print. This should be sufficient + // for users using print preview by default. + static crash_reporter::CrashKeyString<1024> crash_key("main-frame-url"); + crash_key.Set(url.spec()); +} + +void PdfCompositorImpl::UpdateRequestsWithSubframeInfo( + uint64_t frame_guid, + const std::vector<uint64_t>& pending_subframes) { + // Check for each request's pending list. + for (auto it = requests_.begin(); it != requests_.end();) { + auto& request = *it; + // If the request needs this frame, we can remove the dependency, but + // update with this frame's pending list. + auto& pending_list = request->pending_subframes; + if (pending_list.erase(frame_guid)) { + std::copy(pending_subframes.begin(), pending_subframes.end(), + std::inserter(pending_list, pending_list.end())); + if (pending_list.empty()) { + // If the request isn't waiting on any subframes then it is ready. + // Fulfill the request now. + FulfillRequest(request->frame_guid, request->page_number, + std::move(request->serialized_content), + request->subframe_content_map, + std::move(request->callback)); + it = requests_.erase(it); + continue; + } + } + // If the request still has pending frames, keep waiting. + ++it; + } +} + +bool PdfCompositorImpl::IsReadyToComposite( + uint64_t frame_guid, + const ContentToFrameMap& subframe_content_map, + base::flat_set<uint64_t>* pending_subframes) { + pending_subframes->clear(); + base::flat_set<uint64_t> visited_frames; + visited_frames.insert(frame_guid); + CheckFramesForReadiness(frame_guid, subframe_content_map, pending_subframes, + &visited_frames); + return pending_subframes->empty(); +} + +void PdfCompositorImpl::CheckFramesForReadiness( + uint64_t frame_guid, + const ContentToFrameMap& subframe_content_map, + base::flat_set<uint64_t>* pending_subframes, + base::flat_set<uint64_t>* visited) { + for (auto& subframe_content : subframe_content_map) { + auto subframe_guid = subframe_content.second; + // If this frame has been checked, skip it. + auto result = visited->insert(subframe_guid); + if (!result.second) + continue; + + auto iter = frame_info_map_.find(subframe_guid); + if (iter == frame_info_map_.end()) { + pending_subframes->insert(subframe_guid); + } else { + CheckFramesForReadiness(subframe_guid, iter->second->subframe_content_map, + pending_subframes, visited); + } + } +} + +void PdfCompositorImpl::HandleCompositionRequest( + uint64_t frame_guid, + base::Optional<uint32_t> page_num, + mojo::ScopedSharedBufferHandle serialized_content, + const ContentToFrameMap& subframe_content_map, + CompositeToPdfCallback callback) { + base::flat_set<uint64_t> pending_subframes; + if (IsReadyToComposite(frame_guid, subframe_content_map, + &pending_subframes)) { + FulfillRequest(frame_guid, page_num, + GetShmFromMojoHandle(std::move(serialized_content)), + subframe_content_map, std::move(callback)); + return; + } + + // When it is not ready yet, keep its information and + // wait until all dependent subframes are ready. + auto iter = frame_info_map_.find(frame_guid); + if (iter == frame_info_map_.end()) + frame_info_map_[frame_guid] = std::make_unique<FrameInfo>(); + + requests_.push_back(std::make_unique<RequestInfo>( + frame_guid, page_num, GetShmFromMojoHandle(std::move(serialized_content)), + subframe_content_map, std::move(pending_subframes), std::move(callback))); +} + +mojom::PdfCompositor::Status PdfCompositorImpl::CompositeToPdf( + uint64_t frame_guid, + base::Optional<uint32_t> page_num, + std::unique_ptr<base::SharedMemory> shared_mem, + const ContentToFrameMap& subframe_content_map, + base::ReadOnlySharedMemoryRegion* region) { + DeserializationContext subframes = + GetDeserializationContext(subframe_content_map); + + // Read in content and convert it into pdf. + SkMemoryStream stream(shared_mem->memory(), shared_mem->mapped_size()); + int page_count = SkMultiPictureDocumentReadPageCount(&stream); + if (!page_count) { + DLOG(ERROR) << "CompositeToPdf: No page is read."; + return mojom::PdfCompositor::Status::CONTENT_FORMAT_ERROR; + } + + std::vector<SkDocumentPage> pages(page_count); + SkDeserialProcs procs = DeserializationProcs(&subframes); + if (!SkMultiPictureDocumentRead(&stream, pages.data(), page_count, &procs)) { + DLOG(ERROR) << "CompositeToPdf: Page reading failed."; + return mojom::PdfCompositor::Status::CONTENT_FORMAT_ERROR; + } + + SkDynamicMemoryWStream wstream; + sk_sp<SkDocument> doc = MakePdfDocument(creator_, &wstream); + + for (const auto& page : pages) { + SkCanvas* canvas = doc->beginPage(page.fSize.width(), page.fSize.height()); + canvas->drawPicture(page.fPicture); + doc->endPage(); + } + doc->close(); + + base::MappedReadOnlyRegion region_mapping = + CreateReadOnlySharedMemoryRegion(wstream.bytesWritten()); + if (!region_mapping.IsValid()) { + DLOG(ERROR) << "CompositeToPdf: Cannot create new shared memory region."; + return mojom::PdfCompositor::Status::HANDLE_MAP_ERROR; + } + + wstream.copyToAndReset(region_mapping.mapping.memory()); + *region = std::move(region_mapping.region); + return mojom::PdfCompositor::Status::SUCCESS; +} + +sk_sp<SkPicture> PdfCompositorImpl::CompositeSubframe(uint64_t frame_guid) { + // The content of this frame should be available. + auto iter = frame_info_map_.find(frame_guid); + DCHECK(iter != frame_info_map_.end()); + + std::unique_ptr<FrameInfo>& frame_info = iter->second; + frame_info->composited = true; + + // Composite subframes first. + DeserializationContext subframes = + GetDeserializationContext(frame_info->subframe_content_map); + + // Composite the entire frame. + SkMemoryStream stream(iter->second->serialized_content->memory(), + iter->second->serialized_content->mapped_size()); + SkDeserialProcs procs = DeserializationProcs(&subframes); + iter->second->content = SkPicture::MakeFromStream(&stream, &procs); + return iter->second->content; +} + +PdfCompositorImpl::DeserializationContext +PdfCompositorImpl::GetDeserializationContext( + const ContentToFrameMap& subframe_content_map) { + DeserializationContext subframes; + for (auto& content_info : subframe_content_map) { + uint32_t content_id = content_info.first; + uint64_t frame_guid = content_info.second; + auto frame_iter = frame_info_map_.find(frame_guid); + if (frame_iter == frame_info_map_.end()) + continue; + + if (frame_iter->second->composited) + subframes[content_id] = frame_iter->second->content; + else + subframes[content_id] = CompositeSubframe(frame_iter->first); + } + return subframes; +} + +void PdfCompositorImpl::FulfillRequest( + uint64_t frame_guid, + base::Optional<uint32_t> page_num, + std::unique_ptr<base::SharedMemory> serialized_content, + const ContentToFrameMap& subframe_content_map, + CompositeToPdfCallback callback) { + base::ReadOnlySharedMemoryRegion region; + auto status = + CompositeToPdf(frame_guid, page_num, std::move(serialized_content), + subframe_content_map, ®ion); + std::move(callback).Run(status, std::move(region)); +} + +PdfCompositorImpl::FrameContentInfo::FrameContentInfo( + std::unique_ptr<base::SharedMemory> content, + const ContentToFrameMap& map) + : serialized_content(std::move(content)), subframe_content_map(map) {} + +PdfCompositorImpl::FrameContentInfo::FrameContentInfo() {} + +PdfCompositorImpl::FrameContentInfo::~FrameContentInfo() {} + +PdfCompositorImpl::FrameInfo::FrameInfo() {} + +PdfCompositorImpl::FrameInfo::~FrameInfo() {} + +PdfCompositorImpl::RequestInfo::RequestInfo( + uint64_t frame_guid, + base::Optional<uint32_t> page_num, + std::unique_ptr<base::SharedMemory> content, + const ContentToFrameMap& content_info, + const base::flat_set<uint64_t>& pending_subframes, + mojom::PdfCompositor::CompositePageToPdfCallback callback) + : FrameContentInfo(std::move(content), content_info), + frame_guid(frame_guid), + page_number(page_num), + pending_subframes(pending_subframes), + callback(std::move(callback)) {} + +PdfCompositorImpl::RequestInfo::~RequestInfo() {} + +} // namespace printing diff --git a/chromium/components/services/pdf_compositor/pdf_compositor_impl.h b/chromium/components/services/pdf_compositor/pdf_compositor_impl.h new file mode 100644 index 00000000000..4fe2d80a040 --- /dev/null +++ b/chromium/components/services/pdf_compositor/pdf_compositor_impl.h @@ -0,0 +1,191 @@ +// Copyright 2017 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. + +#ifndef COMPONENTS_SERVICES_PDF_COMPOSITOR_PDF_COMPOSITOR_IMPL_H_ +#define COMPONENTS_SERVICES_PDF_COMPOSITOR_PDF_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/macros.h" +#include "base/memory/read_only_shared_memory_region.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/shared_memory.h" +#include "base/optional.h" +#include "components/services/pdf_compositor/public/cpp/pdf_service_mojo_types.h" +#include "components/services/pdf_compositor/public/interfaces/pdf_compositor.mojom.h" +#include "services/service_manager/public/cpp/service_context_ref.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "third_party/skia/include/core/SkRefCnt.h" + +namespace printing { + +class PdfCompositorImpl : public mojom::PdfCompositor { + public: + PdfCompositorImpl( + const std::string& creator, + std::unique_ptr<service_manager::ServiceContextRef> service_ref); + ~PdfCompositorImpl() override; + + // mojom::PdfCompositor + void NotifyUnavailableSubframe(uint64_t frame_guid) override; + void AddSubframeContent( + uint64_t frame_guid, + mojo::ScopedSharedBufferHandle serialized_content, + const ContentToFrameMap& subframe_content_map) override; + void CompositePageToPdf( + uint64_t frame_guid, + uint32_t page_num, + mojo::ScopedSharedBufferHandle serialized_content, + const ContentToFrameMap& subframe_content_map, + mojom::PdfCompositor::CompositePageToPdfCallback callback) override; + void CompositeDocumentToPdf( + uint64_t frame_guid, + mojo::ScopedSharedBufferHandle serialized_content, + const ContentToFrameMap& subframe_content_map, + mojom::PdfCompositor::CompositeDocumentToPdfCallback callback) override; + void SetWebContentsURL(const GURL& url) override; + + protected: + // This is the uniform underlying type for both + // mojom::PdfCompositor::CompositePageToPdfCallback and + // mojom::PdfCompositor::CompositeDocumentToPdfCallback. + using CompositeToPdfCallback = + base::OnceCallback<void(PdfCompositor::Status, + base::ReadOnlySharedMemoryRegion)>; + + // Make this function virtual so tests can override it. + virtual void FulfillRequest( + uint64_t frame_guid, + base::Optional<uint32_t> page_num, + std::unique_ptr<base::SharedMemory> serialized_content, + const ContentToFrameMap& subframe_content_map, + CompositeToPdfCallback callback); + + private: + FRIEND_TEST_ALL_PREFIXES(PdfCompositorImplTest, IsReadyToComposite); + FRIEND_TEST_ALL_PREFIXES(PdfCompositorImplTest, MultiLayerDependency); + FRIEND_TEST_ALL_PREFIXES(PdfCompositorImplTest, DependencyLoop); + + // The map needed during content deserialization. It stores the mapping + // between content id and its actual content. + using DeserializationContext = base::flat_map<uint32_t, sk_sp<SkPicture>>; + + // Base structure to store a frame's content and its subframe + // content information. + struct FrameContentInfo { + FrameContentInfo(std::unique_ptr<base::SharedMemory> content, + const ContentToFrameMap& map); + FrameContentInfo(); + ~FrameContentInfo(); + + // Serialized SkPicture content of this frame. + std::unique_ptr<base::SharedMemory> 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; + }; + + // Other than content, it also stores the status during frame composition. + struct FrameInfo : public FrameContentInfo { + FrameInfo(); + ~FrameInfo(); + + // 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(uint64_t frame_guid, + base::Optional<uint32_t> page_num, + std::unique_ptr<base::SharedMemory> content, + const ContentToFrameMap& content_info, + const base::flat_set<uint64_t>& pending_subframes, + CompositeToPdfCallback callback); + ~RequestInfo(); + + uint64_t frame_guid; + base::Optional<uint32_t> page_number; + + // All pending frame ids whose content is not available but needed + // for composition. + base::flat_set<uint64_t> pending_subframes; + + CompositeToPdfCallback 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); + + // Recursive check all the frames |frame_guid| depends on and put those + // not ready in |pending_subframes|. + void CheckFramesForReadiness(uint64_t frame_guid, + const ContentToFrameMap& subframe_content_map, + base::flat_set<uint64_t>* pending_subframes, + base::flat_set<uint64_t>* visited); + + // The internal implementation for handling page and documentation composition + // requests. + void HandleCompositionRequest( + uint64_t frame_guid, + base::Optional<uint32_t> page_num, + mojo::ScopedSharedBufferHandle serialized_content, + const ContentToFrameMap& subframe_content_ids, + CompositeToPdfCallback callback); + + // The core function for content composition and conversion to a pdf file. + mojom::PdfCompositor::Status CompositeToPdf( + uint64_t frame_guid, + base::Optional<uint32_t> page_num, + std::unique_ptr<base::SharedMemory> shared_mem, + const ContentToFrameMap& subframe_content_map, + base::ReadOnlySharedMemoryRegion* region); + + // Composite the content of a subframe. + sk_sp<SkPicture> CompositeSubframe(uint64_t frame_guid); + + bool CheckForPendingFrame(uint64_t frame_guid, + base::flat_set<uint64_t> visited_frames); + + DeserializationContext GetDeserializationContext( + const ContentToFrameMap& subframe_content_map); + + const std::unique_ptr<service_manager::ServiceContextRef> service_ref_; + const std::string creator_; + + // Keep track of all frames' information indexed by frame id. + FrameMap frame_info_map_; + + std::vector<std::unique_ptr<RequestInfo>> requests_; + + DISALLOW_COPY_AND_ASSIGN(PdfCompositorImpl); +}; + +} // namespace printing + +#endif // COMPONENTS_SERVICES_PDF_COMPOSITOR_PDF_COMPOSITOR_IMPL_H_ diff --git a/chromium/components/services/pdf_compositor/pdf_compositor_impl_unittest.cc b/chromium/components/services/pdf_compositor/pdf_compositor_impl_unittest.cc new file mode 100644 index 00000000000..cc68640562c --- /dev/null +++ b/chromium/components/services/pdf_compositor/pdf_compositor_impl_unittest.cc @@ -0,0 +1,375 @@ +// Copyright 2017 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 <memory> +#include <utility> + +#include "base/callback.h" +#include "base/run_loop.h" +#include "base/test/scoped_task_environment.h" +#include "components/crash/core/common/crash_key.h" +#include "components/services/pdf_compositor/pdf_compositor_impl.h" +#include "components/services/pdf_compositor/public/cpp/pdf_service_mojo_types.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace printing { + +class MockPdfCompositorImpl : public PdfCompositorImpl { + public: + MockPdfCompositorImpl() : PdfCompositorImpl("unittest", nullptr) {} + ~MockPdfCompositorImpl() override {} + + MOCK_METHOD2(OnFulfillRequest, void(uint64_t, int)); + + protected: + void FulfillRequest(uint64_t frame_guid, + base::Optional<uint32_t> page_num, + std::unique_ptr<base::SharedMemory> serialized_content, + const ContentToFrameMap& subframe_content_map, + CompositeToPdfCallback callback) override { + OnFulfillRequest(frame_guid, page_num.has_value() ? page_num.value() : -1); + } +}; + +class PdfCompositorImplTest : public testing::Test { + public: + PdfCompositorImplTest() + : task_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::IO), + run_loop_(std::make_unique<base::RunLoop>()), + is_ready_(false) {} + + void OnIsReadyToCompositeCallback(bool is_ready) { + is_ready_ = is_ready; + run_loop_->Quit(); + } + + bool ResultFromCallback() { + run_loop_->Run(); + run_loop_ = std::make_unique<base::RunLoop>(); + return is_ready_; + } + + void OnCompositeToPdfCallback(mojom::PdfCompositor::Status status, + base::ReadOnlySharedMemoryRegion region) { + // A stub for testing, no implementation. + } + + protected: + base::test::ScopedTaskEnvironment task_environment_; + std::unique_ptr<base::RunLoop> run_loop_; + bool is_ready_; +}; + +class PdfCompositorImplCrashKeyTest : public PdfCompositorImplTest { + public: + PdfCompositorImplCrashKeyTest() {} + ~PdfCompositorImplCrashKeyTest() override {} + + void SetUp() override { + crash_reporter::ResetCrashKeysForTesting(); + crash_reporter::InitializeCrashKeys(); + } + + void TearDown() override { crash_reporter::ResetCrashKeysForTesting(); } + + private: + DISALLOW_COPY_AND_ASSIGN(PdfCompositorImplCrashKeyTest); +}; + +TEST_F(PdfCompositorImplTest, IsReadyToComposite) { + PdfCompositorImpl impl("unittest", nullptr); + // Frame 2 and 3 are painted. + impl.AddSubframeContent(2u, mojo::SharedBufferHandle::Create(10), + ContentToFrameMap()); + impl.AddSubframeContent(3u, mojo::SharedBufferHandle::Create(10), + ContentToFrameMap()); + + // Frame 1 contains content 3 which corresponds to frame 2. + // Frame 1 should be ready as frame 2 is ready. + ContentToFrameMap subframe_content_map; + subframe_content_map[3u] = 2u; + base::flat_set<uint64_t> pending_subframes; + bool is_ready = impl.IsReadyToComposite(1u, std::move(subframe_content_map), + &pending_subframes); + EXPECT_TRUE(is_ready); + EXPECT_TRUE(pending_subframes.empty()); + + // If another page of frame 1 needs content 2 which corresponds to frame 3. + // This page is ready since frame 3 was painted also. + subframe_content_map.clear(); + subframe_content_map[2u] = 3u; + is_ready = impl.IsReadyToComposite(1u, std::move(subframe_content_map), + &pending_subframes); + EXPECT_TRUE(is_ready); + EXPECT_TRUE(pending_subframes.empty()); + + // Frame 1 with content 1, 2 and 3 should not be ready since content 1's + // content in frame 4 is not painted yet. + subframe_content_map.clear(); + subframe_content_map[1u] = 4u; + subframe_content_map[2u] = 3u; + subframe_content_map[3u] = 2u; + is_ready = impl.IsReadyToComposite(1u, std::move(subframe_content_map), + &pending_subframes); + EXPECT_FALSE(is_ready); + ASSERT_EQ(pending_subframes.size(), 1u); + EXPECT_EQ(*pending_subframes.begin(), 4u); + + // Add content of frame 4. Now it is ready for composition. + subframe_content_map.clear(); + subframe_content_map[1u] = 4u; + subframe_content_map[2u] = 3u; + subframe_content_map[3u] = 2u; + impl.AddSubframeContent(4u, mojo::SharedBufferHandle::Create(10), + ContentToFrameMap()); + is_ready = impl.IsReadyToComposite(1u, std::move(subframe_content_map), + &pending_subframes); + EXPECT_TRUE(is_ready); + EXPECT_TRUE(pending_subframes.empty()); +} + +TEST_F(PdfCompositorImplTest, MultiLayerDependency) { + PdfCompositorImpl impl("unittest", nullptr); + // Frame 3 has content 1 which refers to subframe 1. + ContentToFrameMap subframe_content_map; + subframe_content_map[1u] = 1u; + impl.AddSubframeContent(3u, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map)); + + // Frame 5 has content 3 which refers to subframe 3. + // Although frame 3's content is added, its subframe 1's content is not added. + // So frame 5 is not ready. + subframe_content_map.clear(); + subframe_content_map[3u] = 3u; + base::flat_set<uint64_t> pending_subframes; + bool is_ready = impl.IsReadyToComposite(5u, std::move(subframe_content_map), + &pending_subframes); + EXPECT_FALSE(is_ready); + ASSERT_EQ(pending_subframes.size(), 1u); + EXPECT_EQ(*pending_subframes.begin(), 1u); + + // Frame 6 is not ready either since it needs frame 5 to be ready. + subframe_content_map.clear(); + subframe_content_map[1u] = 5u; + is_ready = impl.IsReadyToComposite(6u, std::move(subframe_content_map), + &pending_subframes); + EXPECT_FALSE(is_ready); + ASSERT_EQ(pending_subframes.size(), 1u); + EXPECT_EQ(*pending_subframes.begin(), 5u); + + // When frame 1's content is added, frame 5 is ready. + impl.AddSubframeContent(1u, mojo::SharedBufferHandle::Create(10), + ContentToFrameMap()); + subframe_content_map.clear(); + subframe_content_map[3u] = 3u; + is_ready = impl.IsReadyToComposite(5u, std::move(subframe_content_map), + &pending_subframes); + EXPECT_TRUE(is_ready); + EXPECT_TRUE(pending_subframes.empty()); + + // Add frame 5's content. + subframe_content_map.clear(); + subframe_content_map[3u] = 3u; + impl.AddSubframeContent(5u, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map)); + + // Frame 6 is ready too. + subframe_content_map.clear(); + subframe_content_map[1u] = 5u; + is_ready = impl.IsReadyToComposite(6u, std::move(subframe_content_map), + &pending_subframes); + EXPECT_TRUE(is_ready); + EXPECT_TRUE(pending_subframes.empty()); +} + +TEST_F(PdfCompositorImplTest, DependencyLoop) { + PdfCompositorImpl impl("unittest", nullptr); + // Frame 3 has content 1, which refers to frame 1. + // Frame 1 has content 3, which refers to frame 3. + ContentToFrameMap subframe_content_map; + subframe_content_map[3u] = 3u; + impl.AddSubframeContent(1u, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map)); + + subframe_content_map.clear(); + subframe_content_map[1u] = 1u; + impl.AddSubframeContent(3u, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map)); + + // Both frame 1 and 3 are painted, frame 5 should be ready. + base::flat_set<uint64_t> pending_subframes; + subframe_content_map.clear(); + subframe_content_map[1u] = 3u; + bool is_ready = impl.IsReadyToComposite(5u, std::move(subframe_content_map), + &pending_subframes); + EXPECT_TRUE(is_ready); + EXPECT_TRUE(pending_subframes.empty()); + + // Frame 6 has content 7, which refers to frame 7. + subframe_content_map.clear(); + subframe_content_map[7u] = 7u; + impl.AddSubframeContent(6, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map)); + // Frame 7 should be ready since frame 6's own content is added and it only + // depends on frame 7. + subframe_content_map.clear(); + subframe_content_map[6u] = 6u; + is_ready = impl.IsReadyToComposite(7u, std::move(subframe_content_map), + &pending_subframes); + EXPECT_TRUE(is_ready); + EXPECT_TRUE(pending_subframes.empty()); +} + +TEST_F(PdfCompositorImplTest, MultiRequestsBasic) { + MockPdfCompositorImpl impl; + // Page 0 with frame 3 has content 1, which refers to frame 8. + // When the content is not available, the request is not fulfilled. + ContentToFrameMap subframe_content_map; + subframe_content_map[1u] = 8u; + EXPECT_CALL(impl, OnFulfillRequest(testing::_, testing::_)).Times(0); + impl.CompositePageToPdf( + 3u, 0, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map), + base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback, + base::Unretained(this))); + testing::Mock::VerifyAndClearExpectations(&impl); + + // When frame 8's content is ready, the previous request should be fulfilled. + EXPECT_CALL(impl, OnFulfillRequest(3u, 0)).Times(1); + impl.AddSubframeContent(8u, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map)); + testing::Mock::VerifyAndClearExpectations(&impl); + + // The following requests which only depends on frame 8 should be + // immediately fulfilled. + EXPECT_CALL(impl, OnFulfillRequest(3u, 1)).Times(1); + EXPECT_CALL(impl, OnFulfillRequest(3u, -1)).Times(1); + subframe_content_map.clear(); + subframe_content_map[1u] = 8u; + impl.CompositePageToPdf( + 3u, 1, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map), + base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback, + base::Unretained(this))); + + subframe_content_map.clear(); + subframe_content_map[1u] = 8u; + impl.CompositeDocumentToPdf( + 3u, mojo::SharedBufferHandle::Create(10), std::move(subframe_content_map), + base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback, + base::Unretained(this))); +} + +TEST_F(PdfCompositorImplTest, MultiRequestsOrder) { + MockPdfCompositorImpl impl; + // Page 0 with frame 3 has content 1, which refers to frame 8. + // When the content is not available, the request is not fulfilled. + ContentToFrameMap subframe_content_map; + subframe_content_map[1u] = 8u; + EXPECT_CALL(impl, OnFulfillRequest(testing::_, testing::_)).Times(0); + impl.CompositePageToPdf( + 3u, 0, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map), + base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback, + base::Unretained(this))); + + // The following requests which only depends on frame 8 should be + // immediately fulfilled. + subframe_content_map.clear(); + subframe_content_map[1u] = 8u; + impl.CompositePageToPdf( + 3u, 1, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map), + base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback, + base::Unretained(this))); + + subframe_content_map.clear(); + subframe_content_map[1u] = 8u; + impl.CompositeDocumentToPdf( + 3u, mojo::SharedBufferHandle::Create(10), std::move(subframe_content_map), + base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback, + base::Unretained(this))); + testing::Mock::VerifyAndClearExpectations(&impl); + + // When frame 8's content is ready, the previous request should be + // fulfilled. + EXPECT_CALL(impl, OnFulfillRequest(3u, 0)).Times(1); + EXPECT_CALL(impl, OnFulfillRequest(3u, 1)).Times(1); + EXPECT_CALL(impl, OnFulfillRequest(3u, -1)).Times(1); + subframe_content_map.clear(); + impl.AddSubframeContent(8u, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map)); +} + +TEST_F(PdfCompositorImplTest, MultiRequestsDepOrder) { + MockPdfCompositorImpl impl; + // Page 0 with frame 1 has content 1, which refers to frame + // 2. When the content is not available, the request is not + // fulfilled. + EXPECT_CALL(impl, OnFulfillRequest(testing::_, testing::_)).Times(0); + ContentToFrameMap subframe_content_map; + subframe_content_map[1u] = 2u; + impl.CompositePageToPdf( + 1u, 0, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map), + base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback, + base::Unretained(this))); + + // Page 1 with frame 1 has content 1, which refers to frame + // 3. When the content is not available, the request is not + // fulfilled either. + subframe_content_map.clear(); + subframe_content_map[1u] = 3u; + impl.CompositePageToPdf( + 1u, 1, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map), + base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback, + base::Unretained(this))); + testing::Mock::VerifyAndClearExpectations(&impl); + + // When frame 3 and 2 become available, the pending requests should be + // satisfied, thus be fulfilled in order. + testing::Sequence order; + EXPECT_CALL(impl, OnFulfillRequest(1, 1)).Times(1).InSequence(order); + EXPECT_CALL(impl, OnFulfillRequest(1, 0)).Times(1).InSequence(order); + subframe_content_map.clear(); + impl.AddSubframeContent(3u, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map)); + impl.AddSubframeContent(2u, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map)); +} + +TEST_F(PdfCompositorImplTest, NotifyUnavailableSubframe) { + MockPdfCompositorImpl impl; + // Page 0 with frame 3 has content 1, which refers to frame 8. + // When the content is not available, the request is not fulfilled. + ContentToFrameMap subframe_content_map; + subframe_content_map[1u] = 8u; + EXPECT_CALL(impl, OnFulfillRequest(testing::_, testing::_)).Times(0); + impl.CompositePageToPdf( + 3u, 0, mojo::SharedBufferHandle::Create(10), + std::move(subframe_content_map), + base::BindOnce(&PdfCompositorImplTest::OnCompositeToPdfCallback, + base::Unretained(this))); + testing::Mock::VerifyAndClearExpectations(&impl); + + // Notifies that frame 8's unavailable, the previous request should be + // fulfilled. + EXPECT_CALL(impl, OnFulfillRequest(3u, 0)).Times(1); + impl.NotifyUnavailableSubframe(8u); + testing::Mock::VerifyAndClearExpectations(&impl); +} + +TEST_F(PdfCompositorImplCrashKeyTest, SetCrashKey) { + PdfCompositorImpl impl("unittest", nullptr); + std::string url_str("https://www.example.com/"); + GURL url(url_str); + impl.SetWebContentsURL(url); + + EXPECT_EQ(crash_reporter::GetCrashKeyValue("main-frame-url"), url_str); +} + +} // namespace printing diff --git a/chromium/components/services/pdf_compositor/pdf_compositor_manifest.json b/chromium/components/services/pdf_compositor/pdf_compositor_manifest.json new file mode 100644 index 00000000000..ecbad4346d6 --- /dev/null +++ b/chromium/components/services/pdf_compositor/pdf_compositor_manifest.json @@ -0,0 +1,18 @@ +{ + "name": "pdf_compositor", + "display_name": "PDF Compositor Service", + "sandbox_type": "pdf_compositor", + "interface_provider_specs": { + "service_manager:connector": { + "provides": { + "compositor": [ "printing.mojom.PdfCompositor" ] + }, + "requires": { + "*": [ "app" ], + "content_browser": [ "font_loader" ], + "service_manager": [ "service_manager:all_users" ], + "ui": [ "discardable_memory" ] + } + } + } +} diff --git a/chromium/components/services/pdf_compositor/pdf_compositor_service.cc b/chromium/components/services/pdf_compositor/pdf_compositor_service.cc new file mode 100644 index 00000000000..e71b72ab482 --- /dev/null +++ b/chromium/components/services/pdf_compositor/pdf_compositor_service.cc @@ -0,0 +1,122 @@ +// Copyright 2017 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 "components/services/pdf_compositor/pdf_compositor_service.h" + +#include <utility> + +#include <memory> + +#include "base/lazy_instance.h" +#include "base/memory/discardable_memory.h" +#include "build/build_config.h" +#include "components/services/pdf_compositor/pdf_compositor_impl.h" +#include "components/services/pdf_compositor/public/interfaces/pdf_compositor.mojom.h" +#include "content/public/common/service_names.mojom.h" +#include "content/public/utility/utility_thread.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "services/service_manager/public/cpp/connector.h" +#include "services/service_manager/public/cpp/service_context.h" +#include "services/ui/public/interfaces/constants.mojom.h" +#include "ui/base/ui_base_features.h" + +#if defined(OS_WIN) +#include "content/public/child/dwrite_font_proxy_init_win.h" +#elif defined(OS_MACOSX) +#include "third_party/blink/public/platform/platform.h" +#include "third_party/skia/include/ports/SkFontMgr.h" +#elif defined(OS_POSIX) && !defined(OS_ANDROID) +#include "third_party/blink/public/platform/platform.h" +#endif + +namespace { + +void OnPdfCompositorRequest( + const std::string& creator, + service_manager::ServiceContextRefFactory* ref_factory, + printing::mojom::PdfCompositorRequest request) { + mojo::MakeStrongBinding(std::make_unique<printing::PdfCompositorImpl>( + creator, ref_factory->CreateRef()), + std::move(request)); +} +} // namespace + +namespace printing { + +PdfCompositorService::PdfCompositorService(const std::string& creator) + : creator_(creator.empty() ? "Chromium" : creator), weak_factory_(this) {} + +PdfCompositorService::~PdfCompositorService() { +#if defined(OS_WIN) + content::UninitializeDWriteFontProxy(); +#endif +} + +// static +std::unique_ptr<service_manager::Service> PdfCompositorService::Create( + const std::string& creator) { +#if defined(OS_WIN) + // Initialize direct write font proxy so skia can use it. + content::InitializeDWriteFontProxy(); +#endif + return std::make_unique<printing::PdfCompositorService>(creator); +} + +void PdfCompositorService::PrepareToStart() { + // Set up discardable memory manager. + discardable_memory::mojom::DiscardableSharedMemoryManagerPtr manager_ptr; + if (features::IsMashEnabled()) { +#if defined(USE_AURA) + context()->connector()->BindInterface(ui::mojom::kServiceName, + &manager_ptr); +#else + NOTREACHED(); +#endif + } else { + context()->connector()->BindInterface(content::mojom::kBrowserServiceName, + &manager_ptr); + } + discardable_shared_memory_manager_ = std::make_unique< + discardable_memory::ClientDiscardableSharedMemoryManager>( + std::move(manager_ptr), content::UtilityThread::Get()->GetIOTaskRunner()); + DCHECK(discardable_shared_memory_manager_); + base::DiscardableMemoryAllocator::SetInstance( + discardable_shared_memory_manager_.get()); + +#if defined(OS_POSIX) && !defined(OS_ANDROID) + // Check that we have sandbox support on this platform. + DCHECK(blink::Platform::Current()->GetSandboxSupport()); +#endif + +#if defined(OS_MACOSX) + // Check that font access is granted. + // This doesn't do comprehensive tests to make sure fonts can work properly. + // It is just a quick and simple check to catch things like improper sandbox + // policy setup. + DCHECK(SkFontMgr::RefDefault()->countFamilies()); + + // Initialize a connection to FontLoaderMac service so blink platform's web + // sandbox support can communicate with it to load font. + content::UtilityThread::Get()->InitializeFontLoaderMac( + context()->connector()); +#endif +} + +void PdfCompositorService::OnStart() { + PrepareToStart(); + + ref_factory_ = std::make_unique<service_manager::ServiceContextRefFactory>( + context()->CreateQuitClosure()); + registry_.AddInterface( + base::Bind(&OnPdfCompositorRequest, creator_, ref_factory_.get())); +} + +void PdfCompositorService::OnBindInterface( + const service_manager::BindSourceInfo& source_info, + const std::string& interface_name, + mojo::ScopedMessagePipeHandle interface_pipe) { + registry_.BindInterface(interface_name, std::move(interface_pipe)); +} + +} // namespace printing diff --git a/chromium/components/services/pdf_compositor/pdf_compositor_service.h b/chromium/components/services/pdf_compositor/pdf_compositor_service.h new file mode 100644 index 00000000000..48ec8fb848d --- /dev/null +++ b/chromium/components/services/pdf_compositor/pdf_compositor_service.h @@ -0,0 +1,55 @@ +// Copyright 2017 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. + +#ifndef COMPONENTS_SERVICES_PDF_COMPOSITOR_PDF_COMPOSITOR_SERVICE_H_ +#define COMPONENTS_SERVICES_PDF_COMPOSITOR_PDF_COMPOSITOR_SERVICE_H_ + +#include <memory> +#include <string> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "components/discardable_memory/client/client_discardable_shared_memory_manager.h" +#include "components/services/pdf_compositor/public/interfaces/pdf_compositor.mojom.h" +#include "services/service_manager/public/cpp/binder_registry.h" +#include "services/service_manager/public/cpp/service.h" +#include "services/service_manager/public/cpp/service_context_ref.h" + +namespace printing { + +class PdfCompositorService : public service_manager::Service { + public: + explicit PdfCompositorService(const std::string& creator); + ~PdfCompositorService() override; + + // Factory function for use as an embedded service. + static std::unique_ptr<service_manager::Service> Create( + const std::string& creator); + + // service_manager::Service: + void OnStart() override; + void OnBindInterface(const service_manager::BindSourceInfo& source_info, + const std::string& interface_name, + mojo::ScopedMessagePipeHandle interface_pipe) override; + + virtual void PrepareToStart(); + + private: + // The creator of this service. + // Currently contains the service creator's user agent string if given, + // otherwise just use string "Chromium". + const std::string creator_; + + std::unique_ptr<discardable_memory::ClientDiscardableSharedMemoryManager> + discardable_shared_memory_manager_; + std::unique_ptr<service_manager::ServiceContextRefFactory> ref_factory_; + service_manager::BinderRegistry registry_; + base::WeakPtrFactory<PdfCompositorService> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(PdfCompositorService); +}; + +} // namespace printing + +#endif // COMPONENTS_SERVICES_PDF_COMPOSITOR_PDF_COMPOSITOR_SERVICE_H_ diff --git a/chromium/components/services/pdf_compositor/pdf_compositor_service_unittest.cc b/chromium/components/services/pdf_compositor/pdf_compositor_service_unittest.cc new file mode 100644 index 00000000000..ccdc45dd33d --- /dev/null +++ b/chromium/components/services/pdf_compositor/pdf_compositor_service_unittest.cc @@ -0,0 +1,246 @@ +// Copyright 2017 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 <memory> +#include <utility> + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/memory/read_only_shared_memory_region.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/test/test_discardable_memory_allocator.h" +#include "cc/paint/paint_flags.h" +#include "cc/paint/skia_paint_canvas.h" +#include "components/services/pdf_compositor/pdf_compositor_service.h" +#include "components/services/pdf_compositor/public/cpp/pdf_service_mojo_types.h" +#include "components/services/pdf_compositor/public/interfaces/pdf_compositor.mojom.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/system/platform_handle.h" +#include "services/service_manager/public/cpp/binder_registry.h" +#include "services/service_manager/public/cpp/service_context.h" +#include "services/service_manager/public/cpp/service_test.h" +#include "services/service_manager/public/mojom/service_factory.mojom.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkStream.h" +#include "third_party/skia/src/utils/SkMultiPictureDocument.h" + +namespace printing { + +// In order to test PdfCompositorService, this class overrides PrepareToStart() +// to do nothing. So the test discardable memory allocator set up by +// PdfCompositorServiceTest will be used. Also checks for the service setup are +// skipped since we don't have those setups in unit tests. +class PdfCompositorTestService : public printing::PdfCompositorService { + public: + explicit PdfCompositorTestService(const std::string& creator) + : PdfCompositorService(creator) {} + ~PdfCompositorTestService() override {} + + // PdfCompositorService: + void PrepareToStart() override {} +}; + +class PdfServiceTestClient : public service_manager::test::ServiceTestClient, + public service_manager::mojom::ServiceFactory { + public: + explicit PdfServiceTestClient(service_manager::test::ServiceTest* test) + : service_manager::test::ServiceTestClient(test) { + registry_.AddInterface<service_manager::mojom::ServiceFactory>( + base::Bind(&PdfServiceTestClient::Create, base::Unretained(this))); + } + ~PdfServiceTestClient() override {} + + // service_manager::Service + void OnBindInterface(const service_manager::BindSourceInfo& source_info, + const std::string& interface_name, + mojo::ScopedMessagePipeHandle interface_pipe) override { + registry_.BindInterface(interface_name, std::move(interface_pipe)); + } + + // service_manager::mojom::ServiceFactory + void CreateService( + service_manager::mojom::ServiceRequest request, + const std::string& name, + service_manager::mojom::PIDReceiverPtr pid_receiver) override { + if (!name.compare(mojom::kServiceName)) { + service_context_ = std::make_unique<service_manager::ServiceContext>( + std::make_unique<PdfCompositorTestService>("pdf_compositor_unittest"), + std::move(request)); + } + } + + void Create(service_manager::mojom::ServiceFactoryRequest request) { + service_factory_bindings_.AddBinding(this, std::move(request)); + } + + private: + service_manager::BinderRegistry registry_; + mojo::BindingSet<service_manager::mojom::ServiceFactory> + service_factory_bindings_; + std::unique_ptr<service_manager::ServiceContext> service_context_; +}; + +class PdfCompositorServiceTest : public service_manager::test::ServiceTest { + public: + PdfCompositorServiceTest() : ServiceTest("pdf_compositor_service_unittest") {} + ~PdfCompositorServiceTest() override {} + + MOCK_METHOD1(CallbackOnCompositeSuccess, + void(const base::ReadOnlySharedMemoryRegion&)); + MOCK_METHOD1(CallbackOnCompositeStatus, void(mojom::PdfCompositor::Status)); + void OnCompositeToPdfCallback(mojom::PdfCompositor::Status status, + base::ReadOnlySharedMemoryRegion region) { + if (status == mojom::PdfCompositor::Status::SUCCESS) + CallbackOnCompositeSuccess(region); + else + CallbackOnCompositeStatus(status); + run_loop_->Quit(); + } + + MOCK_METHOD0(ConnectionClosed, void()); + + protected: + // service_manager::test::ServiceTest: + void SetUp() override { + base::DiscardableMemoryAllocator::SetInstance( + &discardable_memory_allocator_); + ServiceTest::SetUp(); + + ASSERT_FALSE(compositor_); + connector()->BindInterface(mojom::kServiceName, &compositor_); + ASSERT_TRUE(compositor_); + + run_loop_ = std::make_unique<base::RunLoop>(); + } + + void TearDown() override { + // Clean up + compositor_.reset(); + base::DiscardableMemoryAllocator::SetInstance(nullptr); + } + + std::unique_ptr<service_manager::Service> CreateService() override { + return std::make_unique<PdfServiceTestClient>(this); + } + + mojo::ScopedSharedBufferHandle CreateMSKP() { + SkDynamicMemoryWStream stream; + sk_sp<SkDocument> doc = SkMakeMultiPictureDocument(&stream); + cc::SkiaPaintCanvas canvas(doc->beginPage(800, 600)); + SkRect rect = SkRect::MakeXYWH(10, 10, 250, 250); + cc::PaintFlags flags; + flags.setAntiAlias(false); + flags.setColor(SK_ColorRED); + flags.setStyle(cc::PaintFlags::kFill_Style); + canvas.drawRect(rect, flags); + doc->endPage(); + doc->close(); + + size_t len = stream.bytesWritten(); + base::MappedReadOnlyRegion memory = + base::ReadOnlySharedMemoryRegion::Create(len); + CHECK(memory.IsValid()); + stream.copyTo(memory.mapping.memory()); + return mojo::WrapReadOnlySharedMemoryRegion(std::move(memory.region)); + } + + void CallCompositorWithSuccess(mojom::PdfCompositorPtr ptr) { + static constexpr uint64_t kFrameGuid = 1234; + auto handle = CreateMSKP(); + ASSERT_TRUE(handle->is_valid()); + EXPECT_CALL(*this, CallbackOnCompositeSuccess(testing::_)).Times(1); + ptr->CompositeDocumentToPdf( + kFrameGuid, std::move(handle), ContentToFrameMap(), + base::BindOnce(&PdfCompositorServiceTest::OnCompositeToPdfCallback, + base::Unretained(this))); + run_loop_->Run(); + } + + std::unique_ptr<base::RunLoop> run_loop_; + mojom::PdfCompositorPtr compositor_; + base::TestDiscardableMemoryAllocator discardable_memory_allocator_; + + private: + DISALLOW_COPY_AND_ASSIGN(PdfCompositorServiceTest); +}; + +// Test callback function is called on error conditions in service. +TEST_F(PdfCompositorServiceTest, InvokeCallbackOnContentError) { + EXPECT_CALL(*this, CallbackOnCompositeStatus( + mojom::PdfCompositor::Status::CONTENT_FORMAT_ERROR)) + .Times(1); + compositor_->CompositeDocumentToPdf( + 5u, mojo::SharedBufferHandle::Create(10), ContentToFrameMap(), + base::BindOnce(&PdfCompositorServiceTest::OnCompositeToPdfCallback, + base::Unretained(this))); + run_loop_->Run(); +} + +// Test callback function is called upon success. +TEST_F(PdfCompositorServiceTest, InvokeCallbackOnSuccess) { + CallCompositorWithSuccess(std::move(compositor_)); +} + +// Test coexistence of multiple service instances. +TEST_F(PdfCompositorServiceTest, MultipleServiceInstances) { + // One service can bind multiple interfaces. + mojom::PdfCompositorPtr another_compositor; + ASSERT_FALSE(another_compositor); + connector()->BindInterface(mojom::kServiceName, &another_compositor); + ASSERT_TRUE(another_compositor); + ASSERT_NE(compositor_.get(), another_compositor.get()); + + // Terminating one interface won't affect another. + compositor_.reset(); + CallCompositorWithSuccess(std::move(another_compositor)); +} + +// Test data structures and content of multiple service instances +// are independent from each other. +TEST_F(PdfCompositorServiceTest, IndependentServiceInstances) { + // Create a new connection 2. + mojom::PdfCompositorPtr compositor2; + ASSERT_FALSE(compositor2); + connector()->BindInterface(mojom::kServiceName, &compositor2); + ASSERT_TRUE(compositor2); + + // In original connection, add frame 4 with content 2 referring + // to subframe 1. + compositor_->AddSubframeContent(1u, CreateMSKP(), ContentToFrameMap()); + + // Original connection can use this subframe 1. + EXPECT_CALL(*this, CallbackOnCompositeSuccess(testing::_)).Times(1); + ContentToFrameMap subframe_content_map; + subframe_content_map[2u] = 1u; + + compositor_->CompositeDocumentToPdf( + 4u, CreateMSKP(), std::move(subframe_content_map), + base::BindOnce(&PdfCompositorServiceTest::OnCompositeToPdfCallback, + base::Unretained(this))); + run_loop_->Run(); + testing::Mock::VerifyAndClearExpectations(this); + + // Connection 2 doesn't know about subframe 1. + subframe_content_map.clear(); + subframe_content_map[2u] = 1u; + EXPECT_CALL(*this, CallbackOnCompositeSuccess(testing::_)).Times(0); + compositor2->CompositeDocumentToPdf( + 4u, CreateMSKP(), std::move(subframe_content_map), + base::BindOnce(&PdfCompositorServiceTest::OnCompositeToPdfCallback, + base::Unretained(this))); + testing::Mock::VerifyAndClearExpectations(this); + + // Add info about subframe 1 to connection 2 so it can use it. + EXPECT_CALL(*this, CallbackOnCompositeSuccess(testing::_)).Times(1); + // Add subframe 1's content. + // Now all content needed for previous request is ready. + compositor2->AddSubframeContent(1u, CreateMSKP(), ContentToFrameMap()); + run_loop_ = std::make_unique<base::RunLoop>(); + run_loop_->Run(); +} + +} // namespace printing diff --git a/chromium/components/services/pdf_compositor/pdf_compositor_service_unittest_manifest.json b/chromium/components/services/pdf_compositor/pdf_compositor_service_unittest_manifest.json new file mode 100644 index 00000000000..19a847f67ce --- /dev/null +++ b/chromium/components/services/pdf_compositor/pdf_compositor_service_unittest_manifest.json @@ -0,0 +1,17 @@ +{ + "name": "pdf_compositor_service_unittest", + "display_name": "Pdf Compositor Service Unittest", + "interface_provider_specs": { + "service_manager:connector": { + "provides": { + "service_manager:service_factory": [ + "service_manager.mojom.ServiceFactory" + ] + }, + "requires": { + "pdf_compositor": [ "compositor" ], + "service_manager": [ "service_manager:service_manager" ] + } + } + } +} diff --git a/chromium/components/services/pdf_compositor/public/cpp/BUILD.gn b/chromium/components/services/pdf_compositor/public/cpp/BUILD.gn new file mode 100644 index 00000000000..414da314966 --- /dev/null +++ b/chromium/components/services/pdf_compositor/public/cpp/BUILD.gn @@ -0,0 +1,34 @@ +# Copyright 2017 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. + +import("//mojo/public/tools/bindings/mojom.gni") + +source_set("factory") { + sources = [ + "pdf_compositor_service_factory.cc", + "pdf_compositor_service_factory.h", + ] + + deps = [ + "//components/services/pdf_compositor/", + "//content/public/common", + "//content/public/utility", + ] + + public_deps = [ + "//components/services/pdf_compositor/public/interfaces", + "//services/service_manager/public/cpp", + ] +} + +source_set("utils") { + sources = [ + "pdf_service_mojo_utils.cc", + "pdf_service_mojo_utils.h", + ] + + public_deps = [ + "//mojo/public/cpp/system:system", + ] +} diff --git a/chromium/components/services/pdf_compositor/public/cpp/pdf_compositor_service_factory.cc b/chromium/components/services/pdf_compositor/public/cpp/pdf_compositor_service_factory.cc new file mode 100644 index 00000000000..0af223b62b1 --- /dev/null +++ b/chromium/components/services/pdf_compositor/public/cpp/pdf_compositor_service_factory.cc @@ -0,0 +1,28 @@ +// Copyright 2017 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 "components/services/pdf_compositor/public/cpp/pdf_compositor_service_factory.h" + +#include "build/build_config.h" +#include "components/services/pdf_compositor/pdf_compositor_service.h" +#include "content/public/utility/utility_thread.h" +#include "third_party/blink/public/platform/web_image_generator.h" +#include "third_party/skia/include/core/SkGraphics.h" + +namespace printing { + +std::unique_ptr<service_manager::Service> CreatePdfCompositorService( + const std::string& creator) { +#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_FUCHSIA) + content::UtilityThread::Get()->EnsureBlinkInitializedWithSandboxSupport(); +#else + content::UtilityThread::Get()->EnsureBlinkInitialized(); +#endif + // Hook up blink's codecs so skia can call them. + SkGraphics::SetImageGeneratorFromEncodedDataFactory( + blink::WebImageGenerator::CreateAsSkImageGenerator); + return printing::PdfCompositorService::Create(creator); +} + +} // namespace printing diff --git a/chromium/components/services/pdf_compositor/public/cpp/pdf_compositor_service_factory.h b/chromium/components/services/pdf_compositor/public/cpp/pdf_compositor_service_factory.h new file mode 100644 index 00000000000..371f47294a0 --- /dev/null +++ b/chromium/components/services/pdf_compositor/public/cpp/pdf_compositor_service_factory.h @@ -0,0 +1,20 @@ +// Copyright 2017 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. + +#ifndef COMPONENTS_SERVICES_PDF_COMPOSITOR_PUBLIC_CPP_PDF_COMPOSITOR_SERVICE_FACTORY_H_ +#define COMPONENTS_SERVICES_PDF_COMPOSITOR_PUBLIC_CPP_PDF_COMPOSITOR_SERVICE_FACTORY_H_ + +#include <memory> +#include <string> + +#include "services/service_manager/public/cpp/service.h" + +namespace printing { + +std::unique_ptr<service_manager::Service> CreatePdfCompositorService( + const std::string& creator); + +} // namespace printing + +#endif // COMPONENTS_SERVICES_PDF_COMPOSITOR_PUBLIC_CPP_PDF_COMPOSITOR_SERVICE_FACTORY_H_ diff --git a/chromium/components/services/pdf_compositor/public/cpp/pdf_service_mojo_types.h b/chromium/components/services/pdf_compositor/public/cpp/pdf_service_mojo_types.h new file mode 100644 index 00000000000..dfc3b5f8e15 --- /dev/null +++ b/chromium/components/services/pdf_compositor/public/cpp/pdf_service_mojo_types.h @@ -0,0 +1,17 @@ +// 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. + +#ifndef COMPONENTS_SERVICES_PDF_COMPOSITOR_PUBLIC_CPP_PDF_SERVICE_MOJO_TYPES_H_ +#define COMPONENTS_SERVICES_PDF_COMPOSITOR_PUBLIC_CPP_PDF_SERVICE_MOJO_TYPES_H_ + +#include "base/containers/flat_map.h" + +namespace printing { + +// Create an alias for map<uint32, uint64> type. +using ContentToFrameMap = base::flat_map<uint32_t, uint64_t>; + +} // namespace printing + +#endif // COMPONENTS_SERVICES_PDF_COMPOSITOR_PUBLIC_CPP_PDF_SERVICE_MOJO_TYPES_H_ diff --git a/chromium/components/services/pdf_compositor/public/cpp/pdf_service_mojo_utils.cc b/chromium/components/services/pdf_compositor/public/cpp/pdf_service_mojo_utils.cc new file mode 100644 index 00000000000..b814cf60dfe --- /dev/null +++ b/chromium/components/services/pdf_compositor/public/cpp/pdf_service_mojo_utils.cc @@ -0,0 +1,53 @@ +// Copyright 2017 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 "components/services/pdf_compositor/public/cpp/pdf_service_mojo_utils.h" + +#include <utility> + +#include "base/memory/shared_memory.h" +#include "base/memory/writable_shared_memory_region.h" +#include "mojo/public/cpp/system/platform_handle.h" + +namespace printing { + +base::MappedReadOnlyRegion CreateReadOnlySharedMemoryRegion(size_t size) { + mojo::ScopedSharedBufferHandle handle = + mojo::SharedBufferHandle::Create(size); + base::WritableSharedMemoryRegion writable_region = + UnwrapWritableSharedMemoryRegion(std::move(handle)); + base::WritableSharedMemoryMapping mapping = writable_region.Map(); + if (!mapping.IsValid()) + return {}; + + base::ReadOnlySharedMemoryRegion readonly_region = + base::WritableSharedMemoryRegion::ConvertToReadOnly( + std::move(writable_region)); + return {std::move(readonly_region), std::move(mapping)}; +} + +std::unique_ptr<base::SharedMemory> GetShmFromMojoHandle( + mojo::ScopedSharedBufferHandle handle) { + base::SharedMemoryHandle memory_handle; + size_t memory_size = 0; + mojo::UnwrappedSharedMemoryHandleProtection protection; + + const MojoResult result = mojo::UnwrapSharedMemoryHandle( + std::move(handle), &memory_handle, &memory_size, &protection); + if (result != MOJO_RESULT_OK) + return nullptr; + + DCHECK_GT(memory_size, 0u); + const bool read_only = + protection == mojo::UnwrappedSharedMemoryHandleProtection::kReadOnly; + std::unique_ptr<base::SharedMemory> shm = + std::make_unique<base::SharedMemory>(memory_handle, read_only); + if (!shm->Map(memory_size)) { + DLOG(ERROR) << "Map shared memory failed."; + return nullptr; + } + return shm; +} + +} // namespace printing diff --git a/chromium/components/services/pdf_compositor/public/cpp/pdf_service_mojo_utils.h b/chromium/components/services/pdf_compositor/public/cpp/pdf_service_mojo_utils.h new file mode 100644 index 00000000000..1e0bd92fe1e --- /dev/null +++ b/chromium/components/services/pdf_compositor/public/cpp/pdf_service_mojo_utils.h @@ -0,0 +1,28 @@ +// Copyright 2017 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. + +#ifndef COMPONENTS_SERVICES_PDF_COMPOSITOR_PUBLIC_CPP_PDF_SERVICE_MOJO_UTILS_H_ +#define COMPONENTS_SERVICES_PDF_COMPOSITOR_PUBLIC_CPP_PDF_SERVICE_MOJO_UTILS_H_ + +#include <memory> + +#include "base/memory/read_only_shared_memory_region.h" +#include "mojo/public/cpp/system/buffer.h" + +namespace base { +class SharedMemory; +} // namespace base + +namespace printing { + +// Similar to base::ReadOnlySharedMemoryRegion::Create(), except it works inside +// sandboxed environments. +base::MappedReadOnlyRegion CreateReadOnlySharedMemoryRegion(size_t size); + +std::unique_ptr<base::SharedMemory> GetShmFromMojoHandle( + mojo::ScopedSharedBufferHandle handle); + +} // namespace printing + +#endif // COMPONENTS_SERVICES_PDF_COMPOSITOR_PUBLIC_CPP_PDF_SERVICE_MOJO_UTILS_H_ diff --git a/chromium/components/services/pdf_compositor/public/interfaces/BUILD.gn b/chromium/components/services/pdf_compositor/public/interfaces/BUILD.gn new file mode 100644 index 00000000000..804ccb4a828 --- /dev/null +++ b/chromium/components/services/pdf_compositor/public/interfaces/BUILD.gn @@ -0,0 +1,15 @@ +# Copyright 2017 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. + +import("//mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "pdf_compositor.mojom", + ] + public_deps = [ + "//mojo/public/mojom/base", + "//url/mojom:url_mojom_gurl", + ] +} diff --git a/chromium/components/services/pdf_compositor/public/interfaces/OWNERS b/chromium/components/services/pdf_compositor/public/interfaces/OWNERS new file mode 100644 index 00000000000..08850f42120 --- /dev/null +++ b/chromium/components/services/pdf_compositor/public/interfaces/OWNERS @@ -0,0 +1,2 @@ +per-file *.mojom=set noparent +per-file *.mojom=file://ipc/SECURITY_OWNERS diff --git a/chromium/components/services/pdf_compositor/public/interfaces/pdf_compositor.mojom b/chromium/components/services/pdf_compositor/public/interfaces/pdf_compositor.mojom new file mode 100644 index 00000000000..934b04e83ae --- /dev/null +++ b/chromium/components/services/pdf_compositor/public/interfaces/pdf_compositor.mojom @@ -0,0 +1,62 @@ +// Copyright 2017 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. + +module printing.mojom; + +import "mojo/public/mojom/base/shared_memory.mojom"; +import "url/mojom/url.mojom"; + +const string kServiceName = "pdf_compositor"; + +interface PdfCompositor { + // The status of composition and conversion execution. + // These values are persisted to logs. Entries should not be renumbered and + // numeric values should never be reused. + enum Status { + SUCCESS = 0, + HANDLE_MAP_ERROR = 1, + CONTENT_FORMAT_ERROR = 2, + COMPOSTING_FAILURE = 3, + }; + + // Notifies that a subframe is unavailable, such as the render frame process + // hosting it crashed or terminated. The subframe will be composited with no + // content in the composited result. + // |frame_guid| is this subframe's global unique id. + NotifyUnavailableSubframe(uint64 frame_guid); + + // Adds the content of a subframe for composition. + // |frame_guid| is this subframe's global unique id. + // |serialized_content| points to a buffer of a serialized Skia picture which + // has the painted content of this frame. + // |subframe_content_map| records content id and its corresponding frame's + // global unique id. + AddSubframeContent(uint64 frame_guid, handle<shared_buffer> serialized_content, + map<uint32, uint64> subframe_content_info); + + // Requests to composite a page and convert it into a PDF file. + // |frame_guid| is the global unique id of the frame to be composited. + // |page_num| is zero-based sequence number of page. + // |sk_handle| points to a buffer of a Skia MultiPictureDocument which has + // the drawing content of this frame or a page of this frame. + // |subframe_content_map| records content id and its corresponding frame's + // global unique id. + CompositePageToPdf(uint64 frame_guid, uint32 page_num, + handle<shared_buffer> sk_handle, + map<uint32, uint64> subframe_content_info) + => (Status status, + mojo_base.mojom.ReadOnlySharedMemoryRegion? pdf_region); + + // Requests to composite the entire document and convert it into a PDF file. + // All the arguments carry the same meaning as CompositePageToPdf() above, + // except this call doesn't have |page_num|. + CompositeDocumentToPdf(uint64 frame_guid, handle<shared_buffer> sk_handle, + map<uint32, uint64> subframe_content_info) + => (Status status, + mojo_base.mojom.ReadOnlySharedMemoryRegion? pdf_region); + + // Sets the URL which is committed in the main frame of the WebContents, + // for use in crash diagnosis. + SetWebContentsURL(url.mojom.Url url); +}; |