diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/chrome/browser/resources/pdf | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/chrome/browser/resources/pdf')
45 files changed, 1907 insertions, 63 deletions
diff --git a/chromium/chrome/browser/resources/pdf/background.js b/chromium/chrome/browser/resources/pdf/background.js index 0a37f6d6313..2114010193f 100644 --- a/chromium/chrome/browser/resources/pdf/background.js +++ b/chromium/chrome/browser/resources/pdf/background.js @@ -2,11 +2,32 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -chrome.streamsPrivate.onExecuteMimeTypeHandler.addListener( - function(mime_type, original_url, content_url, tab_id) { - // TODO(raymes): Currently this doesn't work with embedded PDFs (it - // causes the entire frame to navigate). Also work out how we can - // mask the URL with the URL of the PDF. - chrome.tabs.update(tab_id, { url: 'pdf.html?' + content_url }); - } -); +(function() { + 'use strict'; + + /** + * Keep a stack of stream details for requests. These are pushed onto the + * stack as requests come in and popped off the stack as they are handled by a + * renderer. + * TODO(raymes): This is probably racy for multiple requests. We could + * associate an ID with the request but this code will probably change + * completely when MIME type handling is improved. + */ + var streamsCache = []; + + window.popStreamDetails = function() { + if (streamsCache.length > 0) + return streamsCache.pop(); + }; + + chrome.streamsPrivate.onExecuteMimeTypeHandler.addListener( + function(streamDetails) { + // TODO(raymes): Currently this doesn't work with embedded PDFs (it + // causes the entire frame to navigate). Also work out how we can + // mask the URL with the URL of the PDF. + streamsCache.push(streamDetails); + chrome.tabs.update(streamDetails.tabId, {url: 'index.html'}); + } + ); + +}()); diff --git a/chromium/chrome/browser/resources/pdf/html_office/OWNERS b/chromium/chrome/browser/resources/pdf/html_office/OWNERS new file mode 100644 index 00000000000..feedefa65bd --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/OWNERS @@ -0,0 +1,3 @@ +ganetsky@chromium.org +jliebrand@chromium.org +raymes@chromium.org diff --git a/chromium/chrome/browser/resources/pdf/html_office/codereview.settings b/chromium/chrome/browser/resources/pdf/html_office/codereview.settings new file mode 100644 index 00000000000..ca62ae98a60 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/codereview.settings @@ -0,0 +1,7 @@ +# This file is used by gcl to get repository specific information. +CODE_REVIEW_SERVER: codereview.chromium.org +CC_LIST: raymes@chromium.org,ganetsky@chromium.org,jliebrand@chromium.org +STATUS: http://chromium-status.appspot.com/status +TRY_ON_UPLOAD: False +GITCL_PREUPLOAD: http://src.chromium.org/viewvc/trunk/tools/depot_tools/git-cl-upload-hook?revision=HEAD&root=chrome +GITCL_PREDCOMMIT: http://src.chromium.org/viewvc/trunk/tools/depot_tools/git-cl-upload-hook?revision=HEAD&root=chrome diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_page.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_page.png Binary files differnew file mode 100644 index 00000000000..6977d2b8796 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_page.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_width.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_width.png Binary files differnew file mode 100644 index 00000000000..d9dea06a74f --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_fit_width.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_play.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_play.png Binary files differnew file mode 100644 index 00000000000..ac73ffc1724 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_play.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_print.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_print.png Binary files differnew file mode 100644 index 00000000000..7fa75a3df72 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_print.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_save.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_save.png Binary files differnew file mode 100644 index 00000000000..ea125115ed7 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_save.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_in.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_in.png Binary files differnew file mode 100644 index 00000000000..2cedf813153 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_in.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_out.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_out.png Binary files differnew file mode 100644 index 00000000000..e27c272fd80 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/hiDPI/button_zoom_out.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_page.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_page.png Binary files differnew file mode 100644 index 00000000000..e8e7a7039cf --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_page.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_width.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_width.png Binary files differnew file mode 100644 index 00000000000..700f4861ea5 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_width.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_play.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_play.png Binary files differnew file mode 100644 index 00000000000..d5ceae76ac8 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_play.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_print.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_print.png Binary files differnew file mode 100644 index 00000000000..e044a28837f --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_print.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_save.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_save.png Binary files differnew file mode 100644 index 00000000000..e732a442f35 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_save.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_in.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_in.png Binary files differnew file mode 100644 index 00000000000..e05ae81589a --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_in.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_out.png b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_out.png Binary files differnew file mode 100644 index 00000000000..b76b54c95fc --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/img/lowDPI/button_zoom_out.png diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.css b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.css new file mode 100644 index 00000000000..d4aff70c49d --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.css @@ -0,0 +1,37 @@ +/* Copyright 2013 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. */ + +#icon { + background-position: center center; + background-repeat: no-repeat; + background-size: 100% 100%; + height: 100%; + width: 100%; +} + +:host { + -webkit-user-select: none; + background-image: linear-gradient(rgb(60, 80, 119), rgb(15, 24, 41)); + border: 1px solid rgb(11, 9, 16); + cursor: default; + display: inline-block; + height: 36px; + margin: 0; + pointer-events: all; + width: 43px; +} + +:host(:focus:host) { + outline: none; +} + +:host(:hover:host) { + background-image: linear-gradient(rgb(73, 102, 155), rgb(32, 52, 95)); +} + +:host(.latchable.polymer-selected:host), +:host(:active:host) { + background-color: rgb(75, 103, 156); + background-image: none; +} diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.html b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.html new file mode 100644 index 00000000000..e1eee711067 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.html @@ -0,0 +1,7 @@ +<polymer-element name="viewer-button" attributes="img latchable"> +<template> + <link rel="stylesheet" href="viewer-button.css"> + <div id="icon"></div> +</template> +<script src="viewer-button.js"></script> +</polymer-element> diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.js b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.js new file mode 100644 index 00000000000..73bd53eff1e --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-button/viewer-button.js @@ -0,0 +1,33 @@ +// Copyright 2014 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. + +(function() { + var dpi = ''; + + Polymer('viewer-button', { + img: '', + latchable: false, + ready: function() { + if (!dpi) { + var mql = window.matchMedia('(-webkit-min-device-pixel-ratio: 1.3'); + dpi = mql.matches ? 'hi' : 'low'; + } + }, + imgChanged: function() { + if (this.img) { + this.$.icon.style.backgroundImage = + 'url(' + this.getAttribute('assetpath') + 'img/' + dpi + + 'DPI/' + this.img + ')'; + } else { + this.$.icon.style.backgroundImage = ''; + } + }, + latchableChanged: function() { + if (this.latchable) + this.classList.add('latchable'); + else + this.classList.remove('latchable'); + }, + }); +})(); diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.css b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.css new file mode 100644 index 00000000000..9c7c7c82676 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.css @@ -0,0 +1,18 @@ +:host { + background-color: #ccc; + color: #555; + font-family: sans-serif; + font-size: 20px; + height: 100%; + pointer-events: none; + position: fixed; + text-align: center; + width: 100%; +} + +#load-failed-message { + line-height: 0; + position: absolute; + top: 50%; + width: 100%; +}
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.html b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.html new file mode 100644 index 00000000000..81199fe7d3a --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.html @@ -0,0 +1,7 @@ +<polymer-element name="viewer-error-screen" attributes="text"> +<template> + <link rel="stylesheet" href="viewer-error-screen.css"> + <div id="load-failed-message">{{text}}</div> +</template> +<script src="viewer-error-screen.js"></script> +</polymer-element> diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.js b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.js new file mode 100644 index 00000000000..c63874dd2d7 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-error-screen/viewer-error-screen.js @@ -0,0 +1,5 @@ +// Copyright 2014 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. + +Polymer('viewer-error-screen', {}); diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.css b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.css new file mode 100644 index 00000000000..ec90ec99f53 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.css @@ -0,0 +1,35 @@ +/* Copyright 2013 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. */ + +:host { + -webkit-transition: opacity 400ms ease-in-out; + pointer-events: none; + position: fixed; + right: 0; +} + +#text { + background-color: rgba(0, 0, 0, 0.5); + border-radius: 5px; + color: white; + float: left; + font-family: sans-serif; + font-size: 12px; + font-weight: bold; + line-height: 48px; + text-align: center; + text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8); + width: 62px; +} + +#triangle-right { + border-bottom: 6px solid transparent; + border-left: 8px solid rgba(0, 0, 0, 0.5); + border-top: 6px solid transparent; + display: inline; + float: left; + height: 0; + margin-top: 18px; + width: 0; +}
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.html b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.html new file mode 100644 index 00000000000..de59e1a9eca --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.html @@ -0,0 +1,8 @@ +<polymer-element name="viewer-page-indicator" attributes="index label"> +<template> + <link rel="stylesheet" href="viewer-page-indicator.css"> + <div id="text">{{label}}</div> + <div id="triangle-right"></div> +</template> +<script src="viewer-page-indicator.js"></script> +</polymer-element> diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.js b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.js new file mode 100644 index 00000000000..8612e170348 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-page-indicator/viewer-page-indicator.js @@ -0,0 +1,42 @@ +// Copyright 2014 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. + +Polymer('viewer-page-indicator', { + label: '1', + index: 0, + timerId: undefined, + pageLabels: null, + ready: function() { + var callback = this.fadeIn.bind(this, 2000); + window.addEventListener('scroll', function() { + requestAnimationFrame(callback); + }); + }, + initialFadeIn: function() { + this.fadeIn(6000); + }, + fadeIn: function(displayTime) { + var percent = window.scrollY / + (document.body.scrollHeight - + document.documentElement.clientHeight); + this.style.top = percent * + (document.documentElement.clientHeight - this.offsetHeight) + 'px'; + this.style.opacity = 1; + clearTimeout(this.timerId); + + this.timerId = setTimeout(function() { + this.style.opacity = 0; + this.timerId = undefined; + }.bind(this), displayTime); + }, + pageLabelsChanged: function() { + this.indexChanged(); + }, + indexChanged: function() { + if (this.pageLabels) + this.label = this.pageLabels[this.index]; + else + this.label = String(this.index + 1); + } +}); diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.css b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.css new file mode 100644 index 00000000000..183f439f989 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.css @@ -0,0 +1,41 @@ +:host { + -webkit-transition: opacity 400ms ease-in-out; + background-color: #ccc; + color: #555; + display: table; + font-family: sans-serif; + font-size: 15px; + height: 100%; + pointer-events: none; + position: fixed; + text-align: center; + width: 100%; +} + +#message { + padding-bottom: 10px; +} + +.center { + display: table-cell; + vertical-align: middle; +} + +.form { + border: 1px solid #777; + box-shadow: 1px 1px 1px; + display: inline-block; + padding: 10px; + width: 300px; +} + +#successMessage { + display: inline-block; + padding-left: 5px; + pointer-events: none; +} + +input { + color: #333; + pointer-events: all; +}
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.html b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.html new file mode 100644 index 00000000000..24a82330987 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.html @@ -0,0 +1,14 @@ +<polymer-element name="viewer-password-screen" attributes="text active"> +<template> + <link rel="stylesheet" href="viewer-password-screen.css"> + <div class="center"> + <form class="form"> + <div id="message">{{text}}</div> + <input id="password" type="password" size="20"></input> + <input id="submit" type="submit" on-click={{submit}}></input> + <div id="successMessage">{{successMessage}}</div> + </form> + </div> +</template> +<script src="viewer-password-screen.js"></script> +</polymer-element> diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.js b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.js new file mode 100644 index 00000000000..9ba949f09b6 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-password-screen/viewer-password-screen.js @@ -0,0 +1,51 @@ +// Copyright 2014 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. + +Polymer('viewer-password-screen', { + text: 'This document is password protected. Please enter a password.', + active: false, + timerId: undefined, + ready: function () { + this.activeChanged(); + }, + accept: function() { + this.successMessage = '✔' // Tick. + this.$.successMessage.style.color = 'rgb(0,125,0)'; + this.active = false; + }, + deny: function() { + this.successMessage = '✘'; // Cross. + this.$.successMessage.style.color = 'rgb(255,0,0)'; + this.$.password.disabled = false; + this.$.submit.disabled = false; + this.$.password.focus(); + this.$.password.select(); + }, + submit: function(e) { + // Prevent the default form submission behavior. + e.preventDefault(); + if (this.$.password.value.length == 0) + return; + this.successMessage = '...'; + this.$.successMessage.style.color = 'rgb(0,0,0)'; + this.$.password.disabled = true; + this.$.submit.disabled = true; + this.fire('password-submitted', {password: this.$.password.value}); + }, + activeChanged: function() { + clearTimeout(this.timerId); + this.timerId = undefined; + if (this.active) { + this.style.visibility = 'visible'; + this.style.opacity = 1; + this.successMessage = ''; + this.$.password.focus(); + } else { + this.style.opacity = 0; + this.timerId = setTimeout(function() { + this.style.visibility = 'hidden' + }.bind(this), 400); + } + } +});
\ No newline at end of file diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.css b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.css new file mode 100644 index 00000000000..f5c13e30114 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.css @@ -0,0 +1,71 @@ +/* Copyright 2013 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. */ + +:host { + -webkit-transition: opacity 400ms ease-in-out; + background: rgb(29, 39, 57); + border-radius: 5px; + bottom: 26px; + box-shadow: 0 1px 2px gray, 0 3px 3px rgba(0, 0, 0, .2); + height: auto; + left: 26px; + pointer-events: none; + position: fixed; + width: auto; +} + +.scaler { + -webkit-transform: scale(0.25); + -webkit-transform-origin: 0 0; + float: left; + height: 44px; + margin: 8px; + width: 44px; +} + +#segments { + border-radius: 50%; + height: 176px; + list-style: none; + margin: 0; + overflow: hidden; + padding: 0; + position: absolute; + width: 176px; +} + +.segment { + -webkit-transform-origin: 0 100%; + background: rgb(227, 234, 249); + box-shadow: 0 0 0 6px rgb(29, 39, 57) inset; + height: 50%; + overflow: hidden; + position: absolute; + right: 0; + top: 0; + width: 50%; +} + +.center-circle { + background-color: rgb(29, 39, 57); + border-radius: 50%; + height: 80px; + left: 44px; + margin: 0; + padding: 0; + position: absolute; + top: 44px; + width: 80px; +} + +#text { + color: rgb(227, 234, 249); + float: left; + font-family: sans-serif; + font-size: 16px; + font-weight: bold; + line-height: 58px; + margin-right: 10px; + margin-top: 1px; +} diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.html b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.html new file mode 100644 index 00000000000..bc16ef7a383 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.html @@ -0,0 +1,12 @@ +<polymer-element name="viewer-progress-bar" + attributes="progress text numSegments"> +<template> + <link rel="stylesheet" href="viewer-progress-bar.css"> + <div class="scaler"> + <ul id="segments"></ul> + <div class="center-circle"></div> + </div> + <div id="text">{{text}}</div> +</template> +<script src="viewer-progress-bar.js"></script> +</polymer-element> diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.js b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.js new file mode 100644 index 00000000000..7c4932dec92 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-progress-bar/viewer-progress-bar.js @@ -0,0 +1,43 @@ +// Copyright 2014 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. + +Polymer('viewer-progress-bar', { + progress: 0, + text: 'Loading', + numSegments: 8, + segments: [], + ready: function() { + this.numSegmentsChanged(); + }, + progressChanged: function() { + var numVisible = this.progress * this.segments.length / 100.0; + for (var i = 0; i < this.segments.length; i++) { + this.segments[i].style.visibility = + i < numVisible ? 'visible' : 'hidden'; + } + + if (this.progress >= 100 || this.progress < 0) + this.style.opacity = 0; + }, + numSegmentsChanged: function() { + // Clear the existing segments. + this.segments = []; + var segmentsElement = this.$.segments; + segmentsElement.innerHTML = ''; + + // Create the new segments. + var segment = document.createElement('li'); + segment.classList.add('segment'); + var angle = 360 / this.numSegments; + for (var i = 0; i < this.numSegments; ++i) { + var segmentCopy = segment.cloneNode(true); + segmentCopy.style.webkitTransform = + 'rotate(' + (i * angle) + 'deg) skewY(' + + -1 * (90 - angle) + 'deg)'; + segmentsElement.appendChild(segmentCopy); + this.segments.push(segmentCopy); + } + this.progressChanged(); + } +}); diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.css b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.css new file mode 100644 index 00000000000..64be0e9f12c --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.css @@ -0,0 +1,21 @@ +/* Copyright 2013 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. */ + +:host { + -webkit-transition: opacity 400ms ease-in-out; + bottom: 0; + display: block; + font-size: 0; + opacity: 1; + padding: 30px 30px 15px 30vw; + pointer-events: none; + position: fixed; + right: 0; +} + +#toolbar { + border-radius: 3px; + box-shadow: 0 1px 2px gray, 0 3px 3px rgba(0, 0, 0, .2); + overflow: hidden; +} diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.html b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.html new file mode 100644 index 00000000000..a22bf997418 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.html @@ -0,0 +1,9 @@ +<polymer-element name="viewer-toolbar" attributes="fadingIn"> +<template> + <link rel="stylesheet" href="viewer-toolbar.css"> + <div id="toolbar"> + <content></content> + </div> +</template> +<script src="viewer-toolbar.js"></script> +</polymer-element> diff --git a/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.js b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.js new file mode 100644 index 00000000000..c28b45b670b --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/html_office/elements/viewer-toolbar/viewer-toolbar.js @@ -0,0 +1,57 @@ +// Copyright 2014 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. + +Polymer('viewer-toolbar', { + fadingIn: false, + timerId_: undefined, + inInitialFadeIn_: false, + ready: function() { + this.mousemoveCallback = function(e) { + var rect = this.getBoundingClientRect(); + if (e.clientX >= rect.left && e.clientX <= rect.right && + e.clientY >= rect.top && e.clientY <= rect.bottom) { + this.fadingIn = true; + // If we hover over the toolbar, cancel the initial fade in. + if (this.inInitialFadeIn_) + this.inInitialFadeIn_ = false; + } else { + // Initially we want to keep the toolbar up for a longer period. + if (!this.inInitialFadeIn_) + this.fadingIn = false; + } + }.bind(this); + }, + attached: function() { + this.parentNode.addEventListener('mousemove', this.mousemoveCallback); + }, + detached: function() { + this.parentNode.removeEventListener('mousemove', this.mousemoveCallback); + }, + initialFadeIn: function() { + this.inInitialFadeIn_ = true; + this.fadeIn(); + this.fadeOutAfterDelay(6000); + }, + fadingInChanged: function() { + if (this.fadingIn) { + this.fadeIn(); + } else { + if (this.timerId_ === undefined) + this.fadeOutAfterDelay(3000); + } + }, + fadeIn: function() { + this.style.opacity = 1; + clearTimeout(this.timerId_); + this.timerId_ = undefined; + }, + fadeOutAfterDelay: function(delay) { + this.timerId_ = setTimeout( + function() { + this.style.opacity = 0; + this.timerId_ = undefined; + this.inInitialFadeIn_ = false; + }.bind(this), delay); + } +}); diff --git a/chromium/chrome/browser/resources/pdf/includes.js b/chromium/chrome/browser/resources/pdf/includes.js new file mode 100644 index 00000000000..00de804866e --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/includes.js @@ -0,0 +1,12 @@ +// Copyright 2014 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 src="../../../../third_party/polymer/platform/platform.js"> +<include src="../../../../third_party/polymer/polymer/polymer.js"> +<include src="html_office/elements/viewer-toolbar/viewer-toolbar.js"> +<include src="html_office/elements/viewer-progress-bar/viewer-progress-bar.js"> +<include src="html_office/elements/viewer-password-screen/viewer-password-screen.js"> +<include src="html_office/elements/viewer-page-indicator/viewer-page-indicator.js"> +<include src="html_office/elements/viewer-error-screen/viewer-error-screen.js"> +<include src="html_office/elements/viewer-button/viewer-button.js"> diff --git a/chromium/chrome/browser/resources/pdf/index.css b/chromium/chrome/browser/resources/pdf/index.css new file mode 100644 index 00000000000..4186344c81d --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/index.css @@ -0,0 +1,44 @@ +/* Copyright 2014 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. */ + +body { + background-color: #ccc; + margin: 0; +} + +viewer-toolbar { + visibility: hidden; + z-index: 3; +} + +viewer-page-indicator { + visibility: hidden; + z-index: 3; +} + +viewer-progress-bar { + z-index: 3; +} + +viewer-error-screen { + visibility: hidden; + z-index: 2; +} + +viewer-password-screen { + visibility: hidden; + z-index: 2; +} + +#plugin { + height: 100%; + position: fixed; + width: 100%; + z-index: 1; +} + +#sizer { + position: absolute; + z-index: 0; +} diff --git a/chromium/chrome/browser/resources/pdf/index.html b/chromium/chrome/browser/resources/pdf/index.html new file mode 100644 index 00000000000..456313c5ff9 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/index.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html i18n-values="dir:textdirection"> +<head> + <meta charset="utf-8"> + <!-- TODO(raymes): Turn these <include>s into HTML imports once they are + fully implemented. At that point includes.js can be removed + as the scripts will be pulled in automatically. --> + <script src="includes.js"></script> + <include src="html_office/elements/viewer-button/viewer-button.html"> + <include src="html_office/elements/viewer-error-screen/viewer-error-screen.html"> + <include src="html_office/elements/viewer-page-indicator/viewer-page-indicator.html"> + <include src="html_office/elements/viewer-password-screen/viewer-password-screen.html"> + <include src="html_office/elements/viewer-progress-bar/viewer-progress-bar.html"> + <include src="html_office/elements/viewer-toolbar/viewer-toolbar.html"> + <link rel="stylesheet" type="text/css" href="index.css"> +</head> +<body> + +<div id="sizer"></div> +<viewer-password-screen id="password-screen"></viewer-password-screen> +<viewer-page-indicator id="page-indicator"></viewer-page-indicator> +<viewer-progress-bar id="progress-bar"></viewer-progress-bar> + +<viewer-toolbar id="toolbar"> + <viewer-button id="fit-to-page-button" + assetpath="html_office/elements/viewer-button/" + img="button_fit_page.png" latchable> + </viewer-button> + <viewer-button id="fit-to-width-button" + assetpath="html_office/elements/viewer-button/" + img="button_fit_width.png" latchable> + </viewer-button> + <viewer-button id="zoom-in-button" + assetpath="html_office/elements/viewer-button/" + img="button_zoom_in.png"> + </viewer-button> + <viewer-button id="zoom-out-button" + assetpath="html_office/elements/viewer-button/" + img="button_zoom_out.png"> + </viewer-button> + <a id="save-button-link" download> + <viewer-button id="save-button" + assetpath="html_office/elements/viewer-button/" + img="button_save.png"> + </viewer-button> + </a> + <viewer-button id="print-button" + assetpath="html_office/elements/viewer-button/" + img="button_print.png"> + </viewer-button> +</viewer-toolbar> + +<viewer-error-screen id="error-screen"></viewer-error-screen> + +</body> +<script src="pdf.js"></script> +</html> diff --git a/chromium/chrome/browser/resources/pdf/manifest.json b/chromium/chrome/browser/resources/pdf/manifest.json index 07e577aca3a..f97e970ef63 100644 --- a/chromium/chrome/browser/resources/pdf/manifest.json +++ b/chromium/chrome/browser/resources/pdf/manifest.json @@ -6,10 +6,13 @@ "version": "1", "description": "Chrome PDF Viewer", "offline_enabled": true, + "incognito": "split", "permissions": [ "file://*/", + "ftp://*/", "http://*/", "https://*/", + "chrome://*/", "streamsPrivate" ], "background": { @@ -17,11 +20,10 @@ "transient": true, "persistent": false }, - "offline_enabled": true, "mime_types": [ "application/pdf" ], "web_accessible_resources": [ - "pdf.html" + "index.html" ] } diff --git a/chromium/chrome/browser/resources/pdf/pdf.html b/chromium/chrome/browser/resources/pdf/pdf.html deleted file mode 100644 index 61db52371eb..00000000000 --- a/chromium/chrome/browser/resources/pdf/pdf.html +++ /dev/null @@ -1,15 +0,0 @@ -<!DOCTYPE HTML> -<html i18n-values="dir:textdirection"> -<head> - <meta charset="utf-8"> - <style> - body { - background-color: #ccc; - font-family: sans-serif; - } - </style> -</head> -<body marginwidth="0" marginheight="0" > -</body> -<script src="pdf.js" language="javascript" type="text/javascript"></script> -</html> diff --git a/chromium/chrome/browser/resources/pdf/pdf.js b/chromium/chrome/browser/resources/pdf/pdf.js index c2a47f476a4..60384e5db6e 100644 --- a/chromium/chrome/browser/resources/pdf/pdf.js +++ b/chromium/chrome/browser/resources/pdf/pdf.js @@ -2,50 +2,486 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -(function() { +'use strict'; -var plugin; -var sizer; +<include src="../../../../ui/webui/resources/js/util.js"> +<include src="pdf_scripting_api.js"> +<include src="viewport.js"> -function onScroll() { - var coordinates = [window.pageXOffset, window.pageYOffset]; - plugin.postMessage(coordinates); +/** + * @return {number} Width of a scrollbar in pixels + */ +function getScrollbarWidth() { + var div = document.createElement('div'); + div.style.visibility = 'hidden'; + div.style.overflow = 'scroll'; + div.style.width = '50px'; + div.style.height = '50px'; + div.style.position = 'absolute'; + document.body.appendChild(div); + var result = div.offsetWidth - div.clientWidth; + div.parentNode.removeChild(div); + return result; } -function handleMessage(message) { - if (message.data['type'] == 'document_dimensions') { - if (sizer.style.height != message.data['document_height'] + 'px') { - sizer.style.height = message.data['document_height'] + 'px'; - sizer.style.width = message.data['document_width'] + 'px'; - } +/** + * The minimum number of pixels to offset the toolbar by from the bottom and + * right side of the screen. + */ +PDFViewer.MIN_TOOLBAR_OFFSET = 15; + +/** + * Creates a new PDFViewer. There should only be one of these objects per + * document. + */ +function PDFViewer() { + this.loaded = false; + + // The sizer element is placed behind the plugin element to cause scrollbars + // to be displayed in the window. It is sized according to the document size + // of the pdf and zoom level. + this.sizer_ = $('sizer'); + this.toolbar_ = $('toolbar'); + this.pageIndicator_ = $('page-indicator'); + this.progressBar_ = $('progress-bar'); + this.passwordScreen_ = $('password-screen'); + this.passwordScreen_.addEventListener('password-submitted', + this.onPasswordSubmitted_.bind(this)); + this.errorScreen_ = $('error-screen'); + + // Create the viewport. + this.viewport_ = new Viewport(window, + this.sizer_, + this.viewportChangedCallback_.bind(this), + getScrollbarWidth()); + + // Create the plugin object dynamically so we can set its src. The plugin + // element is sized to fill the entire window and is set to be fixed + // positioning, acting as a viewport. The plugin renders into this viewport + // according to the scroll position of the window. + this.plugin_ = document.createElement('object'); + // NOTE: The plugin's 'id' field must be set to 'plugin' since + // chrome/renderer/printing/print_web_view_helper.cc actually references it. + this.plugin_.id = 'plugin'; + this.plugin_.type = 'application/x-google-chrome-pdf'; + this.plugin_.addEventListener('message', this.handlePluginMessage_.bind(this), + false); + + // Handle scripting messages from outside the extension that wish to interact + // with it. We also send a message indicating that extension has loaded and + // is ready to receive messages. + window.addEventListener('message', this.handleScriptingMessage_.bind(this), + false); + this.sendScriptingMessage_({type: 'readyToReceive'}); + + // If the viewer is started from a MIME type request, there will be a + // background page and stream details object with the details of the request. + // Otherwise, we take the query string of the URL to indicate the URL of the + // PDF to load. This is used for print preview in particular. + if (chrome.extension.getBackgroundPage && + chrome.extension.getBackgroundPage()) { + this.streamDetails = + chrome.extension.getBackgroundPage().popStreamDetails(); } -} -function load() { - window.addEventListener('scroll', - function() { webkitRequestAnimationFrame(onScroll); }); - - // The pdf location is passed in the document url in the format: - // http://<.../pdf.html>?<pdf location>. - var url = window.location.search.substring(1); - plugin = document.createElement('object'); - plugin.setAttribute('width', '100%'); - plugin.setAttribute('height', '100%'); - plugin.setAttribute('type', 'application/x-google-chrome-pdf'); - plugin.setAttribute('src', url); - plugin.style.zIndex = '1'; - plugin.style.position = 'fixed'; - plugin.addEventListener('message', handleMessage, false); - document.body.appendChild(plugin); - - sizer = document.createElement('div'); - sizer.style.zIndex = '0'; - sizer.style.position = 'absolute'; - sizer.style.width = '100%'; - sizer.style.height = '100%'; - document.body.appendChild(sizer); + if (!this.streamDetails) { + // The URL of this page will be of the form + // "chrome-extension://<extension id>?<pdf url>". We pull out the <pdf url> + // part here. + var url = window.location.search.substring(1); + this.streamDetails = { + streamUrl: url, + originalUrl: url, + responseHeaders: '' + }; + } + + this.plugin_.setAttribute('src', this.streamDetails.originalUrl); + this.plugin_.setAttribute('stream-url', this.streamDetails.streamUrl); + var headers = ''; + for (var header in this.streamDetails.responseHeaders) { + headers += header + ': ' + + this.streamDetails.responseHeaders[header] + '\n'; + } + this.plugin_.setAttribute('headers', headers); + + if (window.top == window) + this.plugin_.setAttribute('full-frame', ''); + document.body.appendChild(this.plugin_); + + // Setup the button event listeners. + $('fit-to-width-button').addEventListener('click', + this.viewport_.fitToWidth.bind(this.viewport_)); + $('fit-to-page-button').addEventListener('click', + this.viewport_.fitToPage.bind(this.viewport_)); + $('zoom-in-button').addEventListener('click', + this.viewport_.zoomIn.bind(this.viewport_)); + $('zoom-out-button').addEventListener('click', + this.viewport_.zoomOut.bind(this.viewport_)); + $('save-button-link').href = this.streamDetails.originalUrl; + $('print-button').addEventListener('click', this.print_.bind(this)); + + // Setup the keyboard event listener. + document.onkeydown = this.handleKeyEvent_.bind(this); } -load(); +PDFViewer.prototype = { + /** + * @private + * Handle key events. These may come from the user directly or via the + * scripting API. + * @param {KeyboardEvent} e the event to handle. + */ + handleKeyEvent_: function(e) { + var position = this.viewport_.position; + // Certain scroll events may be sent from outside of the extension. + var fromScriptingAPI = e.type == 'scriptingKeypress'; + + switch (e.keyCode) { + case 33: // Page up key. + // Go to the previous page if we are fit-to-page. + if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) { + this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1); + // Since we do the movement of the page. + e.preventDefault(); + } else if (fromScriptingAPI) { + position.y -= this.viewport.size.height; + this.viewport.position = position; + } + return; + case 34: // Page down key. + // Go to the next page if we are fit-to-page. + if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) { + this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1); + // Since we do the movement of the page. + e.preventDefault(); + } else if (fromScriptingAPI) { + position.y += this.viewport.size.height; + this.viewport.position = position; + } + return; + case 37: // Left arrow key. + // Go to the previous page if there are no horizontal scrollbars. + if (!this.viewport_.documentHasScrollbars().x) { + this.viewport_.goToPage(this.viewport_.getMostVisiblePage() - 1); + // Since we do the movement of the page. + e.preventDefault(); + } else if (fromScriptingAPI) { + position.x -= Viewport.SCROLL_INCREMENT; + this.viewport.position = position; + } + return; + case 38: // Up arrow key. + if (fromScriptingAPI) { + position.y -= Viewport.SCROLL_INCREMENT; + this.viewport.position = position; + } + return; + case 39: // Right arrow key. + // Go to the next page if there are no horizontal scrollbars. + if (!this.viewport_.documentHasScrollbars().x) { + this.viewport_.goToPage(this.viewport_.getMostVisiblePage() + 1); + // Since we do the movement of the page. + e.preventDefault(); + } else if (fromScriptingAPI) { + position.x += Viewport.SCROLL_INCREMENT; + this.viewport.position = position; + } + return; + case 40: // Down arrow key. + if (fromScriptingAPI) { + position.y += Viewport.SCROLL_INCREMENT; + this.viewport.position = position; + } + return; + case 187: // +/= key. + case 107: // Numpad + key. + if (e.ctrlKey || e.metaKey) { + this.viewport_.zoomIn(); + // Since we do the zooming of the page. + e.preventDefault(); + } + return; + case 189: // -/_ key. + case 109: // Numpad - key. + if (e.ctrlKey || e.metaKey) { + this.viewport_.zoomOut(); + // Since we do the zooming of the page. + e.preventDefault(); + } + return; + case 83: // s key. + if (e.ctrlKey || e.metaKey) { + // Simulate a click on the button so that the <a download ...> + // attribute is used. + $('save-button-link').click(); + // Since we do the saving of the page. + e.preventDefault(); + } + return; + case 80: // p key. + if (e.ctrlKey || e.metaKey) { + this.print_(); + // Since we do the printing of the page. + e.preventDefault(); + } + return; + } + }, + + /** + * @private + * Notify the plugin to print. + */ + print_: function() { + this.plugin_.postMessage({ + type: 'print', + }); + }, + + /** + * @private + * Update the loading progress of the document in response to a progress + * message being received from the plugin. + * @param {number} progress the progress as a percentage. + */ + updateProgress_: function(progress) { + this.progressBar_.progress = progress; + if (progress == -1) { + // Document load failed. + this.errorScreen_.style.visibility = 'visible'; + this.sizer_.style.display = 'none'; + this.toolbar_.style.visibility = 'hidden'; + if (this.passwordScreen_.active) { + this.passwordScreen_.deny(); + this.passwordScreen_.active = false; + } + } else if (progress == 100) { + // Document load complete. + this.loaded = true; + var loadEvent = new Event('pdfload'); + window.dispatchEvent(loadEvent); + this.sendScriptingMessage_({ + type: 'documentLoaded' + }); + if (this.lastViewportPosition_) + this.viewport_.position = this.lastViewportPosition_; + } + }, + + /** + * @private + * An event handler for handling password-submitted events. These are fired + * when an event is entered into the password screen. + * @param {Object} event a password-submitted event. + */ + onPasswordSubmitted_: function(event) { + this.plugin_.postMessage({ + type: 'getPasswordComplete', + password: event.detail.password + }); + }, + + /** + * @private + * An event handler for handling message events received from the plugin. + * @param {MessageObject} message a message event. + */ + handlePluginMessage_: function(message) { + switch (message.data.type.toString()) { + case 'documentDimensions': + this.documentDimensions_ = message.data; + this.viewport_.setDocumentDimensions(this.documentDimensions_); + this.toolbar_.style.visibility = 'visible'; + // If we received the document dimensions, the password was good so we + // can dismiss the password screen. + if (this.passwordScreen_.active) + this.passwordScreen_.accept(); + + this.pageIndicator_.initialFadeIn(); + this.toolbar_.initialFadeIn(); + break; + case 'email': + var href = 'mailto:' + message.data.to + '?cc=' + message.data.cc + + '&bcc=' + message.data.bcc + '&subject=' + message.data.subject + + '&body=' + message.data.body; + var w = window.open(href, '_blank', 'width=1,height=1'); + if (w) + w.close(); + break; + case 'getAccessibilityJSONReply': + this.sendScriptingMessage_(message.data); + break; + case 'getPassword': + // If the password screen isn't up, put it up. Otherwise we're + // responding to an incorrect password so deny it. + if (!this.passwordScreen_.active) + this.passwordScreen_.active = true; + else + this.passwordScreen_.deny(); + break; + case 'goToPage': + this.viewport_.goToPage(message.data.page); + break; + case 'loadProgress': + this.updateProgress_(message.data.progress); + break; + case 'navigate': + if (message.data.newTab) + window.open(message.data.url); + else + window.location.href = message.data.url; + break; + case 'setScrollPosition': + var position = this.viewport_.position; + if (message.data.x != undefined) + position.x = message.data.x; + if (message.data.y != undefined) + position.y = message.data.y; + this.viewport_.position = position; + break; + case 'setTranslatedStrings': + this.passwordScreen_.text = message.data.getPasswordString; + this.progressBar_.text = message.data.loadingString; + this.errorScreen_.text = message.data.loadFailedString; + break; + case 'cancelStreamUrl': + chrome.streamsPrivate.abort(this.streamDetails.streamUrl); + break; + } + }, + + /** + * @private + * A callback that's called when the viewport changes. + */ + viewportChangedCallback_: function() { + if (!this.documentDimensions_) + return; + + // Update the buttons selected. + $('fit-to-page-button').classList.remove('polymer-selected'); + $('fit-to-width-button').classList.remove('polymer-selected'); + if (this.viewport_.fittingType == Viewport.FittingType.FIT_TO_PAGE) { + $('fit-to-page-button').classList.add('polymer-selected'); + } else if (this.viewport_.fittingType == + Viewport.FittingType.FIT_TO_WIDTH) { + $('fit-to-width-button').classList.add('polymer-selected'); + } + + var hasScrollbars = this.viewport_.documentHasScrollbars(); + var scrollbarWidth = this.viewport_.scrollbarWidth; + // Offset the toolbar position so that it doesn't move if scrollbars appear. + var toolbarRight = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth); + var toolbarBottom = Math.max(PDFViewer.MIN_TOOLBAR_OFFSET, scrollbarWidth); + if (hasScrollbars.vertical) + toolbarRight -= scrollbarWidth; + if (hasScrollbars.horizontal) + toolbarBottom -= scrollbarWidth; + this.toolbar_.style.right = toolbarRight + 'px'; + this.toolbar_.style.bottom = toolbarBottom + 'px'; + + // Update the page indicator. + var visiblePage = this.viewport_.getMostVisiblePage(); + this.pageIndicator_.index = visiblePage; + if (this.documentDimensions_.pageDimensions.length > 1 && + hasScrollbars.vertical) { + this.pageIndicator_.style.visibility = 'visible'; + } else { + this.pageIndicator_.style.visibility = 'hidden'; + } + + var position = this.viewport_.position; + var zoom = this.viewport_.zoom; + // Notify the plugin of the viewport change. + this.plugin_.postMessage({ + type: 'viewport', + zoom: zoom, + xOffset: position.x, + yOffset: position.y + }); + + var visiblePageDimensions = this.viewport_.getPageScreenRect(visiblePage); + var size = this.viewport_.size; + this.sendScriptingMessage_({ + type: 'viewport', + pageX: visiblePageDimensions.x, + pageY: visiblePageDimensions.y, + pageWidth: visiblePageDimensions.width, + viewportWidth: size.width, + viewportHeight: size.height, + }); + }, + + /** + * @private + * Handle a scripting message from outside the extension (typically sent by + * PDFScriptingAPI in a page containing the extension) to interact with the + * plugin. + * @param {MessageObject} message the message to handle. + */ + handleScriptingMessage_: function(message) { + switch (message.data.type.toString()) { + case 'getAccessibilityJSON': + case 'loadPreviewPage': + this.plugin_.postMessage(message.data); + break; + case 'resetPrintPreviewMode': + if (!this.inPrintPreviewMode_) { + this.inPrintPreviewMode_ = true; + this.viewport_.fitToPage(); + } + + // Stash the scroll location so that it can be restored when the new + // document is loaded. + this.lastViewportPosition_ = this.viewport_.position; + + // TODO(raymes): Disable these properly in the plugin. + var printButton = $('print-button'); + if (printButton) + printButton.parentNode.removeChild(printButton); + var saveButton = $('save-button'); + if (saveButton) + saveButton.parentNode.removeChild(saveButton); + + this.pageIndicator_.pageLabels = message.data.pageNumbers; + + this.plugin_.postMessage({ + type: 'resetPrintPreviewMode', + url: message.data.url, + grayscale: message.data.grayscale, + // If the PDF isn't modifiable we send 0 as the page count so that no + // blank placeholder pages get appended to the PDF. + pageCount: (message.data.modifiable ? + message.data.pageNumbers.length : 0) + }); + break; + case 'sendKeyEvent': + var e = document.createEvent('Event'); + e.initEvent('scriptingKeypress'); + e.keyCode = message.data.keyCode; + this.handleKeyEvent_(e); + break; + } + + }, + + /** + * @private + * Send a scripting message outside the extension (typically to + * PDFScriptingAPI in a page containing the extension). + * @param {Object} message the message to send. + */ + sendScriptingMessage_: function(message) { + window.parent.postMessage(message, '*'); + }, + + /** + * @type {Viewport} the viewport of the PDF viewer. + */ + get viewport() { + return this.viewport_; + } +}; -})(); +var viewer = new PDFViewer(); diff --git a/chromium/chrome/browser/resources/pdf/pdf_extension_test.cc b/chromium/chrome/browser/resources/pdf/pdf_extension_test.cc new file mode 100644 index 00000000000..73b6b807433 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/pdf_extension_test.cc @@ -0,0 +1,103 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/base_paths.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "chrome/browser/extensions/component_loader.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/manifest_handlers/mime_types_handler.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/test/browser_test_utils.h" +#include "grit/browser_resources.h" +#include "net/test/embedded_test_server/embedded_test_server.h" + +class PDFExtensionTest : public ExtensionApiTest { + public: + virtual ~PDFExtensionTest() {} + + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + ExtensionApiTest::SetUpCommandLine(command_line); + command_line->AppendSwitch(switches::kOutOfProcessPdf); + } + + virtual void SetUpOnMainThread() OVERRIDE { + ExtensionApiTest::SetUpOnMainThread(); + ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); + } + + + virtual void CleanUpOnMainThread() OVERRIDE { + ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete()); + ExtensionApiTest::CleanUpOnMainThread(); + } + + void RunTestsInFile(std::string filename, bool requiresPlugin) { + base::FilePath pdf_plugin_src; + PathService::Get(base::DIR_SOURCE_ROOT, &pdf_plugin_src); + pdf_plugin_src = pdf_plugin_src.AppendASCII("pdf"); + if (requiresPlugin && !base::DirectoryExists(pdf_plugin_src)) { + LOG(WARNING) << "Not running " << filename << + " because it requires the PDF plugin which is not available."; + return; + } + ExtensionService* service = extensions::ExtensionSystem::Get( + profile())->extension_service(); + service->component_loader()->Add(IDR_PDF_MANIFEST, + base::FilePath(FILE_PATH_LITERAL("pdf"))); + const extensions::Extension* extension = + service->extensions()->GetByID("mhjfbmdgcfjbbpaeojofohoefgiehjai"); + ASSERT_TRUE(extension); + ASSERT_TRUE(MimeTypesHandler::GetHandler( + extension)->CanHandleMIMEType("application/pdf")); + + ResultCatcher catcher; + + GURL url(embedded_test_server()->GetURL("/pdf/test.pdf")); + GURL extension_url( + "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html?" + + url.spec()); + ui_test_utils::NavigateToURL(browser(), extension_url); + content::WebContents* contents = + browser()->tab_strip_model()->GetActiveWebContents(); + content::WaitForLoadStop(contents); + + base::FilePath test_data_dir; + PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir); + test_data_dir = test_data_dir.Append( + FILE_PATH_LITERAL("chrome/test/data/pdf")); + test_data_dir = test_data_dir.AppendASCII(filename); + + std::string test_js; + ASSERT_TRUE(base::ReadFileToString(test_data_dir, &test_js)); + ASSERT_TRUE(content::ExecuteScript(contents, test_js)); + + if (!catcher.GetNextResult()) + FAIL() << catcher.message(); + } +}; + +IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Basic) { + RunTestsInFile("basic_test.js", false); +} + +// TODO(raymes): This fails with component builds on linux because the plugin +// plugin crashes due to something related to how the plugin DLL is +// compiled/linked. crbug.com/386436. +#if defined(LINUX) && defined(COMPONENT_BUILD) +#define MAYBE_BasicPlugin DISABLED_BasicPlugin +#else +#define MAYBE_BasicPlugin BasicPlugin +#endif + +IN_PROC_BROWSER_TEST_F(PDFExtensionTest, MAYBE_BasicPlugin) { + RunTestsInFile("basic_plugin_test.js", true); +} + +IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Viewport) { + RunTestsInFile("viewport_test.js", false); +} diff --git a/chromium/chrome/browser/resources/pdf/pdf_scripting_api.js b/chromium/chrome/browser/resources/pdf/pdf_scripting_api.js new file mode 100644 index 00000000000..2b98c7c7973 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/pdf_scripting_api.js @@ -0,0 +1,181 @@ +// Copyright 2014 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. + +/** + * Create a new PDFScriptingAPI. This provides a scripting interface to + * the PDF viewer so that it can be customized by things like print preview. + * @param {Window} window the window of the page containing the pdf viewer. + * @param {string} extensionUrl the url of the PDF extension. + */ +function PDFScriptingAPI(window, extensionUrl) { + this.extensionUrl_ = extensionUrl; + this.messageQueue_ = []; + window.addEventListener('message', function(event) { + if (event.origin != this.extensionUrl_) { + console.error('Received message that was not from the extension: ' + + event); + return; + } + switch (event.data.type) { + case 'readyToReceive': + this.setDestinationWindow(event.source); + break; + case 'viewport': + if (this.viewportChangedCallback_) + this.viewportChangedCallback_(event.data.pageX, + event.data.pageY, + event.data.pageWidth, + event.data.viewportWidth, + event.data.viewportHeight); + break; + case 'documentLoaded': + if (this.loadCallback_) + this.loadCallback_(); + break; + case 'getAccessibilityJSONReply': + if (this.accessibilityCallback_) { + this.accessibilityCallback_(event.data.json); + this.accessibilityCallback_ = null; + } + break; + } + }.bind(this), false); +} + +PDFScriptingAPI.prototype = { + /** + * @private + * Send a message to the extension. If we try to send messages prior to the + * extension being ready to receive messages (i.e. before it has finished + * loading) we queue up the messages and flush them later. + * @param {Object} the message to send. + */ + sendMessage_: function(message) { + if (!this.pdfWindow_) { + this.messageQueue_.push(message); + return; + } + + this.pdfWindow_.postMessage(message, this.extensionUrl_); + }, + + /** + * Sets the destination window containing the PDF viewer. This will be called + * when a 'readyToReceive' message is received from the PDF viewer or it can + * be called during tests. It then flushes any pending messages to the window. + * @param {Window} pdfWindow the window containing the PDF viewer. + */ + setDestinationWindow: function(pdfWindow) { + this.pdfWindow_ = pdfWindow; + while (this.messageQueue_.length != 0) { + this.pdfWindow_.postMessage(this.messageQueue_.shift(), + this.extensionUrl_); + } + }, + + /** + * Sets the callback which will be run when the PDF viewport changes. + * @param {Function} callback the callback to be called. + */ + setViewportChangedCallback: function(callback) { + this.viewportChangedCallback_ = callback; + }, + + /** + * Sets the callback which will be run when the PDF document has finished + * loading. + * @param {Function} callback the callback to be called. + */ + setLoadCallback: function(callback) { + this.loadCallback_ = callback; + }, + + /** + * Resets the PDF viewer into print preview mode. + * @param {string} url the url of the PDF to load. + * @param {boolean} grayscale whether or not to display the PDF in grayscale. + * @param {Array.<number>} pageNumbers an array of the page numbers. + * @param {boolean} modifiable whether or not the document is modifiable. + */ + resetPrintPreviewMode: function(url, grayscale, pageNumbers, modifiable) { + this.sendMessage_({ + type: 'resetPrintPreviewMode', + url: url, + grayscale: grayscale, + pageNumbers: pageNumbers, + modifiable: modifiable + }); + }, + + /** + * Load a page into the document while in print preview mode. + * @param {string} url the url of the pdf page to load. + * @param {number} index the index of the page to load. + */ + loadPreviewPage: function(url, index) { + this.sendMessage_({ + type: 'loadPreviewPage', + url: url, + index: index + }); + }, + + /** + * Get accessibility JSON for the document. + * @param {Function} callback a callback to be called with the accessibility + * json that has been retrieved. + * @param {number} [page] the 0-indexed page number to get accessibility data + * for. If this is not provided, data about the entire document is + * returned. + * @return {boolean} true if the function is successful, false if there is an + * outstanding request for accessibility data that has not been answered. + */ + getAccessibilityJSON: function(callback, page) { + if (this.accessibilityCallback_) + return false; + this.accessibilityCallback_ = callback; + var message = { + type: 'getAccessibilityJSON', + }; + if (page || page == 0) + message.page = page; + this.sendMessage_(message); + return true; + }, + + /** + * Send a key event to the extension. + * @param {number} keyCode the key code to send to the extension. + */ + sendKeyEvent: function(keyCode) { + this.sendMessage_({ + type: 'sendKeyEvent', + keyCode: keyCode + }); + }, +}; + +/** + * Creates a PDF viewer with a scripting interface. This is basically 1) an + * iframe which is navigated to the PDF viewer extension and 2) a scripting + * interface which provides access to various features of the viewer for use + * by print preview and accessbility. + * @param {string} src the source URL of the PDF to load initially. + * @return {HTMLIFrameElement} the iframe element containing the PDF viewer. + */ +function PDFCreateOutOfProcessPlugin(src) { + var EXTENSION_URL = 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai'; + var iframe = window.document.createElement('iframe'); + iframe.setAttribute('src', EXTENSION_URL + '/index.html?' + src); + var client = new PDFScriptingAPI(window, EXTENSION_URL); + + // Add the functions to the iframe so that they can be called directly. + iframe.setViewportChangedCallback = + client.setViewportChangedCallback.bind(client); + iframe.setLoadCallback = client.setLoadCallback.bind(client); + iframe.resetPrintPreviewMode = client.resetPrintPreviewMode.bind(client); + iframe.loadPreviewPage = client.loadPreviewPage.bind(client); + iframe.sendKeyEvent = client.sendKeyEvent.bind(client); + return iframe; +} diff --git a/chromium/chrome/browser/resources/pdf/polymer_loader.js b/chromium/chrome/browser/resources/pdf/polymer_loader.js new file mode 100644 index 00000000000..d6199ef2dd4 --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/polymer_loader.js @@ -0,0 +1,12 @@ +// Copyright 2013 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. + +(function() { +// This is necessary because polymer currently doesn't handle the configuration +// where native Custom Elements are available but native Shadow DOM is not. +// TODO(bshe): remove this line when polymer supports the configuration. +document.registerElement = undefined; + +<include src="../../../../third_party/polymer/platform/platform.js"></include> +})(); diff --git a/chromium/chrome/browser/resources/pdf/viewport.js b/chromium/chrome/browser/resources/pdf/viewport.js new file mode 100644 index 00000000000..7da0143bacb --- /dev/null +++ b/chromium/chrome/browser/resources/pdf/viewport.js @@ -0,0 +1,470 @@ +// Copyright 2014 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. + +/** + * Returns the area of the intersection of two rectangles. + * @param {Object} rect1 the first rect + * @param {Object} rect2 the second rect + * @return {number} the area of the intersection of the rects + */ +function getIntersectionArea(rect1, rect2) { + var xOverlap = Math.max(0, + Math.min(rect1.x + rect1.width, rect2.x + rect2.width) - + Math.max(rect1.x, rect2.x)); + var yOverlap = Math.max(0, + Math.min(rect1.y + rect1.height, rect2.y + rect2.height) - + Math.max(rect1.y, rect2.y)); + return xOverlap * yOverlap; +} + +/** + * Create a new viewport. + * @param {Window} window the window + * @param {Object} sizer is the element which represents the size of the + * document in the viewport + * @param {Function} viewportChangedCallback is run when the viewport changes + * @param {number} scrollbarWidth the width of scrollbars on the page + */ +function Viewport(window, + sizer, + viewportChangedCallback, + scrollbarWidth) { + this.window_ = window; + this.sizer_ = sizer; + this.viewportChangedCallback_ = viewportChangedCallback; + this.zoom_ = 1; + this.documentDimensions_ = null; + this.pageDimensions_ = []; + this.scrollbarWidth_ = scrollbarWidth; + this.fittingType_ = Viewport.FittingType.NONE; + + window.addEventListener('scroll', this.updateViewport_.bind(this)); + window.addEventListener('resize', this.resize_.bind(this)); +} + +/** + * Enumeration of page fitting types. + * @enum {string} + */ +Viewport.FittingType = { + NONE: 'none', + FIT_TO_PAGE: 'fit-to-page', + FIT_TO_WIDTH: 'fit-to-width' +}; + +/** + * The increment to scroll a page by in pixels when up/down/left/right arrow + * keys are pressed. Usually we just let the browser handle scrolling on the + * window when these keys are pressed but in certain cases we need to simulate + * these events. + */ +Viewport.SCROLL_INCREMENT = 40; + +/** + * Predefined zoom factors to be used when zooming in/out. These are in + * ascending order. + */ +Viewport.ZOOM_FACTORS = [0.25, 0.333, 0.5, 0.666, 0.75, 0.9, 1, + 1.1, 1.25, 1.5, 1.75, 2, 2.5, 3, 4, 5]; + +/** + * The width of the page shadow around pages in pixels. + */ +Viewport.PAGE_SHADOW = {top: 3, bottom: 7, left: 5, right: 5}; + +Viewport.prototype = { + /** + * @private + * Returns true if the document needs scrollbars at the given zoom level. + * @param {number} zoom compute whether scrollbars are needed at this zoom + * @return {Object} with 'horizontal' and 'vertical' keys which map to bool + * values indicating if the horizontal and vertical scrollbars are needed + * respectively. + */ + documentNeedsScrollbars_: function(zoom) { + var documentWidth = this.documentDimensions_.width * zoom; + var documentHeight = this.documentDimensions_.height * zoom; + return { + horizontal: documentWidth > this.window_.innerWidth, + vertical: documentHeight > this.window_.innerHeight + }; + }, + + /** + * Returns true if the document needs scrollbars at the current zoom level. + * @return {Object} with 'x' and 'y' keys which map to bool values + * indicating if the horizontal and vertical scrollbars are needed + * respectively. + */ + documentHasScrollbars: function() { + return this.documentNeedsScrollbars_(this.zoom_); + }, + + /** + * @private + * Helper function called when the zoomed document size changes. + */ + contentSizeChanged_: function() { + if (this.documentDimensions_) { + this.sizer_.style.width = + this.documentDimensions_.width * this.zoom_ + 'px'; + this.sizer_.style.height = + this.documentDimensions_.height * this.zoom_ + 'px'; + } + }, + + /** + * @private + * Called when the viewport should be updated. + */ + updateViewport_: function() { + this.viewportChangedCallback_(); + }, + + /** + * @private + * Called when the viewport size changes. + */ + resize_: function() { + if (this.fittingType_ == Viewport.FittingType.FIT_TO_PAGE) + this.fitToPage(); + else if (this.fittingType_ == Viewport.FittingType.FIT_TO_WIDTH) + this.fitToWidth(); + else + this.updateViewport_(); + }, + + /** + * @type {Object} the scroll position of the viewport. + */ + get position() { + return { + x: this.window_.pageXOffset, + y: this.window_.pageYOffset + }; + }, + + /** + * Scroll the viewport to the specified position. + * @type {Object} position the position to scroll to. + */ + set position(position) { + this.window_.scrollTo(position.x, position.y); + }, + + /** + * @type {Object} the size of the viewport excluding scrollbars. + */ + get size() { + var needsScrollbars = this.documentNeedsScrollbars_(this.zoom_); + var scrollbarWidth = needsScrollbars.vertical ? this.scrollbarWidth_ : 0; + var scrollbarHeight = needsScrollbars.horizontal ? this.scrollbarWidth_ : 0; + return { + width: this.window_.innerWidth - scrollbarWidth, + height: this.window_.innerHeight - scrollbarHeight + }; + }, + + /** + * @type {number} the zoom level of the viewport. + */ + get zoom() { + return this.zoom_; + }, + + /** + * @private + * Sets the zoom of the viewport. + * @param {number} newZoom the zoom level to zoom to. + */ + setZoom_: function(newZoom) { + var oldZoom = this.zoom_; + this.zoom_ = newZoom; + // Record the scroll position (relative to the middle of the window). + var currentScrollPos = [ + (this.window_.pageXOffset + this.window_.innerWidth / 2) / oldZoom, + (this.window_.pageYOffset + this.window_.innerHeight / 2) / oldZoom + ]; + this.contentSizeChanged_(); + // Scroll to the scaled scroll position. + this.window_.scrollTo( + currentScrollPos[0] * newZoom - this.window_.innerWidth / 2, + currentScrollPos[1] * newZoom - this.window_.innerHeight / 2); + }, + + /** + * @type {number} the width of scrollbars in the viewport in pixels. + */ + get scrollbarWidth() { + return this.scrollbarWidth_; + }, + + /** + * @type {Viewport.FittingType} the fitting type the viewport is currently in. + */ + get fittingType() { + return this.fittingType_; + }, + + /** + * @private + * @param {integer} y the y-coordinate to get the page at. + * @return {integer} the index of a page overlapping the given y-coordinate. + */ + getPageAtY_: function(y) { + var min = 0; + var max = this.pageDimensions_.length - 1; + while (max >= min) { + var page = Math.floor(min + ((max - min) / 2)); + // There might be a gap between the pages, in which case use the bottom + // of the previous page as the top for finding the page. + var top = 0; + if (page > 0) { + top = this.pageDimensions_[page - 1].y + + this.pageDimensions_[page - 1].height; + } + var bottom = this.pageDimensions_[page].y + + this.pageDimensions_[page].height; + + if (top <= y && bottom > y) + return page; + else if (top > y) + max = page - 1; + else + min = page + 1; + } + return 0; + }, + + /** + * Returns the page with the most pixels in the current viewport. + * @return {int} the index of the most visible page. + */ + getMostVisiblePage: function() { + var firstVisiblePage = this.getPageAtY_(this.position.y / this.zoom_); + var mostVisiblePage = {number: 0, area: 0}; + var viewportRect = { + x: this.position.x / this.zoom_, + y: this.position.y / this.zoom_, + width: this.size.width / this.zoom_, + height: this.size.height / this.zoom_ + }; + for (var i = firstVisiblePage; i < this.pageDimensions_.length; i++) { + var area = getIntersectionArea(this.pageDimensions_[i], + viewportRect); + // If we hit a page with 0 area overlap, we must have gone past the + // pages visible in the viewport so we can break. + if (area == 0) + break; + if (area > mostVisiblePage.area) { + mostVisiblePage.area = area; + mostVisiblePage.number = i; + } + } + return mostVisiblePage.number; + }, + + /** + * @private + * Compute the zoom level for fit-to-page or fit-to-width. |pageDimensions| is + * the dimensions for a given page and if |widthOnly| is true, it indicates + * that fit-to-page zoom should be computed rather than fit-to-page. + * @param {Object} pageDimensions the dimensions of a given page + * @param {boolean} widthOnly a bool indicating whether fit-to-page or + * fit-to-width should be computed. + * @return {number} the zoom to use + */ + computeFittingZoom_: function(pageDimensions, widthOnly) { + // First compute the zoom without scrollbars. + var zoomWidth = this.window_.innerWidth / pageDimensions.width; + var zoom; + if (widthOnly) { + zoom = zoomWidth; + } else { + var zoomHeight = this.window_.innerHeight / pageDimensions.height; + zoom = Math.min(zoomWidth, zoomHeight); + } + // Check if there needs to be any scrollbars. + var needsScrollbars = this.documentNeedsScrollbars_(zoom); + + // If the document fits, just return the zoom. + if (!needsScrollbars.horizontal && !needsScrollbars.vertical) + return zoom; + + var zoomedDimensions = { + width: this.documentDimensions_.width * zoom, + height: this.documentDimensions_.height * zoom + }; + + // Check if adding a scrollbar will result in needing the other scrollbar. + var scrollbarWidth = this.scrollbarWidth_; + if (needsScrollbars.horizontal && + zoomedDimensions.height > this.window_.innerHeight - scrollbarWidth) { + needsScrollbars.vertical = true; + } + if (needsScrollbars.vertical && + zoomedDimensions.width > this.window_.innerWidth - scrollbarWidth) { + needsScrollbars.horizontal = true; + } + + // Compute available window space. + var windowWithScrollbars = { + width: this.window_.innerWidth, + height: this.window_.innerHeight + }; + if (needsScrollbars.horizontal) + windowWithScrollbars.height -= scrollbarWidth; + if (needsScrollbars.vertical) + windowWithScrollbars.width -= scrollbarWidth; + + // Recompute the zoom. + zoomWidth = windowWithScrollbars.width / pageDimensions.width; + if (widthOnly) { + zoom = zoomWidth; + } else { + var zoomHeight = windowWithScrollbars.height / pageDimensions.height; + zoom = Math.min(zoomWidth, zoomHeight); + } + return zoom; + }, + + /** + * Zoom the viewport so that the page-width consumes the entire viewport. + */ + fitToWidth: function() { + this.fittingType_ = Viewport.FittingType.FIT_TO_WIDTH; + if (!this.documentDimensions_) + return; + // Track the last y-position so we stay at the same position after zooming. + var oldY = this.window_.pageYOffset / this.zoom_; + // When computing fit-to-width, the maximum width of a page in the document + // is used, which is equal to the size of the document width. + this.setZoom_(this.computeFittingZoom_(this.documentDimensions_, true)); + var page = this.getMostVisiblePage(); + this.window_.scrollTo(0, oldY * this.zoom_); + this.updateViewport_(); + }, + + /** + * Zoom the viewport so that a page consumes the entire viewport. Also scrolls + * to the top of the most visible page. + */ + fitToPage: function() { + this.fittingType_ = Viewport.FittingType.FIT_TO_PAGE; + if (!this.documentDimensions_) + return; + var page = this.getMostVisiblePage(); + this.setZoom_(this.computeFittingZoom_(this.pageDimensions_[page], false)); + // Center the document in the page by scrolling by the amount of empty + // space to the left of the document. + var xOffset = + (this.documentDimensions_.width - this.pageDimensions_[page].width) * + this.zoom_ / 2; + this.window_.scrollTo(xOffset, + this.pageDimensions_[page].y * this.zoom_); + this.updateViewport_(); + }, + + /** + * Zoom out to the next predefined zoom level. + */ + zoomOut: function() { + this.fittingType_ = Viewport.FittingType.NONE; + var nextZoom = Viewport.ZOOM_FACTORS[0]; + for (var i = 0; i < Viewport.ZOOM_FACTORS.length; i++) { + if (Viewport.ZOOM_FACTORS[i] < this.zoom_) + nextZoom = Viewport.ZOOM_FACTORS[i]; + } + this.setZoom_(nextZoom); + this.updateViewport_(); + }, + + /** + * Zoom in to the next predefined zoom level. + */ + zoomIn: function() { + this.fittingType_ = Viewport.FittingType.NONE; + var nextZoom = Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1]; + for (var i = Viewport.ZOOM_FACTORS.length - 1; i >= 0; i--) { + if (Viewport.ZOOM_FACTORS[i] > this.zoom_) + nextZoom = Viewport.ZOOM_FACTORS[i]; + } + this.setZoom_(nextZoom); + this.updateViewport_(); + }, + + /** + * Go to the given page index. + * @param {number} page the index of the page to go to. + */ + goToPage: function(page) { + if (this.pageDimensions_.length == 0) + return; + if (page < 0) + page = 0; + if (page >= this.pageDimensions_.length) + page = this.pageDimensions_.length - 1; + var dimensions = this.pageDimensions_[page]; + this.window_.scrollTo(dimensions.x * this.zoom_, dimensions.y * this.zoom_); + }, + + /** + * Set the dimensions of the document. + * @param {Object} documentDimensions the dimensions of the document + */ + setDocumentDimensions: function(documentDimensions) { + var initialDimensions = !this.documentDimensions_; + this.documentDimensions_ = documentDimensions; + this.pageDimensions_ = this.documentDimensions_.pageDimensions; + if (initialDimensions) { + this.setZoom_(this.computeFittingZoom_(this.documentDimensions_, true)); + if (this.zoom_ > 1) + this.setZoom_(1); + this.window_.scrollTo(0, 0); + } + this.contentSizeChanged_(); + this.resize_(); + }, + + /** + * Get the coordinates of the page contents (excluding the page shadow) + * relative to the screen. + * @param {number} page the index of the page to get the rect for. + * @return {Object} a rect representing the page in screen coordinates. + */ + getPageScreenRect: function(page) { + if (page >= this.pageDimensions_.length) + page = this.pageDimensions_.length - 1; + + var pageDimensions = this.pageDimensions_[page]; + + // Compute the page dimensions minus the shadows. + var insetDimensions = { + x: pageDimensions.x + Viewport.PAGE_SHADOW.left, + y: pageDimensions.y + Viewport.PAGE_SHADOW.top, + width: pageDimensions.width - Viewport.PAGE_SHADOW.left - + Viewport.PAGE_SHADOW.right, + height: pageDimensions.height - Viewport.PAGE_SHADOW.top - + Viewport.PAGE_SHADOW.bottom + }; + + // Compute the x-coordinate of the page within the document. + // TODO(raymes): This should really be set when the PDF plugin passes the + // page coordinates, but it isn't yet. + var x = (this.documentDimensions_.width - pageDimensions.width) / 2 + + Viewport.PAGE_SHADOW.left; + // Compute the space on the left of the document if the document fits + // completely in the screen. + var spaceOnLeft = (this.size.width - + this.documentDimensions_.width * this.zoom_) / 2; + spaceOnLeft = Math.max(spaceOnLeft, 0); + + return { + x: x * this.zoom_ + spaceOnLeft - this.window_.pageXOffset, + y: insetDimensions.y * this.zoom_ - this.window_.pageYOffset, + width: insetDimensions.width * this.zoom_, + height: insetDimensions.height * this.zoom_ + }; + } +}; |