Revert "Fix broken channel icon in chrome://help on CrOS" and try again
[chromium-blink-merge.git] / components / pdf_viewer / pdf_viewer.cc
blob17c65a46272b0328fea433701c7ccdff40f75e45
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/bind.h"
6 #include "base/callback.h"
7 #include "base/containers/hash_tables.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "components/view_manager/public/cpp/types.h"
10 #include "components/view_manager/public/cpp/view.h"
11 #include "components/view_manager/public/cpp/view_observer.h"
12 #include "components/view_manager/public/cpp/view_tree_connection.h"
13 #include "components/view_manager/public/cpp/view_tree_delegate.h"
14 #include "components/view_manager/public/interfaces/gpu.mojom.h"
15 #include "components/view_manager/public/interfaces/surface_id.mojom.h"
16 #include "components/view_manager/public/interfaces/surfaces.mojom.h"
17 #include "gpu/GLES2/gl2chromium.h"
18 #include "gpu/GLES2/gl2extchromium.h"
19 #include "mojo/application/public/cpp/application_connection.h"
20 #include "mojo/application/public/cpp/application_delegate.h"
21 #include "mojo/application/public/cpp/application_impl.h"
22 #include "mojo/application/public/cpp/application_runner.h"
23 #include "mojo/application/public/cpp/connect.h"
24 #include "mojo/application/public/cpp/content_handler_factory.h"
25 #include "mojo/application/public/cpp/interface_factory_impl.h"
26 #include "mojo/application/public/cpp/service_provider_impl.h"
27 #include "mojo/application/public/interfaces/content_handler.mojom.h"
28 #include "mojo/application/public/interfaces/shell.mojom.h"
29 #include "mojo/common/data_pipe_utils.h"
30 #include "mojo/converters/geometry/geometry_type_converters.h"
31 #include "mojo/converters/surfaces/surfaces_utils.h"
32 #include "mojo/public/c/gles2/gles2.h"
33 #include "mojo/public/c/system/main.h"
34 #include "mojo/public/cpp/bindings/binding.h"
35 #include "third_party/pdfium/public/fpdf_ext.h"
36 #include "third_party/pdfium/public/fpdfview.h"
37 #include "ui/gfx/geometry/rect.h"
38 #include "ui/mojo/events/input_events.mojom.h"
39 #include "ui/mojo/events/input_key_codes.mojom.h"
40 #include "ui/mojo/geometry/geometry.mojom.h"
41 #include "ui/mojo/geometry/geometry_util.h"
42 #include "v8/include/v8.h"
44 const uint32_t g_background_color = 0xFF888888;
45 const uint32_t g_transparent_color = 0x00000000;
47 namespace pdf_viewer {
48 namespace {
50 void LostContext(void*) {
51 DCHECK(false);
54 // BitmapUploader is useful if you want to draw a bitmap or color in a View.
55 class BitmapUploader : public mojo::ResourceReturner {
56 public:
57 explicit BitmapUploader(mojo::View* view)
58 : view_(view),
59 color_(g_transparent_color),
60 width_(0),
61 height_(0),
62 format_(BGRA),
63 next_resource_id_(1u),
64 id_namespace_(0u),
65 local_id_(0u),
66 returner_binding_(this) {
68 ~BitmapUploader() override {
69 MojoGLES2DestroyContext(gles2_context_);
72 void Init(mojo::Shell* shell) {
73 mojo::ServiceProviderPtr surfaces_service_provider;
74 mojo::URLRequestPtr request(mojo::URLRequest::New());
75 request->url = mojo::String::From("mojo:view_manager");
76 shell->ConnectToApplication(request.Pass(),
77 mojo::GetProxy(&surfaces_service_provider),
78 nullptr, nullptr);
79 ConnectToService(surfaces_service_provider.get(), &surface_);
80 surface_->GetIdNamespace(
81 base::Bind(&BitmapUploader::SetIdNamespace, base::Unretained(this)));
82 mojo::ResourceReturnerPtr returner_ptr;
83 returner_binding_.Bind(GetProxy(&returner_ptr));
84 surface_->SetResourceReturner(returner_ptr.Pass());
86 mojo::ServiceProviderPtr gpu_service_provider;
87 mojo::URLRequestPtr request2(mojo::URLRequest::New());
88 request2->url = mojo::String::From("mojo:view_manager");
89 shell->ConnectToApplication(request2.Pass(),
90 mojo::GetProxy(&gpu_service_provider), nullptr,
91 nullptr);
92 ConnectToService(gpu_service_provider.get(), &gpu_service_);
94 mojo::CommandBufferPtr gles2_client;
95 gpu_service_->CreateOffscreenGLES2Context(GetProxy(&gles2_client));
96 gles2_context_ = MojoGLES2CreateContext(
97 gles2_client.PassInterface().PassHandle().release().value(),
98 &LostContext, NULL, mojo::Environment::GetDefaultAsyncWaiter());
99 MojoGLES2MakeCurrent(gles2_context_);
102 // Sets the color which is RGBA.
103 void SetColor(uint32_t color) {
104 if (color_ == color)
105 return;
106 color_ = color;
107 if (surface_)
108 Upload();
111 enum Format {
112 RGBA, // Pixel layout on Android.
113 BGRA, // Pixel layout everywhere else.
116 // Sets a bitmap.
117 void SetBitmap(int width,
118 int height,
119 scoped_ptr<std::vector<unsigned char>> data,
120 Format format) {
121 width_ = width;
122 height_ = height;
123 bitmap_ = data.Pass();
124 format_ = format;
125 if (surface_)
126 Upload();
129 private:
130 void Upload() {
131 mojo::Size size;
132 size.width = view_->bounds().width;
133 size.height = view_->bounds().height;
134 if (!size.width || !size.height) {
135 view_->SetSurfaceId(mojo::SurfaceId::New());
136 return;
139 if (id_namespace_ == 0u) // Can't generate a qualified ID yet.
140 return;
142 if (size != surface_size_) {
143 if (local_id_ != 0u) {
144 surface_->DestroySurface(local_id_);
146 local_id_++;
147 surface_->CreateSurface(local_id_);
148 surface_size_ = size;
149 auto qualified_id = mojo::SurfaceId::New();
150 qualified_id->id_namespace = id_namespace_;
151 qualified_id->local = local_id_;
152 view_->SetSurfaceId(qualified_id.Pass());
155 gfx::Rect bounds(size.width, size.height);
156 mojo::PassPtr pass = mojo::CreateDefaultPass(1, bounds);
157 mojo::CompositorFramePtr frame = mojo::CompositorFrame::New();
158 frame->resources.resize(0u);
160 pass->quads.resize(0u);
161 pass->shared_quad_states.push_back(
162 mojo::CreateDefaultSQS(size.To<gfx::Size>()));
164 MojoGLES2MakeCurrent(gles2_context_);
165 if (bitmap_.get()) {
166 mojo::Size bitmap_size;
167 bitmap_size.width = width_;
168 bitmap_size.height = height_;
169 GLuint texture_id = BindTextureForSize(bitmap_size);
170 glTexSubImage2D(GL_TEXTURE_2D,
174 bitmap_size.width,
175 bitmap_size.height,
176 TextureFormat(),
177 GL_UNSIGNED_BYTE,
178 &((*bitmap_)[0]));
180 GLbyte mailbox[GL_MAILBOX_SIZE_CHROMIUM];
181 glGenMailboxCHROMIUM(mailbox);
182 glProduceTextureCHROMIUM(GL_TEXTURE_2D, mailbox);
183 GLuint sync_point = glInsertSyncPointCHROMIUM();
185 mojo::TransferableResourcePtr resource =
186 mojo::TransferableResource::New();
187 resource->id = next_resource_id_++;
188 resource_to_texture_id_map_[resource->id] = texture_id;
189 resource->format = mojo::RESOURCE_FORMAT_RGBA_8888;
190 resource->filter = GL_LINEAR;
191 resource->size = bitmap_size.Clone();
192 mojo::MailboxHolderPtr mailbox_holder = mojo::MailboxHolder::New();
193 mailbox_holder->mailbox = mojo::Mailbox::New();
194 for (int i = 0; i < GL_MAILBOX_SIZE_CHROMIUM; ++i)
195 mailbox_holder->mailbox->name.push_back(mailbox[i]);
196 mailbox_holder->texture_target = GL_TEXTURE_2D;
197 mailbox_holder->sync_point = sync_point;
198 resource->mailbox_holder = mailbox_holder.Pass();
199 resource->is_repeated = false;
200 resource->is_software = false;
202 mojo::QuadPtr quad = mojo::Quad::New();
203 quad->material = mojo::MATERIAL_TEXTURE_CONTENT;
205 mojo::RectPtr rect = mojo::Rect::New();
206 if (width_ <= size.width && height_ <= size.height) {
207 rect->width = width_;
208 rect->height = height_;
209 } else {
210 // The source bitmap is larger than the viewport. Resize it while
211 // maintaining the aspect ratio.
212 float width_ratio = static_cast<float>(width_) / size.width;
213 float height_ratio = static_cast<float>(height_) / size.height;
214 if (width_ratio > height_ratio) {
215 rect->width = size.width;
216 rect->height = height_ / width_ratio;
217 } else {
218 rect->height = size.height;
219 rect->width = width_ / height_ratio;
222 quad->rect = rect.Clone();
223 quad->opaque_rect = rect.Clone();
224 quad->visible_rect = rect.Clone();
225 quad->needs_blending = true;
226 quad->shared_quad_state_index = 0u;
228 mojo::TextureQuadStatePtr texture_state = mojo::TextureQuadState::New();
229 texture_state->resource_id = resource->id;
230 texture_state->premultiplied_alpha = true;
231 texture_state->uv_top_left = mojo::PointF::New();
232 texture_state->uv_bottom_right = mojo::PointF::New();
233 texture_state->uv_bottom_right->x = 1.f;
234 texture_state->uv_bottom_right->y = 1.f;
235 texture_state->background_color = mojo::Color::New();
236 texture_state->background_color->rgba = g_transparent_color;
237 for (int i = 0; i < 4; ++i)
238 texture_state->vertex_opacity.push_back(1.f);
239 texture_state->y_flipped = false;
241 frame->resources.push_back(resource.Pass());
242 quad->texture_quad_state = texture_state.Pass();
243 pass->quads.push_back(quad.Pass());
246 if (color_ != g_transparent_color) {
247 mojo::QuadPtr quad = mojo::Quad::New();
248 quad->material = mojo::MATERIAL_SOLID_COLOR;
249 quad->rect = mojo::Rect::From(bounds);
250 quad->opaque_rect = mojo::Rect::New();
251 quad->visible_rect = mojo::Rect::From(bounds);
252 quad->needs_blending = true;
253 quad->shared_quad_state_index = 0u;
255 mojo::SolidColorQuadStatePtr color_state =
256 mojo::SolidColorQuadState::New();
257 color_state->color = mojo::Color::New();
258 color_state->color->rgba = color_;
259 color_state->force_anti_aliasing_off = false;
261 quad->solid_color_quad_state = color_state.Pass();
262 pass->quads.push_back(quad.Pass());
265 frame->passes.push_back(pass.Pass());
267 surface_->SubmitCompositorFrame(local_id_, frame.Pass(), mojo::Closure());
270 uint32_t BindTextureForSize(const mojo::Size size) {
271 // TODO(jamesr): Recycle textures.
272 GLuint texture = 0u;
273 glGenTextures(1, &texture);
274 glBindTexture(GL_TEXTURE_2D, texture);
275 glTexImage2D(GL_TEXTURE_2D,
277 TextureFormat(),
278 size.width,
279 size.height,
281 TextureFormat(),
282 GL_UNSIGNED_BYTE,
284 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
285 return texture;
288 uint32_t TextureFormat() {
289 return format_ == BGRA ? GL_BGRA_EXT : GL_RGBA;
292 void SetIdNamespace(uint32_t id_namespace) {
293 id_namespace_ = id_namespace;
294 if (color_ != g_transparent_color || bitmap_.get())
295 Upload();
298 // ResourceReturner implementation.
299 void ReturnResources(
300 mojo::Array<mojo::ReturnedResourcePtr> resources) override {
301 MojoGLES2MakeCurrent(gles2_context_);
302 // TODO(jamesr): Recycle.
303 for (size_t i = 0; i < resources.size(); ++i) {
304 mojo::ReturnedResourcePtr resource = resources[i].Pass();
305 DCHECK_EQ(1, resource->count);
306 glWaitSyncPointCHROMIUM(resource->sync_point);
307 uint32_t texture_id = resource_to_texture_id_map_[resource->id];
308 DCHECK_NE(0u, texture_id);
309 resource_to_texture_id_map_.erase(resource->id);
310 glDeleteTextures(1, &texture_id);
314 mojo::View* view_;
315 mojo::GpuPtr gpu_service_;
316 MojoGLES2Context gles2_context_;
318 mojo::Size size_;
319 uint32_t color_;
320 int width_;
321 int height_;
322 Format format_;
323 scoped_ptr<std::vector<unsigned char>> bitmap_;
324 mojo::SurfacePtr surface_;
325 mojo::Size surface_size_;
326 uint32_t next_resource_id_;
327 uint32_t id_namespace_;
328 uint32_t local_id_;
329 base::hash_map<uint32_t, uint32_t> resource_to_texture_id_map_;
330 mojo::Binding<mojo::ResourceReturner> returner_binding_;
332 DISALLOW_COPY_AND_ASSIGN(BitmapUploader);
335 class EmbedderData {
336 public:
337 EmbedderData(mojo::Shell* shell, mojo::View* root) : bitmap_uploader_(root) {
338 bitmap_uploader_.Init(shell);
339 bitmap_uploader_.SetColor(g_background_color);
342 BitmapUploader& bitmap_uploader() { return bitmap_uploader_; }
344 private:
345 BitmapUploader bitmap_uploader_;
347 DISALLOW_COPY_AND_ASSIGN(EmbedderData);
350 class PDFView : public mojo::ApplicationDelegate,
351 public mojo::ViewTreeDelegate,
352 public mojo::ViewObserver,
353 public mojo::InterfaceFactory<mojo::ViewTreeClient> {
354 public:
355 PDFView(mojo::InterfaceRequest<mojo::Application> request,
356 mojo::URLResponsePtr response)
357 : app_(this, request.Pass(), base::Bind(&PDFView::OnTerminate,
358 base::Unretained(this))),
359 current_page_(0), page_count_(0), doc_(nullptr) {
360 FetchPDF(response.Pass());
363 ~PDFView() override {
364 if (doc_)
365 FPDF_CloseDocument(doc_);
366 for (auto& roots : embedder_for_roots_) {
367 roots.first->RemoveObserver(this);
368 delete roots.second;
372 private:
373 // Overridden from ApplicationDelegate:
374 bool ConfigureIncomingConnection(
375 mojo::ApplicationConnection* connection) override {
376 connection->AddService<mojo::ViewTreeClient>(this);
377 return true;
380 // Overridden from ViewTreeDelegate:
381 void OnEmbed(mojo::View* root) override {
382 DCHECK(embedder_for_roots_.find(root) == embedder_for_roots_.end());
383 root->AddObserver(this);
384 EmbedderData* embedder_data = new EmbedderData(app_.shell(), root);
385 embedder_for_roots_[root] = embedder_data;
386 DrawBitmap(embedder_data);
389 void OnConnectionLost(mojo::ViewTreeConnection* connection) override {}
391 // Overridden from ViewObserver:
392 void OnViewBoundsChanged(mojo::View* view,
393 const mojo::Rect& old_bounds,
394 const mojo::Rect& new_bounds) override {
395 DCHECK(embedder_for_roots_.find(view) != embedder_for_roots_.end());
396 DrawBitmap(embedder_for_roots_[view]);
399 void OnViewInputEvent(mojo::View* view,
400 const mojo::EventPtr& event) override {
401 DCHECK(embedder_for_roots_.find(view) != embedder_for_roots_.end());
402 if (event->key_data &&
403 (event->action != mojo::EVENT_TYPE_KEY_PRESSED ||
404 event->key_data->is_char)) {
405 return;
408 if ((event->key_data &&
409 event->key_data->windows_key_code == mojo::KEYBOARD_CODE_DOWN) ||
410 (event->pointer_data && event->pointer_data->vertical_wheel < 0)) {
411 if (current_page_ < (page_count_ - 1)) {
412 current_page_++;
413 DrawBitmap(embedder_for_roots_[view]);
415 } else if ((event->key_data &&
416 event->key_data->windows_key_code == mojo::KEYBOARD_CODE_UP) ||
417 (event->pointer_data &&
418 event->pointer_data->vertical_wheel > 0)) {
419 if (current_page_ > 0) {
420 current_page_--;
421 DrawBitmap(embedder_for_roots_[view]);
426 void OnViewDestroyed(mojo::View* view) override {
427 DCHECK(embedder_for_roots_.find(view) != embedder_for_roots_.end());
428 const auto& it = embedder_for_roots_.find(view);
429 DCHECK(it != embedder_for_roots_.end());
430 delete it->second;
431 embedder_for_roots_.erase(it);
432 if (embedder_for_roots_.size() == 0)
433 app_.Quit();
436 // Overridden from mojo::InterfaceFactory<mojo::ViewTreeClient>:
437 void Create(
438 mojo::ApplicationConnection* connection,
439 mojo::InterfaceRequest<mojo::ViewTreeClient> request) override {
440 mojo::ViewTreeConnection::Create(this, request.Pass());
443 void DrawBitmap(EmbedderData* embedder_data) {
444 if (!doc_)
445 return;
447 FPDF_PAGE page = FPDF_LoadPage(doc_, current_page_);
448 int width = static_cast<int>(FPDF_GetPageWidth(page));
449 int height = static_cast<int>(FPDF_GetPageHeight(page));
451 scoped_ptr<std::vector<unsigned char>> bitmap;
452 bitmap.reset(new std::vector<unsigned char>);
453 bitmap->resize(width * height * 4);
455 FPDF_BITMAP f_bitmap = FPDFBitmap_CreateEx(width, height, FPDFBitmap_BGRA,
456 &(*bitmap)[0], width * 4);
457 FPDFBitmap_FillRect(f_bitmap, 0, 0, width, height, 0xFFFFFFFF);
458 FPDF_RenderPageBitmap(f_bitmap, page, 0, 0, width, height, 0, 0);
459 FPDFBitmap_Destroy(f_bitmap);
461 FPDF_ClosePage(page);
463 embedder_data->bitmap_uploader().SetBitmap(width, height, bitmap.Pass(),
464 BitmapUploader::BGRA);
467 void FetchPDF(mojo::URLResponsePtr response) {
468 data_.clear();
469 mojo::common::BlockingCopyToString(response->body.Pass(), &data_);
470 if (data_.length() >= static_cast<size_t>(std::numeric_limits<int>::max()))
471 return;
472 doc_ = FPDF_LoadMemDocument(data_.data(), static_cast<int>(data_.length()),
473 nullptr);
474 page_count_ = FPDF_GetPageCount(doc_);
477 // Callback from the quit closure. We key off this rather than
478 // ApplicationDelegate::Quit() as we don't want to shut down the messageloop
479 // when we quit (the messageloop is shared among multiple PDFViews).
480 void OnTerminate() {
481 delete this;
484 mojo::ApplicationImpl app_;
485 std::string data_;
486 int current_page_;
487 int page_count_;
488 FPDF_DOCUMENT doc_;
489 std::map<mojo::View*, EmbedderData*> embedder_for_roots_;
491 DISALLOW_COPY_AND_ASSIGN(PDFView);
494 class ContentHandlerImpl : public mojo::ContentHandler {
495 public:
496 ContentHandlerImpl(mojo::InterfaceRequest<ContentHandler> request)
497 : binding_(this, request.Pass()) {}
498 ~ContentHandlerImpl() override {}
500 private:
501 // Overridden from ContentHandler:
502 void StartApplication(mojo::InterfaceRequest<mojo::Application> request,
503 mojo::URLResponsePtr response) override {
504 new PDFView(request.Pass(), response.Pass());
507 mojo::StrongBinding<mojo::ContentHandler> binding_;
509 DISALLOW_COPY_AND_ASSIGN(ContentHandlerImpl);
512 class PDFViewer : public mojo::ApplicationDelegate,
513 public mojo::InterfaceFactory<mojo::ContentHandler> {
514 public:
515 PDFViewer() {
516 v8::V8::InitializeICU();
517 FPDF_InitLibrary();
520 ~PDFViewer() override {
521 FPDF_DestroyLibrary();
524 private:
525 // Overridden from ApplicationDelegate:
526 bool ConfigureIncomingConnection(
527 mojo::ApplicationConnection* connection) override {
528 connection->AddService(this);
529 return true;
532 // Overridden from InterfaceFactory<ContentHandler>:
533 void Create(mojo::ApplicationConnection* connection,
534 mojo::InterfaceRequest<mojo::ContentHandler> request) override {
535 new ContentHandlerImpl(request.Pass());
538 DISALLOW_COPY_AND_ASSIGN(PDFViewer);
541 } // namespace
542 } // namespace pdf_viewer
544 MojoResult MojoMain(MojoHandle application_request) {
545 mojo::ApplicationRunner runner(new pdf_viewer::PDFViewer());
546 return runner.Run(application_request);