// Copyright (c) 2012 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 "pdf/instance.h" #include // for min() #define _USE_MATH_DEFINES // for M_PI #include // for log() and pow() #include #include #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/values.h" #include "chrome/browser/chrome_page_zoom_constants.h" #include "chrome/common/content_restriction.h" #include "content/public/common/page_zoom.h" #include "net/base/escape.h" #include "pdf/draw_utils.h" #include "pdf/number_image_generator.h" #include "pdf/pdf.h" #include "pdf/resource_consts.h" #include "ppapi/c/dev/ppb_cursor_control_dev.h" #include "ppapi/c/pp_errors.h" #include "ppapi/c/pp_rect.h" #include "ppapi/c/private/ppp_pdf.h" #include "ppapi/c/trusted/ppb_url_loader_trusted.h" #include "ppapi/cpp/core.h" #include "ppapi/cpp/dev/font_dev.h" #include "ppapi/cpp/dev/memory_dev.h" #include "ppapi/cpp/dev/text_input_dev.h" #include "ppapi/cpp/module.h" #include "ppapi/cpp/point.h" #include "ppapi/cpp/private/pdf.h" #include "ppapi/cpp/rect.h" #include "ppapi/cpp/resource.h" #include "ppapi/cpp/url_request_info.h" #include "ui/events/keycodes/keyboard_codes.h" #if defined(OS_MACOSX) #include "base/mac/mac_util.h" #endif namespace chrome_pdf { struct ToolbarButtonInfo { uint32 id; Button::ButtonStyle style; PP_ResourceImage normal; PP_ResourceImage highlighted; PP_ResourceImage pressed; }; namespace { // Uncomment following #define to enable thumbnails. // #define ENABLE_THUMBNAILS const uint32 kToolbarSplashTimeoutMs = 6000; const uint32 kMessageTextColor = 0xFF575757; const uint32 kMessageTextSize = 22; const uint32 kProgressFadeTimeoutMs = 250; const uint32 kProgressDelayTimeoutMs = 1000; const uint32 kAutoScrollTimeoutMs = 50; const double kAutoScrollFactor = 0.2; // Javascript methods. const char kJSAccessibility[] = "accessibility"; const char kJSDocumentLoadComplete[] = "documentLoadComplete"; const char kJSGetHeight[] = "getHeight"; const char kJSGetHorizontalScrollbarThickness[] = "getHorizontalScrollbarThickness"; const char kJSGetPageLocationNormalized[] = "getPageLocationNormalized"; const char kJSGetVerticalScrollbarThickness[] = "getVerticalScrollbarThickness"; const char kJSGetWidth[] = "getWidth"; const char kJSGetZoomLevel[] = "getZoomLevel"; const char kJSGoToPage[] = "goToPage"; const char kJSGrayscale[] = "grayscale"; const char kJSLoadPreviewPage[] = "loadPreviewPage"; const char kJSOnLoad[] = "onload"; const char kJSOnPluginSizeChanged[] = "onPluginSizeChanged"; const char kJSOnScroll[] = "onScroll"; const char kJSPageXOffset[] = "pageXOffset"; const char kJSPageYOffset[] = "pageYOffset"; const char kJSPrintPreviewPageCount[] = "printPreviewPageCount"; const char kJSReload[] = "reload"; const char kJSRemovePrintButton[] = "removePrintButton"; const char kJSResetPrintPreviewUrl[] = "resetPrintPreviewUrl"; const char kJSSendKeyEvent[] = "sendKeyEvent"; const char kJSSetPageNumbers[] = "setPageNumbers"; const char kJSSetPageXOffset[] = "setPageXOffset"; const char kJSSetPageYOffset[] = "setPageYOffset"; const char kJSSetZoomLevel[] = "setZoomLevel"; const char kJSZoomFitToHeight[] = "fitToHeight"; const char kJSZoomFitToWidth[] = "fitToWidth"; const char kJSZoomIn[] = "zoomIn"; const char kJSZoomOut[] = "zoomOut"; // URL reference parameters. // For more possible parameters, see RFC 3778 and the "PDF Open Parameters" // document from Adobe. const char kDelimiters[] = "#&"; const char kNamedDest[] = "nameddest"; const char kPage[] = "page"; const char kChromePrint[] = "chrome://print/"; // Dictionary Value key names for the document accessibility info const char kAccessibleNumberOfPages[] = "numberOfPages"; const char kAccessibleLoaded[] = "loaded"; const char kAccessibleCopyable[] = "copyable"; const ToolbarButtonInfo kPDFToolbarButtons[] = { { kFitToPageButtonId, Button::BUTTON_STATE, PP_RESOURCEIMAGE_PDF_BUTTON_FTP, PP_RESOURCEIMAGE_PDF_BUTTON_FTP_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_FTP_PRESSED }, { kFitToWidthButtonId, Button::BUTTON_STATE, PP_RESOURCEIMAGE_PDF_BUTTON_FTW, PP_RESOURCEIMAGE_PDF_BUTTON_FTW_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_FTW_PRESSED }, { kZoomOutButtonId, Button::BUTTON_CLICKABLE, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT_PRESSED }, { kZoomInButtonId, Button::BUTTON_CLICKABLE, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_PRESSED }, { kSaveButtonId, Button::BUTTON_CLICKABLE, PP_RESOURCEIMAGE_PDF_BUTTON_SAVE, PP_RESOURCEIMAGE_PDF_BUTTON_SAVE_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_SAVE_PRESSED }, { kPrintButtonId, Button::BUTTON_CLICKABLE, PP_RESOURCEIMAGE_PDF_BUTTON_PRINT, PP_RESOURCEIMAGE_PDF_BUTTON_PRINT_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_PRINT_PRESSED }, }; const ToolbarButtonInfo kPDFNoPrintToolbarButtons[] = { { kFitToPageButtonId, Button::BUTTON_STATE, PP_RESOURCEIMAGE_PDF_BUTTON_FTP, PP_RESOURCEIMAGE_PDF_BUTTON_FTP_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_FTP_PRESSED }, { kFitToWidthButtonId, Button::BUTTON_STATE, PP_RESOURCEIMAGE_PDF_BUTTON_FTW, PP_RESOURCEIMAGE_PDF_BUTTON_FTW_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_FTW_PRESSED }, { kZoomOutButtonId, Button::BUTTON_CLICKABLE, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT_PRESSED }, { kZoomInButtonId, Button::BUTTON_CLICKABLE, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_PRESSED }, { kSaveButtonId, Button::BUTTON_CLICKABLE, PP_RESOURCEIMAGE_PDF_BUTTON_SAVE, PP_RESOURCEIMAGE_PDF_BUTTON_SAVE_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_SAVE_PRESSED }, { kPrintButtonId, Button::BUTTON_CLICKABLE, PP_RESOURCEIMAGE_PDF_BUTTON_PRINT_DISABLED, PP_RESOURCEIMAGE_PDF_BUTTON_PRINT_DISABLED, PP_RESOURCEIMAGE_PDF_BUTTON_PRINT_DISABLED } }; const ToolbarButtonInfo kPrintPreviewToolbarButtons[] = { { kFitToPageButtonId, Button::BUTTON_STATE, PP_RESOURCEIMAGE_PDF_BUTTON_FTP, PP_RESOURCEIMAGE_PDF_BUTTON_FTP_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_FTP_PRESSED }, { kFitToWidthButtonId, Button::BUTTON_STATE, PP_RESOURCEIMAGE_PDF_BUTTON_FTW, PP_RESOURCEIMAGE_PDF_BUTTON_FTW_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_FTW_PRESSED }, { kZoomOutButtonId, Button::BUTTON_CLICKABLE, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT_PRESSED }, { kZoomInButtonId, Button::BUTTON_CLICKABLE, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_END, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_END_HOVER, PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_END_PRESSED }, }; static const char kPPPPdfInterface[] = PPP_PDF_INTERFACE_1; PP_Var GetLinkAtPosition(PP_Instance instance, PP_Point point) { pp::Var var; void* object = pp::Instance::GetPerInstanceObject(instance, kPPPPdfInterface); if (object) var = static_cast(object)->GetLinkAtPosition(pp::Point(point)); return var.Detach(); } void Transform(PP_Instance instance, PP_PrivatePageTransformType type) { void* object = pp::Instance::GetPerInstanceObject(instance, kPPPPdfInterface); if (object) { Instance* obj_instance = static_cast(object); switch (type) { case PP_PRIVATEPAGETRANSFORMTYPE_ROTATE_90_CW: obj_instance->RotateClockwise(); break; case PP_PRIVATEPAGETRANSFORMTYPE_ROTATE_90_CCW: obj_instance->RotateCounterclockwise(); break; } } } const PPP_Pdf ppp_private = { &GetLinkAtPosition, &Transform }; int ExtractPrintPreviewPageIndex(const std::string& src_url) { // Sample |src_url| format: chrome://print/id/page_index/print.pdf std::vector url_substr; base::SplitString(src_url.substr(strlen(kChromePrint)), '/', &url_substr); if (url_substr.size() != 3) return -1; if (url_substr[2] != "print.pdf") return -1; int page_index = 0; if (!base::StringToInt(url_substr[1], &page_index)) return -1; return page_index; } bool IsPrintPreviewUrl(const std::string& url) { return url.substr(0, strlen(kChromePrint)) == kChromePrint; } void ScalePoint(float scale, pp::Point* point) { point->set_x(static_cast(point->x() * scale)); point->set_y(static_cast(point->y() * scale)); } void ScaleRect(float scale, pp::Rect* rect) { int left = static_cast(floorf(rect->x() * scale)); int top = static_cast(floorf(rect->y() * scale)); int right = static_cast(ceilf((rect->x() + rect->width()) * scale)); int bottom = static_cast(ceilf((rect->y() + rect->height()) * scale)); rect->SetRect(left, top, right - left, bottom - top); } template T ClipToRange(T value, T lower_boundary, T upper_boundary) { DCHECK(lower_boundary <= upper_boundary); return std::max(lower_boundary, std::min(value, upper_boundary)); } } // namespace Instance::Instance(PP_Instance instance) : pp::InstancePrivate(instance), pp::Find_Private(this), pp::Printing_Dev(this), pp::Selection_Dev(this), pp::WidgetClient_Dev(this), pp::Zoom_Dev(this), cursor_(PP_CURSORTYPE_POINTER), timer_pending_(false), current_timer_id_(0), zoom_(1.0), device_scale_(1.0), printing_enabled_(true), hidpi_enabled_(false), full_(IsFullFrame()), zoom_mode_(full_ ? ZOOM_AUTO : ZOOM_SCALE), did_call_start_loading_(false), is_autoscroll_(false), scrollbar_thickness_(-1), scrollbar_reserved_thickness_(-1), current_tb_info_(NULL), current_tb_info_size_(0), paint_manager_(this, this, true), delayed_progress_timer_id_(0), first_paint_(true), painted_first_page_(false), show_page_indicator_(false), document_load_state_(LOAD_STATE_LOADING), preview_document_load_state_(LOAD_STATE_COMPLETE), told_browser_about_unsupported_feature_(false), print_preview_page_count_(0) { loader_factory_.Initialize(this); timer_factory_.Initialize(this); form_factory_.Initialize(this); callback_factory_.Initialize(this); engine_.reset(PDFEngine::Create(this)); pp::Module::Get()->AddPluginInterface(kPPPPdfInterface, &ppp_private); AddPerInstanceObject(kPPPPdfInterface, this); RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_MOUSE); RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_WHEEL); RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_TOUCH); } Instance::~Instance() { if (timer_pending_) { timer_factory_.CancelAll(); timer_pending_ = false; } // The engine may try to access this instance during its destruction. // Make sure this happens early while the instance is still intact. engine_.reset(); RemovePerInstanceObject(kPPPPdfInterface, this); } bool Instance::Init(uint32_t argc, const char* argn[], const char* argv[]) { // For now, we hide HiDPI support behind a flag. if (pp::PDF::IsFeatureEnabled(this, PP_PDFFEATURE_HIDPI)) hidpi_enabled_ = true; printing_enabled_ = pp::PDF::IsFeatureEnabled(this, PP_PDFFEATURE_PRINTING); if (printing_enabled_) { CreateToolbar(kPDFToolbarButtons, arraysize(kPDFToolbarButtons)); } else { CreateToolbar(kPDFNoPrintToolbarButtons, arraysize(kPDFNoPrintToolbarButtons)); } CreateProgressBar(); // Load autoscroll anchor image. autoscroll_anchor_ = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PAN_SCROLL_ICON); #ifdef ENABLE_THUMBNAILS CreateThumbnails(); #endif const char* url = NULL; for (uint32_t i = 0; i < argc; ++i) { if (strcmp(argn[i], "src") == 0) { url = argv[i]; break; } } if (!url) return false; CreatePageIndicator(IsPrintPreviewUrl(url)); if (!full_) { // For PDFs embedded in a frame, we don't get the data automatically like we // do for full-frame loads. Start loading the data manually. LoadUrl(url); } else { DCHECK(!did_call_start_loading_); pp::PDF::DidStartLoading(this); did_call_start_loading_ = true; } ZoomLimitsChanged(kMinZoom, kMaxZoom); text_input_.reset(new pp::TextInput_Dev(this)); url_ = url; return engine_->New(url); } bool Instance::HandleDocumentLoad(const pp::URLLoader& loader) { delayed_progress_timer_id_ = ScheduleTimer(kProgressBarId, kProgressDelayTimeoutMs); return engine_->HandleDocumentLoad(loader); } bool Instance::HandleInputEvent(const pp::InputEvent& event) { // To simplify things, convert the event into device coordinates if it is // a mouse event. pp::InputEvent event_device_res(event); { pp::MouseInputEvent mouse_event(event); if (!mouse_event.is_null()) { pp::Point point = mouse_event.GetPosition(); pp::Point movement = mouse_event.GetMovement(); ScalePoint(device_scale_, &point); ScalePoint(device_scale_, &movement); mouse_event = pp::MouseInputEvent( this, event.GetType(), event.GetTimeStamp(), event.GetModifiers(), mouse_event.GetButton(), point, mouse_event.GetClickCount(), movement); event_device_res = mouse_event; } } // Check if we need to go to autoscroll mode. if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE && (event.GetModifiers() & PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN)) { pp::MouseInputEvent mouse_event(event_device_res); pp::Point pos = mouse_event.GetPosition(); EnableAutoscroll(pos); UpdateCursor(CalculateAutoscroll(pos)); return true; } else { // Quit autoscrolling on any other event. DisableAutoscroll(); } #ifdef ENABLE_THUMBNAILS if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSELEAVE) thumbnails_.SlideOut(); if (thumbnails_.HandleEvent(event_device_res)) return true; #endif if (toolbar_->HandleEvent(event_device_res)) return true; #ifdef ENABLE_THUMBNAILS if (v_scrollbar_.get() && event.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE) { pp::MouseInputEvent mouse_event(event); pp::Point pt = mouse_event.GetPosition(); pp::Rect v_scrollbar_rc; v_scrollbar_->GetLocation(&v_scrollbar_rc); // There is a bug (https://bugs.webkit.org/show_bug.cgi?id=45208) // in the webkit that makes event.u.mouse.button // equal to PP_INPUTEVENT_MOUSEBUTTON_LEFT, even when no button is pressed. // To work around this issue we use modifier for now, and will switch // to button once the bug is fixed and webkit got merged back to our tree. if (v_scrollbar_rc.Contains(pt) && (event.GetModifiers() & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN)) { thumbnails_.SlideIn(); } // When scrollbar is in the scrolling mode we should display thumbnails // even the mouse is outside the thumbnail and scrollbar areas. // If mouse is outside plugin area, we are still getting mouse move events // while scrolling. See bug description for details: // http://code.google.com/p/chromium/issues/detail?id=56444 if (!v_scrollbar_rc.Contains(pt) && thumbnails_.visible() && !(event.GetModifiers() & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) && !thumbnails_.rect().Contains(pt)) { thumbnails_.SlideOut(); } } #endif // Need to pass the event to the engine first, since if we're over an edit // control we want it to get keyboard events (like space) instead of the // scrollbar. // TODO: will have to offset the mouse coordinates once we support bidi and // there could be scrollbars on the left. pp::InputEvent offset_event(event_device_res); bool try_engine_first = true; switch (offset_event.GetType()) { case PP_INPUTEVENT_TYPE_MOUSEDOWN: case PP_INPUTEVENT_TYPE_MOUSEUP: case PP_INPUTEVENT_TYPE_MOUSEMOVE: case PP_INPUTEVENT_TYPE_MOUSEENTER: case PP_INPUTEVENT_TYPE_MOUSELEAVE: { pp::MouseInputEvent mouse_event(event_device_res); pp::MouseInputEvent mouse_event_dip(event); pp::Point point = mouse_event.GetPosition(); point.set_x(point.x() - available_area_.x()); offset_event = pp::MouseInputEvent( this, event.GetType(), event.GetTimeStamp(), event.GetModifiers(), mouse_event.GetButton(), point, mouse_event.GetClickCount(), mouse_event.GetMovement()); if (!engine_->IsSelecting()) { if (!IsOverlayScrollbar() && !available_area_.Contains(mouse_event.GetPosition())) { try_engine_first = false; } else if (IsOverlayScrollbar()) { pp::Rect temp; if ((v_scrollbar_.get() && v_scrollbar_->GetLocation(&temp) && temp.Contains(mouse_event_dip.GetPosition())) || (h_scrollbar_.get() && h_scrollbar_->GetLocation(&temp) && temp.Contains(mouse_event_dip.GetPosition()))) { try_engine_first = false; } } } break; } default: break; } if (try_engine_first && engine_->HandleEvent(offset_event)) return true; // Left/Right arrows should scroll to the beginning of the Prev/Next page if // there is no horizontal scroll bar. // If fit-to-height, PgDown/PgUp should scroll to the beginning of the // Prev/Next page. Spacebar / shift+spacebar should do the same. if (v_scrollbar_.get() && event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN) { pp::KeyboardInputEvent keyboard_event(event); bool no_h_scrollbar = !h_scrollbar_.get(); uint32_t key_code = keyboard_event.GetKeyCode(); bool page_down = no_h_scrollbar && key_code == ui::VKEY_RIGHT; bool page_up = no_h_scrollbar && key_code == ui::VKEY_LEFT; if (zoom_mode_ == ZOOM_FIT_TO_PAGE) { bool has_shift = keyboard_event.GetModifiers() & PP_INPUTEVENT_MODIFIER_SHIFTKEY; bool key_is_space = key_code == ui::VKEY_SPACE; page_down |= key_is_space || key_code == ui::VKEY_NEXT; page_up |= (key_is_space && has_shift) || (key_code == ui::VKEY_PRIOR); } if (page_down) { int page = engine_->GetFirstVisiblePage(); if (page == -1) return true; // Engine calculates visible page including delimiter to the page size. // We need to check here if the page itself is completely out of view and // scroll to the next one in that case. if (engine_->GetPageRect(page).bottom() * zoom_ <= v_scrollbar_->GetValue()) page++; ScrollToPage(page + 1); UpdateCursor(PP_CURSORTYPE_POINTER); return true; } else if (page_up) { int page = engine_->GetFirstVisiblePage(); if (page == -1) return true; if (engine_->GetPageRect(page).y() * zoom_ >= v_scrollbar_->GetValue()) page--; ScrollToPage(page); UpdateCursor(PP_CURSORTYPE_POINTER); return true; } } if (v_scrollbar_.get() && v_scrollbar_->HandleEvent(event)) { UpdateCursor(PP_CURSORTYPE_POINTER); return true; } if (h_scrollbar_.get() && h_scrollbar_->HandleEvent(event)) { UpdateCursor(PP_CURSORTYPE_POINTER); return true; } if (timer_pending_ && (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEUP || event.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE)) { timer_factory_.CancelAll(); timer_pending_ = false; } else if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE && engine_->IsSelecting()) { bool set_timer = false; pp::MouseInputEvent mouse_event(event); if (v_scrollbar_.get() && (mouse_event.GetPosition().y() <= 0 || mouse_event.GetPosition().y() >= (plugin_dip_size_.height() - 1))) { v_scrollbar_->ScrollBy( PP_SCROLLBY_LINE, mouse_event.GetPosition().y() >= 0 ? 1: -1); set_timer = true; } if (h_scrollbar_.get() && (mouse_event.GetPosition().x() <= 0 || mouse_event.GetPosition().x() >= (plugin_dip_size_.width() - 1))) { h_scrollbar_->ScrollBy(PP_SCROLLBY_LINE, mouse_event.GetPosition().x() >= 0 ? 1: -1); set_timer = true; } if (set_timer) { last_mouse_event_ = pp::MouseInputEvent(event); pp::CompletionCallback callback = timer_factory_.NewCallback(&Instance::OnTimerFired); pp::Module::Get()->core()->CallOnMainThread(kDragTimerMs, callback); timer_pending_ = true; } } if (event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN && event.GetModifiers() & kDefaultKeyModifier) { pp::KeyboardInputEvent keyboard_event(event); switch (keyboard_event.GetKeyCode()) { case 'A': engine_->SelectAll(); return true; } } // Return true for unhandled clicks so the plugin takes focus. return (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN); } void Instance::DidChangeView(const pp::View& view) { pp::Rect view_rect(view.GetRect()); float device_scale = 1.0f; float old_device_scale = device_scale_; if (hidpi_enabled_) device_scale = view.GetDeviceScale(); pp::Size view_device_size(view_rect.width() * device_scale, view_rect.height() * device_scale); if (view_device_size == plugin_size_ && device_scale == device_scale_) return; // We don't care about the position, only the size. image_data_ = pp::ImageData(); device_scale_ = device_scale; plugin_dip_size_ = view_rect.size(); plugin_size_ = view_device_size; paint_manager_.SetSize(view_device_size, device_scale_); image_data_ = pp::ImageData(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, plugin_size_, false); if (image_data_.is_null()) { DCHECK(plugin_size_.IsEmpty()); return; } // View dimensions changed, disable autoscroll (if it was enabled). DisableAutoscroll(); OnGeometryChanged(zoom_, old_device_scale); } pp::Var Instance::GetInstanceObject() { if (instance_object_.is_undefined()) { PDFScriptableObject* object = new PDFScriptableObject(this); // The pp::Var takes ownership of object here. instance_object_ = pp::VarPrivate(this, object); } return instance_object_; } pp::Var Instance::GetLinkAtPosition(const pp::Point& point) { pp::Point offset_point(point); ScalePoint(device_scale_, &offset_point); offset_point.set_x(offset_point.x() - available_area_.x()); return engine_->GetLinkAtPosition(offset_point); } pp::Var Instance::GetSelectedText(bool html) { if (html || !engine_->HasPermission(PDFEngine::PERMISSION_COPY)) return pp::Var(); return engine_->GetSelectedText(); } void Instance::InvalidateWidget(pp::Widget_Dev widget, const pp::Rect& dirty_rect) { if (v_scrollbar_.get() && *v_scrollbar_ == widget) { if (!image_data_.is_null()) v_scrollbar_->Paint(dirty_rect.pp_rect(), &image_data_); } else if (h_scrollbar_.get() && *h_scrollbar_ == widget) { if (!image_data_.is_null()) h_scrollbar_->Paint(dirty_rect.pp_rect(), &image_data_); } else { // Possible to hit this condition since sometimes the scrollbar codes posts // a task to do something later, and we could have deleted our reference in // the meantime. return; } pp::Rect dirty_rect_scaled = dirty_rect; ScaleRect(device_scale_, &dirty_rect_scaled); paint_manager_.InvalidateRect(dirty_rect_scaled); } void Instance::ScrollbarValueChanged(pp::Scrollbar_Dev scrollbar, uint32_t value) { value = GetScaled(value); if (v_scrollbar_.get() && *v_scrollbar_ == scrollbar) { engine_->ScrolledToYPosition(value); pp::Rect rc; v_scrollbar_->GetLocation(&rc); int32 doc_height = GetDocumentPixelHeight(); doc_height -= GetScaled(rc.height()); #ifdef ENABLE_THUMBNAILS if (thumbnails_.visible()) { thumbnails_.SetPosition(value, doc_height, true); } #endif pp::Point origin( plugin_size_.width() - page_indicator_.rect().width() - GetScaled(GetScrollbarReservedThickness()), page_indicator_.GetYPosition(value, doc_height, plugin_size_.height())); page_indicator_.MoveTo(origin, page_indicator_.visible()); } else if (h_scrollbar_.get() && *h_scrollbar_ == scrollbar) { engine_->ScrolledToXPosition(value); } } void Instance::ScrollbarOverlayChanged(pp::Scrollbar_Dev scrollbar, bool overlay) { scrollbar_reserved_thickness_ = overlay ? 0 : scrollbar_thickness_; OnGeometryChanged(zoom_, device_scale_); } uint32_t Instance::QuerySupportedPrintOutputFormats() { return engine_->QuerySupportedPrintOutputFormats(); } int32_t Instance::PrintBegin(const PP_PrintSettings_Dev& print_settings) { // For us num_pages is always equal to the number of pages in the PDF // document irrespective of the printable area. int32_t ret = engine_->GetNumberOfPages(); if (!ret) return 0; uint32_t supported_formats = engine_->QuerySupportedPrintOutputFormats(); if ((print_settings.format & supported_formats) == 0) return 0; print_settings_.is_printing = true; print_settings_.pepper_print_settings = print_settings; engine_->PrintBegin(); return ret; } pp::Resource Instance::PrintPages( const PP_PrintPageNumberRange_Dev* page_ranges, uint32_t page_range_count) { if (!print_settings_.is_printing) return pp::Resource(); print_settings_.print_pages_called_ = true; return engine_->PrintPages(page_ranges, page_range_count, print_settings_.pepper_print_settings); } void Instance::PrintEnd() { if (print_settings_.print_pages_called_) UserMetricsRecordAction("PDF.PrintPage"); print_settings_.Clear(); engine_->PrintEnd(); } bool Instance::IsPrintScalingDisabled() { return !engine_->GetPrintScaling(); } bool Instance::StartFind(const std::string& text, bool case_sensitive) { engine_->StartFind(text.c_str(), case_sensitive); return true; } void Instance::SelectFindResult(bool forward) { engine_->SelectFindResult(forward); } void Instance::StopFind() { engine_->StopFind(); } void Instance::Zoom(double scale, bool text_only) { UserMetricsRecordAction("PDF.ZoomFromBrowser"); SetZoom(ZOOM_SCALE, scale); } void Instance::ZoomChanged(double factor) { if (full_) Zoom_Dev::ZoomChanged(factor); } void Instance::OnPaint(const std::vector& paint_rects, std::vector* ready, std::vector* pending) { if (image_data_.is_null()) { DCHECK(plugin_size_.IsEmpty()); return; } if (first_paint_) { first_paint_ = false; pp::Rect rect = pp::Rect(pp::Point(), plugin_size_); FillRect(rect, kBackgroundColor); ready->push_back(PaintManager::ReadyRect(rect, image_data_, true)); *pending = paint_rects; return; } engine_->PrePaint(); for (size_t i = 0; i < paint_rects.size(); i++) { // Intersect with plugin area since there could be pending invalidates from // when the plugin area was larger. pp::Rect rect = paint_rects[i].Intersect(pp::Rect(pp::Point(), plugin_size_)); if (rect.IsEmpty()) continue; pp::Rect pdf_rect = available_area_.Intersect(rect); if (!pdf_rect.IsEmpty()) { pdf_rect.Offset(available_area_.x() * -1, 0); std::vector pdf_ready; std::vector pdf_pending; engine_->Paint(pdf_rect, &image_data_, &pdf_ready, &pdf_pending); for (size_t j = 0; j < pdf_ready.size(); ++j) { pdf_ready[j].Offset(available_area_.point()); ready->push_back( PaintManager::ReadyRect(pdf_ready[j], image_data_, false)); } for (size_t j = 0; j < pdf_pending.size(); ++j) { pdf_pending[j].Offset(available_area_.point()); pending->push_back(pdf_pending[j]); } } for (size_t j = 0; j < background_parts_.size(); ++j) { pp::Rect intersection = background_parts_[j].location.Intersect(rect); if (!intersection.IsEmpty()) { FillRect(intersection, background_parts_[j].color); ready->push_back( PaintManager::ReadyRect(intersection, image_data_, false)); } } if (document_load_state_ == LOAD_STATE_FAILED) { pp::Point top_center; top_center.set_x(plugin_size_.width() / 2); top_center.set_y(plugin_size_.height() / 2); DrawText(top_center, PP_RESOURCESTRING_PDFLOAD_FAILED); } #ifdef ENABLE_THUMBNAILS thumbnails_.Paint(&image_data_, rect); #endif } engine_->PostPaint(); // Must paint scrollbars after the background parts, in case we have an // overlay scrollbar that's over the background. We also do this in a separate // loop because the scrollbar painting logic uses the signal of whether there // are pending paints or not to figure out if it should draw right away or // not. for (size_t i = 0; i < paint_rects.size(); i++) { PaintIfWidgetIntersects(h_scrollbar_.get(), paint_rects[i], ready, pending); PaintIfWidgetIntersects(v_scrollbar_.get(), paint_rects[i], ready, pending); } if (progress_bar_.visible()) PaintOverlayControl(&progress_bar_, &image_data_, ready); if (page_indicator_.visible()) PaintOverlayControl(&page_indicator_, &image_data_, ready); if (toolbar_->current_transparency() != kTransparentAlpha) PaintOverlayControl(toolbar_.get(), &image_data_, ready); // Paint autoscroll anchor if needed. if (is_autoscroll_) { size_t limit = ready->size(); for (size_t i = 0; i < limit; i++) { pp::Rect anchor_rect = autoscroll_rect_.Intersect((*ready)[i].rect); if (!anchor_rect.IsEmpty()) { pp::Rect draw_rc = pp::Rect( pp::Point(anchor_rect.x() - autoscroll_rect_.x(), anchor_rect.y() - autoscroll_rect_.y()), anchor_rect.size()); // Paint autoscroll anchor. AlphaBlend(autoscroll_anchor_, draw_rc, &image_data_, anchor_rect.point(), kOpaqueAlpha); } } } } void Instance::PaintOverlayControl( Control* ctrl, pp::ImageData* image_data, std::vector* ready) { // Make sure that we only paint overlay controls over an area that's ready, // i.e. not pending. Otherwise we'll mark the control rect as ready and // it'll overwrite the pdf region. std::list ctrl_rects; for (size_t i = 0; i < ready->size(); i++) { pp::Rect rc = ctrl->rect().Intersect((*ready)[i].rect); if (!rc.IsEmpty()) ctrl_rects.push_back(rc); } if (!ctrl_rects.empty()) { ctrl->PaintMultipleRects(image_data, ctrl_rects); std::list::iterator iter; for (iter = ctrl_rects.begin(); iter != ctrl_rects.end(); ++iter) { ready->push_back(PaintManager::ReadyRect(*iter, *image_data, false)); } } } void Instance::DidOpen(int32_t result) { if (result == PP_OK) { engine_->HandleDocumentLoad(embed_loader_); } else if (result != PP_ERROR_ABORTED) { // Can happen in tests. NOTREACHED(); } } void Instance::DidOpenPreview(int32_t result) { if (result == PP_OK) { preview_engine_.reset(PDFEngine::Create(new PreviewModeClient(this))); preview_engine_->HandleDocumentLoad(embed_preview_loader_); } else { NOTREACHED(); } } void Instance::PaintIfWidgetIntersects( pp::Widget_Dev* widget, const pp::Rect& rect, std::vector* ready, std::vector* pending) { if (!widget) return; pp::Rect location; if (!widget->GetLocation(&location)) return; ScaleRect(device_scale_, &location); location = location.Intersect(rect); if (location.IsEmpty()) return; if (IsOverlayScrollbar()) { // If we're using overlay scrollbars, and there are pending paints under the // scrollbar, don't update the scrollbar instantly. While it would be nice, // we would need to double buffer the plugin area in order to make this // work. This is because we'd need to always have a copy of what the pdf // under the scrollbar looks like, and additionally we couldn't paint the // pdf under the scrollbar if it's ready until we got the preceding flush. // So in practice, it would make painting slower and introduce extra buffer // copies for the general case. for (size_t i = 0; i < pending->size(); ++i) { if ((*pending)[i].Intersects(location)) return; } // Even if none of the pending paints are under the scrollbar, we never want // to paint it if it's over the pdf if there are other pending paints. // Otherwise different parts of the pdf plugin would display at different // scroll offsets. if (!pending->empty() && available_area_.Intersects(rect)) { pending->push_back(location); return; } } pp::Rect location_dip = location; ScaleRect(1.0f / device_scale_, &location_dip); DCHECK(!image_data_.is_null()); widget->Paint(location_dip, &image_data_); ready->push_back(PaintManager::ReadyRect(location, image_data_, true)); } void Instance::OnTimerFired(int32_t) { HandleInputEvent(last_mouse_event_); } void Instance::OnClientTimerFired(int32_t id) { engine_->OnCallback(id); } void Instance::OnControlTimerFired(int32_t, const uint32& control_id, const uint32& timer_id) { if (control_id == toolbar_->id()) { toolbar_->OnTimerFired(timer_id); } else if (control_id == progress_bar_.id()) { if (timer_id == delayed_progress_timer_id_) { if (document_load_state_ == LOAD_STATE_LOADING && !progress_bar_.visible()) { progress_bar_.Fade(true, kProgressFadeTimeoutMs); } delayed_progress_timer_id_ = 0; } else { progress_bar_.OnTimerFired(timer_id); } } else if (control_id == kAutoScrollId) { if (is_autoscroll_) { if (autoscroll_x_ != 0 && h_scrollbar_.get()) { h_scrollbar_->ScrollBy(PP_SCROLLBY_PIXEL, autoscroll_x_); } if (autoscroll_y_ != 0 && v_scrollbar_.get()) { v_scrollbar_->ScrollBy(PP_SCROLLBY_PIXEL, autoscroll_y_); } // Reschedule timer. ScheduleTimer(kAutoScrollId, kAutoScrollTimeoutMs); } } else if (control_id == kPageIndicatorId) { page_indicator_.OnTimerFired(timer_id); } #ifdef ENABLE_THUMBNAILS else if (control_id == thumbnails_.id()) { thumbnails_.OnTimerFired(timer_id); } #endif } void Instance::CalculateBackgroundParts() { background_parts_.clear(); int v_scrollbar_thickness = GetScaled(v_scrollbar_.get() ? GetScrollbarReservedThickness() : 0); int h_scrollbar_thickness = GetScaled(h_scrollbar_.get() ? GetScrollbarReservedThickness() : 0); int width_without_scrollbar = std::max( plugin_size_.width() - v_scrollbar_thickness, 0); int height_without_scrollbar = std::max( plugin_size_.height() - h_scrollbar_thickness, 0); int left_width = available_area_.x(); int right_start = available_area_.right(); int right_width = abs(width_without_scrollbar - available_area_.right()); int bottom = std::min(available_area_.bottom(), height_without_scrollbar); // Add the left, right, and bottom rectangles. Note: we assume only // horizontal centering. BackgroundPart part = { pp::Rect(0, 0, left_width, bottom), kBackgroundColor }; if (!part.location.IsEmpty()) background_parts_.push_back(part); part.location = pp::Rect(right_start, 0, right_width, bottom); if (!part.location.IsEmpty()) background_parts_.push_back(part); part.location = pp::Rect( 0, bottom, width_without_scrollbar, height_without_scrollbar - bottom); if (!part.location.IsEmpty()) background_parts_.push_back(part); if (h_scrollbar_thickness #if defined(OS_MACOSX) || #else && #endif v_scrollbar_thickness) { part.color = 0xFFFFFFFF; part.location = pp::Rect(plugin_size_.width() - v_scrollbar_thickness, plugin_size_.height() - h_scrollbar_thickness, h_scrollbar_thickness, v_scrollbar_thickness); background_parts_.push_back(part); } } int Instance::GetDocumentPixelWidth() const { return static_cast(ceil(document_size_.width() * zoom_ * device_scale_)); } int Instance::GetDocumentPixelHeight() const { return static_cast(ceil(document_size_.height() * zoom_ * device_scale_)); } void Instance::FillRect(const pp::Rect& rect, uint32 color) { DCHECK(!image_data_.is_null() || rect.IsEmpty()); uint32* buffer_start = static_cast(image_data_.data()); int stride = image_data_.stride(); uint32* ptr = buffer_start + rect.y() * stride / 4 + rect.x(); int height = rect.height(); int width = rect.width(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) *(ptr + x) = color; ptr += stride / 4; } } void Instance::DocumentSizeUpdated(const pp::Size& size) { document_size_ = size; OnGeometryChanged(zoom_, device_scale_); } void Instance::Invalidate(const pp::Rect& rect) { pp::Rect offset_rect(rect); offset_rect.Offset(available_area_.point()); paint_manager_.InvalidateRect(offset_rect); } void Instance::Scroll(const pp::Point& point) { pp::Rect scroll_area = available_area_; if (IsOverlayScrollbar()) { pp::Rect rc; if (h_scrollbar_.get()) { h_scrollbar_->GetLocation(&rc); ScaleRect(device_scale_, &rc); if (scroll_area.bottom() > rc.y()) { scroll_area.set_height(rc.y() - scroll_area.y()); paint_manager_.InvalidateRect(rc); } } if (v_scrollbar_.get()) { v_scrollbar_->GetLocation(&rc); ScaleRect(device_scale_, &rc); if (scroll_area.right() > rc.x()) { scroll_area.set_width(rc.x() - scroll_area.x()); paint_manager_.InvalidateRect(rc); } } } paint_manager_.ScrollRect(scroll_area, point); if (toolbar_->current_transparency() != kTransparentAlpha) paint_manager_.InvalidateRect(toolbar_->GetControlsRect()); if (progress_bar_.visible()) paint_manager_.InvalidateRect(progress_bar_.rect()); if (is_autoscroll_) paint_manager_.InvalidateRect(autoscroll_rect_); if (show_page_indicator_) { page_indicator_.set_current_page(GetPageNumberToDisplay()); page_indicator_.Splash(); } if (page_indicator_.visible()) paint_manager_.InvalidateRect(page_indicator_.rect()); // Run the scroll callback asynchronously. This function can be invoked by a // layout change which should not re-enter into JS synchronously. pp::CompletionCallback callback = callback_factory_.NewCallback(&Instance::RunCallback, on_scroll_callback_); pp::Module::Get()->core()->CallOnMainThread(0, callback); } void Instance::ScrollToX(int position) { if (!h_scrollbar_.get()) { NOTREACHED(); return; } int position_dip = static_cast(position / device_scale_); h_scrollbar_->SetValue(position_dip); } void Instance::ScrollToY(int position) { if (!v_scrollbar_.get()) { NOTREACHED(); return; } int position_dip = static_cast(position / device_scale_); v_scrollbar_->SetValue(ClipToRange(position_dip, 0, valid_v_range_)); } void Instance::ScrollToPage(int page) { if (!v_scrollbar_.get()) return; if (engine_->GetNumberOfPages() == 0) return; int index = ClipToRange(page, 0, engine_->GetNumberOfPages() - 1); pp::Rect rect = engine_->GetPageRect(index); // If we are trying to scroll pass the last page, // scroll to the end of the last page. int position = index < page ? rect.bottom() : rect.y(); ScrollToY(position * zoom_ * device_scale_); } void Instance::NavigateTo(const std::string& url, bool open_in_new_tab) { std::string url_copy(url); // Empty |url_copy| is ok, and will effectively be a reload. // Skip the code below so an empty URL does not turn into "http://", which // will cause GURL to fail a DCHECK. if (!url_copy.empty()) { // If |url_copy| starts with '#', then it's for the same URL with a // different URL fragment. if (url_copy[0] == '#') { url_copy = url_ + url_copy; // Changing the href does not actually do anything when navigating in the // same tab, so do the actual page scroll here. Then fall through so the // href gets updated. if (!open_in_new_tab) { int page_number = GetInitialPage(url_copy); if (page_number >= 0) ScrollToPage(page_number); } } // If there's no scheme, add http. if (url_copy.find("://") == std::string::npos && url_copy.find("mailto:") == std::string::npos) { url_copy = "http://" + url_copy; } // Make sure |url_copy| starts with a valid scheme. if (url_copy.find("http://") != 0 && url_copy.find("https://") != 0 && url_copy.find("ftp://") != 0 && url_copy.find("file://") != 0 && url_copy.find("mailto:") != 0) { return; } // Make sure |url_copy| is not only a scheme. if (url_copy == "http://" || url_copy == "https://" || url_copy == "ftp://" || url_copy == "file://" || url_copy == "mailto:") { return; } } if (open_in_new_tab) { GetWindowObject().Call("open", url_copy); } else { GetWindowObject().GetProperty("top").GetProperty("location"). SetProperty("href", url_copy); } } void Instance::UpdateCursor(PP_CursorType_Dev cursor) { if (cursor == cursor_) return; cursor_ = cursor; const PPB_CursorControl_Dev* cursor_interface = reinterpret_cast( pp::Module::Get()->GetBrowserInterface(PPB_CURSOR_CONTROL_DEV_INTERFACE)); if (!cursor_interface) { NOTREACHED(); return; } cursor_interface->SetCursor( pp_instance(), cursor_, pp::ImageData().pp_resource(), NULL); } void Instance::UpdateTickMarks(const std::vector& tickmarks) { if (!v_scrollbar_.get()) return; float inverse_scale = 1.0f / device_scale_; std::vector scaled_tickmarks = tickmarks; for (size_t i = 0; i < scaled_tickmarks.size(); i++) { ScaleRect(inverse_scale, &scaled_tickmarks[i]); } v_scrollbar_->SetTickMarks( scaled_tickmarks.empty() ? NULL : &scaled_tickmarks[0], tickmarks.size()); } void Instance::NotifyNumberOfFindResultsChanged(int total, bool final_result) { NumberOfFindResultsChanged(total, final_result); } void Instance::NotifySelectedFindResultChanged(int current_find_index) { SelectedFindResultChanged(current_find_index); } void Instance::OnEvent(uint32 control_id, uint32 event_id, void* data) { if (event_id == Button::EVENT_ID_BUTTON_CLICKED || event_id == Button::EVENT_ID_BUTTON_STATE_CHANGED) { switch (control_id) { case kFitToPageButtonId: UserMetricsRecordAction("PDF.FitToPageButton"); SetZoom(ZOOM_FIT_TO_PAGE, 0); ZoomChanged(zoom_); break; case kFitToWidthButtonId: UserMetricsRecordAction("PDF.FitToWidthButton"); SetZoom(ZOOM_FIT_TO_WIDTH, 0); ZoomChanged(zoom_); break; case kZoomOutButtonId: case kZoomInButtonId: UserMetricsRecordAction(control_id == kZoomOutButtonId ? "PDF.ZoomOutButton" : "PDF.ZoomInButton"); SetZoom(ZOOM_SCALE, CalculateZoom(control_id)); ZoomChanged(zoom_); break; case kSaveButtonId: UserMetricsRecordAction("PDF.SaveButton"); SaveAs(); break; case kPrintButtonId: UserMetricsRecordAction("PDF.PrintButton"); Print(); break; } } if (control_id == kThumbnailsId && event_id == ThumbnailControl::EVENT_ID_THUMBNAIL_SELECTED) { int page = *static_cast(data); pp::Rect page_rc(engine_->GetPageRect(page)); ScrollToY(static_cast(page_rc.y() * zoom_ * device_scale_)); } } void Instance::Invalidate(uint32 control_id, const pp::Rect& rc) { paint_manager_.InvalidateRect(rc); } uint32 Instance::ScheduleTimer(uint32 control_id, uint32 timeout_ms) { current_timer_id_++; pp::CompletionCallback callback = timer_factory_.NewCallback(&Instance::OnControlTimerFired, control_id, current_timer_id_); pp::Module::Get()->core()->CallOnMainThread(timeout_ms, callback); return current_timer_id_; } void Instance::SetEventCapture(uint32 control_id, bool set_capture) { // TODO(gene): set event capture here. } void Instance::SetCursor(uint32 control_id, PP_CursorType_Dev cursor_type) { UpdateCursor(cursor_type); } pp::Instance* Instance::GetInstance() { return this; } void Instance::GetDocumentPassword( pp::CompletionCallbackWithOutput callback) { std::string message(GetLocalizedString(PP_RESOURCESTRING_PDFGETPASSWORD)); pp::Var result = pp::PDF::ModalPromptForPassword(this, message); *callback.output() = result.pp_var(); callback.Run(PP_OK); } void Instance::Alert(const std::string& message) { GetWindowObject().Call("alert", message); } bool Instance::Confirm(const std::string& message) { pp::Var result = GetWindowObject().Call("confirm", message); return result.is_bool() ? result.AsBool() : false; } std::string Instance::Prompt(const std::string& question, const std::string& default_answer) { pp::Var result = GetWindowObject().Call("prompt", question, default_answer); return result.is_string() ? result.AsString() : std::string(); } std::string Instance::GetURL() { return url_; } void Instance::Email(const std::string& to, const std::string& cc, const std::string& bcc, const std::string& subject, const std::string& body) { std::string javascript = "var href = 'mailto:" + net::EscapeUrlEncodedData(to, false) + "?cc=" + net::EscapeUrlEncodedData(cc, false) + "&bcc=" + net::EscapeUrlEncodedData(bcc, false) + "&subject=" + net::EscapeUrlEncodedData(subject, false) + "&body=" + net::EscapeUrlEncodedData(body, false) + "';var temp = window.open(href, '_blank', " + "'width=1,height=1');if(temp) temp.close();"; ExecuteScript(javascript); } void Instance::Print() { if (!printing_enabled_ || (!engine_->HasPermission(PDFEngine::PERMISSION_PRINT_LOW_QUALITY) && !engine_->HasPermission(PDFEngine::PERMISSION_PRINT_HIGH_QUALITY))) { return; } pp::CompletionCallback callback = callback_factory_.NewCallback(&Instance::OnPrint); pp::Module::Get()->core()->CallOnMainThread(0, callback); } void Instance::OnPrint(int32_t) { pp::PDF::Print(this); } void Instance::SaveAs() { pp::PDF::SaveAs(this); } void Instance::SubmitForm(const std::string& url, const void* data, int length) { pp::URLRequestInfo request(this); request.SetURL(url); request.SetMethod("POST"); request.AppendDataToBody(reinterpret_cast(data), length); pp::CompletionCallback callback = form_factory_.NewCallback(&Instance::FormDidOpen); form_loader_ = CreateURLLoaderInternal(); int rv = form_loader_.Open(request, callback); if (rv != PP_OK_COMPLETIONPENDING) callback.Run(rv); } void Instance::FormDidOpen(int32_t result) { // TODO: inform the user of success/failure. if (result != PP_OK) { NOTREACHED(); } } std::string Instance::ShowFileSelectionDialog() { // Seems like very low priority to implement, since the pdf has no way to get // the file data anyways. Javascript doesn't let you do this synchronously. NOTREACHED(); return std::string(); } pp::URLLoader Instance::CreateURLLoader() { if (full_) { if (!did_call_start_loading_) { did_call_start_loading_ = true; pp::PDF::DidStartLoading(this); } // Disable save and print until the document is fully loaded, since they // would generate an incomplete document. Need to do this each time we // call DidStartLoading since that resets the content restrictions. pp::PDF::SetContentRestriction(this, CONTENT_RESTRICTION_SAVE | CONTENT_RESTRICTION_PRINT); } return CreateURLLoaderInternal(); } void Instance::ScheduleCallback(int id, int delay_in_ms) { pp::CompletionCallback callback = timer_factory_.NewCallback(&Instance::OnClientTimerFired); pp::Module::Get()->core()->CallOnMainThread(delay_in_ms, callback, id); } void Instance::SearchString(const base::char16* string, const base::char16* term, bool case_sensitive, std::vector* results) { if (!pp::PDF::IsAvailable()) { NOTREACHED(); return; } PP_PrivateFindResult* pp_results; int count = 0; pp::PDF::SearchString( this, reinterpret_cast(string), reinterpret_cast(term), case_sensitive, &pp_results, &count); results->resize(count); for (int i = 0; i < count; ++i) { (*results)[i].start_index = pp_results[i].start_index; (*results)[i].length = pp_results[i].length; } pp::Memory_Dev memory; memory.MemFree(pp_results); } void Instance::DocumentPaintOccurred() { if (painted_first_page_) return; painted_first_page_ = true; UpdateToolbarPosition(false); toolbar_->Splash(kToolbarSplashTimeoutMs); if (engine_->GetNumberOfPages() > 1) show_page_indicator_ = true; else show_page_indicator_ = false; if (v_scrollbar_.get() && show_page_indicator_) { page_indicator_.set_current_page(GetPageNumberToDisplay()); page_indicator_.Splash(kToolbarSplashTimeoutMs, kPageIndicatorInitialFadeTimeoutMs); } } void Instance::DocumentLoadComplete(int page_count) { // Clear focus state for OSK. FormTextFieldFocusChange(false); // Update progress control. if (progress_bar_.visible()) progress_bar_.Fade(false, kProgressFadeTimeoutMs); DCHECK(document_load_state_ == LOAD_STATE_LOADING); document_load_state_ = LOAD_STATE_COMPLETE; UserMetricsRecordAction("PDF.LoadSuccess"); if (did_call_start_loading_) { pp::PDF::DidStopLoading(this); did_call_start_loading_ = false; } if (on_load_callback_.is_string()) ExecuteScript(on_load_callback_); // Note: If we are in print preview mode on_load_callback_ might call // ScrollTo{X|Y}() and we don't want to scroll again and override it. // #page=N is not supported in Print Preview. if (!IsPrintPreview()) { int initial_page = GetInitialPage(url_); if (initial_page >= 0) ScrollToPage(initial_page); } if (!full_) return; if (!pp::PDF::IsAvailable()) return; int content_restrictions = CONTENT_RESTRICTION_CUT | CONTENT_RESTRICTION_PASTE; if (!engine_->HasPermission(PDFEngine::PERMISSION_COPY)) content_restrictions |= CONTENT_RESTRICTION_COPY; if (!engine_->HasPermission(PDFEngine::PERMISSION_PRINT_LOW_QUALITY) && !engine_->HasPermission(PDFEngine::PERMISSION_PRINT_HIGH_QUALITY)) { printing_enabled_ = false; if (current_tb_info_ == kPDFToolbarButtons) { // Remove Print button. CreateToolbar(kPDFNoPrintToolbarButtons, arraysize(kPDFNoPrintToolbarButtons)); UpdateToolbarPosition(false); Invalidate(pp::Rect(plugin_size_)); } } pp::PDF::SetContentRestriction(this, content_restrictions); pp::PDF::HistogramPDFPageCount(this, page_count); } void Instance::RotateClockwise() { engine_->RotateClockwise(); } void Instance::RotateCounterclockwise() { engine_->RotateCounterclockwise(); } void Instance::PreviewDocumentLoadComplete() { if (preview_document_load_state_ != LOAD_STATE_LOADING || preview_pages_info_.empty()) { return; } preview_document_load_state_ = LOAD_STATE_COMPLETE; int dest_page_index = preview_pages_info_.front().second; int src_page_index = ExtractPrintPreviewPageIndex(preview_pages_info_.front().first); if (src_page_index > 0 && dest_page_index > -1 && preview_engine_.get()) engine_->AppendPage(preview_engine_.get(), dest_page_index); preview_pages_info_.pop(); // |print_preview_page_count_| is not updated yet. Do not load any // other preview pages till we get this information. if (print_preview_page_count_ == 0) return; if (preview_pages_info_.size()) LoadAvailablePreviewPage(); } void Instance::DocumentLoadFailed() { DCHECK(document_load_state_ == LOAD_STATE_LOADING); UserMetricsRecordAction("PDF.LoadFailure"); // Hide progress control. progress_bar_.Fade(false, kProgressFadeTimeoutMs); if (did_call_start_loading_) { pp::PDF::DidStopLoading(this); did_call_start_loading_ = false; } document_load_state_ = LOAD_STATE_FAILED; paint_manager_.InvalidateRect(pp::Rect(pp::Point(), plugin_size_)); } void Instance::PreviewDocumentLoadFailed() { UserMetricsRecordAction("PDF.PreviewDocumentLoadFailure"); if (preview_document_load_state_ != LOAD_STATE_LOADING || preview_pages_info_.empty()) { return; } preview_document_load_state_ = LOAD_STATE_FAILED; preview_pages_info_.pop(); if (preview_pages_info_.size()) LoadAvailablePreviewPage(); } pp::Instance* Instance::GetPluginInstance() { return GetInstance(); } void Instance::DocumentHasUnsupportedFeature(const std::string& feature) { std::string metric("PDF_Unsupported_"); metric += feature; if (!unsupported_features_reported_.count(metric)) { unsupported_features_reported_.insert(metric); UserMetricsRecordAction(metric); } // Since we use an info bar, only do this for full frame plugins.. if (!full_) return; if (told_browser_about_unsupported_feature_) return; told_browser_about_unsupported_feature_ = true; pp::PDF::HasUnsupportedFeature(this); } void Instance::DocumentLoadProgress(uint32 available, uint32 doc_size) { double progress = 0.0; if (doc_size == 0) { // Document size is unknown. Use heuristics. // We'll make progress logarithmic from 0 to 100M. static const double kFactor = log(100000000.0) / 100.0; if (available > 0) { progress = log(static_cast(available)) / kFactor; if (progress > 100.0) progress = 100.0; } } else { progress = 100.0 * static_cast(available) / doc_size; } progress_bar_.SetProgress(progress); } void Instance::FormTextFieldFocusChange(bool in_focus) { if (!text_input_.get()) return; if (in_focus) text_input_->SetTextInputType(PP_TEXTINPUT_TYPE_DEV_TEXT); else text_input_->SetTextInputType(PP_TEXTINPUT_TYPE_DEV_NONE); } // Called by PDFScriptableObject. bool Instance::HasScriptableMethod(const pp::Var& method, pp::Var* exception) { std::string method_str = method.AsString(); return (method_str == kJSAccessibility || method_str == kJSDocumentLoadComplete || method_str == kJSGetHeight || method_str == kJSGetHorizontalScrollbarThickness || method_str == kJSGetPageLocationNormalized || method_str == kJSGetVerticalScrollbarThickness || method_str == kJSGetWidth || method_str == kJSGetZoomLevel || method_str == kJSGoToPage || method_str == kJSGrayscale || method_str == kJSLoadPreviewPage || method_str == kJSOnLoad || method_str == kJSOnPluginSizeChanged || method_str == kJSOnScroll || method_str == kJSPageXOffset || method_str == kJSPageYOffset || method_str == kJSPrintPreviewPageCount || method_str == kJSReload || method_str == kJSRemovePrintButton || method_str == kJSResetPrintPreviewUrl || method_str == kJSSendKeyEvent || method_str == kJSSetPageNumbers || method_str == kJSSetPageXOffset || method_str == kJSSetPageYOffset || method_str == kJSSetZoomLevel || method_str == kJSZoomFitToHeight || method_str == kJSZoomFitToWidth || method_str == kJSZoomIn || method_str == kJSZoomOut); } pp::Var Instance::CallScriptableMethod(const pp::Var& method, const std::vector& args, pp::Var* exception) { std::string method_str = method.AsString(); if (method_str == kJSGrayscale) { if (args.size() == 1 && args[0].is_bool()) { engine_->SetGrayscale(args[0].AsBool()); // Redraw paint_manager_.InvalidateRect(pp::Rect(pp::Point(), plugin_size_)); #ifdef ENABLE_THUMBNAILS if (thumbnails_.visible()) thumbnails_.Show(true, true); #endif return pp::Var(true); } return pp::Var(false); } if (method_str == kJSOnLoad) { if (args.size() == 1 && args[0].is_string()) { on_load_callback_ = args[0]; return pp::Var(true); } return pp::Var(false); } if (method_str == kJSOnScroll) { if (args.size() == 1 && args[0].is_string()) { on_scroll_callback_ = args[0]; return pp::Var(true); } return pp::Var(false); } if (method_str == kJSOnPluginSizeChanged) { if (args.size() == 1 && args[0].is_string()) { on_plugin_size_changed_callback_ = args[0]; return pp::Var(true); } return pp::Var(false); } if (method_str == kJSReload) { document_load_state_ = LOAD_STATE_LOADING; if (!full_) LoadUrl(url_); preview_engine_.reset(); print_preview_page_count_ = 0; engine_.reset(PDFEngine::Create(this)); engine_->New(url_.c_str()); #ifdef ENABLE_THUMBNAILS thumbnails_.ResetEngine(engine_.get()); #endif return pp::Var(); } if (method_str == kJSResetPrintPreviewUrl) { if (args.size() == 1 && args[0].is_string()) { url_ = args[0].AsString(); preview_pages_info_ = std::queue(); preview_document_load_state_ = LOAD_STATE_COMPLETE; } return pp::Var(); } if (method_str == kJSZoomFitToHeight) { SetZoom(ZOOM_FIT_TO_PAGE, 0); return pp::Var(); } if (method_str == kJSZoomFitToWidth) { SetZoom(ZOOM_FIT_TO_WIDTH, 0); return pp::Var(); } if (method_str == kJSZoomIn) { SetZoom(ZOOM_SCALE, CalculateZoom(kZoomInButtonId)); return pp::Var(); } if (method_str == kJSZoomOut) { SetZoom(ZOOM_SCALE, CalculateZoom(kZoomOutButtonId)); return pp::Var(); } if (method_str == kJSSetZoomLevel) { if (args.size() == 1 && args[0].is_number()) SetZoom(ZOOM_SCALE, args[0].AsDouble()); return pp::Var(); } if (method_str == kJSGetZoomLevel) { return pp::Var(zoom_); } if (method_str == kJSGetHeight) { return pp::Var(plugin_size_.height()); } if (method_str == kJSGetWidth) { return pp::Var(plugin_size_.width()); } if (method_str == kJSGetHorizontalScrollbarThickness) { return pp::Var( h_scrollbar_.get() ? GetScrollbarReservedThickness() : 0); } if (method_str == kJSGetVerticalScrollbarThickness) { return pp::Var( v_scrollbar_.get() ? GetScrollbarReservedThickness() : 0); } if (method_str == kJSDocumentLoadComplete) { return pp::Var((document_load_state_ != LOAD_STATE_LOADING)); } if (method_str == kJSPageYOffset) { return pp::Var(static_cast( v_scrollbar_.get() ? v_scrollbar_->GetValue() : 0)); } if (method_str == kJSSetPageYOffset) { if (args.size() == 1 && args[0].is_number() && v_scrollbar_.get()) ScrollToY(GetScaled(args[0].AsInt())); return pp::Var(); } if (method_str == kJSPageXOffset) { return pp::Var(static_cast( h_scrollbar_.get() ? h_scrollbar_->GetValue() : 0)); } if (method_str == kJSSetPageXOffset) { if (args.size() == 1 && args[0].is_number() && h_scrollbar_.get()) ScrollToX(GetScaled(args[0].AsInt())); return pp::Var(); } if (method_str == kJSRemovePrintButton) { CreateToolbar(kPrintPreviewToolbarButtons, arraysize(kPrintPreviewToolbarButtons)); UpdateToolbarPosition(false); Invalidate(pp::Rect(plugin_size_)); return pp::Var(); } if (method_str == kJSGoToPage) { if (args.size() == 1 && args[0].is_string()) { ScrollToPage(atoi(args[0].AsString().c_str())); } return pp::Var(); } if (method_str == kJSAccessibility) { if (args.size() == 0) { base::DictionaryValue node; node.SetInteger(kAccessibleNumberOfPages, engine_->GetNumberOfPages()); node.SetBoolean(kAccessibleLoaded, document_load_state_ != LOAD_STATE_LOADING); bool has_permissions = engine_->HasPermission(PDFEngine::PERMISSION_COPY) || engine_->HasPermission(PDFEngine::PERMISSION_COPY_ACCESSIBLE); node.SetBoolean(kAccessibleCopyable, has_permissions); std::string json; base::JSONWriter::Write(&node, &json); return pp::Var(json); } else if (args[0].is_number()) { return pp::Var(engine_->GetPageAsJSON(args[0].AsInt())); } } if (method_str == kJSPrintPreviewPageCount) { if (args.size() == 1 && args[0].is_number()) SetPrintPreviewMode(args[0].AsInt()); return pp::Var(); } if (method_str == kJSLoadPreviewPage) { if (args.size() == 2 && args[0].is_string() && args[1].is_number()) ProcessPreviewPageInfo(args[0].AsString(), args[1].AsInt()); return pp::Var(); } if (method_str == kJSGetPageLocationNormalized) { const size_t kMaxLength = 30; char location_info[kMaxLength]; int page_idx = engine_->GetMostVisiblePage(); if (page_idx < 0) return pp::Var(std::string()); pp::Rect rect = engine_->GetPageContentsRect(page_idx); int v_scrollbar_reserved_thickness = v_scrollbar_.get() ? GetScaled(GetScrollbarReservedThickness()) : 0; rect.set_x(rect.x() + ((plugin_size_.width() - v_scrollbar_reserved_thickness - available_area_.width()) / 2)); base::snprintf(location_info, kMaxLength, "%0.4f;%0.4f;%0.4f;%0.4f;", rect.x() / static_cast(plugin_size_.width()), rect.y() / static_cast(plugin_size_.height()), rect.width() / static_cast(plugin_size_.width()), rect.height()/ static_cast(plugin_size_.height())); return pp::Var(std::string(location_info)); } if (method_str == kJSSetPageNumbers) { if (args.size() != 1 || !args[0].is_string()) return pp::Var(); const int num_pages_signed = engine_->GetNumberOfPages(); if (num_pages_signed <= 0) return pp::Var(); scoped_ptr page_ranges(static_cast( base::JSONReader::Read(args[0].AsString(), false))); const size_t num_pages = static_cast(num_pages_signed); if (!page_ranges.get() || page_ranges->GetSize() != num_pages) return pp::Var(); std::vector print_preview_page_numbers; for (size_t index = 0; index < num_pages; ++index) { int page_number = 0; // |page_number| is 1-based. if (!page_ranges->GetInteger(index, &page_number) || page_number < 1) return pp::Var(); print_preview_page_numbers.push_back(page_number); } print_preview_page_numbers_ = print_preview_page_numbers; page_indicator_.set_current_page(GetPageNumberToDisplay()); return pp::Var(); } // This is here to work around https://bugs.webkit.org/show_bug.cgi?id=16735. // In JS, creating a synthetic keyboard event and dispatching it always // result in a keycode of 0. if (method_str == kJSSendKeyEvent) { if (args.size() == 1 && args[0].is_number()) { pp::KeyboardInputEvent event( this, // instance PP_INPUTEVENT_TYPE_KEYDOWN, // HandleInputEvent only care about this. 0, // timestamp, not used for kbd events. 0, // no modifiers. args[0].AsInt(), // keycode. pp::Var()); // no char text needed. HandleInputEvent(event); } } return pp::Var(); } void Instance::OnGeometryChanged(double old_zoom, float old_device_scale) { bool force_no_horizontal_scrollbar = false; int scrollbar_thickness = GetScrollbarThickness(); if (old_device_scale != device_scale_) { // Change in device scale forces us to recreate resources ConfigureNumberImageGenerator(); CreateToolbar(current_tb_info_, current_tb_info_size_); // Load autoscroll anchor image. autoscroll_anchor_ = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PAN_SCROLL_ICON); ConfigurePageIndicator(); ConfigureProgressBar(); pp::Point scroll_position = engine_->GetScrollPosition(); ScalePoint(device_scale_ / old_device_scale, &scroll_position); engine_->SetScrollPosition(scroll_position); } UpdateZoomScale(); if (zoom_ != old_zoom || device_scale_ != old_device_scale) engine_->ZoomUpdated(zoom_ * device_scale_); if (zoom_ != old_zoom) ZoomChanged(zoom_); available_area_ = pp::Rect(plugin_size_); if (GetDocumentPixelHeight() > plugin_size_.height()) { CreateVerticalScrollbar(); } else { DestroyVerticalScrollbar(); } int v_scrollbar_reserved_thickness = v_scrollbar_.get() ? GetScaled(GetScrollbarReservedThickness()) : 0; if (!force_no_horizontal_scrollbar && GetDocumentPixelWidth() > (plugin_size_.width() - v_scrollbar_reserved_thickness)) { CreateHorizontalScrollbar(); // Adding the horizontal scrollbar now might cause us to need vertical // scrollbars. if (GetDocumentPixelHeight() > plugin_size_.height() - GetScaled(GetScrollbarReservedThickness())) { CreateVerticalScrollbar(); } } else { DestroyHorizontalScrollbar(); } #ifdef ENABLE_THUMBNAILS int thumbnails_pos = 0, thumbnails_total = 0; #endif if (v_scrollbar_.get()) { v_scrollbar_->SetScale(device_scale_); available_area_.set_width( std::max(0, plugin_size_.width() - v_scrollbar_reserved_thickness)); #ifdef ENABLE_THUMBNAILS int height = plugin_size_.height(); #endif int height_dip = plugin_dip_size_.height(); #if defined(OS_MACOSX) // Before Lion, Mac always had the resize at the bottom. After that, it // never did. if ((base::mac::IsOSSnowLeopard() && full_) || (base::mac::IsOSLionOrLater() && h_scrollbar_.get())) { #else if (h_scrollbar_.get()) { #endif // defined(OS_MACOSX) #ifdef ENABLE_THUMBNAILS height -= GetScaled(GetScrollbarThickness()); #endif height_dip -= GetScrollbarThickness(); } #ifdef ENABLE_THUMBNAILS int32 doc_height = GetDocumentPixelHeight(); #endif int32 doc_height_dip = static_cast(GetDocumentPixelHeight() / device_scale_); #if defined(OS_MACOSX) // On the Mac we always allow room for the resize button (whose width is // the same as that of the scrollbar) in full mode. However, if there is no // no horizontal scrollbar, the end of the scrollbar will scroll past the // end of the document. This is because the scrollbar assumes that its own // height (in the case of a vscroll bar) is the same as the height of the // viewport. Since the viewport is actually larger, we compensate by // adjusting the document height. Similar logic applies below for the // horizontal scrollbar. // For example, if the document size is 1000, and the viewport size is 200, // then the scrollbar position at the end will be 800. In this case the // viewport is actally 215 (assuming 15 as the scrollbar width) but the // scrollbar thinks it is 200. We want the scrollbar position at the end to // be 785. Making the document size 985 achieves this. if (full_ && !h_scrollbar_.get()) { #ifdef ENABLE_THUMBNAILS doc_height -= GetScaled(GetScrollbarThickness()); #endif doc_height_dip -= GetScrollbarThickness(); } #endif // defined(OS_MACOSX) int32 position; position = v_scrollbar_->GetValue(); position = static_cast(position * zoom_ / old_zoom); valid_v_range_ = doc_height_dip - height_dip; if (position > valid_v_range_) position = valid_v_range_; v_scrollbar_->SetValue(position); PP_Rect loc; loc.point.x = static_cast(available_area_.right() / device_scale_); if (IsOverlayScrollbar()) loc.point.x -= scrollbar_thickness; loc.point.y = 0; loc.size.width = scrollbar_thickness; loc.size.height = height_dip; v_scrollbar_->SetLocation(loc); v_scrollbar_->SetDocumentSize(doc_height_dip); #ifdef ENABLE_THUMBNAILS thumbnails_pos = position; thumbnails_total = doc_height - height; #endif } if (h_scrollbar_.get()) { h_scrollbar_->SetScale(device_scale_); available_area_.set_height( std::max(0, plugin_size_.height() - GetScaled(GetScrollbarReservedThickness()))); int width_dip = plugin_dip_size_.width(); // See note above. #if defined(OS_MACOSX) if ((base::mac::IsOSSnowLeopard() && full_) || (base::mac::IsOSLionOrLater() && v_scrollbar_.get())) { #else if (v_scrollbar_.get()) { #endif width_dip -= GetScrollbarThickness(); } int32 doc_width_dip = static_cast(GetDocumentPixelWidth() / device_scale_); #if defined(OS_MACOSX) // See comment in the above if (v_scrollbar_.get()) block. if (full_ && !v_scrollbar_.get()) doc_width_dip -= GetScrollbarThickness(); #endif // defined(OS_MACOSX) int32 position; position = h_scrollbar_->GetValue(); position = static_cast(position * zoom_ / old_zoom); position = std::min(position, doc_width_dip - width_dip); h_scrollbar_->SetValue(position); PP_Rect loc; loc.point.x = 0; loc.point.y = static_cast(available_area_.bottom() / device_scale_); if (IsOverlayScrollbar()) loc.point.y -= scrollbar_thickness; loc.size.width = width_dip; loc.size.height = scrollbar_thickness; h_scrollbar_->SetLocation(loc); h_scrollbar_->SetDocumentSize(doc_width_dip); } int doc_width = GetDocumentPixelWidth(); if (doc_width < available_area_.width()) { available_area_.Offset((available_area_.width() - doc_width) / 2, 0); available_area_.set_width(doc_width); } int doc_height = GetDocumentPixelHeight(); if (doc_height < available_area_.height()) { available_area_.set_height(doc_height); } // We'll invalidate the entire plugin anyways. UpdateToolbarPosition(false); UpdateProgressBarPosition(false); UpdatePageIndicatorPosition(false); #ifdef ENABLE_THUMBNAILS // Update thumbnail control position. thumbnails_.SetPosition(thumbnails_pos, thumbnails_total, false); pp::Rect thumbnails_rc(plugin_size_.width() - GetScaled(kThumbnailsWidth), 0, GetScaled(kThumbnailsWidth), plugin_size_.height()); if (v_scrollbar_.get()) thumbnails_rc.Offset(-v_scrollbar_reserved_thickness, 0); if (h_scrollbar_.get()) thumbnails_rc.Inset(0, 0, 0, v_scrollbar_reserved_thickness); thumbnails_.SetRect(thumbnails_rc, false); #endif CalculateBackgroundParts(); engine_->PageOffsetUpdated(available_area_.point()); engine_->PluginSizeUpdated(available_area_.size()); if (!document_size_.GetArea()) return; paint_manager_.InvalidateRect(pp::Rect(pp::Point(), plugin_size_)); // Run the plugin size change callback asynchronously. This function can be // invoked by a layout change which should not re-enter into JS synchronously. pp::CompletionCallback callback = callback_factory_.NewCallback(&Instance::RunCallback, on_plugin_size_changed_callback_); pp::Module::Get()->core()->CallOnMainThread(0, callback); } void Instance::RunCallback(int32_t, pp::Var callback) { if (callback.is_string()) ExecuteScript(callback); } void Instance::CreateHorizontalScrollbar() { if (h_scrollbar_.get()) return; h_scrollbar_.reset(new pp::Scrollbar_Dev(this, false)); } void Instance::CreateVerticalScrollbar() { if (v_scrollbar_.get()) return; v_scrollbar_.reset(new pp::Scrollbar_Dev(this, true)); } void Instance::DestroyHorizontalScrollbar() { if (!h_scrollbar_.get()) return; if (h_scrollbar_->GetValue()) engine_->ScrolledToXPosition(0); h_scrollbar_.reset(); } void Instance::DestroyVerticalScrollbar() { if (!v_scrollbar_.get()) return; if (v_scrollbar_->GetValue()) engine_->ScrolledToYPosition(0); v_scrollbar_.reset(); page_indicator_.Show(false, true); } int Instance::GetScrollbarThickness() { if (scrollbar_thickness_ == -1) { pp::Scrollbar_Dev temp_scrollbar(this, false); scrollbar_thickness_ = temp_scrollbar.GetThickness(); scrollbar_reserved_thickness_ = temp_scrollbar.IsOverlay() ? 0 : scrollbar_thickness_; } return scrollbar_thickness_; } int Instance::GetScrollbarReservedThickness() { GetScrollbarThickness(); return scrollbar_reserved_thickness_; } bool Instance::IsOverlayScrollbar() { return GetScrollbarReservedThickness() == 0; } void Instance::CreateToolbar(const ToolbarButtonInfo* tb_info, size_t size) { toolbar_.reset(new FadingControls()); DCHECK(tb_info); DCHECK(size >= 0); // Remember the current toolbar information in case we need to recreate the // images later. current_tb_info_ = tb_info; current_tb_info_size_ = size; int max_height = 0; pp::Point origin(kToolbarFadingOffsetLeft, kToolbarFadingOffsetTop); ScalePoint(device_scale_, &origin); std::list buttons; for (size_t i = 0; i < size; i++) { Button* btn = new Button; pp::ImageData normal_face = CreateResourceImage(tb_info[i].normal); btn->CreateButton(tb_info[i].id, origin, true, toolbar_.get(), tb_info[i].style, normal_face, CreateResourceImage(tb_info[i].highlighted), CreateResourceImage(tb_info[i].pressed)); buttons.push_back(btn); origin += pp::Point(normal_face.size().width(), 0); max_height = std::max(max_height, normal_face.size().height()); } pp::Rect rc_toolbar(0, 0, origin.x() + GetToolbarRightOffset(), origin.y() + max_height + GetToolbarBottomOffset()); toolbar_->CreateFadingControls( kToolbarId, rc_toolbar, false, this, kTransparentAlpha); std::list::iterator iter; for (iter = buttons.begin(); iter != buttons.end(); ++iter) { toolbar_->AddControl(*iter); } } int Instance::GetToolbarRightOffset() { int scrollbar_thickness = GetScrollbarThickness(); return GetScaled(kToolbarFadingOffsetRight) + 2 * scrollbar_thickness; } int Instance::GetToolbarBottomOffset() { int scrollbar_thickness = GetScrollbarThickness(); return GetScaled(kToolbarFadingOffsetBottom) + scrollbar_thickness; } std::vector Instance::GetThumbnailResources() { std::vector num_images(10); num_images[0] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_0); num_images[1] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_1); num_images[2] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_2); num_images[3] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_3); num_images[4] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_4); num_images[5] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_5); num_images[6] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_6); num_images[7] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_7); num_images[8] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_8); num_images[9] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_9); return num_images; } std::vector Instance::GetProgressBarResources( pp::ImageData* background) { std::vector result(9); result[0] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_0); result[1] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_1); result[2] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_2); result[3] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_3); result[4] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_4); result[5] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_5); result[6] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_6); result[7] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_7); result[8] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_8); *background = CreateResourceImage( PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_BACKGROUND); return result; } void Instance::CreatePageIndicator(bool always_visible) { page_indicator_.CreatePageIndicator(kPageIndicatorId, false, this, number_image_generator(), always_visible); ConfigurePageIndicator(); } void Instance::ConfigurePageIndicator() { pp::ImageData background = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PAGE_INDICATOR_BACKGROUND); page_indicator_.Configure(pp::Point(), background); } void Instance::CreateProgressBar() { pp::ImageData background; std::vector images = GetProgressBarResources(&background); std::string text = GetLocalizedString(PP_RESOURCESTRING_PDFPROGRESSLOADING); progress_bar_.CreateProgressControl(kProgressBarId, false, this, 0.0, device_scale_, images, background, text); } void Instance::ConfigureProgressBar() { pp::ImageData background; std::vector images = GetProgressBarResources(&background); progress_bar_.Reconfigure(background, images, device_scale_); } void Instance::CreateThumbnails() { thumbnails_.CreateThumbnailControl( kThumbnailsId, pp::Rect(), false, this, engine_.get(), number_image_generator()); } void Instance::LoadUrl(const std::string& url) { LoadUrlInternal(url, &embed_loader_, &Instance::DidOpen); } void Instance::LoadPreviewUrl(const std::string& url) { LoadUrlInternal(url, &embed_preview_loader_, &Instance::DidOpenPreview); } void Instance::LoadUrlInternal(const std::string& url, pp::URLLoader* loader, void (Instance::* method)(int32_t)) { pp::URLRequestInfo request(this); request.SetURL(url); request.SetMethod("GET"); *loader = CreateURLLoaderInternal(); pp::CompletionCallback callback = loader_factory_.NewCallback(method); int rv = loader->Open(request, callback); if (rv != PP_OK_COMPLETIONPENDING) callback.Run(rv); } pp::URLLoader Instance::CreateURLLoaderInternal() { pp::URLLoader loader(this); const PPB_URLLoaderTrusted* trusted_interface = reinterpret_cast( pp::Module::Get()->GetBrowserInterface( PPB_URLLOADERTRUSTED_INTERFACE)); if (trusted_interface) trusted_interface->GrantUniversalAccess(loader.pp_resource()); return loader; } int Instance::GetInitialPage(const std::string& url) { size_t found_idx = url.find('#'); if (found_idx == std::string::npos) return -1; const std::string& ref = url.substr(found_idx + 1); std::vector fragments; Tokenize(ref, kDelimiters, &fragments); // Page number to return, zero-based. int page = -1; // Handle the case of http://foo.com/bar#NAMEDDEST. This is not explicitly // mentioned except by example in the Adobe "PDF Open Parameters" document. if ((fragments.size() == 1) && (fragments[0].find('=') == std::string::npos)) return engine_->GetNamedDestinationPage(fragments[0]); for (size_t i = 0; i < fragments.size(); ++i) { std::vector key_value; base::SplitString(fragments[i], '=', &key_value); if (key_value.size() != 2) continue; const std::string& key = key_value[0]; const std::string& value = key_value[1]; if (base::strcasecmp(kPage, key.c_str()) == 0) { // |page_value| is 1-based. int page_value = -1; if (base::StringToInt(value, &page_value) && page_value > 0) page = page_value - 1; continue; } if (base::strcasecmp(kNamedDest, key.c_str()) == 0) { // |page_value| is 0-based. int page_value = engine_->GetNamedDestinationPage(value); if (page_value >= 0) page = page_value; continue; } } return page; } void Instance::UpdateToolbarPosition(bool invalidate) { pp::Rect ctrl_rc = toolbar_->GetControlsRect(); int min_toolbar_width = ctrl_rc.width() + GetToolbarRightOffset() + GetScaled(kToolbarFadingOffsetLeft); int min_toolbar_height = ctrl_rc.width() + GetToolbarBottomOffset() + GetScaled(kToolbarFadingOffsetBottom); // Update toolbar position if (plugin_size_.width() < min_toolbar_width || plugin_size_.height() < min_toolbar_height) { // Disable toolbar if it does not fit on the screen. toolbar_->Show(false, invalidate); } else { pp::Point offset( plugin_size_.width() - GetToolbarRightOffset() - ctrl_rc.right(), plugin_size_.height() - GetToolbarBottomOffset() - ctrl_rc.bottom()); toolbar_->MoveBy(offset, invalidate); int toolbar_width = std::max(plugin_size_.width() / 2, min_toolbar_width); toolbar_->ExpandLeft(toolbar_width - toolbar_->rect().width()); toolbar_->Show(painted_first_page_, invalidate); } } void Instance::UpdateProgressBarPosition(bool invalidate) { // TODO(gene): verify we don't overlap with toolbar. int scrollbar_thickness = GetScrollbarThickness(); pp::Point progress_origin( scrollbar_thickness + GetScaled(kProgressOffsetLeft), plugin_size_.height() - progress_bar_.rect().height() - scrollbar_thickness - GetScaled(kProgressOffsetBottom)); progress_bar_.MoveTo(progress_origin, invalidate); } void Instance::UpdatePageIndicatorPosition(bool invalidate) { int32 doc_height = static_cast(document_size_.height() * zoom_); pp::Point origin( plugin_size_.width() - page_indicator_.rect().width() - GetScaled(GetScrollbarReservedThickness()), page_indicator_.GetYPosition(engine_->GetVerticalScrollbarYPosition(), doc_height, plugin_size_.height())); page_indicator_.MoveTo(origin, invalidate); } void Instance::SetZoom(ZoomMode zoom_mode, double scale) { double old_zoom = zoom_; zoom_mode_ = zoom_mode; if (zoom_mode_ == ZOOM_SCALE) zoom_ = scale; UpdateZoomScale(); engine_->ZoomUpdated(zoom_ * device_scale_); OnGeometryChanged(old_zoom, device_scale_); // If fit-to-height, snap to the beginning of the most visible page. if (zoom_mode_ == ZOOM_FIT_TO_PAGE) { ScrollToPage(engine_->GetMostVisiblePage()); } // Update sticky buttons to the current zoom style. Button* ftp_btn = static_cast( toolbar_->GetControl(kFitToPageButtonId)); Button* ftw_btn = static_cast( toolbar_->GetControl(kFitToWidthButtonId)); switch (zoom_mode_) { case ZOOM_FIT_TO_PAGE: ftp_btn->SetPressedState(true); ftw_btn->SetPressedState(false); break; case ZOOM_FIT_TO_WIDTH: ftw_btn->SetPressedState(true); ftp_btn->SetPressedState(false); break; default: ftw_btn->SetPressedState(false); ftp_btn->SetPressedState(false); } } void Instance::UpdateZoomScale() { switch (zoom_mode_) { case ZOOM_SCALE: break; // Keep current scale. case ZOOM_FIT_TO_PAGE: { int page_num = engine_->GetFirstVisiblePage(); if (page_num == -1) break; pp::Rect rc = engine_->GetPageRect(page_num); if (!rc.height()) break; // Calculate fit to width zoom level. double ftw_zoom = static_cast(plugin_dip_size_.width() - GetScrollbarReservedThickness()) / document_size_.width(); // Calculate fit to height zoom level. If document will not fit // horizontally, adjust zoom level to allow space for horizontal // scrollbar. double fth_zoom = static_cast(plugin_dip_size_.height()) / rc.height(); if (fth_zoom * document_size_.width() > plugin_dip_size_.width() - GetScrollbarReservedThickness()) fth_zoom = static_cast(plugin_dip_size_.height() - GetScrollbarReservedThickness()) / rc.height(); zoom_ = std::min(ftw_zoom, fth_zoom); } break; case ZOOM_FIT_TO_WIDTH: case ZOOM_AUTO: if (!document_size_.width()) break; zoom_ = static_cast(plugin_dip_size_.width() - GetScrollbarReservedThickness()) / document_size_.width(); if (zoom_mode_ == ZOOM_AUTO && zoom_ > 1.0) zoom_ = 1.0; break; } zoom_ = ClipToRange(zoom_, kMinZoom, kMaxZoom); } double Instance::CalculateZoom(uint32 control_id) const { if (control_id == kZoomInButtonId) { for (size_t i = 0; i < chrome_page_zoom::kPresetZoomFactorsSize; ++i) { double current_zoom = chrome_page_zoom::kPresetZoomFactors[i]; if (current_zoom - content::kEpsilon > zoom_) return current_zoom; } } else { for (size_t i = chrome_page_zoom::kPresetZoomFactorsSize; i > 0; --i) { double current_zoom = chrome_page_zoom::kPresetZoomFactors[i - 1]; if (current_zoom + content::kEpsilon < zoom_) return current_zoom; } } return zoom_; } pp::ImageData Instance::CreateResourceImage(PP_ResourceImage image_id) { pp::ImageData resource_data; if (hidpi_enabled_) { resource_data = pp::PDF::GetResourceImageForScale(this, image_id, device_scale_); } return resource_data.data() ? resource_data : pp::PDF::GetResourceImage(this, image_id); } std::string Instance::GetLocalizedString(PP_ResourceString id) { pp::Var rv(pp::PDF::GetLocalizedString(this, id)); if (!rv.is_string()) return std::string(); return rv.AsString(); } void Instance::DrawText(const pp::Point& top_center, PP_ResourceString id) { std::string str(GetLocalizedString(id)); pp::FontDescription_Dev description; description.set_family(PP_FONTFAMILY_SANSSERIF); description.set_size(kMessageTextSize * device_scale_); pp::Font_Dev font(this, description); int length = font.MeasureSimpleText(str); pp::Point point(top_center); point.set_x(point.x() - length / 2); DCHECK(!image_data_.is_null()); font.DrawSimpleText(&image_data_, str, point, kMessageTextColor); } void Instance::SetPrintPreviewMode(int page_count) { if (!IsPrintPreview() || page_count <= 0) { print_preview_page_count_ = 0; return; } print_preview_page_count_ = page_count; ScrollToPage(0); engine_->AppendBlankPages(print_preview_page_count_); if (preview_pages_info_.size() > 0) LoadAvailablePreviewPage(); } bool Instance::IsPrintPreview() { return IsPrintPreviewUrl(url_); } int Instance::GetPageNumberToDisplay() { int page = engine_->GetMostVisiblePage(); if (IsPrintPreview() && !print_preview_page_numbers_.empty()) { page = ClipToRange(page, 0, print_preview_page_numbers_.size() - 1); return print_preview_page_numbers_[page]; } return page + 1; } void Instance::ProcessPreviewPageInfo(const std::string& url, int dst_page_index) { if (!IsPrintPreview() || print_preview_page_count_ < 0) return; int src_page_index = ExtractPrintPreviewPageIndex(url); if (src_page_index < 1) return; preview_pages_info_.push(std::make_pair(url, dst_page_index)); LoadAvailablePreviewPage(); } void Instance::LoadAvailablePreviewPage() { if (preview_pages_info_.size() <= 0) return; std::string url = preview_pages_info_.front().first; int dst_page_index = preview_pages_info_.front().second; int src_page_index = ExtractPrintPreviewPageIndex(url); if (src_page_index < 1 || dst_page_index >= print_preview_page_count_ || preview_document_load_state_ == LOAD_STATE_LOADING) { return; } preview_document_load_state_ = LOAD_STATE_LOADING; LoadPreviewUrl(url); } void Instance::EnableAutoscroll(const pp::Point& origin) { if (is_autoscroll_) return; pp::Size client_size = plugin_size_; if (v_scrollbar_.get()) client_size.Enlarge(-GetScrollbarThickness(), 0); if (h_scrollbar_.get()) client_size.Enlarge(0, -GetScrollbarThickness()); // Do not allow autoscroll if client area is too small. if (autoscroll_anchor_.size().width() > client_size.width() || autoscroll_anchor_.size().height() > client_size.height()) return; autoscroll_rect_ = pp::Rect( pp::Point(origin.x() - autoscroll_anchor_.size().width() / 2, origin.y() - autoscroll_anchor_.size().height() / 2), autoscroll_anchor_.size()); // Make sure autoscroll anchor is in the client area. if (autoscroll_rect_.right() > client_size.width()) { autoscroll_rect_.set_x( client_size.width() - autoscroll_anchor_.size().width()); } if (autoscroll_rect_.bottom() > client_size.height()) { autoscroll_rect_.set_y( client_size.height() - autoscroll_anchor_.size().height()); } if (autoscroll_rect_.x() < 0) autoscroll_rect_.set_x(0); if (autoscroll_rect_.y() < 0) autoscroll_rect_.set_y(0); is_autoscroll_ = true; Invalidate(kAutoScrollId, autoscroll_rect_); ScheduleTimer(kAutoScrollId, kAutoScrollTimeoutMs); } void Instance::DisableAutoscroll() { if (is_autoscroll_) { is_autoscroll_ = false; Invalidate(kAutoScrollId, autoscroll_rect_); } } PP_CursorType_Dev Instance::CalculateAutoscroll(const pp::Point& mouse_pos) { // Scroll only if mouse pointer is outside of the anchor area. if (autoscroll_rect_.Contains(mouse_pos)) { autoscroll_x_ = 0; autoscroll_y_ = 0; return PP_CURSORTYPE_MIDDLEPANNING; } // Relative position to the center of anchor area. pp::Point rel_pos = mouse_pos - autoscroll_rect_.CenterPoint(); // Calculate angle from the X axis. Angle is in range from -pi to pi. double angle = atan2(static_cast(rel_pos.y()), static_cast(rel_pos.x())); autoscroll_x_ = rel_pos.x() * kAutoScrollFactor; autoscroll_y_ = rel_pos.y() * kAutoScrollFactor; // Angle is from -pi to pi. Screen Y is increasing toward bottom, // so negative angle represent north direction. if (angle < - (M_PI * 7.0 / 8.0)) { // going west return PP_CURSORTYPE_WESTPANNING; } else if (angle < - (M_PI * 5.0 / 8.0)) { // going north-west return PP_CURSORTYPE_NORTHWESTPANNING; } else if (angle < - (M_PI * 3.0 / 8.0)) { // going north. return PP_CURSORTYPE_NORTHPANNING; } else if (angle < - (M_PI * 1.0 / 8.0)) { // going north-east return PP_CURSORTYPE_NORTHEASTPANNING; } else if (angle < M_PI * 1.0 / 8.0) { // going east. return PP_CURSORTYPE_EASTPANNING; } else if (angle < M_PI * 3.0 / 8.0) { // going south-east return PP_CURSORTYPE_SOUTHEASTPANNING; } else if (angle < M_PI * 5.0 / 8.0) { // going south. return PP_CURSORTYPE_SOUTHPANNING; } else if (angle < M_PI * 7.0 / 8.0) { // going south-west return PP_CURSORTYPE_SOUTHWESTPANNING; } // went around the circle, going west again return PP_CURSORTYPE_WESTPANNING; } void Instance::ConfigureNumberImageGenerator() { std::vector num_images = GetThumbnailResources(); pp::ImageData number_background = CreateResourceImage( PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_NUM_BACKGROUND); number_image_generator_->Configure(number_background, num_images, device_scale_); } NumberImageGenerator* Instance::number_image_generator() { if (!number_image_generator_.get()) { number_image_generator_.reset(new NumberImageGenerator(this)); ConfigureNumberImageGenerator(); } return number_image_generator_.get(); } int Instance::GetScaled(int x) const { return static_cast(x * device_scale_); } void Instance::UserMetricsRecordAction(const std::string& action) { pp::PDF::UserMetricsRecordAction(this, pp::Var(action)); } PDFScriptableObject::PDFScriptableObject(Instance* instance) : instance_(instance) { } PDFScriptableObject::~PDFScriptableObject() { } bool PDFScriptableObject::HasMethod(const pp::Var& name, pp::Var* exception) { return instance_->HasScriptableMethod(name, exception); } pp::Var PDFScriptableObject::Call(const pp::Var& method, const std::vector& args, pp::Var* exception) { return instance_->CallScriptableMethod(method, args, exception); } } // namespace chrome_pdf