[Eraser strings] Remove unused Supervised User infobar and corresponding strings
[chromium-blink-merge.git] / chrome / browser / resources / pdf / pdf.js
blob61dd1600f01b3e8c82e191763bc4fe2d7dc240d8
1 // Copyright 2013 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 'use strict';
7 /**
8  * @return {number} Width of a scrollbar in pixels
9  */
10 function getScrollbarWidth() {
11   var div = document.createElement('div');
12   div.style.visibility = 'hidden';
13   div.style.overflow = 'scroll';
14   div.style.width = '50px';
15   div.style.height = '50px';
16   div.style.position = 'absolute';
17   document.body.appendChild(div);
18   var result = div.offsetWidth - div.clientWidth;
19   div.parentNode.removeChild(div);
20   return result;
23 /**
24  * Return the filename component of a URL.
25  * @param {string} url The URL to get the filename from.
26  * @return {string} The filename component.
27  */
28 function getFilenameFromURL(url) {
29   var components = url.split(/\/|\\/);
30   return components[components.length - 1];
33 /**
34  * Called when navigation happens in the current tab.
35  * @param {string} url The url to be opened in the current tab.
36  */
37 function onNavigateInCurrentTab(url) {
38   // Prefer the tabs API because it can navigate from one file:// URL to
39   // another.
40   if (chrome.tabs)
41     chrome.tabs.update({url: url});
42   else
43     window.location.href = url;
46 /**
47  * Called when navigation happens in the new tab.
48  * @param {string} url The url to be opened in the new tab.
49  */
50 function onNavigateInNewTab(url) {
51   // Prefer the tabs API because it guarantees we can just open a new tab.
52   // window.open doesn't have this guarantee.
53   if (chrome.tabs)
54     chrome.tabs.create({url: url});
55   else
56     window.open(url);
59 /**
60  * Whether keydown events should currently be ignored. Events are ignored when
61  * an editable element has focus, to allow for proper editing controls.
62  * @param {HTMLElement} activeElement The currently selected DOM node.
63  * @return {boolean} True if keydown events should be ignored.
64  */
65 function shouldIgnoreKeyEvents(activeElement) {
66   while (activeElement.shadowRoot != null &&
67          activeElement.shadowRoot.activeElement != null) {
68     activeElement = activeElement.shadowRoot.activeElement;
69   }
71   return (activeElement.isContentEditable ||
72           activeElement.tagName == 'INPUT' ||
73           activeElement.tagName == 'TEXTAREA');
76 /**
77  * The minimum number of pixels to offset the toolbar by from the bottom and
78  * right side of the screen.
79  */
80 PDFViewer.MIN_TOOLBAR_OFFSET = 15;
82 /**
83  * The height of the toolbar along the top of the page. The document will be
84  * shifted down by this much in the viewport.
85  */
86 PDFViewer.MATERIAL_TOOLBAR_HEIGHT = 64;
88 /**
89  * Creates a new PDFViewer. There should only be one of these objects per
90  * document.
91  * @constructor
92  * @param {!BrowserApi} browserApi An object providing an API to the browser.
93  */
94 function PDFViewer(browserApi) {
95   this.browserApi_ = browserApi;
96   this.loadState_ = LoadState.LOADING;
97   this.parentWindow_ = null;
99   this.delayedScriptingMessages_ = [];
101   this.isPrintPreview_ = this.browserApi_.getStreamInfo().originalUrl.indexOf(
102                              'chrome://print') == 0;
103   this.isMaterial_ = location.pathname.substring(1) === 'index-material.html';
105   // The sizer element is placed behind the plugin element to cause scrollbars
106   // to be displayed in the window. It is sized according to the document size
107   // of the pdf and zoom level.
108   this.sizer_ = $('sizer');
109   this.toolbar_ = $('toolbar');
110   this.pageIndicator_ = $('page-indicator');
111   this.progressBar_ = $('progress-bar');
112   this.passwordScreen_ = $('password-screen');
113   this.passwordScreen_.addEventListener('password-submitted',
114                                         this.onPasswordSubmitted_.bind(this));
115   this.errorScreen_ = $('error-screen');
117   // Create the viewport.
118   var topToolbarHeight =
119       this.isMaterial_ ? PDFViewer.MATERIAL_TOOLBAR_HEIGHT : 0;
120   this.viewport_ = new Viewport(window,
121                                 this.sizer_,
122                                 this.viewportChanged_.bind(this),
123                                 this.beforeZoom_.bind(this),
124                                 this.afterZoom_.bind(this),
125                                 getScrollbarWidth(),
126                                 this.browserApi_.getDefaultZoom(),
127                                 topToolbarHeight);
129   // Create the plugin object dynamically so we can set its src. The plugin
130   // element is sized to fill the entire window and is set to be fixed
131   // positioning, acting as a viewport. The plugin renders into this viewport
132   // according to the scroll position of the window.
133   this.plugin_ = document.createElement('embed');
134   // NOTE: The plugin's 'id' field must be set to 'plugin' since
135   // chrome/renderer/printing/print_web_view_helper.cc actually references it.
136   this.plugin_.id = 'plugin';
137   this.plugin_.type = 'application/x-google-chrome-pdf';
138   this.plugin_.addEventListener('message', this.handlePluginMessage_.bind(this),
139                                 false);
141   // Handle scripting messages from outside the extension that wish to interact
142   // with it. We also send a message indicating that extension has loaded and
143   // is ready to receive messages.
144   window.addEventListener('message', this.handleScriptingMessage.bind(this),
145                           false);
147   document.title = decodeURIComponent(
148       getFilenameFromURL(this.browserApi_.getStreamInfo().originalUrl));
149   this.plugin_.setAttribute('src',
150                             this.browserApi_.getStreamInfo().originalUrl);
151   this.plugin_.setAttribute('stream-url',
152                             this.browserApi_.getStreamInfo().streamUrl);
153   var headers = '';
154   for (var header in this.browserApi_.getStreamInfo().responseHeaders) {
155     headers += header + ': ' +
156         this.browserApi_.getStreamInfo().responseHeaders[header] + '\n';
157   }
158   this.plugin_.setAttribute('headers', headers);
160   if (this.isMaterial_) {
161     this.plugin_.setAttribute('is-material', '');
162     this.plugin_.setAttribute('top-toolbar-height',
163                               PDFViewer.MATERIAL_TOOLBAR_HEIGHT);
164   }
166   if (!this.browserApi_.getStreamInfo().embedded)
167     this.plugin_.setAttribute('full-frame', '');
168   document.body.appendChild(this.plugin_);
170   // Setup the button event listeners.
171   if (!this.isMaterial_) {
172     $('fit-to-width-button').addEventListener('click',
173         this.viewport_.fitToWidth.bind(this.viewport_));
174     $('fit-to-page-button').addEventListener('click',
175         this.viewport_.fitToPage.bind(this.viewport_));
176     $('zoom-in-button').addEventListener('click',
177         this.viewport_.zoomIn.bind(this.viewport_));
178     $('zoom-out-button').addEventListener('click',
179         this.viewport_.zoomOut.bind(this.viewport_));
180     $('save-button').addEventListener('click', this.save_.bind(this));
181     $('print-button').addEventListener('click', this.print_.bind(this));
182   }
184   if (this.isMaterial_) {
185     this.zoomToolbar_ = $('zoom-toolbar');
186     this.zoomToolbar_.addEventListener('fit-to-width',
187         this.viewport_.fitToWidth.bind(this.viewport_));
188     this.zoomToolbar_.addEventListener('fit-to-page',
189         this.fitToPage_.bind(this));
190     this.zoomToolbar_.addEventListener('zoom-in',
191         this.viewport_.zoomIn.bind(this.viewport_));
192     this.zoomToolbar_.addEventListener('zoom-out',
193         this.viewport_.zoomOut.bind(this.viewport_));
195     this.materialToolbar_ = $('material-toolbar');
196     this.materialToolbar_.docTitle = document.title;
197     this.materialToolbar_.addEventListener('save', this.save_.bind(this));
198     this.materialToolbar_.addEventListener('print', this.print_.bind(this));
199     this.materialToolbar_.addEventListener('rotate-right',
200         this.rotateClockwise_.bind(this));
201     this.materialToolbar_.addEventListener('rotate-left',
202         this.rotateCounterClockwise_.bind(this));
204     document.body.addEventListener('change-page', function(e) {
205       this.viewport_.goToPage(e.detail.page);
206     }.bind(this));
208     this.toolbarManager_ =
209         new ToolbarManager(window, this.materialToolbar_, this.zoomToolbar_);
211     // Must attach to mouseup on the plugin element, since it eats mousedown and
212     // click events.
213     this.plugin_.addEventListener(
214         'mouseup',
215         this.materialToolbar_.hideDropdowns.bind(this.materialToolbar_));
216   }
218   // Set up the ZoomManager.
219   this.zoomManager_ = new ZoomManager(
220       this.viewport_, this.browserApi_.setZoom.bind(this.browserApi_),
221       this.browserApi_.getDefaultZoom());
222   this.browserApi_.addZoomEventListener(
223       this.zoomManager_.onBrowserZoomChange.bind(this.zoomManager_));
225   // Setup the keyboard event listener.
226   document.addEventListener('keydown', this.handleKeyEvent_.bind(this));
227   document.addEventListener('mousemove', this.handleMouseEvent_.bind(this));
229   // Parse open pdf parameters.
230   this.paramsParser_ =
231       new OpenPDFParamsParser(this.getNamedDestination_.bind(this));
232   this.navigator_ = new Navigator(this.browserApi_.getStreamInfo().originalUrl,
233                                   this.viewport_, this.paramsParser_,
234                                   onNavigateInCurrentTab, onNavigateInNewTab);
235   this.viewportScroller_ =
236       new ViewportScroller(this.viewport_, this.plugin_, window);
239 PDFViewer.prototype = {
240   /**
241    * @private
242    * Handle key events. These may come from the user directly or via the
243    * scripting API.
244    * @param {KeyboardEvent} e the event to handle.
245    */
246   handleKeyEvent_: function(e) {
247     var position = this.viewport_.position;
248     // Certain scroll events may be sent from outside of the extension.
249     var fromScriptingAPI = e.fromScriptingAPI;
251     if (shouldIgnoreKeyEvents(document.activeElement) || e.defaultPrevented)
252       return;
254     if (this.isMaterial_)
255       this.toolbarManager_.hideToolbarsAfterTimeout(e);
257     var pageUpHandler = function() {
258       // Go to the previous page if we are fit-to-page.
259       if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
260         this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1);
261         // Since we do the movement of the page.
262         e.preventDefault();
263       } else if (fromScriptingAPI) {
264         position.y -= this.viewport.size.height;
265         this.viewport.position = position;
266       }
267     }.bind(this);
268     var pageDownHandler = function() {
269       // Go to the next page if we are fit-to-page.
270       if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
271         this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1);
272         // Since we do the movement of the page.
273         e.preventDefault();
274       } else if (fromScriptingAPI) {
275         position.y += this.viewport.size.height;
276         this.viewport.position = position;
277       }
278     }.bind(this);
280     switch (e.keyCode) {
281       case 27:  // Escape key.
282         if (this.isMaterial_)
283           this.toolbarManager_.hideSingleToolbarLayer();
284         return;
285       case 32:  // Space key.
286         if (e.shiftKey)
287           pageUpHandler();
288         else
289           pageDownHandler();
290         return;
291       case 33:  // Page up key.
292         pageUpHandler();
293         return;
294       case 34:  // Page down key.
295         pageDownHandler();
296         return;
297       case 37:  // Left arrow key.
298         if (!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
299           // Go to the previous page if there are no horizontal scrollbars.
300           if (!this.viewport_.documentHasScrollbars().horizontal) {
301             this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1);
302             // Since we do the movement of the page.
303             e.preventDefault();
304           } else if (fromScriptingAPI) {
305             position.x -= Viewport.SCROLL_INCREMENT;
306             this.viewport.position = position;
307           }
308         }
309         return;
310       case 38:  // Up arrow key.
311         if (fromScriptingAPI) {
312           position.y -= Viewport.SCROLL_INCREMENT;
313           this.viewport.position = position;
314         }
315         return;
316       case 39:  // Right arrow key.
317         if (!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey)) {
318           // Go to the next page if there are no horizontal scrollbars.
319           if (!this.viewport_.documentHasScrollbars().horizontal) {
320             this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1);
321             // Since we do the movement of the page.
322             e.preventDefault();
323           } else if (fromScriptingAPI) {
324             position.x += Viewport.SCROLL_INCREMENT;
325             this.viewport.position = position;
326           }
327         }
328         return;
329       case 40:  // Down arrow key.
330         if (fromScriptingAPI) {
331           position.y += Viewport.SCROLL_INCREMENT;
332           this.viewport.position = position;
333         }
334         return;
335       case 65:  // a key.
336         if (e.ctrlKey || e.metaKey) {
337           this.plugin_.postMessage({
338             type: 'selectAll'
339           });
340           // Since we do selection ourselves.
341           e.preventDefault();
342         }
343         return;
344       case 71: // g key.
345         if (this.isMaterial_ && (e.ctrlKey || e.metaKey)) {
346           this.toolbarManager_.showToolbars();
347           this.materialToolbar_.selectPageNumber();
348           // To prevent the default "find text" behaviour in Chrome.
349           e.preventDefault();
350         }
351         return;
352       case 219:  // left bracket.
353         if (e.ctrlKey)
354           this.rotateCounterClockwise_();
355         return;
356       case 221:  // right bracket.
357         if (e.ctrlKey)
358           this.rotateClockwise_();
359         return;
360     }
362     // Give print preview a chance to handle the key event.
363     if (!fromScriptingAPI && this.isPrintPreview_) {
364       this.sendScriptingMessage_({
365         type: 'sendKeyEvent',
366         keyEvent: SerializeKeyEvent(e)
367       });
368     } else if (this.isMaterial_) {
369       // Show toolbars as a fallback.
370       if (!(e.shiftKey || e.ctrlKey || e.altKey))
371         this.toolbarManager_.showToolbars();
372     }
373   },
375   handleMouseEvent_: function(e) {
376     if (this.isMaterial_)
377       this.toolbarManager_.showToolbarsForMouseMove(e);
378   },
380   /**
381    * @private
382    * Rotate the plugin clockwise.
383    */
384   rotateClockwise_: function() {
385     this.plugin_.postMessage({
386       type: 'rotateClockwise'
387     });
388   },
390   /**
391    * @private
392    * Rotate the plugin counter-clockwise.
393    */
394   rotateCounterClockwise_: function() {
395     this.plugin_.postMessage({
396       type: 'rotateCounterclockwise'
397     });
398   },
400   fitToPage_: function() {
401     this.viewport_.fitToPage();
402     this.toolbarManager_.forceHideTopToolbar();
403   },
405   /**
406    * @private
407    * Notify the plugin to print.
408    */
409   print_: function() {
410     this.plugin_.postMessage({
411       type: 'print'
412     });
413   },
415   /**
416    * @private
417    * Notify the plugin to save.
418    */
419   save_: function() {
420     this.plugin_.postMessage({
421       type: 'save'
422     });
423   },
425   /**
426    * Fetches the page number corresponding to the given named destination from
427    * the plugin.
428    * @param {string} name The namedDestination to fetch page number from plugin.
429    */
430   getNamedDestination_: function(name) {
431     this.plugin_.postMessage({
432       type: 'getNamedDestination',
433       namedDestination: name
434     });
435   },
437   /**
438    * @private
439    * Sends a 'documentLoaded' message to the PDFScriptingAPI if the document has
440    * finished loading.
441    */
442   sendDocumentLoadedMessage_: function() {
443     if (this.loadState_ == LoadState.LOADING)
444       return;
445     this.sendScriptingMessage_({
446       type: 'documentLoaded',
447       load_state: this.loadState_
448     });
449   },
451   /**
452    * @private
453    * Handle open pdf parameters. This function updates the viewport as per
454    * the parameters mentioned in the url while opening pdf. The order is
455    * important as later actions can override the effects of previous actions.
456    * @param {Object} viewportPosition The initial position of the viewport to be
457    *     displayed.
458    */
459   handleURLParams_: function(viewportPosition) {
460     if (viewportPosition.page != undefined)
461       this.viewport_.goToPage(viewportPosition.page);
462     if (viewportPosition.position) {
463       // Make sure we don't cancel effect of page parameter.
464       this.viewport_.position = {
465         x: this.viewport_.position.x + viewportPosition.position.x,
466         y: this.viewport_.position.y + viewportPosition.position.y
467       };
468     }
469     if (viewportPosition.zoom)
470       this.viewport_.setZoom(viewportPosition.zoom);
471   },
473   /**
474    * @private
475    * Update the loading progress of the document in response to a progress
476    * message being received from the plugin.
477    * @param {number} progress the progress as a percentage.
478    */
479   updateProgress_: function(progress) {
480     if (this.isMaterial_)
481       this.materialToolbar_.loadProgress = progress;
482     else
483       this.progressBar_.progress = progress;
485     if (progress == -1) {
486       // Document load failed.
487       this.errorScreen_.style.visibility = 'visible';
488       this.sizer_.style.display = 'none';
489       if (!this.isMaterial_)
490         this.toolbar_.style.visibility = 'hidden';
491       if (this.passwordScreen_.active) {
492         this.passwordScreen_.deny();
493         this.passwordScreen_.active = false;
494       }
495       this.loadState_ = LoadState.FAILED;
496       this.sendDocumentLoadedMessage_();
497     } else if (progress == 100) {
498       // Document load complete.
499       if (this.lastViewportPosition_)
500         this.viewport_.position = this.lastViewportPosition_;
501       this.paramsParser_.getViewportFromUrlParams(
502           this.browserApi_.getStreamInfo().originalUrl,
503           this.handleURLParams_.bind(this));
504       this.loadState_ = LoadState.SUCCESS;
505       this.sendDocumentLoadedMessage_();
506       while (this.delayedScriptingMessages_.length > 0)
507         this.handleScriptingMessage(this.delayedScriptingMessages_.shift());
509       if (this.isMaterial_)
510         this.toolbarManager_.hideToolbarsAfterTimeout();
511     }
512   },
514   /**
515    * @private
516    * An event handler for handling password-submitted events. These are fired
517    * when an event is entered into the password screen.
518    * @param {Object} event a password-submitted event.
519    */
520   onPasswordSubmitted_: function(event) {
521     this.plugin_.postMessage({
522       type: 'getPasswordComplete',
523       password: event.detail.password
524     });
525   },
527   /**
528    * @private
529    * An event handler for handling message events received from the plugin.
530    * @param {MessageObject} message a message event.
531    */
532   handlePluginMessage_: function(message) {
533     switch (message.data.type.toString()) {
534       case 'documentDimensions':
535         this.documentDimensions_ = message.data;
536         this.viewport_.setDocumentDimensions(this.documentDimensions_);
537         // If we received the document dimensions, the password was good so we
538         // can dismiss the password screen.
539         if (this.passwordScreen_.active)
540           this.passwordScreen_.accept();
542         if (this.isMaterial_) {
543           this.materialToolbar_.docLength =
544               this.documentDimensions_.pageDimensions.length;
545         } else {
546           this.pageIndicator_.initialFadeIn();
547           this.toolbar_.initialFadeIn();
548         }
549         break;
550       case 'email':
551         var href = 'mailto:' + message.data.to + '?cc=' + message.data.cc +
552             '&bcc=' + message.data.bcc + '&subject=' + message.data.subject +
553             '&body=' + message.data.body;
554         window.location.href = href;
555         break;
556       case 'getAccessibilityJSONReply':
557         this.sendScriptingMessage_(message.data);
558         break;
559       case 'getPassword':
560         // If the password screen isn't up, put it up. Otherwise we're
561         // responding to an incorrect password so deny it.
562         if (!this.passwordScreen_.active)
563           this.passwordScreen_.active = true;
564         else
565           this.passwordScreen_.deny();
566         break;
567       case 'getSelectedTextReply':
568         this.sendScriptingMessage_(message.data);
569         break;
570       case 'goToPage':
571         this.viewport_.goToPage(message.data.page);
572         break;
573       case 'loadProgress':
574         this.updateProgress_(message.data.progress);
575         break;
576       case 'navigate':
577         // If in print preview, always open a new tab.
578         if (this.isPrintPreview_)
579           this.navigator_.navigate(message.data.url, true);
580         else
581           this.navigator_.navigate(message.data.url, message.data.newTab);
582         break;
583       case 'setScrollPosition':
584         var position = this.viewport_.position;
585         if (message.data.x !== undefined)
586           position.x = message.data.x;
587         if (message.data.y !== undefined)
588           position.y = message.data.y;
589         this.viewport_.position = position;
590         break;
591       case 'setTranslatedStrings':
592         this.passwordScreen_.text = message.data.getPasswordString;
593         if (!this.isMaterial_) {
594           this.progressBar_.text = message.data.loadingString;
595           if (!this.isPrintPreview_)
596             this.progressBar_.style.visibility = 'visible';
597         }
598         this.errorScreen_.text = message.data.loadFailedString;
599         break;
600       case 'cancelStreamUrl':
601         chrome.mimeHandlerPrivate.abortStream();
602         break;
603       case 'bookmarks':
604         this.bookmarks_ = message.data.bookmarks;
605         if (this.isMaterial_ && this.bookmarks_.length !== 0)
606           this.materialToolbar_.bookmarks = this.bookmarks;
607         break;
608       case 'setIsSelecting':
609         this.viewportScroller_.setEnableScrolling(message.data.isSelecting);
610         break;
611       case 'getNamedDestinationReply':
612         this.paramsParser_.onNamedDestinationReceived(
613             message.data.pageNumber);
614         break;
615     }
616   },
618   /**
619    * @private
620    * A callback that's called before the zoom changes. Notify the plugin to stop
621    * reacting to scroll events while zoom is taking place to avoid flickering.
622    */
623   beforeZoom_: function() {
624     this.plugin_.postMessage({
625       type: 'stopScrolling'
626     });
627   },
629   /**
630    * @private
631    * A callback that's called after the zoom changes. Notify the plugin of the
632    * zoom change and to continue reacting to scroll events.
633    */
634   afterZoom_: function() {
635     var position = this.viewport_.position;
636     var zoom = this.viewport_.zoom;
637     if (this.isMaterial_)
638       this.zoomToolbar_.zoomValue = 100 * zoom;
639     this.plugin_.postMessage({
640       type: 'viewport',
641       zoom: zoom,
642       xOffset: position.x,
643       yOffset: position.y
644     });
645     this.zoomManager_.onPdfZoomChange();
646   },
648   /**
649    * @private
650    * A callback that's called after the viewport changes.
651    */
652   viewportChanged_: function() {
653     if (!this.documentDimensions_)
654       return;
656     // Update the buttons selected.
657     if (!this.isMaterial_) {
658       $('fit-to-page-button').classList.remove('polymer-selected');
659       $('fit-to-width-button').classList.remove('polymer-selected');
660       if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) {
661         $('fit-to-page-button').classList.add('polymer-selected');
662       } else if (this.viewport_.fittingType ==
663                  Viewport.FittingType.FIT_TO_WIDTH) {
664         $('fit-to-width-button').classList.add('polymer-selected');
665       }
666     }
668     // Offset the toolbar position so that it doesn't move if scrollbars appear.
669     var hasScrollbars = this.viewport_.documentHasScrollbars();
670     var scrollbarWidth = this.viewport_.scrollbarWidth;
671     var verticalScrollbarWidth = hasScrollbars.vertical ? scrollbarWidth : 0;
672     var horizontalScrollbarWidth =
673         hasScrollbars.horizontal ? scrollbarWidth : 0;
674     var toolbarRight = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth);
675     var toolbarBottom = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth);
676     toolbarRight -= verticalScrollbarWidth;
677     toolbarBottom -= horizontalScrollbarWidth;
678     if (!this.isMaterial_) {
679       this.toolbar_.style.right = toolbarRight + 'px';
680       this.toolbar_.style.bottom = toolbarBottom + 'px';
681       // Hide the toolbar if it doesn't fit in the viewport.
682       if (this.toolbar_.offsetLeft < 0 || this.toolbar_.offsetTop < 0)
683         this.toolbar_.style.visibility = 'hidden';
684       else
685         this.toolbar_.style.visibility = 'visible';
686     }
688     // Update the page indicator.
689     var visiblePage = this.viewport_.getMostVisiblePage();
690     if (this.isMaterial_) {
691       this.materialToolbar_.pageNo = visiblePage + 1;
692     } else {
693       this.pageIndicator_.index = visiblePage;
694       if (this.documentDimensions_.pageDimensions.length > 1 &&
695           hasScrollbars.vertical) {
696         this.pageIndicator_.style.visibility = 'visible';
697       } else {
698         this.pageIndicator_.style.visibility = 'hidden';
699       }
700     }
702     var visiblePageDimensions = this.viewport_.getPageScreenRect(visiblePage);
703     var size = this.viewport_.size;
704     this.sendScriptingMessage_({
705       type: 'viewport',
706       pageX: visiblePageDimensions.x,
707       pageY: visiblePageDimensions.y,
708       pageWidth: visiblePageDimensions.width,
709       viewportWidth: size.width,
710       viewportHeight: size.height
711     });
712   },
714   /**
715    * Handle a scripting message from outside the extension (typically sent by
716    * PDFScriptingAPI in a page containing the extension) to interact with the
717    * plugin.
718    * @param {MessageObject} message the message to handle.
719    */
720   handleScriptingMessage: function(message) {
721     if (this.parentWindow_ != message.source) {
722       this.parentWindow_ = message.source;
723       // Ensure that we notify the embedder if the document is loaded.
724       if (this.loadState_ != LoadState.LOADING)
725         this.sendDocumentLoadedMessage_();
726     }
728     if (this.handlePrintPreviewScriptingMessage_(message))
729       return;
731     // Delay scripting messages from users of the scripting API until the
732     // document is loaded. This simplifies use of the APIs.
733     if (this.loadState_ != LoadState.SUCCESS) {
734       this.delayedScriptingMessages_.push(message);
735       return;
736     }
738     switch (message.data.type.toString()) {
739       case 'getAccessibilityJSON':
740       case 'getSelectedText':
741       case 'print':
742       case 'selectAll':
743         this.plugin_.postMessage(message.data);
744         break;
745     }
746   },
748   /**
749    * @private
750    * Handle scripting messages specific to print preview.
751    * @param {MessageObject} message the message to handle.
752    * @return {boolean} true if the message was handled, false otherwise.
753    */
754   handlePrintPreviewScriptingMessage_: function(message) {
755     if (!this.isPrintPreview_)
756       return false;
758     switch (message.data.type.toString()) {
759       case 'loadPreviewPage':
760         this.plugin_.postMessage(message.data);
761         return true;
762       case 'resetPrintPreviewMode':
763         this.loadState_ = LoadState.LOADING;
764         if (!this.inPrintPreviewMode_) {
765           this.inPrintPreviewMode_ = true;
766           this.viewport_.fitToPage();
767         }
769         // Stash the scroll location so that it can be restored when the new
770         // document is loaded.
771         this.lastViewportPosition_ = this.viewport_.position;
773         // TODO(raymes): Disable these properly in the plugin.
774         var printButton = $('print-button');
775         if (printButton)
776           printButton.parentNode.removeChild(printButton);
777         var saveButton = $('save-button');
778         if (saveButton)
779           saveButton.parentNode.removeChild(saveButton);
781         if (!this.isMaterial_)
782           this.pageIndicator_.pageLabels = message.data.pageNumbers;
784         this.plugin_.postMessage({
785           type: 'resetPrintPreviewMode',
786           url: message.data.url,
787           grayscale: message.data.grayscale,
788           // If the PDF isn't modifiable we send 0 as the page count so that no
789           // blank placeholder pages get appended to the PDF.
790           pageCount: (message.data.modifiable ?
791                       message.data.pageNumbers.length : 0)
792         });
793         return true;
794       case 'sendKeyEvent':
795         this.handleKeyEvent_(DeserializeKeyEvent(message.data.keyEvent));
796         return true;
797     }
799     return false;
800   },
802   /**
803    * @private
804    * Send a scripting message outside the extension (typically to
805    * PDFScriptingAPI in a page containing the extension).
806    * @param {Object} message the message to send.
807    */
808   sendScriptingMessage_: function(message) {
809     if (this.parentWindow_)
810       this.parentWindow_.postMessage(message, '*');
811   },
814   /**
815    * @type {Viewport} the viewport of the PDF viewer.
816    */
817   get viewport() {
818     return this.viewport_;
819   },
821   /**
822    * Each bookmark is an Object containing a:
823    * - title
824    * - page (optional)
825    * - array of children (themselves bookmarks)
826    * @type {Array} the top-level bookmarks of the PDF.
827    */
828   get bookmarks() {
829     return this.bookmarks_;
830   }