// Copyright 2015 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 "base/bind.h" #include "base/callback.h" #include "base/containers/hash_tables.h" #include "base/memory/scoped_ptr.h" #include "components/bitmap_uploader/bitmap_uploader.h" #include "components/mus/common/types.h" #include "components/mus/public/cpp/scoped_window_ptr.h" #include "components/mus/public/cpp/window.h" #include "components/mus/public/cpp/window_observer.h" #include "components/mus/public/cpp/window_tree_connection.h" #include "components/mus/public/cpp/window_tree_delegate.h" #include "components/mus/public/interfaces/input_events.mojom.h" #include "components/mus/public/interfaces/input_key_codes.mojom.h" #include "components/web_view/public/interfaces/frame.mojom.h" #include "mojo/application/public/cpp/application_connection.h" #include "mojo/application/public/cpp/application_delegate.h" #include "mojo/application/public/cpp/application_impl.h" #include "mojo/application/public/cpp/application_runner.h" #include "mojo/application/public/cpp/connect.h" #include "mojo/application/public/cpp/interface_factory_impl.h" #include "mojo/application/public/cpp/service_provider_impl.h" #include "mojo/application/public/interfaces/content_handler.mojom.h" #include "mojo/application/public/interfaces/shell.mojom.h" #include "mojo/common/data_pipe_utils.h" #include "mojo/public/c/system/main.h" #include "mojo/public/cpp/bindings/binding.h" #include "mojo/services/tracing/public/cpp/tracing_impl.h" #include "third_party/pdfium/public/fpdf_ext.h" #include "third_party/pdfium/public/fpdfview.h" #include "ui/gfx/geometry/rect.h" #include "v8/include/v8.h" const uint32_t g_background_color = 0xFF888888; namespace pdf_viewer { namespace { // Responsible for managing a particlar view displaying a PDF document. class PDFView : public mus::WindowTreeDelegate, public mus::WindowObserver, public web_view::mojom::FrameClient, public mojo::InterfaceFactory { public: using DeleteCallback = base::Callback; PDFView(mojo::ApplicationImpl* app, mojo::ApplicationConnection* connection, FPDF_DOCUMENT doc, const DeleteCallback& delete_callback) : app_ref_(app->app_lifetime_helper()->CreateAppRefCount()), doc_(doc), current_page_(0), page_count_(FPDF_GetPageCount(doc_)), shell_(app->shell()), root_(nullptr), frame_client_binding_(this), delete_callback_(delete_callback) { connection->AddService(this); } void Close() { if (root_) mus::ScopedWindowPtr::DeleteWindowOrWindowManager(root_); else delete this; } private: ~PDFView() override { DCHECK(!root_); if (!delete_callback_.is_null()) delete_callback_.Run(this); } void DrawBitmap() { if (!doc_) return; FPDF_PAGE page = FPDF_LoadPage(doc_, current_page_); int width = static_cast(FPDF_GetPageWidth(page)); int height = static_cast(FPDF_GetPageHeight(page)); scoped_ptr> bitmap; bitmap.reset(new std::vector); bitmap->resize(width * height * 4); FPDF_BITMAP f_bitmap = FPDFBitmap_CreateEx(width, height, FPDFBitmap_BGRA, &(*bitmap)[0], width * 4); FPDFBitmap_FillRect(f_bitmap, 0, 0, width, height, 0xFFFFFFFF); FPDF_RenderPageBitmap(f_bitmap, page, 0, 0, width, height, 0, 0); FPDFBitmap_Destroy(f_bitmap); FPDF_ClosePage(page); bitmap_uploader_->SetBitmap(width, height, bitmap.Pass(), bitmap_uploader::BitmapUploader::BGRA); } // WindowTreeDelegate: void OnEmbed(mus::Window* root) override { DCHECK(!root_); root_ = root; root_->AddObserver(this); bitmap_uploader_.reset(new bitmap_uploader::BitmapUploader(root_)); bitmap_uploader_->Init(shell_); bitmap_uploader_->SetColor(g_background_color); DrawBitmap(); } void OnConnectionLost(mus::WindowTreeConnection* connection) override { root_ = nullptr; delete this; } // WindowObserver: void OnWindowBoundsChanged(mus::Window* view, const gfx::Rect& old_bounds, const gfx::Rect& new_bounds) override { DrawBitmap(); } void OnWindowInputEvent(mus::Window* view, const mus::mojom::EventPtr& event) override { if (event->key_data && (event->action != mus::mojom::EVENT_TYPE_KEY_PRESSED || event->key_data->is_char)) { return; } // TODO(rjkroege): Make panning and scrolling more performant and // responsive to gesture events. if ((event->key_data && event->key_data->windows_key_code == mus::mojom::KEYBOARD_CODE_DOWN) || (event->pointer_data && event->pointer_data->wheel_data && event->pointer_data->wheel_data->delta_y < 0)) { if (current_page_ < (page_count_ - 1)) { current_page_++; DrawBitmap(); } } else if ((event->key_data && event->key_data->windows_key_code == mus::mojom::KEYBOARD_CODE_UP) || (event->pointer_data && event->pointer_data->wheel_data && event->pointer_data->wheel_data->delta_y > 0)) { if (current_page_ > 0) { current_page_--; DrawBitmap(); } } } void OnWindowDestroyed(mus::Window* view) override { DCHECK_EQ(root_, view); root_ = nullptr; bitmap_uploader_.reset(); } // web_view::mojom::FrameClient: void OnConnect(web_view::mojom::FramePtr frame, uint32_t change_id, uint32_t view_id, web_view::mojom::WindowConnectType view_connect_type, mojo::Array frame_data, int64_t navigation_start_time_ticks, const OnConnectCallback& callback) override { callback.Run(); frame_ = frame.Pass(); frame_->DidCommitProvisionalLoad(); } void OnFrameAdded(uint32_t change_id, web_view::mojom::FrameDataPtr frame_data) override {} void OnFrameRemoved(uint32_t change_id, uint32_t frame_id) override {} void OnFrameClientPropertyChanged(uint32_t frame_id, const mojo::String& name, mojo::Array new_value) override {} void OnPostMessageEvent(uint32_t source_frame_id, uint32_t target_frame_id, web_view::mojom::HTMLMessageEventPtr event) override { } void OnWillNavigate(const mojo::String& origin, const OnWillNavigateCallback& callback) override {} void OnFrameLoadingStateChanged(uint32_t frame_id, bool loading) override {} void OnDispatchFrameLoadEvent(uint32_t frame_id) override {} void Find(int32_t request_id, const mojo::String& search_text, web_view::mojom::FindOptionsPtr options, bool wrap_within_frame, const FindCallback& callback) override { NOTIMPLEMENTED(); bool found_results = false; callback.Run(found_results); } void StopFinding(bool clear_selection) override {} void HighlightFindResults(int32_t request_id, const mojo::String& search_test, web_view::mojom::FindOptionsPtr options, bool reset) override { NOTIMPLEMENTED(); } void StopHighlightingFindResults() override {} // mojo::InterfaceFactory: void Create( mojo::ApplicationConnection* connection, mojo::InterfaceRequest request) override { frame_client_binding_.Bind(request.Pass()); } scoped_ptr app_ref_; FPDF_DOCUMENT doc_; int current_page_; int page_count_; scoped_ptr bitmap_uploader_; mojo::Shell* shell_; mus::Window* root_; web_view::mojom::FramePtr frame_; mojo::Binding frame_client_binding_; DeleteCallback delete_callback_; DISALLOW_COPY_AND_ASSIGN(PDFView); }; // Responsible for managing all the views for displaying a PDF document. class PDFViewerApplicationDelegate : public mojo::ApplicationDelegate, public mojo::InterfaceFactory { public: PDFViewerApplicationDelegate( mojo::InterfaceRequest request, mojo::URLResponsePtr response, const mojo::Callback& destruct_callback) : app_(this, request.Pass(), base::Bind(&PDFViewerApplicationDelegate::OnTerminate, base::Unretained(this))), doc_(nullptr), is_destroying_(false), destruct_callback_(destruct_callback) { FetchPDF(response.Pass()); } ~PDFViewerApplicationDelegate() override { is_destroying_ = true; if (doc_) FPDF_CloseDocument(doc_); while (!pdf_views_.empty()) pdf_views_.front()->Close(); destruct_callback_.Run(); } private: void FetchPDF(mojo::URLResponsePtr response) { data_.clear(); mojo::common::BlockingCopyToString(response->body.Pass(), &data_); if (data_.length() >= static_cast(std::numeric_limits::max())) return; doc_ = FPDF_LoadMemDocument(data_.data(), static_cast(data_.length()), nullptr); } // Callback from the quit closure. We key off this rather than // ApplicationDelegate::Quit() as we don't want to shut down the messageloop // when we quit (the messageloop is shared among multiple PDFViews). void OnTerminate() { delete this; } void OnPDFViewDestroyed(PDFView* pdf_view) { DCHECK(std::find(pdf_views_.begin(), pdf_views_.end(), pdf_view) != pdf_views_.end()); pdf_views_.erase(std::find(pdf_views_.begin(), pdf_views_.end(), pdf_view)); } // ApplicationDelegate: bool ConfigureIncomingConnection( mojo::ApplicationConnection* connection) override { connection->AddService(this); return true; } // mojo::InterfaceFactory: void Create( mojo::ApplicationConnection* connection, mojo::InterfaceRequest request) override { PDFView* pdf_view = new PDFView( &app_, connection, doc_, base::Bind(&PDFViewerApplicationDelegate::OnPDFViewDestroyed, base::Unretained(this))); pdf_views_.push_back(pdf_view); mus::WindowTreeConnection::Create( pdf_view, request.Pass(), mus::WindowTreeConnection::CreateType::DONT_WAIT_FOR_EMBED); } mojo::ApplicationImpl app_; std::string data_; std::vector pdf_views_; FPDF_DOCUMENT doc_; bool is_destroying_; mojo::Callback destruct_callback_; DISALLOW_COPY_AND_ASSIGN(PDFViewerApplicationDelegate); }; class ContentHandlerImpl : public mojo::ContentHandler { public: ContentHandlerImpl(mojo::InterfaceRequest request) : binding_(this, request.Pass()) {} ~ContentHandlerImpl() override {} private: // ContentHandler: void StartApplication( mojo::InterfaceRequest request, mojo::URLResponsePtr response, const mojo::Callback& destruct_callback) override { new PDFViewerApplicationDelegate( request.Pass(), response.Pass(), destruct_callback); } mojo::StrongBinding binding_; DISALLOW_COPY_AND_ASSIGN(ContentHandlerImpl); }; class PDFViewer : public mojo::ApplicationDelegate, public mojo::InterfaceFactory { public: PDFViewer() { v8::V8::InitializeICU(); FPDF_InitLibrary(); } ~PDFViewer() override { FPDF_DestroyLibrary(); } private: // ApplicationDelegate: void Initialize(mojo::ApplicationImpl* app) override { tracing_.Initialize(app); } bool ConfigureIncomingConnection( mojo::ApplicationConnection* connection) override { connection->AddService(this); return true; } // InterfaceFactory: void Create(mojo::ApplicationConnection* connection, mojo::InterfaceRequest request) override { new ContentHandlerImpl(request.Pass()); } mojo::TracingImpl tracing_; DISALLOW_COPY_AND_ASSIGN(PDFViewer); }; } // namespace } // namespace pdf_viewer MojoResult MojoMain(MojoHandle application_request) { mojo::ApplicationRunner runner(new pdf_viewer::PDFViewer()); return runner.Run(application_request); }