Bug 1299405 - [jsplugins][UI] Implement presentation mode. r=evelyn
MozReview-Commit-ID: DYkul38pL3v
--- a/browser/extensions/mortar/host/pdf/chrome/js/toolbar.js
+++ b/browser/extensions/mortar/host/pdf/chrome/js/toolbar.js
@@ -199,16 +199,20 @@ class Toolbar {
this._viewport.save();
break;
case 'pageRotateCw':
this._viewport.rotateClockwise();
break;
case 'pageRotateCcw':
this._viewport.rotateCounterClockwise();
break;
+ case 'presentationMode':
+ case 'secondaryPresentationMode':
+ this._viewport.fullscreen = true;
+ break;
case 'secondaryToolbarToggle':
this._secondaryToolbar.toggle();
break;
}
}
_pageNumberChanged() {
let newPage = parseFloat(this._elements.pageNumber.value);
--- a/browser/extensions/mortar/host/pdf/chrome/js/viewport.js
+++ b/browser/extensions/mortar/host/pdf/chrome/js/viewport.js
@@ -1,25 +1,24 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
class Viewport {
constructor() {
+ this._viewerContainer = document.getElementById('viewerContainer');
+ this._fullscreenWrapper = document.getElementById('fullscreenWrapper');
this._canvasContainer = document.getElementById('canvasContainer');
this._viewportController = document.getElementById('viewportController');
this._sizer = document.getElementById('sizer');
+ this._fullscreenStatus = 'none';
this._scrollbarWidth = this._getScrollbarWidth();
- this._hasVisibleScrollbars = {
- horizontal: false,
- vertical: false
- };
this._page = 0;
this._zoom = 1;
this._fitting = 'auto';
// Caches the next position set during a series of actions and will be set
// to scrollbar's position until calling "_refresh".
this._nextPosition = null;
@@ -92,16 +91,42 @@ class Viewport {
return;
}
newPage = Math.max(0, Math.min(pageCount - 1, newPage));
this._setPage(newPage);
this._refresh();
}
+ get fullscreen() {
+ return this._fullscreenStatus != 'none';
+ }
+
+ set fullscreen(enable) {
+ if (this._fullscreenStatus == 'changing' ||
+ this._fullscreenStatus == (enable ? 'fullscreen' : 'none')) {
+ return;
+ }
+
+ // The next step after sending "setFullscreen" will happen in the function
+ // "_handleFullscreenChange" triggered by "fullscreenChange" event. The
+ // "_fullscreenStatus" will also be reset there. Note that the viewport
+ // stops refreshing while in the "changing" status to avoid flickers.
+ //
+ // XXX: Since we rely on "fullscreenChange" event to reset the status, the
+ // viewport might freeze if, for some reason, the event isn't sent back
+ // and we get stuck in the "changing" status. Not sure if it's the case
+ // we need to worry about though.
+ this._fullscreenStatus = 'changing';
+ this._doAction({
+ type: 'setFullscreen',
+ fullscreen: enable
+ });
+ }
+
_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);
@@ -144,29 +169,35 @@ class Viewport {
this._setZoom(this._computeFittingZoom());
this._setPage(this._page);
if (typeof this.onDimensionChanged === 'function') {
this.onDimensionChanged();
}
this._refresh();
}
- _computeFittingZoom() {
+ _computeFittingZoom(pageIndex) {
let newZoom = this._zoom;
let fitting = this._fitting;
- if (fitting == 'none') {
+ if (pageIndex === undefined) {
+ pageIndex = this._page;
+ }
+
+ if (fitting == 'none' || pageIndex < 0 || pageIndex >= this.pageCount) {
return newZoom;
}
let FITTING_PADDING = 40;
let MAX_AUTO_ZOOM = 1.25;
- let page = this._pageDimensions[this._page];
- let viewportRect = this.getBoundingClientRect();
+ let page = this._pageDimensions[pageIndex];
+ let viewportRect = this.fullscreen ?
+ this._viewerContainer.getBoundingClientRect() :
+ this.getBoundingClientRect();
let pageWidthZoom = (viewportRect.width - FITTING_PADDING) / page.width;
let pageHeightZoom = viewportRect.height / page.height;
switch (fitting) {
case 'auto':
let isLandscape = (page.width > page.height);
// For pages in landscape mode, fit the page height to the viewer
@@ -284,29 +315,40 @@ class Viewport {
this.onZoomChanged(this._zoom);
}
}
_setPage(newPage) {
if (newPage < 0 || newPage >= this.pageCount) {
return;
}
+
+ if (this.fullscreen) {
+ let pageDimension = this._pageDimensions[newPage];
+ let newZoom = this._computeFittingZoom(newPage);
+
+ this._fullscreenWrapper.style.width =
+ (pageDimension.width * newZoom) + 'px';
+ this._fullscreenWrapper.style.height =
+ (pageDimension.height * newZoom) + 'px';
+
+ if (newZoom != this._zoom) {
+ this._setZoom(newZoom);
+ }
+ this._notifyRuntimeOfResized();
+ }
+
this._setPosition(
this._pageDimensions[newPage].x * this._zoom,
this._pageDimensions[newPage].y * this._zoom
);
}
_updateCanvasSize() {
let hasScrollbars = this._documentHasVisibleScrollbars(this._zoom);
- if (hasScrollbars.horizontal == this._hasVisibleScrollbars.horizontal &&
- hasScrollbars.vertical == this._hasVisibleScrollbars.vertical) {
- return;
- }
- this._hasVisibleScrollbars = hasScrollbars;
this._canvasContainer.style.bottom =
hasScrollbars.horizontal ? this._scrollbarWidth + 'px' : 0;
this._canvasContainer.style.right =
hasScrollbars.vertical ? this._scrollbarWidth + 'px' : 0;
this._notifyRuntimeOfResized();
}
_contentSizeChanged() {
@@ -344,16 +386,49 @@ class Viewport {
if (typeof listener === 'function') {
listener(evt);
} else if (typeof listener.handleEvent === 'function') {
listener.handleEvent(evt);
}
});
}
+ _handleFullscreenChange(fullscreen) {
+ // Set status to "changing" again in case it isn't triggered by setter.
+ this._fullscreenStatus = 'changing';
+ this._viewerContainer.classList.toggle('pdfPresentationMode', fullscreen);
+
+ // XXX: DOM elements' size changing hasn't taken place when fullscreenChange
+ // event is triggered in our setup, so "setTimeout" is necessary to get
+ // the exact size. The 100ms delay is set based on try-and-error. We
+ // might need to find a proper way to know when exactly the resizing is
+ // done.
+ setTimeout(() => {
+ let currentPage = this._page;
+
+ if (fullscreen) {
+ this._previousZoom = this._zoom;
+ this._previousFitting = this._fitting;
+ this._fitting = 'page-fit';
+ // No need to call "_setZoom" here because we will deal with zooming
+ // case in the "_setPage" below.
+ } else {
+ this._zoom = this._previousZoom;
+ this._fitting = this._previousFitting;
+ this._setZoom(this._computeFittingZoom());
+ }
+
+ this._fullscreenStatus = fullscreen ? 'fullscreen' : 'none';
+
+ // Reset position to the beginning of the current page.
+ this._setPage(currentPage);
+ this._refresh();
+ }, 100);
+ }
+
_getEventTarget(type) {
switch(type) {
case 'keydown':
case 'keyup':
case 'keypress':
return window;
}
return this._viewportController;
@@ -361,16 +436,20 @@ class Viewport {
_doAction(message) {
if (this._actionHandler) {
this._actionHandler(message);
}
}
_refresh() {
+ if (this._fullscreenStatus == 'changing') {
+ return;
+ }
+
if (this._nextPosition) {
this._viewportController.scrollTo(
this._nextPosition.x, this._nextPosition.y);
this._nextPosition = null;
}
this._runtimePosition = this.getScrollOffset();
this._doAction({
@@ -387,31 +466,38 @@ class Viewport {
this.onPageChanged(newPage);
}
}
}
handleEvent(evt) {
switch(evt.type) {
case 'resize':
- this._resize();
- this._notifyRuntimeOfResized();
- this._refresh();
+ this.invokeResize();
break;
case 'scroll':
this._nextPosition = null;
let position = this.getScrollOffset();
if (this._runtimePosition.x != position.x ||
this._runtimePosition.y != position.y) {
this._refresh();
}
break;
}
}
+ invokeResize() {
+ if (this._fullscreenStatus == 'changing') {
+ return;
+ }
+ this._resize();
+ this._notifyRuntimeOfResized();
+ this._refresh();
+ }
+
rotateClockwise() {
this._doAction({
type: 'rotateClockwise'
});
}
rotateCounterClockwise() {
this._doAction({
@@ -497,11 +583,14 @@ class Viewport {
notify(message) {
switch (message.type) {
case 'loadProgress':
this._updateProgress(message.progress);
break;
case 'documentDimensions':
this._setDocumentDimensions(message);
break;
+ case 'fullscreenChange':
+ this._handleFullscreenChange(message.fullscreen);
+ break;
}
}
}
--- a/browser/extensions/mortar/host/pdf/chrome/style/viewer.css
+++ b/browser/extensions/mortar/host/pdf/chrome/style/viewer.css
@@ -48,40 +48,16 @@ select {
.hidden {
display: none !important;
}
[hidden] {
display: none !important;
}
-#viewerContainer.pdfPresentationMode:fullscreen {
- top: 0px;
- border-top: 2px solid transparent;
- background-color: #000;
- width: 100%;
- height: 100%;
- overflow: hidden;
- cursor: none;
- -moz-user-select: none;
-}
-
-.pdfPresentationMode:fullscreen a:not(.internalLink) {
- display: none;
-}
-
-.pdfPresentationMode:fullscreen .textLayer > div {
- cursor: none;
-}
-
-.pdfPresentationMode.pdfPresentationModeControls > *,
-.pdfPresentationMode.pdfPresentationModeControls .textLayer > div {
- cursor: default;
-}
-
/* outer/inner center provides horizontal center */
.outerCenter {
pointer-events: none;
position: relative;
}
html[dir='ltr'] .outerCenter {
float: right;
right: 50%;
@@ -173,33 +149,55 @@ html[dir='ltr'] #sidebarContent {
html[dir='rtl'] #sidebarContent {
right: 0;
box-shadow: inset 1px 0 0 hsla(0,0%,0%,.25);
}
#viewerContainer {
overflow: hidden;
position: absolute;
- top: var(--toolbar-height);
+ top: calc(var(--toolbar-height) + 2px);
right: 0;
bottom: 0;
left: 0;
outline: none;
}
html[dir='ltr'] #viewerContainer {
box-shadow: inset 1px 0 0 hsla(0,0%,100%,.05);
}
html[dir='rtl'] #viewerContainer {
box-shadow: inset -1px 0 0 hsla(0,0%,100%,.05);
}
+#viewerContainer.pdfPresentationMode {
+ position: fixed;
+ top: 0;
+ left: 0;
+ background-color: #000;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ cursor: none;
+ -moz-user-select: none;
+ z-index: 99999;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.pdfPresentationMode #fullscreenWrapper {
+ position: relative;
+ overflow: hidden;
+}
+
#canvasContainer, #viewportController {
position: absolute;
left: 0;
- top: 2px;
+ top: 0;
right: 0;
bottom: 0;
outline: none;
overflow: hidden;
}
#canvasContainer canvas {
display: block;
@@ -207,16 +205,20 @@ html[dir='rtl'] #viewerContainer {
left: 0;
top: 0;
}
#viewportController {
overflow: auto;
}
+.pdfPresentationMode #viewportController {
+ overflow: hidden;
+}
+
#sizer {
margin: 0 auto;
}
.toolbar {
position: relative;
left: 0;
right: 0;
--- a/browser/extensions/mortar/host/pdf/chrome/viewer.html
+++ b/browser/extensions/mortar/host/pdf/chrome/viewer.html
@@ -209,19 +209,21 @@
<div class="glimmer">
</div>
</div>
</div>
</div>
</div>
<div id="viewerContainer">
- <div id="canvasContainer"></div>
- <div id="viewportController" tabindex="0">
- <div id="sizer"></div>
+ <div id="fullscreenWrapper">
+ <div id="canvasContainer"></div>
+ <div id="viewportController" tabindex="0">
+ <div id="sizer"></div>
+ </div>
</div>
</div>
<div id="errorWrapper" hidden='true'>
<div id="errorMessageLeft">
<span id="errorMessage"></span>
<button id="errorShowMore" data-l10n-id="error_more_info">
More Information