diff --git a/browser/extensions/pdfjs/content/web/viewer.js b/browser/extensions/pdfjs/content/web/viewer.js
index d72606f7472b..ffa4eb68e87b 100644
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -17,42 +17,24 @@
/* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar,
DownloadManager, getFileName, scrollIntoView, getPDFFileNameFromURL,
PDFHistory, Preferences, SidebarView, ViewHistory, PageView,
- ThumbnailView, URL, noContextMenuHandler, SecondaryToolbar,
+ PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar,
PasswordPrompt, PresentationMode, HandTool, Promise,
DocumentProperties, DocumentOutlineView, DocumentAttachmentsView,
- OverlayManager, PDFFindController, PDFFindBar */
+ OverlayManager, PDFFindController, PDFFindBar, getVisibleElements,
+ watchScroll, PDFViewer, PDFRenderingQueue, PresentationModeState,
+ RenderingStates, DEFAULT_SCALE, UNKNOWN_SCALE,
+ IGNORE_CURRENT_POSITION_ON_ZOOM: true */
'use strict';
var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
-var DEFAULT_SCALE = 'auto';
var DEFAULT_SCALE_DELTA = 1.1;
-var UNKNOWN_SCALE = 0;
-var DEFAULT_CACHE_SIZE = 10;
-var CSS_UNITS = 96.0 / 72.0;
-var SCROLLBAR_PADDING = 40;
-var VERTICAL_PADDING = 5;
-var MAX_AUTO_SCALE = 1.25;
var MIN_SCALE = 0.25;
var MAX_SCALE = 10.0;
var VIEW_HISTORY_MEMORY = 20;
var SCALE_SELECT_CONTAINER_PADDING = 8;
var SCALE_SELECT_PADDING = 22;
-var THUMBNAIL_SCROLL_MARGIN = -19;
-var CLEANUP_TIMEOUT = 30000;
-var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
-var RenderingStates = {
- INITIAL: 0,
- RUNNING: 1,
- PAUSED: 2,
- FINISHED: 3
-};
-var FindStates = {
- FIND_FOUND: 0,
- FIND_NOTFOUND: 1,
- FIND_WRAPPED: 2,
- FIND_PENDING: 3
-};
+var PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading';
PDFJS.imageResourcesPath = './images/';
PDFJS.workerSrc = '../build/pdf.worker.js';
@@ -62,6 +44,14 @@ PDFJS.imageResourcesPath = './images/';
var mozL10n = document.mozL10n || document.webL10n;
+var CSS_UNITS = 96.0 / 72.0;
+var DEFAULT_SCALE = 'auto';
+var UNKNOWN_SCALE = 0;
+var MAX_AUTO_SCALE = 1.25;
+var SCROLLBAR_PADDING = 40;
+var VERTICAL_PADDING = 5;
+var DEFAULT_CACHE_SIZE = 10;
+
// optimised CSS custom property getter/setter
var CustomStyle = (function CustomStyleClosure() {
@@ -184,6 +174,91 @@ function scrollIntoView(element, spot) {
parent.scrollTop = offsetY;
}
+/**
+ * Helper function to start monitoring the scroll event and converting them into
+ * PDF.js friendly one: with scroll debounce and scroll direction.
+ */
+function watchScroll(viewAreaElement, callback) {
+ var debounceScroll = function debounceScroll(evt) {
+ if (rAF) {
+ return;
+ }
+ // schedule an invocation of scroll for next animation frame.
+ rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
+ rAF = null;
+
+ var currentY = viewAreaElement.scrollTop;
+ var lastY = state.lastY;
+ if (currentY > lastY) {
+ state.down = true;
+ } else if (currentY < lastY) {
+ state.down = false;
+ }
+ state.lastY = currentY;
+ // else do nothing and use previous value
+ callback(state);
+ });
+ };
+
+ var state = {
+ down: true,
+ lastY: viewAreaElement.scrollTop,
+ _eventHandler: debounceScroll
+ };
+
+ var rAF = null;
+ viewAreaElement.addEventListener('scroll', debounceScroll, true);
+ return state;
+}
+
+/**
+ * Generic helper to find out what elements are visible within a scroll pane.
+ */
+function getVisibleElements(scrollEl, views, sortByVisibility) {
+ var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
+ var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
+
+ var visible = [], view;
+ var currentHeight, viewHeight, hiddenHeight, percentHeight;
+ var currentWidth, viewWidth;
+ for (var i = 0, ii = views.length; i < ii; ++i) {
+ view = views[i];
+ currentHeight = view.el.offsetTop + view.el.clientTop;
+ viewHeight = view.el.clientHeight;
+ if ((currentHeight + viewHeight) < top) {
+ continue;
+ }
+ if (currentHeight > bottom) {
+ break;
+ }
+ currentWidth = view.el.offsetLeft + view.el.clientLeft;
+ viewWidth = view.el.clientWidth;
+ if ((currentWidth + viewWidth) < left || currentWidth > right) {
+ continue;
+ }
+ hiddenHeight = Math.max(0, top - currentHeight) +
+ Math.max(0, currentHeight + viewHeight - bottom);
+ percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
+
+ visible.push({ id: view.id, x: currentWidth, y: currentHeight,
+ view: view, percent: percentHeight });
+ }
+
+ var first = visible[0];
+ var last = visible[visible.length - 1];
+
+ if (sortByVisibility) {
+ visible.sort(function(a, b) {
+ var pc = a.percent - b.percent;
+ if (Math.abs(pc) > 0.001) {
+ return -pc;
+ }
+ return a.id - b.id; // ensure stability
+ });
+ }
+ return {first: first, last: last, views: visible};
+}
+
/**
* Event handler to suppress context menu.
*/
@@ -311,14 +386,15 @@ var Cache = function cacheCache(size) {
-
var DEFAULT_PREFERENCES = {
showPreviousViewOnLoad: true,
defaultZoomValue: '',
sidebarViewOnLoad: 0,
enableHandToolOnLoad: false,
enableWebGL: false,
+ pdfBugEnabled: false,
disableRange: false,
+ disableStream: false,
disableAutoFetch: false,
disableFontFace: false,
disableTextLayer: false,
@@ -592,9 +668,6 @@ Preferences._readFromStorage = function (prefObj) {
-var cache = new Cache(DEFAULT_CACHE_SIZE);
-var currentPageNumber = 1;
-
/**
* View History - This is a utility for saving various view parameters for
@@ -836,6 +909,13 @@ var PDFFindBar = (function PDFFindBarClosure() {
+var FindStates = {
+ FIND_FOUND: 0,
+ FIND_NOTFOUND: 1,
+ FIND_WRAPPED: 2,
+ FIND_PENDING: 3
+};
+
/**
* Provides "search" or "find" functionality for the PDF.
* This object actually performs the search for a given string.
@@ -856,11 +936,12 @@ var PDFFindController = (function PDFFindControllerClosure() {
pageIdx: null,
matchIdx: null
};
+ this.pagesToSearch = null;
this.resumePageIdx = null;
this.state = null;
this.dirtyMatch = false;
this.findTimeout = null;
- this.pdfPageSource = options.pdfPageSource || null;
+ this.pdfViewer = options.pdfViewer || null;
this.integratedFind = options.integratedFind || false;
this.charactersToNormalize = {
'\u2018': '\'', // Left single quotation mark
@@ -956,7 +1037,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.pageContents = [];
var extractTextPromisesResolves = [];
- var numPages = this.pdfPageSource.pdfDocument.numPages;
+ var numPages = this.pdfViewer.pagesCount;
for (var i = 0; i < numPages; i++) {
this.extractTextPromises.push(new Promise(function (resolve) {
extractTextPromisesResolves.push(resolve);
@@ -965,7 +1046,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
var self = this;
function extractPageText(pageIndex) {
- self.pdfPageSource.pages[pageIndex].getTextContent().then(
+ self.pdfViewer.getPageTextContent(pageIndex).then(
function textContentResolved(textContent) {
var textItems = textContent.items;
var str = [];
@@ -978,7 +1059,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
self.pageContents.push(str.join(''));
extractTextPromisesResolves[pageIndex](pageIndex);
- if ((pageIndex + 1) < self.pdfPageSource.pages.length) {
+ if ((pageIndex + 1) < self.pdfViewer.pagesCount) {
extractPageText(pageIndex + 1);
}
}
@@ -1008,13 +1089,13 @@ var PDFFindController = (function PDFFindControllerClosure() {
},
updatePage: function PDFFindController_updatePage(index) {
- var page = this.pdfPageSource.pages[index];
+ var page = this.pdfViewer.getPageView(index);
if (this.selected.pageIdx === index) {
// If the page is selected, scroll the page into view, which triggers
// rendering the page, which adds the textLayer. Once the textLayer is
// build, it will scroll onto the selected match.
- page.scrollIntoView();
+ this.pdfViewer.scrollPageIntoView(index + 1);
}
if (page.textLayer) {
@@ -1024,8 +1105,8 @@ var PDFFindController = (function PDFFindControllerClosure() {
nextMatch: function PDFFindController_nextMatch() {
var previous = this.state.findPrevious;
- var currentPageIndex = this.pdfPageSource.page - 1;
- var numPages = this.pdfPageSource.pages.length;
+ var currentPageIndex = this.pdfViewer.currentPageNumber - 1;
+ var numPages = this.pdfViewer.pagesCount;
this.active = true;
@@ -1067,6 +1148,8 @@ var PDFFindController = (function PDFFindControllerClosure() {
}
var offset = this.offset;
+ // Keep track of how many pages we should maximally iterate through.
+ this.pagesToSearch = numPages;
// If there's already a matchIdx that means we are iterating through a
// page's matches.
if (offset.matchIdx !== null) {
@@ -1105,8 +1188,8 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.advanceOffsetPage(previous);
if (offset.wrapped) {
offset.matchIdx = null;
- if (!this.hadMatch) {
- // No point in wrapping, there were no matches.
+ if (this.pagesToSearch < 0) {
+ // No point in wrapping again, there were no matches.
this.updateMatch(false);
// while matches were not found, searching for a page
// with matches should nevertheless halt.
@@ -1139,11 +1222,12 @@ var PDFFindController = (function PDFFindControllerClosure() {
var numPages = this.extractTextPromises.length;
offset.pageIdx = (previous ? offset.pageIdx - 1 : offset.pageIdx + 1);
offset.matchIdx = null;
+
+ this.pagesToSearch--;
if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
offset.pageIdx = (previous ? numPages - 1 : 0);
offset.wrapped = true;
- return;
}
},
@@ -1165,7 +1249,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.updateUIState(state, this.state.findPrevious);
if (this.selected.pageIdx !== -1) {
- this.updatePage(this.selected.pageIdx, true);
+ this.updatePage(this.selected.pageIdx);
}
},
@@ -1191,12 +1275,11 @@ var PDFHistory = {
initialized: false,
initialDestination: null,
- initialize: function pdfHistoryInitialize(fingerprint) {
- if (PDFJS.disableHistory || PDFView.isViewerEmbedded) {
- // The browsing history is only enabled when the viewer is standalone,
- // i.e. not when it is embedded in a web page.
- return;
- }
+ /**
+ * @param {string} fingerprint
+ * @param {IPDFLinkService} linkService
+ */
+ initialize: function pdfHistoryInitialize(fingerprint, linkService) {
this.initialized = true;
this.reInitialized = false;
this.allowHashChange = true;
@@ -1211,6 +1294,7 @@ var PDFHistory = {
this.nextHashParam = '';
this.fingerprint = fingerprint;
+ this.linkService = linkService;
this.currentUid = this.uid = 0;
this.current = {};
@@ -1221,7 +1305,7 @@ var PDFHistory = {
if (state.target.dest) {
this.initialDestination = state.target.dest;
} else {
- PDFView.initialBookmark = state.target.hash;
+ linkService.setHash(state.target.hash);
}
this.currentUid = state.uid;
this.uid = state.uid + 1;
@@ -1359,7 +1443,7 @@ var PDFHistory = {
params.hash = (this.current.hash && this.current.dest &&
this.current.dest === params.dest) ?
this.current.hash :
- PDFView.getDestinationHash(params.dest).split('#')[1];
+ this.linkService.getDestinationHash(params.dest).split('#')[1];
}
if (params.page) {
params.page |= 0;
@@ -1368,7 +1452,7 @@ var PDFHistory = {
var target = window.history.state.target;
if (!target) {
// Invoked when the user specifies an initial bookmark,
- // thus setting PDFView.initialBookmark, when the document is loaded.
+ // thus setting initialBookmark, when the document is loaded.
this._pushToHistory(params, false);
this.previousHash = window.location.hash.substring(1);
}
@@ -1493,9 +1577,9 @@ var PDFHistory = {
this.historyUnlocked = false;
if (state.target.dest) {
- PDFView.navigateTo(state.target.dest);
+ this.linkService.navigateTo(state.target.dest);
} else {
- PDFView.setHash(state.target.hash);
+ this.linkService.setHash(state.target.hash);
}
this.currentUid = state.uid;
if (state.uid > this.uid) {
@@ -1603,7 +1687,7 @@ var SecondaryToolbar = {
},
downloadClick: function secondaryToolbarDownloadClick(evt) {
- PDFView.download();
+ PDFViewerApplication.download();
this.close();
},
@@ -1612,23 +1696,23 @@ var SecondaryToolbar = {
},
firstPageClick: function secondaryToolbarFirstPageClick(evt) {
- PDFView.page = 1;
+ PDFViewerApplication.page = 1;
this.close();
},
lastPageClick: function secondaryToolbarLastPageClick(evt) {
- if (PDFView.pdfDocument) {
- PDFView.page = PDFView.pdfDocument.numPages;
+ if (PDFViewerApplication.pdfDocument) {
+ PDFViewerApplication.page = PDFViewerApplication.pagesCount;
}
this.close();
},
pageRotateCwClick: function secondaryToolbarPageRotateCwClick(evt) {
- PDFView.rotatePages(90);
+ PDFViewerApplication.rotatePages(90);
},
pageRotateCcwClick: function secondaryToolbarPageRotateCcwClick(evt) {
- PDFView.rotatePages(-90);
+ PDFViewerApplication.rotatePages(-90);
},
documentPropertiesClick: function secondaryToolbarDocumentPropsClick(evt) {
@@ -1727,7 +1811,7 @@ var PresentationMode = {
},
/**
- * Initialize a timeout that is used to reset PDFView.currentPosition when the
+ * Initialize a timeout that is used to specify switchInProgress when the
* browser transitions to fullscreen mode. Since resize events are triggered
* multiple times during the switch to fullscreen mode, this is necessary in
* order to prevent the page from being scrolled partially, or completely,
@@ -1740,9 +1824,8 @@ var PresentationMode = {
}
this.switchInProgress = setTimeout(function switchInProgressTimeout() {
delete this.switchInProgress;
+ this._notifyStateChange();
}.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS);
-
- PDFView.currentPosition = null;
},
_resetSwitchInProgress: function presentationMode_resetSwitchInProgress() {
@@ -1753,11 +1836,12 @@ var PresentationMode = {
},
request: function presentationModeRequest() {
- if (!PDFView.supportsFullscreen || this.isFullscreen ||
+ if (!PDFViewerApplication.supportsFullscreen || this.isFullscreen ||
!this.viewer.hasChildNodes()) {
return false;
}
this._setSwitchInProgress();
+ this._notifyStateChange();
if (this.container.requestFullscreen) {
this.container.requestFullscreen();
@@ -1772,23 +1856,33 @@ var PresentationMode = {
}
this.args = {
- page: PDFView.page,
- previousScale: PDFView.currentScaleValue
+ page: PDFViewerApplication.page,
+ previousScale: PDFViewerApplication.currentScaleValue
};
return true;
},
+ _notifyStateChange: function presentationModeNotifyStateChange() {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('presentationmodechanged', true, true, {
+ active: PresentationMode.active,
+ switchInProgress: !!PresentationMode.switchInProgress
+ });
+ window.dispatchEvent(event);
+ },
+
enter: function presentationModeEnter() {
this.active = true;
this._resetSwitchInProgress();
+ this._notifyStateChange();
// Ensure that the correct page is scrolled into view when entering
// Presentation Mode, by waiting until fullscreen mode in enabled.
// Note: This is only necessary in non-Mozilla browsers.
setTimeout(function enterPresentationModeTimeout() {
- PDFView.page = this.args.page;
- PDFView.setScale('page-fit', true);
+ PDFViewerApplication.page = this.args.page;
+ PDFViewerApplication.setScale('page-fit', true);
}.bind(this), 0);
window.addEventListener('mousemove', this.mouseMove, false);
@@ -1802,15 +1896,17 @@ var PresentationMode = {
},
exit: function presentationModeExit() {
- var page = PDFView.page;
+ var page = PDFViewerApplication.page;
// Ensure that the correct page is scrolled into view when exiting
// Presentation Mode, by waiting until fullscreen mode is disabled.
// Note: This is only necessary in non-Mozilla browsers.
setTimeout(function exitPresentationModeTimeout() {
this.active = false;
- PDFView.setScale(this.args.previousScale);
- PDFView.page = page;
+ this._notifyStateChange();
+
+ PDFViewerApplication.setScale(this.args.previousScale, true);
+ PDFViewerApplication.page = page;
this.args = null;
}.bind(this), 0);
@@ -1819,7 +1915,7 @@ var PresentationMode = {
window.removeEventListener('contextmenu', this.contextMenu, false);
this.hideControls();
- PDFView.clearMouseScrollState();
+ PDFViewerApplication.clearMouseScrollState();
HandTool.exitPresentationMode();
this.container.removeAttribute('contextmenu');
this.contextMenuOpen = false;
@@ -1870,7 +1966,7 @@ var PresentationMode = {
if (!isInternalLink) {
// Unless an internal link was clicked, advance one page.
evt.preventDefault();
- PDFView.page += (evt.shiftKey ? -1 : 1);
+ PDFViewerApplication.page += (evt.shiftKey ? -1 : 1);
}
}
},
@@ -2383,6 +2479,8 @@ var DocumentProperties = {
producerField: null,
versionField: null,
pageCountField: null,
+ url: null,
+ pdfDocument: null,
initialize: function documentPropertiesInitialize(options) {
this.overlayName = options.overlayName;
@@ -2420,7 +2518,7 @@ var DocumentProperties = {
return;
}
// Get the file size (if it hasn't already been set).
- PDFView.pdfDocument.getDownloadInfo().then(function(data) {
+ this.pdfDocument.getDownloadInfo().then(function(data) {
if (data.length === this.rawFileSize) {
return;
}
@@ -2429,10 +2527,10 @@ var DocumentProperties = {
}.bind(this));
// Get the document properties.
- PDFView.pdfDocument.getMetadata().then(function(data) {
+ this.pdfDocument.getMetadata().then(function(data) {
var fields = [
{ field: this.fileNameField,
- content: getPDFFileNameFromURL(PDFView.url) },
+ content: getPDFFileNameFromURL(this.url) },
{ field: this.fileSizeField, content: this.parseFileSize() },
{ field: this.titleField, content: data.info.Title },
{ field: this.authorField, content: data.info.Author },
@@ -2445,7 +2543,7 @@ var DocumentProperties = {
{ field: this.creatorField, content: data.info.Creator },
{ field: this.producerField, content: data.info.Producer },
{ field: this.versionField, content: data.info.PDFFormatVersion },
- { field: this.pageCountField, content: PDFView.pdfDocument.numPages }
+ { field: this.pageCountField, content: this.pdfDocument.numPages }
];
// Show the properties in the dialog.
@@ -2546,1543 +2644,194 @@ var DocumentProperties = {
};
-var PDFView = {
- pages: [],
- thumbnails: [],
- currentScale: UNKNOWN_SCALE,
- currentScaleValue: null,
- initialBookmark: document.location.hash.substring(1),
- container: null,
- thumbnailContainer: null,
- initialized: false,
- fellback: false,
- pdfDocument: null,
- sidebarOpen: false,
- printing: false,
- pageViewScroll: null,
- thumbnailViewScroll: null,
- pageRotation: 0,
- mouseScrollTimeStamp: 0,
- mouseScrollDelta: 0,
- lastScroll: 0,
- previousPageNumber: 1,
- isViewerEmbedded: (window.parent !== window),
- idleTimeout: null,
- currentPosition: null,
- url: '',
-
- // called once when the document is loaded
- initialize: function pdfViewInitialize() {
- var self = this;
- var container = this.container = document.getElementById('viewerContainer');
- this.pageViewScroll = {};
- this.watchScroll(container, this.pageViewScroll, updateViewarea);
-
- var thumbnailContainer = this.thumbnailContainer =
- document.getElementById('thumbnailView');
- this.thumbnailViewScroll = {};
- this.watchScroll(thumbnailContainer, this.thumbnailViewScroll,
- this.renderHighestPriority.bind(this));
-
- Preferences.initialize();
-
- this.findController = new PDFFindController({
- pdfPageSource: this,
- integratedFind: this.supportsIntegratedFind
- });
-
- this.findBar = new PDFFindBar({
- bar: document.getElementById('findbar'),
- toggleButton: document.getElementById('viewFind'),
- findField: document.getElementById('findInput'),
- highlightAllCheckbox: document.getElementById('findHighlightAll'),
- caseSensitiveCheckbox: document.getElementById('findMatchCase'),
- findMsg: document.getElementById('findMsg'),
- findStatusIcon: document.getElementById('findStatusIcon'),
- findPreviousButton: document.getElementById('findPrevious'),
- findNextButton: document.getElementById('findNext'),
- findController: this.findController
- });
-
- this.findController.setFindBar(this.findBar);
-
- HandTool.initialize({
- container: container,
- toggleHandTool: document.getElementById('toggleHandTool')
- });
-
- SecondaryToolbar.initialize({
- toolbar: document.getElementById('secondaryToolbar'),
- presentationMode: PresentationMode,
- toggleButton: document.getElementById('secondaryToolbarToggle'),
- presentationModeButton:
- document.getElementById('secondaryPresentationMode'),
- openFile: document.getElementById('secondaryOpenFile'),
- print: document.getElementById('secondaryPrint'),
- download: document.getElementById('secondaryDownload'),
- viewBookmark: document.getElementById('secondaryViewBookmark'),
- firstPage: document.getElementById('firstPage'),
- lastPage: document.getElementById('lastPage'),
- pageRotateCw: document.getElementById('pageRotateCw'),
- pageRotateCcw: document.getElementById('pageRotateCcw'),
- documentProperties: DocumentProperties,
- documentPropertiesButton: document.getElementById('documentProperties')
- });
-
- PresentationMode.initialize({
- container: container,
- secondaryToolbar: SecondaryToolbar,
- firstPage: document.getElementById('contextFirstPage'),
- lastPage: document.getElementById('contextLastPage'),
- pageRotateCw: document.getElementById('contextPageRotateCw'),
- pageRotateCcw: document.getElementById('contextPageRotateCcw')
- });
-
- PasswordPrompt.initialize({
- overlayName: 'passwordOverlay',
- passwordField: document.getElementById('password'),
- passwordText: document.getElementById('passwordText'),
- passwordSubmit: document.getElementById('passwordSubmit'),
- passwordCancel: document.getElementById('passwordCancel')
- });
-
- DocumentProperties.initialize({
- overlayName: 'documentPropertiesOverlay',
- closeButton: document.getElementById('documentPropertiesClose'),
- fileNameField: document.getElementById('fileNameField'),
- fileSizeField: document.getElementById('fileSizeField'),
- titleField: document.getElementById('titleField'),
- authorField: document.getElementById('authorField'),
- subjectField: document.getElementById('subjectField'),
- keywordsField: document.getElementById('keywordsField'),
- creationDateField: document.getElementById('creationDateField'),
- modificationDateField: document.getElementById('modificationDateField'),
- creatorField: document.getElementById('creatorField'),
- producerField: document.getElementById('producerField'),
- versionField: document.getElementById('versionField'),
- pageCountField: document.getElementById('pageCountField')
- });
-
- container.addEventListener('scroll', function() {
- self.lastScroll = Date.now();
- }, false);
-
- var initializedPromise = Promise.all([
- Preferences.get('enableWebGL').then(function resolved(value) {
- PDFJS.disableWebGL = !value;
- }),
- Preferences.get('sidebarViewOnLoad').then(function resolved(value) {
- self.preferenceSidebarViewOnLoad = value;
- }),
- Preferences.get('disableTextLayer').then(function resolved(value) {
- if (PDFJS.disableTextLayer === true) {
- return;
- }
- PDFJS.disableTextLayer = value;
- }),
- Preferences.get('disableRange').then(function resolved(value) {
- if (PDFJS.disableRange === true) {
- return;
- }
- PDFJS.disableRange = value;
- }),
- Preferences.get('disableAutoFetch').then(function resolved(value) {
- PDFJS.disableAutoFetch = value;
- }),
- Preferences.get('disableFontFace').then(function resolved(value) {
- if (PDFJS.disableFontFace === true) {
- return;
- }
- PDFJS.disableFontFace = value;
- }),
- Preferences.get('useOnlyCssZoom').then(function resolved(value) {
- PDFJS.useOnlyCssZoom = value;
- })
- // TODO move more preferences and other async stuff here
- ]).catch(function (reason) { });
-
- return initializedPromise.then(function () {
- PDFView.initialized = true;
- });
- },
-
- getPage: function pdfViewGetPage(n) {
- return this.pdfDocument.getPage(n);
- },
-
- // Helper function to keep track whether a div was scrolled up or down and
- // then call a callback.
- watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) {
- state.down = true;
- state.lastY = viewAreaElement.scrollTop;
- state.rAF = null;
- viewAreaElement.addEventListener('scroll', function debounceScroll(evt) {
- if (state.rAF) {
- return;
- }
- // schedule an invocation of webViewerScrolled for next animation frame.
- state.rAF = window.requestAnimationFrame(function webViewerScrolled() {
- state.rAF = null;
- if (!PDFView.pdfDocument) {
- return;
- }
- var currentY = viewAreaElement.scrollTop;
- var lastY = state.lastY;
- if (currentY > lastY) {
- state.down = true;
- } else if (currentY < lastY) {
- state.down = false;
- }
- // else do nothing and use previous value
- state.lastY = currentY;
- callback();
- });
- }, true);
- },
-
- _setScaleUpdatePages: function pdfView_setScaleUpdatePages(
- newScale, newValue, resetAutoSettings, noScroll) {
- this.currentScaleValue = newValue;
- if (newScale === this.currentScale) {
- return;
- }
- for (var i = 0, ii = this.pages.length; i < ii; i++) {
- this.pages[i].update(newScale);
- }
- this.currentScale = newScale;
-
- if (!noScroll) {
- var page = this.page, dest;
- if (this.currentPosition && !IGNORE_CURRENT_POSITION_ON_ZOOM) {
- page = this.currentPosition.page;
- dest = [null, { name: 'XYZ' }, this.currentPosition.left,
- this.currentPosition.top, null];
- }
- this.pages[page - 1].scrollIntoView(dest);
- }
- var event = document.createEvent('UIEvents');
- event.initUIEvent('scalechange', false, false, window, 0);
- event.scale = newScale;
- event.resetAutoSettings = resetAutoSettings;
- window.dispatchEvent(event);
- },
-
- setScale: function pdfViewSetScale(value, resetAutoSettings, noScroll) {
- if (value === 'custom') {
- return;
- }
- var scale = parseFloat(value);
-
- if (scale > 0) {
- this._setScaleUpdatePages(scale, value, true, noScroll);
- } else {
- var currentPage = this.pages[this.page - 1];
- if (!currentPage) {
- return;
- }
- var hPadding = PresentationMode.active ? 0 : SCROLLBAR_PADDING;
- var vPadding = PresentationMode.active ? 0 : VERTICAL_PADDING;
- var pageWidthScale = (this.container.clientWidth - hPadding) /
- currentPage.width * currentPage.scale;
- var pageHeightScale = (this.container.clientHeight - vPadding) /
- currentPage.height * currentPage.scale;
- switch (value) {
- case 'page-actual':
- scale = 1;
- break;
- case 'page-width':
- scale = pageWidthScale;
- break;
- case 'page-height':
- scale = pageHeightScale;
- break;
- case 'page-fit':
- scale = Math.min(pageWidthScale, pageHeightScale);
- break;
- case 'auto':
- var isLandscape = (currentPage.width > currentPage.height);
- var horizontalScale = isLandscape ? pageHeightScale : pageWidthScale;
- scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
- break;
- default:
- console.error('pdfViewSetScale: \'' + value +
- '\' is an unknown zoom value.');
- return;
- }
- this._setScaleUpdatePages(scale, value, resetAutoSettings, noScroll);
-
- selectScaleOption(value);
- }
- },
-
- zoomIn: function pdfViewZoomIn(ticks) {
- var newScale = this.currentScale;
- do {
- newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
- newScale = Math.ceil(newScale * 10) / 10;
- newScale = Math.min(MAX_SCALE, newScale);
- } while (--ticks && newScale < MAX_SCALE);
- this.setScale(newScale, true);
- },
-
- zoomOut: function pdfViewZoomOut(ticks) {
- var newScale = this.currentScale;
- do {
- newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
- newScale = Math.floor(newScale * 10) / 10;
- newScale = Math.max(MIN_SCALE, newScale);
- } while (--ticks && newScale > MIN_SCALE);
- this.setScale(newScale, true);
- },
-
- set page(val) {
- var pages = this.pages;
- var event = document.createEvent('UIEvents');
- event.initUIEvent('pagechange', false, false, window, 0);
-
- if (!(0 < val && val <= pages.length)) {
- this.previousPageNumber = val;
- event.pageNumber = this.page;
- window.dispatchEvent(event);
- return;
- }
-
- pages[val - 1].updateStats();
- this.previousPageNumber = currentPageNumber;
- currentPageNumber = val;
- event.pageNumber = val;
- window.dispatchEvent(event);
-
- // checking if the this.page was called from the updateViewarea function:
- // avoiding the creation of two "set page" method (internal and public)
- if (updateViewarea.inProgress) {
- return;
- }
- // Avoid scrolling the first page during loading
- if (this.loading && val === 1) {
- return;
- }
- pages[val - 1].scrollIntoView();
- },
-
- get page() {
- return currentPageNumber;
- },
-
- get supportsPrinting() {
- var canvas = document.createElement('canvas');
- var value = 'mozPrintCallback' in canvas;
- // shadow
- Object.defineProperty(this, 'supportsPrinting', { value: value,
- enumerable: true,
- configurable: true,
- writable: false });
- return value;
- },
-
- get supportsFullscreen() {
- var doc = document.documentElement;
- var support = doc.requestFullscreen || doc.mozRequestFullScreen ||
- doc.webkitRequestFullScreen || doc.msRequestFullscreen;
-
- if (document.fullscreenEnabled === false ||
- document.mozFullScreenEnabled === false ||
- document.webkitFullscreenEnabled === false ||
- document.msFullscreenEnabled === false) {
- support = false;
- }
-
- Object.defineProperty(this, 'supportsFullscreen', { value: support,
- enumerable: true,
- configurable: true,
- writable: false });
- return support;
- },
-
- get supportsIntegratedFind() {
- var support = false;
- support = FirefoxCom.requestSync('supportsIntegratedFind');
- Object.defineProperty(this, 'supportsIntegratedFind', { value: support,
- enumerable: true,
- configurable: true,
- writable: false });
- return support;
- },
-
- get supportsDocumentFonts() {
- var support = true;
- support = FirefoxCom.requestSync('supportsDocumentFonts');
- Object.defineProperty(this, 'supportsDocumentFonts', { value: support,
- enumerable: true,
- configurable: true,
- writable: false });
- return support;
- },
-
- get supportsDocumentColors() {
- var support = true;
- support = FirefoxCom.requestSync('supportsDocumentColors');
- Object.defineProperty(this, 'supportsDocumentColors', { value: support,
- enumerable: true,
- configurable: true,
- writable: false });
- return support;
- },
-
- get loadingBar() {
- var bar = new ProgressBar('#loadingBar', {});
- Object.defineProperty(this, 'loadingBar', { value: bar,
- enumerable: true,
- configurable: true,
- writable: false });
- return bar;
- },
-
- get isHorizontalScrollbarEnabled() {
- return (PresentationMode.active ? false :
- (this.container.scrollWidth > this.container.clientWidth));
- },
-
- initPassiveLoading: function pdfViewInitPassiveLoading() {
- var pdfDataRangeTransport = {
- rangeListeners: [],
- progressListeners: [],
-
- addRangeListener: function PdfDataRangeTransport_addRangeListener(
- listener) {
- this.rangeListeners.push(listener);
- },
-
- addProgressListener: function PdfDataRangeTransport_addProgressListener(
- listener) {
- this.progressListeners.push(listener);
- },
-
- onDataRange: function PdfDataRangeTransport_onDataRange(begin, chunk) {
- var listeners = this.rangeListeners;
- for (var i = 0, n = listeners.length; i < n; ++i) {
- listeners[i](begin, chunk);
- }
- },
-
- onDataProgress: function PdfDataRangeTransport_onDataProgress(loaded) {
- var listeners = this.progressListeners;
- for (var i = 0, n = listeners.length; i < n; ++i) {
- listeners[i](loaded);
- }
- },
-
- requestDataRange: function PdfDataRangeTransport_requestDataRange(
- begin, end) {
- FirefoxCom.request('requestDataRange', { begin: begin, end: end });
- }
- };
-
- window.addEventListener('message', function windowMessage(e) {
- if (e.source !== null) {
- // The message MUST originate from Chrome code.
- console.warn('Rejected untrusted message from ' + e.origin);
- return;
- }
- var args = e.data;
-
- if (typeof args !== 'object' || !('pdfjsLoadAction' in args)) {
- return;
- }
- switch (args.pdfjsLoadAction) {
- case 'supportsRangedLoading':
- PDFView.open(args.pdfUrl, 0, undefined, pdfDataRangeTransport, {
- length: args.length,
- initialData: args.data
- });
- break;
- case 'range':
- pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
- break;
- case 'rangeProgress':
- pdfDataRangeTransport.onDataProgress(args.loaded);
- break;
- case 'progress':
- PDFView.progress(args.loaded / args.total);
- break;
- case 'complete':
- if (!args.data) {
- PDFView.error(mozL10n.get('loading_error', null,
- 'An error occurred while loading the PDF.'), e);
- break;
- }
- PDFView.open(args.data, 0);
- break;
- }
- });
- FirefoxCom.requestSync('initPassiveLoading', null);
- },
-
- setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
- this.url = url;
- try {
- this.setTitle(decodeURIComponent(getFileName(url)) || url);
- } catch (e) {
- // decodeURIComponent may throw URIError,
- // fall back to using the unprocessed url in that case
- this.setTitle(url);
- }
- },
-
- setTitle: function pdfViewSetTitle(title) {
- document.title = title;
- },
-
- close: function pdfViewClose() {
- var errorWrapper = document.getElementById('errorWrapper');
- errorWrapper.setAttribute('hidden', 'true');
-
- if (!this.pdfDocument) {
- return;
- }
-
- this.pdfDocument.destroy();
- this.pdfDocument = null;
-
- var thumbsView = document.getElementById('thumbnailView');
- while (thumbsView.hasChildNodes()) {
- thumbsView.removeChild(thumbsView.lastChild);
- }
-
- var container = document.getElementById('viewer');
- while (container.hasChildNodes()) {
- container.removeChild(container.lastChild);
- }
-
- if (typeof PDFBug !== 'undefined') {
- PDFBug.cleanup();
- }
- },
-
- // TODO(mack): This function signature should really be pdfViewOpen(url, args)
- open: function pdfViewOpen(file, scale, password,
- pdfDataRangeTransport, args) {
- if (this.pdfDocument) {
- // Reload the preferences if a document was previously opened.
- Preferences.reload();
- }
- this.close();
-
- var parameters = {password: password};
- if (typeof file === 'string') { // URL
- this.setTitleUsingUrl(file);
- parameters.url = file;
- } else if (file && 'byteLength' in file) { // ArrayBuffer
- parameters.data = file;
- } else if (file.url && file.originalUrl) {
- this.setTitleUsingUrl(file.originalUrl);
- parameters.url = file.url;
- }
- if (args) {
- for (var prop in args) {
- parameters[prop] = args[prop];
- }
- }
-
- var self = this;
- self.loading = true;
- self.downloadComplete = false;
-
- var passwordNeeded = function passwordNeeded(updatePassword, reason) {
- PasswordPrompt.updatePassword = updatePassword;
- PasswordPrompt.reason = reason;
- PasswordPrompt.open();
- };
-
- function getDocumentProgress(progressData) {
- self.progress(progressData.loaded / progressData.total);
- }
-
- PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded,
- getDocumentProgress).then(
- function getDocumentCallback(pdfDocument) {
- self.load(pdfDocument, scale);
- self.loading = false;
- },
- function getDocumentError(exception) {
- var message = exception && exception.message;
- var loadingErrorMessage = mozL10n.get('loading_error', null,
- 'An error occurred while loading the PDF.');
-
- if (exception instanceof PDFJS.InvalidPDFException) {
- // change error message also for other builds
- loadingErrorMessage = mozL10n.get('invalid_file_error', null,
- 'Invalid or corrupted PDF file.');
- } else if (exception instanceof PDFJS.MissingPDFException) {
- // special message for missing PDF's
- loadingErrorMessage = mozL10n.get('missing_file_error', null,
- 'Missing PDF file.');
- } else if (exception instanceof PDFJS.UnexpectedResponseException) {
- loadingErrorMessage = mozL10n.get('unexpected_response_error', null,
- 'Unexpected server response.');
- }
-
- var moreInfo = {
- message: message
- };
- self.error(loadingErrorMessage, moreInfo);
- self.loading = false;
- }
- );
-
- if (args && args.length) {
- DocumentProperties.setFileSize(args.length);
- }
- },
-
- download: function pdfViewDownload() {
- function downloadByUrl() {
- downloadManager.downloadUrl(url, filename);
- }
-
- var url = this.url.split('#')[0];
- var filename = getPDFFileNameFromURL(url);
- var downloadManager = new DownloadManager();
- downloadManager.onerror = function (err) {
- // This error won't really be helpful because it's likely the
- // fallback won't work either (or is already open).
- PDFView.error('PDF failed to download.');
- };
-
- if (!this.pdfDocument) { // the PDF is not ready yet
- downloadByUrl();
- return;
- }
-
- if (!this.downloadComplete) { // the PDF is still downloading
- downloadByUrl();
- return;
- }
-
- this.pdfDocument.getData().then(
- function getDataSuccess(data) {
- var blob = PDFJS.createBlob(data, 'application/pdf');
- downloadManager.download(blob, url, filename);
- },
- downloadByUrl // Error occurred try downloading with just the url.
- ).then(null, downloadByUrl);
- },
-
- fallback: function pdfViewFallback(featureId) {
- // Only trigger the fallback once so we don't spam the user with messages
- // for one PDF.
- if (this.fellback)
- return;
- this.fellback = true;
- var url = this.url.split('#')[0];
- FirefoxCom.request('fallback', { featureId: featureId, url: url },
- function response(download) {
- if (!download) {
- return;
- }
- PDFView.download();
- });
- },
-
- navigateTo: function pdfViewNavigateTo(dest) {
- var destString = '';
- var self = this;
-
- var goToDestination = function(destRef) {
- self.pendingRefStr = null;
- // dest array looks like that:
- var pageNumber = destRef instanceof Object ?
- self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
- (destRef + 1);
- if (pageNumber) {
- if (pageNumber > self.pages.length) {
- pageNumber = self.pages.length;
- }
- var currentPage = self.pages[pageNumber - 1];
- currentPage.scrollIntoView(dest);
-
- // Update the browsing history.
- PDFHistory.push({ dest: dest, hash: destString, page: pageNumber });
- } else {
- self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
- var pageNum = pageIndex + 1;
- self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum;
- goToDestination(destRef);
- });
- }
- };
-
- this.destinationsPromise.then(function() {
- if (typeof dest === 'string') {
- destString = dest;
- dest = self.destinations[dest];
- }
- if (!(dest instanceof Array)) {
- return; // invalid destination
- }
- goToDestination(dest[0]);
- });
- },
-
- getDestinationHash: function pdfViewGetDestinationHash(dest) {
- if (typeof dest === 'string') {
- return PDFView.getAnchorUrl('#' + escape(dest));
- }
- if (dest instanceof Array) {
- var destRef = dest[0]; // see navigateTo method for dest format
- var pageNumber = destRef instanceof Object ?
- this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
- (destRef + 1);
- if (pageNumber) {
- var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber);
- var destKind = dest[1];
- if (typeof destKind === 'object' && 'name' in destKind &&
- destKind.name === 'XYZ') {
- var scale = (dest[4] || this.currentScaleValue);
- var scaleNumber = parseFloat(scale);
- if (scaleNumber) {
- scale = scaleNumber * 100;
- }
- pdfOpenParams += '&zoom=' + scale;
- if (dest[2] || dest[3]) {
- pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
- }
- }
- return pdfOpenParams;
- }
- }
- return '';
- },
-
- /**
- * Prefix the full url on anchor links to make sure that links are resolved
- * relative to the current URL instead of the one defined in .
- * @param {String} anchor The anchor hash, including the #.
- */
- getAnchorUrl: function getAnchorUrl(anchor) {
- return this.url.split('#')[0] + anchor;
- },
-
- /**
- * Show the error box.
- * @param {String} message A message that is human readable.
- * @param {Object} moreInfo (optional) Further information about the error
- * that is more technical. Should have a 'message'
- * and optionally a 'stack' property.
- */
- error: function pdfViewError(message, moreInfo) {
- var moreInfoText = mozL10n.get('error_version_info',
- {version: PDFJS.version || '?', build: PDFJS.build || '?'},
- 'PDF.js v{{version}} (build: {{build}})') + '\n';
- if (moreInfo) {
- moreInfoText +=
- mozL10n.get('error_message', {message: moreInfo.message},
- 'Message: {{message}}');
- if (moreInfo.stack) {
- moreInfoText += '\n' +
- mozL10n.get('error_stack', {stack: moreInfo.stack},
- 'Stack: {{stack}}');
- } else {
- if (moreInfo.filename) {
- moreInfoText += '\n' +
- mozL10n.get('error_file', {file: moreInfo.filename},
- 'File: {{file}}');
- }
- if (moreInfo.lineNumber) {
- moreInfoText += '\n' +
- mozL10n.get('error_line', {line: moreInfo.lineNumber},
- 'Line: {{line}}');
- }
- }
- }
-
- console.error(message + '\n' + moreInfoText);
- this.fallback();
- },
-
- progress: function pdfViewProgress(level) {
- var percent = Math.round(level * 100);
- // When we transition from full request to range requests, it's possible
- // that we discard some of the loaded data. This can cause the loading
- // bar to move backwards. So prevent this by only updating the bar if it
- // increases.
- if (percent > PDFView.loadingBar.percent || isNaN(percent)) {
- PDFView.loadingBar.percent = percent;
- }
- },
-
- load: function pdfViewLoad(pdfDocument, scale) {
- var self = this;
- var isOnePageRenderedResolved = false;
- var resolveOnePageRendered = null;
- var onePageRendered = new Promise(function (resolve) {
- resolveOnePageRendered = resolve;
- });
- function bindOnAfterDraw(pageView, thumbnailView) {
- // when page is painted, using the image as thumbnail base
- pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
- if (!isOnePageRenderedResolved) {
- isOnePageRenderedResolved = true;
- resolveOnePageRendered();
- }
- thumbnailView.setImage(pageView.canvas);
- };
- }
-
- PDFView.findController.reset();
-
- this.pdfDocument = pdfDocument;
-
- DocumentProperties.resolveDataAvailable();
-
- var downloadedPromise = pdfDocument.getDownloadInfo().then(function() {
- self.downloadComplete = true;
- PDFView.loadingBar.hide();
- var outerContainer = document.getElementById('outerContainer');
- outerContainer.classList.remove('loadingInProgress');
- });
-
- var pagesCount = pdfDocument.numPages;
-
- var id = pdfDocument.fingerprint;
- document.getElementById('numPages').textContent =
- mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
- document.getElementById('pageNumber').max = pagesCount;
-
- PDFView.documentFingerprint = id;
- var store = PDFView.store = new ViewHistory(id);
-
- this.pageRotation = 0;
-
- var pages = this.pages = [];
- var pagesRefMap = this.pagesRefMap = {};
- var thumbnails = this.thumbnails = [];
-
- var resolvePagesPromise;
- var pagesPromise = new Promise(function (resolve) {
- resolvePagesPromise = resolve;
- });
- this.pagesPromise = pagesPromise;
-
- var firstPagePromise = pdfDocument.getPage(1);
- var container = document.getElementById('viewer');
- var thumbsView = document.getElementById('thumbnailView');
-
- // Fetch a single page so we can get a viewport that will be the default
- // viewport for all pages
- firstPagePromise.then(function(pdfPage) {
- var viewport = pdfPage.getViewport((scale || 1.0) * CSS_UNITS);
- for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
- var viewportClone = viewport.clone();
- var pageView = new PageView(container, pageNum, scale,
- self.navigateTo.bind(self),
- viewportClone);
- var thumbnailView = new ThumbnailView(thumbsView, pageNum,
- viewportClone);
- bindOnAfterDraw(pageView, thumbnailView);
- pages.push(pageView);
- thumbnails.push(thumbnailView);
- }
-
- // Fetch all the pages since the viewport is needed before printing
- // starts to create the correct size canvas. Wait until one page is
- // rendered so we don't tie up too many resources early on.
- onePageRendered.then(function () {
- if (!PDFJS.disableAutoFetch) {
- var getPagesLeft = pagesCount;
- for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
- pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
- var pageView = pages[pageNum - 1];
- if (!pageView.pdfPage) {
- pageView.setPdfPage(pdfPage);
- }
- var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
- pagesRefMap[refStr] = pageNum;
- getPagesLeft--;
- if (!getPagesLeft) {
- resolvePagesPromise();
- }
- }.bind(null, pageNum));
- }
- } else {
- // XXX: Printing is semi-broken with auto fetch disabled.
- resolvePagesPromise();
- }
- });
-
- downloadedPromise.then(function () {
- var event = document.createEvent('CustomEvent');
- event.initCustomEvent('documentload', true, true, {});
- window.dispatchEvent(event);
- });
-
- PDFView.loadingBar.setWidth(container);
-
- PDFView.findController.resolveFirstPage();
-
- // Initialize the browsing history.
- PDFHistory.initialize(self.documentFingerprint);
- });
-
- // Fetch the necessary preference values.
- var showPreviousViewOnLoad;
- var showPreviousViewOnLoadPromise =
- Preferences.get('showPreviousViewOnLoad').then(function (prefValue) {
- showPreviousViewOnLoad = prefValue;
- });
- var defaultZoomValue;
- var defaultZoomValuePromise =
- Preferences.get('defaultZoomValue').then(function (prefValue) {
- defaultZoomValue = prefValue;
- });
-
- var storePromise = store.initializedPromise;
- Promise.all([firstPagePromise, storePromise, showPreviousViewOnLoadPromise,
- defaultZoomValuePromise]).then(function resolved() {
- var storedHash = null;
- if (showPreviousViewOnLoad && store.get('exists', false)) {
- var pageNum = store.get('page', '1');
- var zoom = defaultZoomValue || store.get('zoom', PDFView.currentScale);
- var left = store.get('scrollLeft', '0');
- var top = store.get('scrollTop', '0');
-
- storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
- left + ',' + top;
- } else if (defaultZoomValue) {
- storedHash = 'page=1&zoom=' + defaultZoomValue;
- }
- self.setInitialView(storedHash, scale);
-
- // Make all navigation keys work on document load,
- // unless the viewer is embedded in a web page.
- if (!self.isViewerEmbedded) {
- self.container.focus();
- self.container.blur();
- }
- }, function rejected(reason) {
- console.error(reason);
-
- firstPagePromise.then(function () {
- self.setInitialView(null, scale);
- });
- });
-
- pagesPromise.then(function() {
- if (PDFView.supportsPrinting) {
- pdfDocument.getJavaScript().then(function(javaScript) {
- if (javaScript.length) {
- console.warn('Warning: JavaScript is not supported');
- PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript);
- }
- // Hack to support auto printing.
- var regex = /\bprint\s*\(/g;
- for (var i = 0, ii = javaScript.length; i < ii; i++) {
- var js = javaScript[i];
- if (js && regex.test(js)) {
- setTimeout(function() {
- window.print();
- });
- return;
- }
- }
- });
- }
- });
-
- var destinationsPromise =
- this.destinationsPromise = pdfDocument.getDestinations();
- destinationsPromise.then(function(destinations) {
- self.destinations = destinations;
- });
-
- // outline depends on destinations and pagesRefMap
- var promises = [pagesPromise, destinationsPromise,
- PDFView.animationStartedPromise];
- Promise.all(promises).then(function() {
- pdfDocument.getOutline().then(function(outline) {
- self.outline = new DocumentOutlineView(outline);
- document.getElementById('viewOutline').disabled = !outline;
-
- if (outline &&
- self.preferenceSidebarViewOnLoad === SidebarView.OUTLINE) {
- self.switchSidebarView('outline', true);
- }
- });
- pdfDocument.getAttachments().then(function(attachments) {
- self.attachments = new DocumentAttachmentsView(attachments);
- document.getElementById('viewAttachments').disabled = !attachments;
-
- if (attachments &&
- self.preferenceSidebarViewOnLoad === SidebarView.ATTACHMENTS) {
- self.switchSidebarView('attachments', true);
- }
- });
- });
-
- if (self.preferenceSidebarViewOnLoad === SidebarView.THUMBS) {
- Promise.all([firstPagePromise, onePageRendered]).then(function () {
- self.switchSidebarView('thumbs', true);
- });
- }
-
- pdfDocument.getMetadata().then(function(data) {
- var info = data.info, metadata = data.metadata;
- self.documentInfo = info;
- self.metadata = metadata;
-
- // Provides some basic debug information
- console.log('PDF ' + pdfDocument.fingerprint + ' [' +
- info.PDFFormatVersion + ' ' + (info.Producer || '-').trim() +
- ' / ' + (info.Creator || '-').trim() + ']' +
- ' (PDF.js: ' + (PDFJS.version || '-') +
- (!PDFJS.disableWebGL ? ' [WebGL]' : '') + ')');
-
- var pdfTitle;
- if (metadata && metadata.has('dc:title')) {
- var title = metadata.get('dc:title');
- // Ghostscript sometimes return 'Untitled', sets the title to 'Untitled'
- if (title !== 'Untitled') {
- pdfTitle = title;
- }
- }
-
- if (!pdfTitle && info && info['Title']) {
- pdfTitle = info['Title'];
- }
-
- if (pdfTitle) {
- self.setTitle(pdfTitle + ' - ' + document.title);
- }
-
- if (info.IsAcroFormPresent) {
- console.warn('Warning: AcroForm/XFA is not supported');
- PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.forms);
- }
-
- var versionId = String(info.PDFFormatVersion).slice(-1) | 0;
- var generatorId = 0;
- var KNOWN_GENERATORS = ["acrobat distiller", "acrobat pdfwritter",
- "adobe livecycle", "adobe pdf library", "adobe photoshop", "ghostscript",
- "tcpdf", "cairo", "dvipdfm", "dvips", "pdftex", "pdfkit", "itext",
- "prince", "quarkxpress", "mac os x", "microsoft", "openoffice", "oracle",
- "luradocument", "pdf-xchange", "antenna house", "aspose.cells", "fpdf"];
- var generatorId = 0;
- if (info.Producer) {
- KNOWN_GENERATORS.some(function (generator, s, i) {
- if (generator.indexOf(s) < 0) {
- return false;
- }
- generatorId = i + 1;
- return true;
- }.bind(null, info.Producer.toLowerCase()));
- }
- var formType = !info.IsAcroFormPresent ? null : info.IsXFAPresent ?
- 'xfa' : 'acroform';
- FirefoxCom.request('reportTelemetry', JSON.stringify({
- type: 'documentInfo',
- version: versionId,
- generator: generatorId,
- formType: formType
- }));
- });
- },
-
- setInitialView: function pdfViewSetInitialView(storedHash, scale) {
- // Reset the current scale, as otherwise the page's scale might not get
- // updated if the zoom level stayed the same.
- this.currentScale = UNKNOWN_SCALE;
- this.currentScaleValue = null;
- // When opening a new file (when one is already loaded in the viewer):
- // Reset 'currentPageNumber', since otherwise the page's scale will be wrong
- // if 'currentPageNumber' is larger than the number of pages in the file.
- document.getElementById('pageNumber').value = currentPageNumber = 1;
- // Reset the current position when loading a new file,
- // to prevent displaying the wrong position in the document.
- this.currentPosition = null;
-
- if (PDFHistory.initialDestination) {
- this.navigateTo(PDFHistory.initialDestination);
- PDFHistory.initialDestination = null;
- } else if (this.initialBookmark) {
- this.setHash(this.initialBookmark);
- PDFHistory.push({ hash: this.initialBookmark }, !!this.initialBookmark);
- this.initialBookmark = null;
- } else if (storedHash) {
- this.setHash(storedHash);
- } else if (scale) {
- this.setScale(scale, true);
- this.page = 1;
- }
-
- if (PDFView.currentScale === UNKNOWN_SCALE) {
- // Scale was not initialized: invalid bookmark or scale was not specified.
- // Setting the default one.
- this.setScale(DEFAULT_SCALE, true);
- }
- },
-
- renderHighestPriority:
- function pdfViewRenderHighestPriority(currentlyVisiblePages) {
- if (PDFView.idleTimeout) {
- clearTimeout(PDFView.idleTimeout);
- PDFView.idleTimeout = null;
- }
-
- // Pages have a higher priority than thumbnails, so check them first.
- var visiblePages = currentlyVisiblePages || this.getVisiblePages();
- var pageView = this.getHighestPriority(visiblePages, this.pages,
- this.pageViewScroll.down);
- if (pageView) {
- this.renderView(pageView, 'page');
- return;
- }
- // No pages needed rendering so check thumbnails.
- if (this.sidebarOpen) {
- var visibleThumbs = this.getVisibleThumbs();
- var thumbView = this.getHighestPriority(visibleThumbs,
- this.thumbnails,
- this.thumbnailViewScroll.down);
- if (thumbView) {
- this.renderView(thumbView, 'thumbnail');
- return;
- }
- }
-
- if (this.printing) {
- // If printing is currently ongoing do not reschedule cleanup.
- return;
- }
-
- PDFView.idleTimeout = setTimeout(function () {
- PDFView.cleanup();
- }, CLEANUP_TIMEOUT);
- },
-
- cleanup: function pdfViewCleanup() {
- for (var i = 0, ii = this.pages.length; i < ii; i++) {
- if (this.pages[i] &&
- this.pages[i].renderingState !== RenderingStates.FINISHED) {
- this.pages[i].reset();
- }
- }
- this.pdfDocument.cleanup();
-
- ThumbnailView.tempImageCache = null;
- },
-
- getHighestPriority: function pdfViewGetHighestPriority(visible, views,
- scrolledDown) {
- // The state has changed figure out which page has the highest priority to
- // render next (if any).
- // Priority:
- // 1 visible pages
- // 2 if last scrolled down page after the visible pages
- // 2 if last scrolled up page before the visible pages
- var visibleViews = visible.views;
-
- var numVisible = visibleViews.length;
- if (numVisible === 0) {
- return false;
- }
- for (var i = 0; i < numVisible; ++i) {
- var view = visibleViews[i].view;
- if (!this.isViewFinished(view)) {
- return view;
- }
- }
-
- // All the visible views have rendered, try to render next/previous pages.
- if (scrolledDown) {
- var nextPageIndex = visible.last.id;
- // ID's start at 1 so no need to add 1.
- if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) {
- return views[nextPageIndex];
- }
- } else {
- var previousPageIndex = visible.first.id - 2;
- if (views[previousPageIndex] &&
- !this.isViewFinished(views[previousPageIndex])) {
- return views[previousPageIndex];
- }
- }
- // Everything that needs to be rendered has been.
- return false;
- },
-
- isViewFinished: function pdfViewIsViewFinished(view) {
- return view.renderingState === RenderingStates.FINISHED;
- },
-
- // Render a page or thumbnail view. This calls the appropriate function based
- // on the views state. If the view is already rendered it will return false.
- renderView: function pdfViewRender(view, type) {
- var state = view.renderingState;
- switch (state) {
- case RenderingStates.FINISHED:
- return false;
- case RenderingStates.PAUSED:
- PDFView.highestPriorityPage = type + view.id;
- view.resume();
- break;
- case RenderingStates.RUNNING:
- PDFView.highestPriorityPage = type + view.id;
- break;
- case RenderingStates.INITIAL:
- PDFView.highestPriorityPage = type + view.id;
- view.draw(this.renderHighestPriority.bind(this));
- break;
- }
- return true;
- },
-
- setHash: function pdfViewSetHash(hash) {
- var validFitZoomValues = ['Fit','FitB','FitH','FitBH',
- 'FitV','FitBV','FitR'];
-
- if (!hash) {
- return;
- }
-
- if (hash.indexOf('=') >= 0) {
- var params = PDFView.parseQueryString(hash);
- // borrowing syntax from "Parameters for Opening PDF Files"
- if ('nameddest' in params) {
- PDFHistory.updateNextHashParam(params.nameddest);
- PDFView.navigateTo(params.nameddest);
- return;
- }
- var pageNumber, dest;
- if ('page' in params) {
- pageNumber = (params.page | 0) || 1;
- }
- if ('zoom' in params) {
- var zoomArgs = params.zoom.split(','); // scale,left,top
- // building destination array
-
- // If the zoom value, it has to get divided by 100. If it is a string,
- // it should stay as it is.
- var zoomArg = zoomArgs[0];
- var zoomArgNumber = parseFloat(zoomArg);
- var destName = 'XYZ';
- if (zoomArgNumber) {
- zoomArg = zoomArgNumber / 100;
- } else if (validFitZoomValues.indexOf(zoomArg) >= 0) {
- destName = zoomArg;
- }
- dest = [null, { name: destName },
- zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
- zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
- zoomArg];
- }
- if (dest) {
- var currentPage = this.pages[(pageNumber || this.page) - 1];
- currentPage.scrollIntoView(dest);
- } else if (pageNumber) {
- this.page = pageNumber; // simple page
- }
- if ('pagemode' in params) {
- if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
- params.pagemode === 'attachments') {
- this.switchSidebarView((params.pagemode === 'bookmarks' ?
- 'outline' : params.pagemode), true);
- } else if (params.pagemode === 'none' && this.sidebarOpen) {
- document.getElementById('sidebarToggle').click();
- }
- }
- } else if (/^\d+$/.test(hash)) { // page number
- this.page = hash;
- } else { // named destination
- PDFHistory.updateNextHashParam(unescape(hash));
- PDFView.navigateTo(unescape(hash));
- }
- },
-
- switchSidebarView: function pdfViewSwitchSidebarView(view, openSidebar) {
- if (openSidebar && !this.sidebarOpen) {
- document.getElementById('sidebarToggle').click();
- }
- var thumbsView = document.getElementById('thumbnailView');
- var outlineView = document.getElementById('outlineView');
- var attachmentsView = document.getElementById('attachmentsView');
-
- var thumbsButton = document.getElementById('viewThumbnail');
- var outlineButton = document.getElementById('viewOutline');
- var attachmentsButton = document.getElementById('viewAttachments');
-
- switch (view) {
- case 'thumbs':
- var wasAnotherViewVisible = thumbsView.classList.contains('hidden');
-
- thumbsButton.classList.add('toggled');
- outlineButton.classList.remove('toggled');
- attachmentsButton.classList.remove('toggled');
- thumbsView.classList.remove('hidden');
- outlineView.classList.add('hidden');
- attachmentsView.classList.add('hidden');
-
- PDFView.renderHighestPriority();
-
- if (wasAnotherViewVisible) {
- // Ensure that the thumbnail of the current page is visible
- // when switching from another view.
- scrollIntoView(document.getElementById('thumbnailContainer' +
- this.page));
- }
- break;
-
- case 'outline':
- thumbsButton.classList.remove('toggled');
- outlineButton.classList.add('toggled');
- attachmentsButton.classList.remove('toggled');
- thumbsView.classList.add('hidden');
- outlineView.classList.remove('hidden');
- attachmentsView.classList.add('hidden');
-
- if (outlineButton.getAttribute('disabled')) {
- return;
- }
- break;
-
- case 'attachments':
- thumbsButton.classList.remove('toggled');
- outlineButton.classList.remove('toggled');
- attachmentsButton.classList.add('toggled');
- thumbsView.classList.add('hidden');
- outlineView.classList.add('hidden');
- attachmentsView.classList.remove('hidden');
-
- if (attachmentsButton.getAttribute('disabled')) {
- return;
- }
- break;
- }
- },
-
- getVisiblePages: function pdfViewGetVisiblePages() {
- if (!PresentationMode.active) {
- return this.getVisibleElements(this.container, this.pages, true);
- } else {
- // The algorithm in getVisibleElements doesn't work in all browsers and
- // configurations when presentation mode is active.
- var visible = [];
- var currentPage = this.pages[this.page - 1];
- visible.push({ id: currentPage.id, view: currentPage });
- return { first: currentPage, last: currentPage, views: visible };
- }
- },
-
- getVisibleThumbs: function pdfViewGetVisibleThumbs() {
- return this.getVisibleElements(this.thumbnailContainer, this.thumbnails);
- },
-
- // Generic helper to find out what elements are visible within a scroll pane.
- getVisibleElements: function pdfViewGetVisibleElements(
- scrollEl, views, sortByVisibility) {
- var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
- var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
-
- var visible = [], view;
- var currentHeight, viewHeight, hiddenHeight, percentHeight;
- var currentWidth, viewWidth;
- for (var i = 0, ii = views.length; i < ii; ++i) {
- view = views[i];
- currentHeight = view.el.offsetTop + view.el.clientTop;
- viewHeight = view.el.clientHeight;
- if ((currentHeight + viewHeight) < top) {
- continue;
- }
- if (currentHeight > bottom) {
- break;
- }
- currentWidth = view.el.offsetLeft + view.el.clientLeft;
- viewWidth = view.el.clientWidth;
- if ((currentWidth + viewWidth) < left || currentWidth > right) {
- continue;
- }
- hiddenHeight = Math.max(0, top - currentHeight) +
- Math.max(0, currentHeight + viewHeight - bottom);
- percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
-
- visible.push({ id: view.id, x: currentWidth, y: currentHeight,
- view: view, percent: percentHeight });
- }
-
- var first = visible[0];
- var last = visible[visible.length - 1];
-
- if (sortByVisibility) {
- visible.sort(function(a, b) {
- var pc = a.percent - b.percent;
- if (Math.abs(pc) > 0.001) {
- return -pc;
- }
- return a.id - b.id; // ensure stability
- });
- }
- return {first: first, last: last, views: visible};
- },
-
- // Helper function to parse query string (e.g. ?param1=value&parm2=...).
- parseQueryString: function pdfViewParseQueryString(query) {
- var parts = query.split('&');
- var params = {};
- for (var i = 0, ii = parts.length; i < ii; ++i) {
- var param = parts[i].split('=');
- var key = param[0];
- var value = param.length > 1 ? param[1] : null;
- params[decodeURIComponent(key)] = decodeURIComponent(value);
- }
- return params;
- },
-
- beforePrint: function pdfViewSetupBeforePrint() {
- if (!this.supportsPrinting) {
- var printMessage = mozL10n.get('printing_not_supported', null,
- 'Warning: Printing is not fully supported by this browser.');
- this.error(printMessage);
- return;
- }
-
- var alertNotReady = false;
- var i, ii;
- if (!this.pages.length) {
- alertNotReady = true;
- } else {
- for (i = 0, ii = this.pages.length; i < ii; ++i) {
- if (!this.pages[i].pdfPage) {
- alertNotReady = true;
- break;
- }
- }
- }
- if (alertNotReady) {
- var notReadyMessage = mozL10n.get('printing_not_ready', null,
- 'Warning: The PDF is not fully loaded for printing.');
- window.alert(notReadyMessage);
- return;
- }
-
- this.printing = true;
- this.renderHighestPriority();
-
- var body = document.querySelector('body');
- body.setAttribute('data-mozPrintCallback', true);
- for (i = 0, ii = this.pages.length; i < ii; ++i) {
- this.pages[i].beforePrint();
- }
-
- FirefoxCom.request('reportTelemetry', JSON.stringify({
- type: 'print'
- }));
- },
-
- afterPrint: function pdfViewSetupAfterPrint() {
- var div = document.getElementById('printContainer');
- while (div.hasChildNodes()) {
- div.removeChild(div.lastChild);
- }
-
- this.printing = false;
- this.renderHighestPriority();
- },
-
- rotatePages: function pdfViewRotatePages(delta) {
- var currentPage = this.pages[this.page - 1];
- var i, l;
- this.pageRotation = (this.pageRotation + 360 + delta) % 360;
-
- for (i = 0, l = this.pages.length; i < l; i++) {
- var page = this.pages[i];
- page.update(page.scale, this.pageRotation);
- }
-
- for (i = 0, l = this.thumbnails.length; i < l; i++) {
- var thumb = this.thumbnails[i];
- thumb.update(this.pageRotation);
- }
-
- this.setScale(this.currentScaleValue, true, true);
-
- this.renderHighestPriority();
-
- if (currentPage) {
- currentPage.scrollIntoView();
- }
- },
-
- /**
- * This function flips the page in presentation mode if the user scrolls up
- * or down with large enough motion and prevents page flipping too often.
- *
- * @this {PDFView}
- * @param {number} mouseScrollDelta The delta value from the mouse event.
- */
- mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) {
- var MOUSE_SCROLL_COOLDOWN_TIME = 50;
-
- var currentTime = (new Date()).getTime();
- var storedTime = this.mouseScrollTimeStamp;
-
- // In case one page has already been flipped there is a cooldown time
- // which has to expire before next page can be scrolled on to.
- if (currentTime > storedTime &&
- currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
- return;
- }
-
- // In case the user decides to scroll to the opposite direction than before
- // clear the accumulated delta.
- if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) ||
- (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) {
- this.clearMouseScrollState();
- }
-
- this.mouseScrollDelta += mouseScrollDelta;
-
- var PAGE_FLIP_THRESHOLD = 120;
- if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) {
-
- var PageFlipDirection = {
- UP: -1,
- DOWN: 1
- };
-
- // In presentation mode scroll one page at a time.
- var pageFlipDirection = (this.mouseScrollDelta > 0) ?
- PageFlipDirection.UP :
- PageFlipDirection.DOWN;
- this.clearMouseScrollState();
- var currentPage = this.page;
-
- // In case we are already on the first or the last page there is no need
- // to do anything.
- if ((currentPage === 1 && pageFlipDirection === PageFlipDirection.UP) ||
- (currentPage === this.pages.length &&
- pageFlipDirection === PageFlipDirection.DOWN)) {
- return;
- }
-
- this.page += pageFlipDirection;
- this.mouseScrollTimeStamp = currentTime;
- }
- },
-
- /**
- * This function clears the member attributes used with mouse scrolling in
- * presentation mode.
- *
- * @this {PDFView}
- */
- clearMouseScrollState: function pdfViewClearMouseScrollState() {
- this.mouseScrollTimeStamp = 0;
- this.mouseScrollDelta = 0;
- }
+var PresentationModeState = {
+ UNKNOWN: 0,
+ NORMAL: 1,
+ CHANGING: 2,
+ FULLSCREEN: 3,
};
+var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
-var PageView = function pageView(container, id, scale,
- navigateTo, defaultViewport) {
+
+var CLEANUP_TIMEOUT = 30000;
+
+var RenderingStates = {
+ INITIAL: 0,
+ RUNNING: 1,
+ PAUSED: 2,
+ FINISHED: 3
+};
+
+/**
+ * Controls rendering of the views for pages and thumbnails.
+ * @class
+ */
+var PDFRenderingQueue = (function PDFRenderingQueueClosure() {
+ /**
+ * @constructs
+ */
+ function PDFRenderingQueue() {
+ this.pdfViewer = null;
+ this.pdfThumbnailViewer = null;
+ this.onIdle = null;
+
+ this.highestPriorityPage = null;
+ this.idleTimeout = null;
+ this.printing = false;
+ this.isThumbnailViewEnabled = false;
+ }
+
+ PDFRenderingQueue.prototype = /** @lends PDFRenderingQueue.prototype */ {
+ /**
+ * @param {PDFViewer} pdfViewer
+ */
+ setViewer: function PDFRenderingQueue_setViewer(pdfViewer) {
+ this.pdfViewer = pdfViewer;
+ },
+
+ /**
+ * @param {PDFThumbnailViewer} pdfThumbnailViewer
+ */
+ setThumbnailViewer:
+ function PDFRenderingQueue_setThumbnailViewer(pdfThumbnailViewer) {
+ this.pdfThumbnailViewer = pdfThumbnailViewer;
+ },
+
+ /**
+ * @param {IRenderableView} view
+ * @returns {boolean}
+ */
+ isHighestPriority: function PDFRenderingQueue_isHighestPriority(view) {
+ return this.highestPriorityPage === view.renderingId;
+ },
+
+ renderHighestPriority: function
+ PDFRenderingQueue_renderHighestPriority(currentlyVisiblePages) {
+ if (this.idleTimeout) {
+ clearTimeout(this.idleTimeout);
+ this.idleTimeout = null;
+ }
+
+ // Pages have a higher priority than thumbnails, so check them first.
+ if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
+ return;
+ }
+ // No pages needed rendering so check thumbnails.
+ if (this.pdfThumbnailViewer && this.isThumbnailViewEnabled) {
+ if (this.pdfThumbnailViewer.forceRendering()) {
+ return;
+ }
+ }
+
+ if (this.printing) {
+ // If printing is currently ongoing do not reschedule cleanup.
+ return;
+ }
+
+ if (this.onIdle) {
+ this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
+ }
+ },
+
+ getHighestPriority: function
+ PDFRenderingQueue_getHighestPriority(visible, views, scrolledDown) {
+ // The state has changed figure out which page has the highest priority to
+ // render next (if any).
+ // Priority:
+ // 1 visible pages
+ // 2 if last scrolled down page after the visible pages
+ // 2 if last scrolled up page before the visible pages
+ var visibleViews = visible.views;
+
+ var numVisible = visibleViews.length;
+ if (numVisible === 0) {
+ return false;
+ }
+ for (var i = 0; i < numVisible; ++i) {
+ var view = visibleViews[i].view;
+ if (!this.isViewFinished(view)) {
+ return view;
+ }
+ }
+
+ // All the visible views have rendered, try to render next/previous pages.
+ if (scrolledDown) {
+ var nextPageIndex = visible.last.id;
+ // ID's start at 1 so no need to add 1.
+ if (views[nextPageIndex] &&
+ !this.isViewFinished(views[nextPageIndex])) {
+ return views[nextPageIndex];
+ }
+ } else {
+ var previousPageIndex = visible.first.id - 2;
+ if (views[previousPageIndex] &&
+ !this.isViewFinished(views[previousPageIndex])) {
+ return views[previousPageIndex];
+ }
+ }
+ // Everything that needs to be rendered has been.
+ return null;
+ },
+
+ /**
+ * @param {IRenderableView} view
+ * @returns {boolean}
+ */
+ isViewFinished: function PDFRenderingQueue_isViewFinished(view) {
+ return view.renderingState === RenderingStates.FINISHED;
+ },
+
+ /**
+ * Render a page or thumbnail view. This calls the appropriate function
+ * based on the views state. If the view is already rendered it will return
+ * false.
+ * @param {IRenderableView} view
+ */
+ renderView: function PDFRenderingQueue_renderView(view) {
+ var state = view.renderingState;
+ switch (state) {
+ case RenderingStates.FINISHED:
+ return false;
+ case RenderingStates.PAUSED:
+ this.highestPriorityPage = view.renderingId;
+ view.resume();
+ break;
+ case RenderingStates.RUNNING:
+ this.highestPriorityPage = view.renderingId;
+ break;
+ case RenderingStates.INITIAL:
+ this.highestPriorityPage = view.renderingId;
+ view.draw(this.renderHighestPriority.bind(this));
+ break;
+ }
+ return true;
+ },
+ };
+
+ return PDFRenderingQueue;
+})();
+
+
+/**
+ * @constructor
+ * @param {HTMLDivElement} container - The viewer element.
+ * @param {number} id - The page unique ID (normally its number).
+ * @param {number} scale - The page scale display.
+ * @param {PageViewport} defaultViewport - The page viewport.
+ * @param {IPDFLinkService} linkService - The navigation/linking service.
+ * @param {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ * @param {Cache} cache - The page cache.
+ * @param {PDFPageSource} pageSource
+ * @param {PDFViewer} viewer
+ *
+ * @implements {IRenderableView}
+ */
+var PageView = function pageView(container, id, scale, defaultViewport,
+ linkService, renderingQueue, cache,
+ pageSource, viewer) {
this.id = id;
+ this.renderingId = 'page' + id;
this.rotation = 0;
this.scale = scale || 1.0;
@@ -4090,6 +2839,12 @@ var PageView = function pageView(container, id, scale,
this.pdfPageRotate = defaultViewport.rotation;
this.hasRestrictedScaling = false;
+ this.linkService = linkService;
+ this.renderingQueue = renderingQueue;
+ this.cache = cache;
+ this.pageSource = pageSource;
+ this.viewer = viewer;
+
this.renderingState = RenderingStates.INITIAL;
this.resume = null;
@@ -4300,10 +3055,10 @@ var PageView = function pageView(container, id, scale,
function setupAnnotations(pageDiv, pdfPage, viewport) {
function bindLink(link, dest) {
- link.href = PDFView.getDestinationHash(dest);
+ link.href = linkService.getDestinationHash(dest);
link.onclick = function pageViewSetupLinksOnclick() {
if (dest) {
- PDFView.navigateTo(dest);
+ linkService.navigateTo(dest);
}
return false;
};
@@ -4313,47 +3068,9 @@ var PageView = function pageView(container, id, scale,
}
function bindNamedAction(link, action) {
- link.href = PDFView.getAnchorUrl('');
+ link.href = linkService.getAnchorUrl('');
link.onclick = function pageViewSetupNamedActionOnClick() {
- // See PDF reference, table 8.45 - Named action
- switch (action) {
- case 'GoToPage':
- document.getElementById('pageNumber').focus();
- break;
-
- case 'GoBack':
- PDFHistory.back();
- break;
-
- case 'GoForward':
- PDFHistory.forward();
- break;
-
- case 'Find':
- if (!PDFView.supportsIntegratedFind) {
- PDFView.findBar.toggle();
- }
- break;
-
- case 'NextPage':
- PDFView.page++;
- break;
-
- case 'PrevPage':
- PDFView.page--;
- break;
-
- case 'LastPage':
- PDFView.page = PDFView.pages.length;
- break;
-
- case 'FirstPage':
- PDFView.page = 1;
- break;
-
- default:
- break; // No action according to spec
- }
+ linkService.executeNamedAction(action);
return false;
};
link.className = 'internalLink';
@@ -4434,99 +3151,6 @@ var PageView = function pageView(container, id, scale,
return this.viewport.convertToPdfPoint(x, y);
};
- this.scrollIntoView = function pageViewScrollIntoView(dest) {
- if (PresentationMode.active) {
- if (PDFView.page !== this.id) {
- // Avoid breaking PDFView.getVisiblePages in presentation mode.
- PDFView.page = this.id;
- return;
- }
- dest = null;
- PDFView.setScale(PDFView.currentScaleValue, true, true);
- }
- if (!dest) {
- scrollIntoView(div);
- return;
- }
-
- var x = 0, y = 0;
- var width = 0, height = 0, widthScale, heightScale;
- var changeOrientation = (this.rotation % 180 === 0 ? false : true);
- var pageWidth = (changeOrientation ? this.height : this.width) /
- this.scale / CSS_UNITS;
- var pageHeight = (changeOrientation ? this.width : this.height) /
- this.scale / CSS_UNITS;
- var scale = 0;
- switch (dest[1].name) {
- case 'XYZ':
- x = dest[2];
- y = dest[3];
- scale = dest[4];
- // If x and/or y coordinates are not supplied, default to
- // _top_ left of the page (not the obvious bottom left,
- // since aligning the bottom of the intended page with the
- // top of the window is rarely helpful).
- x = x !== null ? x : 0;
- y = y !== null ? y : pageHeight;
- break;
- case 'Fit':
- case 'FitB':
- scale = 'page-fit';
- break;
- case 'FitH':
- case 'FitBH':
- y = dest[2];
- scale = 'page-width';
- break;
- case 'FitV':
- case 'FitBV':
- x = dest[2];
- width = pageWidth;
- height = pageHeight;
- scale = 'page-height';
- break;
- case 'FitR':
- x = dest[2];
- y = dest[3];
- width = dest[4] - x;
- height = dest[5] - y;
- widthScale = (PDFView.container.clientWidth - SCROLLBAR_PADDING) /
- width / CSS_UNITS;
- heightScale = (PDFView.container.clientHeight - SCROLLBAR_PADDING) /
- height / CSS_UNITS;
- scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
- break;
- default:
- return;
- }
-
- if (scale && scale !== PDFView.currentScale) {
- PDFView.setScale(scale, true, true);
- } else if (PDFView.currentScale === UNKNOWN_SCALE) {
- PDFView.setScale(DEFAULT_SCALE, true, true);
- }
-
- if (scale === 'page-fit' && !dest[4]) {
- scrollIntoView(div);
- return;
- }
-
- var boundingRect = [
- this.viewport.convertToViewportPoint(x, y),
- this.viewport.convertToViewportPoint(x + width, y + height)
- ];
- var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
- var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
-
- scrollIntoView(div, { left: left, top: top });
- };
-
- this.getTextContent = function pageviewGetTextContent() {
- return PDFView.getPage(this.id).then(function(pdfPage) {
- return pdfPage.getTextContent();
- });
- };
-
this.draw = function pageviewDraw(callback) {
var pdfPage = this.pdfPage;
@@ -4534,7 +3158,7 @@ var PageView = function pageView(container, id, scale,
return;
}
if (!pdfPage) {
- var promise = PDFView.getPage(this.id);
+ var promise = this.pageSource.getPage();
promise.then(function(pdfPage) {
delete this.pagePdfPromise;
this.setPdfPage(pdfPage);
@@ -4602,6 +3226,7 @@ var PageView = function pageView(container, id, scale,
canvas._viewport = viewport;
var textLayerDiv = null;
+ var textLayer = null;
if (!PDFJS.disableTextLayer) {
textLayerDiv = document.createElement('div');
textLayerDiv.className = 'textLayer';
@@ -4613,16 +3238,12 @@ var PageView = function pageView(container, id, scale,
} else {
div.appendChild(textLayerDiv);
}
+
+ textLayer = this.viewer.createTextLayerBuilder(textLayerDiv, this.id - 1,
+ this.viewport);
}
- var textLayer = this.textLayer =
- textLayerDiv ? new TextLayerBuilder({
- textLayerDiv: textLayerDiv,
- pageIndex: this.id - 1,
- lastScrollSource: PDFView,
- viewport: this.viewport,
- isViewerInPresentationMode: PresentationMode.active,
- findController: PDFView.findController
- }) : null;
+ this.textLayer = textLayer;
+
// TODO(mack): use data attributes to store these
ctx._scaleX = outputScale.sx;
ctx._scaleY = outputScale.sy;
@@ -4657,20 +3278,7 @@ var PageView = function pageView(container, id, scale,
self.zoomLayer = null;
}
- if (self.textLayer && self.textLayer.textDivs &&
- self.textLayer.textDivs.length > 0 &&
- !PDFView.supportsDocumentColors) {
- console.error(mozL10n.get('document_colors_disabled', null,
- 'PDF documents are not allowed to use their own colors: ' +
- '\'Allow pages to choose their own colors\' ' +
- 'is deactivated in the browser.'));
- PDFView.fallback();
- }
- if (error) {
- PDFView.error(mozL10n.get('rendering_error', null,
- 'An error occurred while rendering the page.'), error);
- }
-
+ self.error = error;
self.stats = pdfPage.stats;
self.updateStats();
if (self.onAfterDraw) {
@@ -4683,26 +3291,15 @@ var PageView = function pageView(container, id, scale,
});
div.dispatchEvent(event);
- FirefoxCom.request('reportTelemetry', JSON.stringify({
- type: 'pageInfo'
- }));
- // It is a good time to report stream and font types
- PDFView.pdfDocument.getStats().then(function (stats) {
- FirefoxCom.request('reportTelemetry', JSON.stringify({
- type: 'documentStats',
- stats: stats
- }));
- });
callback();
}
var renderContext = {
canvasContext: ctx,
viewport: this.viewport,
- textLayer: textLayer,
// intent: 'default', // === 'display'
continueCallback: function pdfViewcContinueCallback(cont) {
- if (PDFView.highestPriorityPage !== 'page' + self.id) {
+ if (!self.renderingQueue.isHighestPriority(self)) {
self.renderingState = RenderingStates.PAUSED;
self.resume = function resumeCallback() {
self.renderingState = RenderingStates.RUNNING;
@@ -4719,7 +3316,7 @@ var PageView = function pageView(container, id, scale,
function pdfPageRenderCallback() {
pageViewDrawCallback(null);
if (textLayer) {
- self.getTextContent().then(
+ self.pdfPage.getTextContent().then(
function textContentResolved(textContent) {
textLayer.setTextContent(textContent);
}
@@ -4807,222 +3404,6 @@ var PageView = function pageView(container, id, scale,
};
-var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
- var anchor = document.createElement('a');
- anchor.href = PDFView.getAnchorUrl('#page=' + id);
- anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
- anchor.onclick = function stopNavigation() {
- PDFView.page = id;
- return false;
- };
-
- this.pdfPage = undefined;
- this.viewport = defaultViewport;
- this.pdfPageRotate = defaultViewport.rotation;
-
- this.rotation = 0;
- this.pageWidth = this.viewport.width;
- this.pageHeight = this.viewport.height;
- this.pageRatio = this.pageWidth / this.pageHeight;
- this.id = id;
-
- this.canvasWidth = 98;
- this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
- this.scale = (this.canvasWidth / this.pageWidth);
-
- var div = this.el = document.createElement('div');
- div.id = 'thumbnailContainer' + id;
- div.className = 'thumbnail';
-
- if (id === 1) {
- // Highlight the thumbnail of the first page when no page number is
- // specified (or exists in cache) when the document is loaded.
- div.classList.add('selected');
- }
-
- var ring = document.createElement('div');
- ring.className = 'thumbnailSelectionRing';
- ring.style.width = this.canvasWidth + 'px';
- ring.style.height = this.canvasHeight + 'px';
-
- div.appendChild(ring);
- anchor.appendChild(div);
- container.appendChild(anchor);
-
- this.hasImage = false;
- this.renderingState = RenderingStates.INITIAL;
-
- this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) {
- this.pdfPage = pdfPage;
- this.pdfPageRotate = pdfPage.rotate;
- var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
- this.viewport = pdfPage.getViewport(1, totalRotation);
- this.update();
- };
-
- this.update = function thumbnailViewUpdate(rotation) {
- if (rotation !== undefined) {
- this.rotation = rotation;
- }
- var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
- this.viewport = this.viewport.clone({
- scale: 1,
- rotation: totalRotation
- });
- this.pageWidth = this.viewport.width;
- this.pageHeight = this.viewport.height;
- this.pageRatio = this.pageWidth / this.pageHeight;
-
- this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
- this.scale = (this.canvasWidth / this.pageWidth);
-
- div.removeAttribute('data-loaded');
- ring.textContent = '';
- ring.style.width = this.canvasWidth + 'px';
- ring.style.height = this.canvasHeight + 'px';
-
- this.hasImage = false;
- this.renderingState = RenderingStates.INITIAL;
- this.resume = null;
- };
-
- this.getPageDrawContext = function thumbnailViewGetPageDrawContext() {
- var canvas = document.createElement('canvas');
- canvas.id = 'thumbnail' + id;
-
- canvas.width = this.canvasWidth;
- canvas.height = this.canvasHeight;
- canvas.className = 'thumbnailImage';
- canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
- {page: id}, 'Thumbnail of Page {{page}}'));
-
- div.setAttribute('data-loaded', true);
-
- ring.appendChild(canvas);
-
- var ctx = canvas.getContext('2d');
- ctx.save();
- ctx.fillStyle = 'rgb(255, 255, 255)';
- ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
- ctx.restore();
- return ctx;
- };
-
- this.drawingRequired = function thumbnailViewDrawingRequired() {
- return !this.hasImage;
- };
-
- this.draw = function thumbnailViewDraw(callback) {
- if (!this.pdfPage) {
- var promise = PDFView.getPage(this.id);
- promise.then(function(pdfPage) {
- this.setPdfPage(pdfPage);
- this.draw(callback);
- }.bind(this));
- return;
- }
-
- if (this.renderingState !== RenderingStates.INITIAL) {
- console.error('Must be in new state before drawing');
- }
-
- this.renderingState = RenderingStates.RUNNING;
- if (this.hasImage) {
- callback();
- return;
- }
-
- var self = this;
- var ctx = this.getPageDrawContext();
- var drawViewport = this.viewport.clone({ scale: this.scale });
- var renderContext = {
- canvasContext: ctx,
- viewport: drawViewport,
- continueCallback: function(cont) {
- if (PDFView.highestPriorityPage !== 'thumbnail' + self.id) {
- self.renderingState = RenderingStates.PAUSED;
- self.resume = function() {
- self.renderingState = RenderingStates.RUNNING;
- cont();
- };
- return;
- }
- cont();
- }
- };
- this.pdfPage.render(renderContext).promise.then(
- function pdfPageRenderCallback() {
- self.renderingState = RenderingStates.FINISHED;
- callback();
- },
- function pdfPageRenderError(error) {
- self.renderingState = RenderingStates.FINISHED;
- callback();
- }
- );
- this.hasImage = true;
- };
-
- function getTempCanvas(width, height) {
- var tempCanvas = ThumbnailView.tempImageCache;
- if (!tempCanvas) {
- tempCanvas = document.createElement('canvas');
- ThumbnailView.tempImageCache = tempCanvas;
- }
- tempCanvas.width = width;
- tempCanvas.height = height;
- return tempCanvas;
- }
-
- this.setImage = function thumbnailViewSetImage(img) {
- if (!this.pdfPage) {
- var promise = PDFView.getPage(this.id);
- promise.then(function(pdfPage) {
- this.setPdfPage(pdfPage);
- this.setImage(img);
- }.bind(this));
- return;
- }
- if (this.hasImage || !img) {
- return;
- }
- this.renderingState = RenderingStates.FINISHED;
- var ctx = this.getPageDrawContext();
-
- var reducedImage = img;
- var reducedWidth = img.width;
- var reducedHeight = img.height;
-
- // drawImage does an awful job of rescaling the image, doing it gradually
- var MAX_SCALE_FACTOR = 2.0;
- if (Math.max(img.width / ctx.canvas.width,
- img.height / ctx.canvas.height) > MAX_SCALE_FACTOR) {
- reducedWidth >>= 1;
- reducedHeight >>= 1;
- reducedImage = getTempCanvas(reducedWidth, reducedHeight);
- var reducedImageCtx = reducedImage.getContext('2d');
- reducedImageCtx.drawImage(img, 0, 0, img.width, img.height,
- 0, 0, reducedWidth, reducedHeight);
- while (Math.max(reducedWidth / ctx.canvas.width,
- reducedHeight / ctx.canvas.height) > MAX_SCALE_FACTOR) {
- reducedImageCtx.drawImage(reducedImage,
- 0, 0, reducedWidth, reducedHeight,
- 0, 0, reducedWidth >> 1, reducedHeight >> 1);
- reducedWidth >>= 1;
- reducedHeight >>= 1;
- }
- }
-
- ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight,
- 0, 0, ctx.canvas.width, ctx.canvas.height);
-
- this.hasImage = true;
- };
-};
-
-ThumbnailView.tempImageCache = null;
-
-
var FIND_SCROLL_OFFSET_TOP = -50;
var FIND_SCROLL_OFFSET_LEFT = -400;
var MAX_TEXT_DIVS_TO_RENDER = 100000;
@@ -5034,11 +3415,23 @@ function isAllWhitespace(str) {
return !NonWhitespaceRegexp.test(str);
}
+/**
+ * @typedef {Object} TextLayerBuilderOptions
+ * @property {HTMLDivElement} textLayerDiv - The text layer container.
+ * @property {number} pageIndex - The page index.
+ * @property {PageViewport} viewport - The viewport of the text layer.
+ * @property {ILastScrollSource} lastScrollSource - The object that records when
+ * last time scroll happened.
+ * @property {boolean} isViewerInPresentationMode
+ * @property {PDFFindController} findController
+ */
+
/**
* TextLayerBuilder provides text-selection functionality for the PDF.
* It does this by creating overlay divs over the PDF text. These divs
* contain text that matches the PDF text they are overlaying. This object
* also provides a way to highlight text that is being searched for.
+ * @class
*/
var TextLayerBuilder = (function TextLayerBuilderClosure() {
function TextLayerBuilder(options) {
@@ -5383,23 +3776,2340 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
})();
-var DocumentOutlineView = function documentOutlineView(outline) {
- var outlineView = document.getElementById('outlineView');
+/**
+ * @typedef {Object} PDFViewerOptions
+ * @property {HTMLDivElement} container - The container for the viewer element.
+ * @property {HTMLDivElement} viewer - (optional) The viewer element.
+ * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
+ * queue object.
+ */
+
+/**
+ * Simple viewer control to display PDF content/pages.
+ * @class
+ * @implements {ILastScrollSource}
+ * @implements {IRenderableView}
+ */
+var PDFViewer = (function pdfViewer() {
+ /**
+ * @constructs PDFViewer
+ * @param {PDFViewerOptions} options
+ */
+ function PDFViewer(options) {
+ this.container = options.container;
+ this.viewer = options.viewer || options.container.firstElementChild;
+ this.linkService = options.linkService || new SimpleLinkService(this);
+
+ this.defaultRenderingQueue = !options.renderingQueue;
+ if (this.defaultRenderingQueue) {
+ // Custom rendering queue is not specified, using default one
+ this.renderingQueue = new PDFRenderingQueue();
+ this.renderingQueue.setViewer(this);
+ } else {
+ this.renderingQueue = options.renderingQueue;
+ }
+
+ this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
+ this.lastScroll = 0;
+ this.updateInProgress = false;
+ this.presentationModeState = PresentationModeState.UNKNOWN;
+ this._resetView();
+ }
+
+ PDFViewer.prototype = /** @lends PDFViewer.prototype */{
+ get pagesCount() {
+ return this.pages.length;
+ },
+
+ getPageView: function (index) {
+ return this.pages[index];
+ },
+
+ get currentPageNumber() {
+ return this._currentPageNumber;
+ },
+
+ set currentPageNumber(val) {
+ if (!this.pdfDocument) {
+ this._currentPageNumber = val;
+ return;
+ }
+
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('pagechange', true, true, window, 0);
+ event.updateInProgress = this.updateInProgress;
+
+ if (!(0 < val && val <= this.pagesCount)) {
+ event.pageNumber = this._currentPageNumber;
+ event.previousPageNumber = val;
+ this.container.dispatchEvent(event);
+ return;
+ }
+
+ this.pages[val - 1].updateStats();
+ event.previousPageNumber = this._currentPageNumber;
+ this._currentPageNumber = val;
+ event.pageNumber = val;
+ this.container.dispatchEvent(event);
+ },
+
+ /**
+ * @returns {number}
+ */
+ get currentScale() {
+ return this._currentScale;
+ },
+
+ /**
+ * @param {number} val - Scale of the pages in percents.
+ */
+ set currentScale(val) {
+ if (isNaN(val)) {
+ throw new Error('Invalid numeric scale');
+ }
+ if (!this.pdfDocument) {
+ this._currentScale = val;
+ this._currentScaleValue = val.toString();
+ return;
+ }
+ this._setScale(val, false);
+ },
+
+ /**
+ * @returns {string}
+ */
+ get currentScaleValue() {
+ return this._currentScaleValue;
+ },
+
+ /**
+ * @param val - The scale of the pages (in percent or predefined value).
+ */
+ set currentScaleValue(val) {
+ if (!this.pdfDocument) {
+ this._currentScale = isNaN(val) ? UNKNOWN_SCALE : val;
+ this._currentScaleValue = val;
+ return;
+ }
+ this._setScale(val, false);
+ },
+
+ /**
+ * @returns {number}
+ */
+ get pagesRotation() {
+ return this._pagesRotation;
+ },
+
+ /**
+ * @param {number} rotation - The rotation of the pages (0, 90, 180, 270).
+ */
+ set pagesRotation(rotation) {
+ this._pagesRotation = rotation;
+
+ for (var i = 0, l = this.pages.length; i < l; i++) {
+ var page = this.pages[i];
+ page.update(page.scale, rotation);
+ }
+
+ this._setScale(this._currentScaleValue, true);
+ },
+
+ /**
+ * @param pdfDocument {PDFDocument}
+ */
+ setDocument: function (pdfDocument) {
+ if (this.pdfDocument) {
+ this._resetView();
+ }
+
+ this.pdfDocument = pdfDocument;
+ if (!pdfDocument) {
+ return;
+ }
+
+ var pagesCount = pdfDocument.numPages;
+ var pagesRefMap = this.pagesRefMap = {};
+ var self = this;
+
+ var resolvePagesPromise;
+ var pagesPromise = new Promise(function (resolve) {
+ resolvePagesPromise = resolve;
+ });
+ this.pagesPromise = pagesPromise;
+ pagesPromise.then(function () {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('pagesloaded', true, true, {
+ pagesCount: pagesCount
+ });
+ self.container.dispatchEvent(event);
+ });
+
+ var isOnePageRenderedResolved = false;
+ var resolveOnePageRendered = null;
+ var onePageRendered = new Promise(function (resolve) {
+ resolveOnePageRendered = resolve;
+ });
+ this.onePageRendered = onePageRendered;
+
+ var bindOnAfterDraw = function (pageView) {
+ // when page is painted, using the image as thumbnail base
+ pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
+ if (!isOnePageRenderedResolved) {
+ isOnePageRenderedResolved = true;
+ resolveOnePageRendered();
+ }
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('pagerendered', true, true, {
+ pageNumber: pageView.id
+ });
+ self.container.dispatchEvent(event);
+ };
+ };
+
+ var firstPagePromise = pdfDocument.getPage(1);
+ this.firstPagePromise = firstPagePromise;
+
+ // Fetch a single page so we can get a viewport that will be the default
+ // viewport for all pages
+ return firstPagePromise.then(function(pdfPage) {
+ var scale = this._currentScale || 1.0;
+ var viewport = pdfPage.getViewport(scale * CSS_UNITS);
+ for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+ var pageSource = new PDFPageSource(pdfDocument, pageNum);
+ var pageView = new PageView(this.viewer, pageNum, scale,
+ viewport.clone(), this.linkService,
+ this.renderingQueue, this.cache,
+ pageSource, this);
+ bindOnAfterDraw(pageView);
+ this.pages.push(pageView);
+ }
+
+ // Fetch all the pages since the viewport is needed before printing
+ // starts to create the correct size canvas. Wait until one page is
+ // rendered so we don't tie up too many resources early on.
+ onePageRendered.then(function () {
+ if (!PDFJS.disableAutoFetch) {
+ var getPagesLeft = pagesCount;
+ for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+ pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
+ var pageView = self.pages[pageNum - 1];
+ if (!pageView.pdfPage) {
+ pageView.setPdfPage(pdfPage);
+ }
+ var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
+ pagesRefMap[refStr] = pageNum;
+ getPagesLeft--;
+ if (!getPagesLeft) {
+ resolvePagesPromise();
+ }
+ }.bind(null, pageNum));
+ }
+ } else {
+ // XXX: Printing is semi-broken with auto fetch disabled.
+ resolvePagesPromise();
+ }
+ });
+
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('pagesinit', true, true, null);
+ self.container.dispatchEvent(event);
+
+ if (this.defaultRenderingQueue) {
+ this.update();
+ }
+ }.bind(this));
+ },
+
+ _resetView: function () {
+ this.cache = new Cache(DEFAULT_CACHE_SIZE);
+ this.pages = [];
+ this._currentPageNumber = 1;
+ this._currentScale = UNKNOWN_SCALE;
+ this._currentScaleValue = null;
+ this.location = null;
+ this._pagesRotation = 0;
+
+ var container = this.viewer;
+ while (container.hasChildNodes()) {
+ container.removeChild(container.lastChild);
+ }
+ },
+
+ _scrollUpdate: function () {
+ this.lastScroll = Date.now();
+
+ if (this.pagesCount === 0) {
+ return;
+ }
+ this.update();
+ },
+
+ _setScaleUpdatePages: function pdfViewer_setScaleUpdatePages(
+ newScale, newValue, noScroll, preset) {
+ this._currentScaleValue = newValue;
+ if (newScale === this._currentScale) {
+ return;
+ }
+ for (var i = 0, ii = this.pages.length; i < ii; i++) {
+ this.pages[i].update(newScale);
+ }
+ this._currentScale = newScale;
+
+ if (!noScroll) {
+ var page = this._currentPageNumber, dest;
+ var inPresentationMode =
+ this.presentationModeState === PresentationModeState.CHANGING ||
+ this.presentationModeState === PresentationModeState.FULLSCREEN;
+ if (this.location && !inPresentationMode &&
+ !IGNORE_CURRENT_POSITION_ON_ZOOM) {
+ page = this.location.pageNumber;
+ dest = [null, { name: 'XYZ' }, this.location.left,
+ this.location.top, null];
+ }
+ this.scrollPageIntoView(page, dest);
+ }
+
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('scalechange', true, true, window, 0);
+ event.scale = newScale;
+ if (preset) {
+ event.presetValue = newValue;
+ }
+ this.container.dispatchEvent(event);
+ },
+
+ _setScale: function pdfViewer_setScale(value, noScroll) {
+ if (value === 'custom') {
+ return;
+ }
+ var scale = parseFloat(value);
+
+ if (scale > 0) {
+ this._setScaleUpdatePages(scale, value, noScroll, false);
+ } else {
+ var currentPage = this.pages[this._currentPageNumber - 1];
+ if (!currentPage) {
+ return;
+ }
+ var inPresentationMode =
+ this.presentationModeState === PresentationModeState.FULLSCREEN;
+ var hPadding = inPresentationMode ? 0 : SCROLLBAR_PADDING;
+ var vPadding = inPresentationMode ? 0 : VERTICAL_PADDING;
+ var pageWidthScale = (this.container.clientWidth - hPadding) /
+ currentPage.width * currentPage.scale;
+ var pageHeightScale = (this.container.clientHeight - vPadding) /
+ currentPage.height * currentPage.scale;
+ switch (value) {
+ case 'page-actual':
+ scale = 1;
+ break;
+ case 'page-width':
+ scale = pageWidthScale;
+ break;
+ case 'page-height':
+ scale = pageHeightScale;
+ break;
+ case 'page-fit':
+ scale = Math.min(pageWidthScale, pageHeightScale);
+ break;
+ case 'auto':
+ var isLandscape = (currentPage.width > currentPage.height);
+ // For pages in landscape mode, fit the page height to the viewer
+ // *unless* the page would thus become too wide to fit horizontally.
+ var horizontalScale = isLandscape ?
+ Math.min(pageHeightScale, pageWidthScale) : pageWidthScale;
+ scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
+ break;
+ default:
+ console.error('pdfViewSetScale: \'' + value +
+ '\' is an unknown zoom value.');
+ return;
+ }
+ this._setScaleUpdatePages(scale, value, noScroll, true);
+ }
+ },
+
+ /**
+ * Scrolls page into view.
+ * @param {number} pageNumber
+ * @param {Array} dest - (optional) original PDF destination array:
+ *
+ */
+ scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber,
+ dest) {
+ var pageView = this.pages[pageNumber - 1];
+ var pageViewDiv = pageView.el;
+
+ if (this.presentationModeState ===
+ PresentationModeState.FULLSCREEN) {
+ if (this.linkService.page !== pageView.id) {
+ // Avoid breaking getVisiblePages in presentation mode.
+ this.linkService.page = pageView.id;
+ return;
+ }
+ dest = null;
+ // Fixes the case when PDF has different page sizes.
+ this._setScale(this.currentScaleValue, true);
+ }
+ if (!dest) {
+ scrollIntoView(pageViewDiv);
+ return;
+ }
+
+ var x = 0, y = 0;
+ var width = 0, height = 0, widthScale, heightScale;
+ var changeOrientation = (pageView.rotation % 180 === 0 ? false : true);
+ var pageWidth = (changeOrientation ? pageView.height : pageView.width) /
+ pageView.scale / CSS_UNITS;
+ var pageHeight = (changeOrientation ? pageView.width : pageView.height) /
+ pageView.scale / CSS_UNITS;
+ var scale = 0;
+ switch (dest[1].name) {
+ case 'XYZ':
+ x = dest[2];
+ y = dest[3];
+ scale = dest[4];
+ // If x and/or y coordinates are not supplied, default to
+ // _top_ left of the page (not the obvious bottom left,
+ // since aligning the bottom of the intended page with the
+ // top of the window is rarely helpful).
+ x = x !== null ? x : 0;
+ y = y !== null ? y : pageHeight;
+ break;
+ case 'Fit':
+ case 'FitB':
+ scale = 'page-fit';
+ break;
+ case 'FitH':
+ case 'FitBH':
+ y = dest[2];
+ scale = 'page-width';
+ break;
+ case 'FitV':
+ case 'FitBV':
+ x = dest[2];
+ width = pageWidth;
+ height = pageHeight;
+ scale = 'page-height';
+ break;
+ case 'FitR':
+ x = dest[2];
+ y = dest[3];
+ width = dest[4] - x;
+ height = dest[5] - y;
+ var viewerContainer = this.container;
+ widthScale = (viewerContainer.clientWidth - SCROLLBAR_PADDING) /
+ width / CSS_UNITS;
+ heightScale = (viewerContainer.clientHeight - SCROLLBAR_PADDING) /
+ height / CSS_UNITS;
+ scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
+ break;
+ default:
+ return;
+ }
+
+ if (scale && scale !== this.currentScale) {
+ this.currentScaleValue = scale;
+ } else if (this.currentScale === UNKNOWN_SCALE) {
+ this.currentScaleValue = DEFAULT_SCALE;
+ }
+
+ if (scale === 'page-fit' && !dest[4]) {
+ scrollIntoView(pageViewDiv);
+ return;
+ }
+
+ var boundingRect = [
+ pageView.viewport.convertToViewportPoint(x, y),
+ pageView.viewport.convertToViewportPoint(x + width, y + height)
+ ];
+ var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
+ var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
+
+ scrollIntoView(pageViewDiv, { left: left, top: top });
+ },
+
+ _updateLocation: function (firstPage) {
+ var currentScale = this._currentScale;
+ var currentScaleValue = this._currentScaleValue;
+ var normalizedScaleValue =
+ parseFloat(currentScaleValue) === currentScale ?
+ Math.round(currentScale * 10000) / 100 : currentScaleValue;
+
+ var pageNumber = firstPage.id;
+ var pdfOpenParams = '#page=' + pageNumber;
+ pdfOpenParams += '&zoom=' + normalizedScaleValue;
+ var currentPageView = this.pages[pageNumber - 1];
+ var container = this.container;
+ var topLeft = currentPageView.getPagePoint(
+ (container.scrollLeft - firstPage.x),
+ (container.scrollTop - firstPage.y));
+ var intLeft = Math.round(topLeft[0]);
+ var intTop = Math.round(topLeft[1]);
+ pdfOpenParams += ',' + intLeft + ',' + intTop;
+
+ this.location = {
+ pageNumber: pageNumber,
+ scale: normalizedScaleValue,
+ top: intTop,
+ left: intLeft,
+ pdfOpenParams: pdfOpenParams
+ };
+ },
+
+ update: function () {
+ var visible = this._getVisiblePages();
+ var visiblePages = visible.views;
+ if (visiblePages.length === 0) {
+ return;
+ }
+
+ this.updateInProgress = true;
+
+ var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
+ 2 * visiblePages.length + 1);
+ this.cache.resize(suggestedCacheSize);
+
+ this.renderingQueue.renderHighestPriority(visible);
+
+ var currentId = this.currentPageNumber;
+ var firstPage = visible.first;
+
+ for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
+ i < ii; ++i) {
+ var page = visiblePages[i];
+
+ if (page.percent < 100) {
+ break;
+ }
+ if (page.id === currentId) {
+ stillFullyVisible = true;
+ break;
+ }
+ }
+
+ if (!stillFullyVisible) {
+ currentId = visiblePages[0].id;
+ }
+
+ if (this.presentationModeState !== PresentationModeState.FULLSCREEN) {
+ this.currentPageNumber = currentId;
+ }
+
+ this._updateLocation(firstPage);
+
+ this.updateInProgress = false;
+
+ var event = document.createEvent('UIEvents');
+ event.initUIEvent('updateviewarea', true, true, window, 0);
+ this.container.dispatchEvent(event);
+ },
+
+ containsElement: function (element) {
+ return this.container.contains(element);
+ },
+
+ focus: function () {
+ this.container.focus();
+ },
+
+ blur: function () {
+ this.container.blur();
+ },
+
+ get isHorizontalScrollbarEnabled() {
+ return (this.presentationModeState === PresentationModeState.FULLSCREEN ?
+ false : (this.container.scrollWidth > this.container.clientWidth));
+ },
+
+ _getVisiblePages: function () {
+ if (this.presentationModeState !== PresentationModeState.FULLSCREEN) {
+ return getVisibleElements(this.container, this.pages, true);
+ } else {
+ // The algorithm in getVisibleElements doesn't work in all browsers and
+ // configurations when presentation mode is active.
+ var visible = [];
+ var currentPage = this.pages[this._currentPageNumber - 1];
+ visible.push({ id: currentPage.id, view: currentPage });
+ return { first: currentPage, last: currentPage, views: visible };
+ }
+ },
+
+ cleanup: function () {
+ for (var i = 0, ii = this.pages.length; i < ii; i++) {
+ if (this.pages[i] &&
+ this.pages[i].renderingState !== RenderingStates.FINISHED) {
+ this.pages[i].reset();
+ }
+ }
+ },
+
+ forceRendering: function (currentlyVisiblePages) {
+ var visiblePages = currentlyVisiblePages || this._getVisiblePages();
+ var pageView = this.renderingQueue.getHighestPriority(visiblePages,
+ this.pages,
+ this.scroll.down);
+ if (pageView) {
+ this.renderingQueue.renderView(pageView);
+ return true;
+ }
+ return false;
+ },
+
+ getPageTextContent: function (pageIndex) {
+ return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
+ return page.getTextContent();
+ });
+ },
+
+ /**
+ * @param textLayerDiv {HTMLDivElement}
+ * @param pageIndex {number}
+ * @param viewport {PageViewport}
+ * @returns {TextLayerBuilder}
+ */
+ createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
+ var isViewerInPresentationMode =
+ this.presentationModeState === PresentationModeState.FULLSCREEN;
+ return new TextLayerBuilder({
+ textLayerDiv: textLayerDiv,
+ pageIndex: pageIndex,
+ viewport: viewport,
+ lastScrollSource: this,
+ isViewerInPresentationMode: isViewerInPresentationMode,
+ findController: this.findController
+ });
+ },
+
+ setFindController: function (findController) {
+ this.findController = findController;
+ },
+ };
+
+ return PDFViewer;
+})();
+
+var SimpleLinkService = (function SimpleLinkServiceClosure() {
+ function SimpleLinkService(pdfViewer) {
+ this.pdfViewer = pdfViewer;
+ }
+ SimpleLinkService.prototype = {
+ /**
+ * @returns {number}
+ */
+ get page() {
+ return this.pdfViewer.currentPageNumber;
+ },
+ /**
+ * @param {number} value
+ */
+ set page(value) {
+ this.pdfViewer.currentPageNumber = value;
+ },
+ /**
+ * @param dest - The PDF destination object.
+ */
+ navigateTo: function (dest) {},
+ /**
+ * @param dest - The PDF destination object.
+ * @returns {string} The hyperlink to the PDF object.
+ */
+ getDestinationHash: function (dest) {
+ return '#';
+ },
+ /**
+ * @param hash - The PDF parameters/hash.
+ * @returns {string} The hyperlink to the PDF object.
+ */
+ getAnchorUrl: function (hash) {
+ return '#';
+ },
+ /**
+ * @param {string} hash
+ */
+ setHash: function (hash) {},
+ /**
+ * @param {string} action
+ */
+ executeNamedAction: function (action) {},
+ };
+ return SimpleLinkService;
+})();
+
+/**
+ * PDFPage object source.
+ * @class
+ */
+var PDFPageSource = (function PDFPageSourceClosure() {
+ /**
+ * @constructs
+ * @param {PDFDocument} pdfDocument
+ * @param {number} pageNumber
+ * @constructor
+ */
+ function PDFPageSource(pdfDocument, pageNumber) {
+ this.pdfDocument = pdfDocument;
+ this.pageNumber = pageNumber;
+ }
+
+ PDFPageSource.prototype = /** @lends PDFPageSource.prototype */ {
+ /**
+ * @returns {Promise}
+ */
+ getPage: function () {
+ return this.pdfDocument.getPage(this.pageNumber);
+ }
+ };
+
+ return PDFPageSource;
+})();
+
+
+var PDFViewerApplication = {
+ initialBookmark: document.location.hash.substring(1),
+ initialized: false,
+ fellback: false,
+ pdfDocument: null,
+ sidebarOpen: false,
+ printing: false,
+ /** @type {PDFViewer} */
+ pdfViewer: null,
+ /** @type {PDFThumbnailViewer} */
+ pdfThumbnailViewer: null,
+ /** @type {PDFRenderingQueue} */
+ pdfRenderingQueue: null,
+ pageRotation: 0,
+ updateScaleControls: true,
+ isInitialViewSet: false,
+ animationStartedPromise: null,
+ mouseScrollTimeStamp: 0,
+ mouseScrollDelta: 0,
+ preferenceSidebarViewOnLoad: SidebarView.NONE,
+ preferencePdfBugEnabled: false,
+ isViewerEmbedded: (window.parent !== window),
+ url: '',
+
+ // called once when the document is loaded
+ initialize: function pdfViewInitialize() {
+ var pdfRenderingQueue = new PDFRenderingQueue();
+ pdfRenderingQueue.onIdle = this.cleanup.bind(this);
+ this.pdfRenderingQueue = pdfRenderingQueue;
+
+ var container = document.getElementById('viewerContainer');
+ var viewer = document.getElementById('viewer');
+ this.pdfViewer = new PDFViewer({
+ container: container,
+ viewer: viewer,
+ renderingQueue: pdfRenderingQueue,
+ linkService: this
+ });
+ pdfRenderingQueue.setViewer(this.pdfViewer);
+
+ var thumbnailContainer = document.getElementById('thumbnailView');
+ this.pdfThumbnailViewer = new PDFThumbnailViewer({
+ container: thumbnailContainer,
+ renderingQueue: pdfRenderingQueue,
+ linkService: this
+ });
+ pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
+
+ Preferences.initialize();
+
+ this.findController = new PDFFindController({
+ pdfViewer: this.pdfViewer,
+ integratedFind: this.supportsIntegratedFind
+ });
+ this.pdfViewer.setFindController(this.findController);
+
+ this.findBar = new PDFFindBar({
+ bar: document.getElementById('findbar'),
+ toggleButton: document.getElementById('viewFind'),
+ findField: document.getElementById('findInput'),
+ highlightAllCheckbox: document.getElementById('findHighlightAll'),
+ caseSensitiveCheckbox: document.getElementById('findMatchCase'),
+ findMsg: document.getElementById('findMsg'),
+ findStatusIcon: document.getElementById('findStatusIcon'),
+ findPreviousButton: document.getElementById('findPrevious'),
+ findNextButton: document.getElementById('findNext'),
+ findController: this.findController
+ });
+
+ this.findController.setFindBar(this.findBar);
+
+ HandTool.initialize({
+ container: container,
+ toggleHandTool: document.getElementById('toggleHandTool')
+ });
+
+ SecondaryToolbar.initialize({
+ toolbar: document.getElementById('secondaryToolbar'),
+ presentationMode: PresentationMode,
+ toggleButton: document.getElementById('secondaryToolbarToggle'),
+ presentationModeButton:
+ document.getElementById('secondaryPresentationMode'),
+ openFile: document.getElementById('secondaryOpenFile'),
+ print: document.getElementById('secondaryPrint'),
+ download: document.getElementById('secondaryDownload'),
+ viewBookmark: document.getElementById('secondaryViewBookmark'),
+ firstPage: document.getElementById('firstPage'),
+ lastPage: document.getElementById('lastPage'),
+ pageRotateCw: document.getElementById('pageRotateCw'),
+ pageRotateCcw: document.getElementById('pageRotateCcw'),
+ documentProperties: DocumentProperties,
+ documentPropertiesButton: document.getElementById('documentProperties')
+ });
+
+ PresentationMode.initialize({
+ container: container,
+ secondaryToolbar: SecondaryToolbar,
+ firstPage: document.getElementById('contextFirstPage'),
+ lastPage: document.getElementById('contextLastPage'),
+ pageRotateCw: document.getElementById('contextPageRotateCw'),
+ pageRotateCcw: document.getElementById('contextPageRotateCcw')
+ });
+
+ PasswordPrompt.initialize({
+ overlayName: 'passwordOverlay',
+ passwordField: document.getElementById('password'),
+ passwordText: document.getElementById('passwordText'),
+ passwordSubmit: document.getElementById('passwordSubmit'),
+ passwordCancel: document.getElementById('passwordCancel')
+ });
+
+ DocumentProperties.initialize({
+ overlayName: 'documentPropertiesOverlay',
+ closeButton: document.getElementById('documentPropertiesClose'),
+ fileNameField: document.getElementById('fileNameField'),
+ fileSizeField: document.getElementById('fileSizeField'),
+ titleField: document.getElementById('titleField'),
+ authorField: document.getElementById('authorField'),
+ subjectField: document.getElementById('subjectField'),
+ keywordsField: document.getElementById('keywordsField'),
+ creationDateField: document.getElementById('creationDateField'),
+ modificationDateField: document.getElementById('modificationDateField'),
+ creatorField: document.getElementById('creatorField'),
+ producerField: document.getElementById('producerField'),
+ versionField: document.getElementById('versionField'),
+ pageCountField: document.getElementById('pageCountField')
+ });
+
+ var self = this;
+ var initializedPromise = Promise.all([
+ Preferences.get('enableWebGL').then(function resolved(value) {
+ PDFJS.disableWebGL = !value;
+ }),
+ Preferences.get('sidebarViewOnLoad').then(function resolved(value) {
+ self.preferenceSidebarViewOnLoad = value;
+ }),
+ Preferences.get('pdfBugEnabled').then(function resolved(value) {
+ self.preferencePdfBugEnabled = value;
+ }),
+ Preferences.get('disableTextLayer').then(function resolved(value) {
+ if (PDFJS.disableTextLayer === true) {
+ return;
+ }
+ PDFJS.disableTextLayer = value;
+ }),
+ Preferences.get('disableRange').then(function resolved(value) {
+ if (PDFJS.disableRange === true) {
+ return;
+ }
+ PDFJS.disableRange = value;
+ }),
+ Preferences.get('disableAutoFetch').then(function resolved(value) {
+ PDFJS.disableAutoFetch = value;
+ }),
+ Preferences.get('disableFontFace').then(function resolved(value) {
+ if (PDFJS.disableFontFace === true) {
+ return;
+ }
+ PDFJS.disableFontFace = value;
+ }),
+ Preferences.get('useOnlyCssZoom').then(function resolved(value) {
+ PDFJS.useOnlyCssZoom = value;
+ })
+ // TODO move more preferences and other async stuff here
+ ]).catch(function (reason) { });
+
+ return initializedPromise.then(function () {
+ PDFViewerApplication.initialized = true;
+ });
+ },
+
+ zoomIn: function pdfViewZoomIn(ticks) {
+ var newScale = this.pdfViewer.currentScale;
+ do {
+ newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
+ newScale = Math.ceil(newScale * 10) / 10;
+ newScale = Math.min(MAX_SCALE, newScale);
+ } while (--ticks && newScale < MAX_SCALE);
+ this.setScale(newScale, true);
+ },
+
+ zoomOut: function pdfViewZoomOut(ticks) {
+ var newScale = this.pdfViewer.currentScale;
+ do {
+ newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
+ newScale = Math.floor(newScale * 10) / 10;
+ newScale = Math.max(MIN_SCALE, newScale);
+ } while (--ticks && newScale > MIN_SCALE);
+ this.setScale(newScale, true);
+ },
+
+ get currentScaleValue() {
+ return this.pdfViewer.currentScaleValue;
+ },
+
+ get pagesCount() {
+ return this.pdfDocument.numPages;
+ },
+
+ set page(val) {
+ this.pdfViewer.currentPageNumber = val;
+ },
+
+ get page() {
+ return this.pdfViewer.currentPageNumber;
+ },
+
+ get supportsPrinting() {
+ var canvas = document.createElement('canvas');
+ var value = 'mozPrintCallback' in canvas;
+ // shadow
+ Object.defineProperty(this, 'supportsPrinting', { value: value,
+ enumerable: true,
+ configurable: true,
+ writable: false });
+ return value;
+ },
+
+ get supportsFullscreen() {
+ var doc = document.documentElement;
+ var support = doc.requestFullscreen || doc.mozRequestFullScreen ||
+ doc.webkitRequestFullScreen || doc.msRequestFullscreen;
+
+ if (document.fullscreenEnabled === false ||
+ document.mozFullScreenEnabled === false ||
+ document.webkitFullscreenEnabled === false ||
+ document.msFullscreenEnabled === false) {
+ support = false;
+ }
+
+ Object.defineProperty(this, 'supportsFullscreen', { value: support,
+ enumerable: true,
+ configurable: true,
+ writable: false });
+ return support;
+ },
+
+ get supportsIntegratedFind() {
+ var support = false;
+ support = FirefoxCom.requestSync('supportsIntegratedFind');
+ Object.defineProperty(this, 'supportsIntegratedFind', { value: support,
+ enumerable: true,
+ configurable: true,
+ writable: false });
+ return support;
+ },
+
+ get supportsDocumentFonts() {
+ var support = true;
+ support = FirefoxCom.requestSync('supportsDocumentFonts');
+ Object.defineProperty(this, 'supportsDocumentFonts', { value: support,
+ enumerable: true,
+ configurable: true,
+ writable: false });
+ return support;
+ },
+
+ get supportsDocumentColors() {
+ var support = true;
+ support = FirefoxCom.requestSync('supportsDocumentColors');
+ Object.defineProperty(this, 'supportsDocumentColors', { value: support,
+ enumerable: true,
+ configurable: true,
+ writable: false });
+ return support;
+ },
+
+ get loadingBar() {
+ var bar = new ProgressBar('#loadingBar', {});
+ Object.defineProperty(this, 'loadingBar', { value: bar,
+ enumerable: true,
+ configurable: true,
+ writable: false });
+ return bar;
+ },
+
+ initPassiveLoading: function pdfViewInitPassiveLoading() {
+ var pdfDataRangeTransportReadyResolve;
+ var pdfDataRangeTransportReady = new Promise(function (resolve) {
+ pdfDataRangeTransportReadyResolve = resolve;
+ });
+ var pdfDataRangeTransport = {
+ rangeListeners: [],
+ progressListeners: [],
+ progressiveReadListeners: [],
+ ready: pdfDataRangeTransportReady,
+
+ addRangeListener: function PdfDataRangeTransport_addRangeListener(
+ listener) {
+ this.rangeListeners.push(listener);
+ },
+
+ addProgressListener: function PdfDataRangeTransport_addProgressListener(
+ listener) {
+ this.progressListeners.push(listener);
+ },
+
+ addProgressiveReadListener:
+ function PdfDataRangeTransport_addProgressiveReadListener(listener) {
+ this.progressiveReadListeners.push(listener);
+ },
+
+ onDataRange: function PdfDataRangeTransport_onDataRange(begin, chunk) {
+ var listeners = this.rangeListeners;
+ for (var i = 0, n = listeners.length; i < n; ++i) {
+ listeners[i](begin, chunk);
+ }
+ },
+
+ onDataProgress: function PdfDataRangeTransport_onDataProgress(loaded) {
+ this.ready.then(function () {
+ var listeners = this.progressListeners;
+ for (var i = 0, n = listeners.length; i < n; ++i) {
+ listeners[i](loaded);
+ }
+ }.bind(this));
+ },
+
+ onDataProgressiveRead:
+ function PdfDataRangeTransport_onDataProgress(chunk) {
+ this.ready.then(function () {
+ var listeners = this.progressiveReadListeners;
+ for (var i = 0, n = listeners.length; i < n; ++i) {
+ listeners[i](chunk);
+ }
+ }.bind(this));
+ },
+
+ transportReady: function PdfDataRangeTransport_transportReady() {
+ pdfDataRangeTransportReadyResolve();
+ },
+
+ requestDataRange: function PdfDataRangeTransport_requestDataRange(
+ begin, end) {
+ FirefoxCom.request('requestDataRange', { begin: begin, end: end });
+ }
+ };
+
+ window.addEventListener('message', function windowMessage(e) {
+ if (e.source !== null) {
+ // The message MUST originate from Chrome code.
+ console.warn('Rejected untrusted message from ' + e.origin);
+ return;
+ }
+ var args = e.data;
+
+ if (typeof args !== 'object' || !('pdfjsLoadAction' in args)) {
+ return;
+ }
+ switch (args.pdfjsLoadAction) {
+ case 'supportsRangedLoading':
+ PDFViewerApplication.open(args.pdfUrl, 0, undefined,
+ pdfDataRangeTransport, {
+ length: args.length,
+ initialData: args.data
+ });
+ break;
+ case 'range':
+ pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
+ break;
+ case 'rangeProgress':
+ pdfDataRangeTransport.onDataProgress(args.loaded);
+ break;
+ case 'progressiveRead':
+ pdfDataRangeTransport.onDataProgressiveRead(args.chunk);
+ break;
+ case 'progress':
+ PDFViewerApplication.progress(args.loaded / args.total);
+ break;
+ case 'complete':
+ if (!args.data) {
+ PDFViewerApplication.error(mozL10n.get('loading_error', null,
+ 'An error occurred while loading the PDF.'), e);
+ break;
+ }
+ PDFViewerApplication.open(args.data, 0);
+ break;
+ }
+ });
+ FirefoxCom.requestSync('initPassiveLoading', null);
+ },
+
+ setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
+ this.url = url;
+ try {
+ this.setTitle(decodeURIComponent(getFileName(url)) || url);
+ } catch (e) {
+ // decodeURIComponent may throw URIError,
+ // fall back to using the unprocessed url in that case
+ this.setTitle(url);
+ }
+ },
+
+ setTitle: function pdfViewSetTitle(title) {
+ document.title = title;
+ },
+
+ close: function pdfViewClose() {
+ var errorWrapper = document.getElementById('errorWrapper');
+ errorWrapper.setAttribute('hidden', 'true');
+
+ if (!this.pdfDocument) {
+ return;
+ }
+
+ this.pdfDocument.destroy();
+ this.pdfDocument = null;
+
+ this.pdfThumbnailViewer.setDocument(null);
+ this.pdfViewer.setDocument(null);
+
+ if (typeof PDFBug !== 'undefined') {
+ PDFBug.cleanup();
+ }
+ },
+
+ // TODO(mack): This function signature should really be pdfViewOpen(url, args)
+ open: function pdfViewOpen(file, scale, password,
+ pdfDataRangeTransport, args) {
+ if (this.pdfDocument) {
+ // Reload the preferences if a document was previously opened.
+ Preferences.reload();
+ }
+ this.close();
+
+ var parameters = {password: password};
+ if (typeof file === 'string') { // URL
+ this.setTitleUsingUrl(file);
+ parameters.url = file;
+ } else if (file && 'byteLength' in file) { // ArrayBuffer
+ parameters.data = file;
+ } else if (file.url && file.originalUrl) {
+ this.setTitleUsingUrl(file.originalUrl);
+ parameters.url = file.url;
+ }
+ if (args) {
+ for (var prop in args) {
+ parameters[prop] = args[prop];
+ }
+ }
+
+ var self = this;
+ self.loading = true;
+ self.downloadComplete = false;
+
+ var passwordNeeded = function passwordNeeded(updatePassword, reason) {
+ PasswordPrompt.updatePassword = updatePassword;
+ PasswordPrompt.reason = reason;
+ PasswordPrompt.open();
+ };
+
+ function getDocumentProgress(progressData) {
+ self.progress(progressData.loaded / progressData.total);
+ }
+
+ PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded,
+ getDocumentProgress).then(
+ function getDocumentCallback(pdfDocument) {
+ self.load(pdfDocument, scale);
+ self.loading = false;
+ },
+ function getDocumentError(exception) {
+ var message = exception && exception.message;
+ var loadingErrorMessage = mozL10n.get('loading_error', null,
+ 'An error occurred while loading the PDF.');
+
+ if (exception instanceof PDFJS.InvalidPDFException) {
+ // change error message also for other builds
+ loadingErrorMessage = mozL10n.get('invalid_file_error', null,
+ 'Invalid or corrupted PDF file.');
+ } else if (exception instanceof PDFJS.MissingPDFException) {
+ // special message for missing PDF's
+ loadingErrorMessage = mozL10n.get('missing_file_error', null,
+ 'Missing PDF file.');
+ } else if (exception instanceof PDFJS.UnexpectedResponseException) {
+ loadingErrorMessage = mozL10n.get('unexpected_response_error', null,
+ 'Unexpected server response.');
+ }
+
+ var moreInfo = {
+ message: message
+ };
+ self.error(loadingErrorMessage, moreInfo);
+ self.loading = false;
+ }
+ );
+
+ if (args && args.length) {
+ DocumentProperties.setFileSize(args.length);
+ }
+ },
+
+ download: function pdfViewDownload() {
+ function downloadByUrl() {
+ downloadManager.downloadUrl(url, filename);
+ }
+
+ var url = this.url.split('#')[0];
+ var filename = getPDFFileNameFromURL(url);
+ var downloadManager = new DownloadManager();
+ downloadManager.onerror = function (err) {
+ // This error won't really be helpful because it's likely the
+ // fallback won't work either (or is already open).
+ PDFViewerApplication.error('PDF failed to download.');
+ };
+
+ if (!this.pdfDocument) { // the PDF is not ready yet
+ downloadByUrl();
+ return;
+ }
+
+ if (!this.downloadComplete) { // the PDF is still downloading
+ downloadByUrl();
+ return;
+ }
+
+ this.pdfDocument.getData().then(
+ function getDataSuccess(data) {
+ var blob = PDFJS.createBlob(data, 'application/pdf');
+ downloadManager.download(blob, url, filename);
+ },
+ downloadByUrl // Error occurred try downloading with just the url.
+ ).then(null, downloadByUrl);
+ },
+
+ fallback: function pdfViewFallback(featureId) {
+ // Only trigger the fallback once so we don't spam the user with messages
+ // for one PDF.
+ if (this.fellback)
+ return;
+ this.fellback = true;
+ var url = this.url.split('#')[0];
+ FirefoxCom.request('fallback', { featureId: featureId, url: url },
+ function response(download) {
+ if (!download) {
+ return;
+ }
+ PDFViewerApplication.download();
+ });
+ },
+
+ navigateTo: function pdfViewNavigateTo(dest) {
+ var destString = '';
+ var self = this;
+
+ var goToDestination = function(destRef) {
+ self.pendingRefStr = null;
+ // dest array looks like that:
+ var pageNumber = destRef instanceof Object ?
+ self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
+ (destRef + 1);
+ if (pageNumber) {
+ if (pageNumber > self.pagesCount) {
+ pageNumber = self.pagesCount;
+ }
+ self.pdfViewer.scrollPageIntoView(pageNumber, dest);
+
+ // Update the browsing history.
+ PDFHistory.push({ dest: dest, hash: destString, page: pageNumber });
+ } else {
+ self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
+ var pageNum = pageIndex + 1;
+ self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum;
+ goToDestination(destRef);
+ });
+ }
+ };
+
+ var destinationPromise;
+ if (typeof dest === 'string') {
+ destString = dest;
+ destinationPromise = this.pdfDocument.getDestination(dest);
+ } else {
+ destinationPromise = Promise.resolve(dest);
+ }
+ destinationPromise.then(function(destination) {
+ dest = destination;
+ if (!(destination instanceof Array)) {
+ return; // invalid destination
+ }
+ goToDestination(destination[0]);
+ });
+ },
+
+ executeNamedAction: function pdfViewExecuteNamedAction(action) {
+ // See PDF reference, table 8.45 - Named action
+ switch (action) {
+ case 'GoToPage':
+ document.getElementById('pageNumber').focus();
+ break;
+
+ case 'GoBack':
+ PDFHistory.back();
+ break;
+
+ case 'GoForward':
+ PDFHistory.forward();
+ break;
+
+ case 'Find':
+ if (!this.supportsIntegratedFind) {
+ this.findBar.toggle();
+ }
+ break;
+
+ case 'NextPage':
+ this.page++;
+ break;
+
+ case 'PrevPage':
+ this.page--;
+ break;
+
+ case 'LastPage':
+ this.page = this.pagesCount;
+ break;
+
+ case 'FirstPage':
+ this.page = 1;
+ break;
+
+ default:
+ break; // No action according to spec
+ }
+ },
+
+ getDestinationHash: function pdfViewGetDestinationHash(dest) {
+ if (typeof dest === 'string') {
+ return this.getAnchorUrl('#' + escape(dest));
+ }
+ if (dest instanceof Array) {
+ var destRef = dest[0]; // see navigateTo method for dest format
+ var pageNumber = destRef instanceof Object ?
+ this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
+ (destRef + 1);
+ if (pageNumber) {
+ var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
+ var destKind = dest[1];
+ if (typeof destKind === 'object' && 'name' in destKind &&
+ destKind.name === 'XYZ') {
+ var scale = (dest[4] || this.currentScaleValue);
+ var scaleNumber = parseFloat(scale);
+ if (scaleNumber) {
+ scale = scaleNumber * 100;
+ }
+ pdfOpenParams += '&zoom=' + scale;
+ if (dest[2] || dest[3]) {
+ pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
+ }
+ }
+ return pdfOpenParams;
+ }
+ }
+ return '';
+ },
+
+ /**
+ * Prefix the full url on anchor links to make sure that links are resolved
+ * relative to the current URL instead of the one defined in .
+ * @param {String} anchor The anchor hash, including the #.
+ */
+ getAnchorUrl: function getAnchorUrl(anchor) {
+ return this.url.split('#')[0] + anchor;
+ },
+
+ /**
+ * Show the error box.
+ * @param {String} message A message that is human readable.
+ * @param {Object} moreInfo (optional) Further information about the error
+ * that is more technical. Should have a 'message'
+ * and optionally a 'stack' property.
+ */
+ error: function pdfViewError(message, moreInfo) {
+ var moreInfoText = mozL10n.get('error_version_info',
+ {version: PDFJS.version || '?', build: PDFJS.build || '?'},
+ 'PDF.js v{{version}} (build: {{build}})') + '\n';
+ if (moreInfo) {
+ moreInfoText +=
+ mozL10n.get('error_message', {message: moreInfo.message},
+ 'Message: {{message}}');
+ if (moreInfo.stack) {
+ moreInfoText += '\n' +
+ mozL10n.get('error_stack', {stack: moreInfo.stack},
+ 'Stack: {{stack}}');
+ } else {
+ if (moreInfo.filename) {
+ moreInfoText += '\n' +
+ mozL10n.get('error_file', {file: moreInfo.filename},
+ 'File: {{file}}');
+ }
+ if (moreInfo.lineNumber) {
+ moreInfoText += '\n' +
+ mozL10n.get('error_line', {line: moreInfo.lineNumber},
+ 'Line: {{line}}');
+ }
+ }
+ }
+
+ console.error(message + '\n' + moreInfoText);
+ this.fallback();
+ },
+
+ progress: function pdfViewProgress(level) {
+ var percent = Math.round(level * 100);
+ // When we transition from full request to range requests, it's possible
+ // that we discard some of the loaded data. This can cause the loading
+ // bar to move backwards. So prevent this by only updating the bar if it
+ // increases.
+ if (percent > this.loadingBar.percent || isNaN(percent)) {
+ this.loadingBar.percent = percent;
+ }
+ },
+
+ load: function pdfViewLoad(pdfDocument, scale) {
+ var self = this;
+ scale = scale || UNKNOWN_SCALE;
+
+ this.findController.reset();
+
+ this.pdfDocument = pdfDocument;
+
+ DocumentProperties.url = this.url;
+ DocumentProperties.pdfDocument = pdfDocument;
+ DocumentProperties.resolveDataAvailable();
+
+ var downloadedPromise = pdfDocument.getDownloadInfo().then(function() {
+ self.downloadComplete = true;
+ self.loadingBar.hide();
+ var outerContainer = document.getElementById('outerContainer');
+ outerContainer.classList.remove('loadingInProgress');
+ });
+
+ var pagesCount = pdfDocument.numPages;
+ document.getElementById('numPages').textContent =
+ mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
+ document.getElementById('pageNumber').max = pagesCount;
+
+ var id = this.documentFingerprint = pdfDocument.fingerprint;
+ var store = this.store = new ViewHistory(id);
+
+ var pdfViewer = this.pdfViewer;
+ pdfViewer.currentScale = scale;
+ pdfViewer.setDocument(pdfDocument);
+ var firstPagePromise = pdfViewer.firstPagePromise;
+ var pagesPromise = pdfViewer.pagesPromise;
+ var onePageRendered = pdfViewer.onePageRendered;
+
+ this.pageRotation = 0;
+ this.isInitialViewSet = false;
+ this.pagesRefMap = pdfViewer.pagesRefMap;
+
+ this.pdfThumbnailViewer.setDocument(pdfDocument);
+
+ firstPagePromise.then(function(pdfPage) {
+ downloadedPromise.then(function () {
+ var event = document.createEvent('CustomEvent');
+ event.initCustomEvent('documentload', true, true, {});
+ window.dispatchEvent(event);
+ });
+
+ self.loadingBar.setWidth(document.getElementById('viewer'));
+
+ self.findController.resolveFirstPage();
+
+ if (!PDFJS.disableHistory && !self.isViewerEmbedded) {
+ // The browsing history is only enabled when the viewer is standalone,
+ // i.e. not when it is embedded in a web page.
+ PDFHistory.initialize(self.documentFingerprint, self);
+ }
+ });
+
+ // Fetch the necessary preference values.
+ var showPreviousViewOnLoad;
+ var showPreviousViewOnLoadPromise =
+ Preferences.get('showPreviousViewOnLoad').then(function (prefValue) {
+ showPreviousViewOnLoad = prefValue;
+ });
+ var defaultZoomValue;
+ var defaultZoomValuePromise =
+ Preferences.get('defaultZoomValue').then(function (prefValue) {
+ defaultZoomValue = prefValue;
+ });
+
+ var storePromise = store.initializedPromise;
+ Promise.all([firstPagePromise, storePromise, showPreviousViewOnLoadPromise,
+ defaultZoomValuePromise]).then(function resolved() {
+ var storedHash = null;
+ if (showPreviousViewOnLoad && store.get('exists', false)) {
+ var pageNum = store.get('page', '1');
+ var zoom = defaultZoomValue ||
+ store.get('zoom', self.pdfViewer.currentScale);
+ var left = store.get('scrollLeft', '0');
+ var top = store.get('scrollTop', '0');
+
+ storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
+ left + ',' + top;
+ } else if (defaultZoomValue) {
+ storedHash = 'page=1&zoom=' + defaultZoomValue;
+ }
+ self.setInitialView(storedHash, scale);
+
+ // Make all navigation keys work on document load,
+ // unless the viewer is embedded in a web page.
+ if (!self.isViewerEmbedded) {
+ self.pdfViewer.focus();
+ self.pdfViewer.blur();
+ }
+ }, function rejected(reason) {
+ console.error(reason);
+
+ firstPagePromise.then(function () {
+ self.setInitialView(null, scale);
+ });
+ });
+
+ pagesPromise.then(function() {
+ if (self.supportsPrinting) {
+ pdfDocument.getJavaScript().then(function(javaScript) {
+ if (javaScript.length) {
+ console.warn('Warning: JavaScript is not supported');
+ self.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript);
+ }
+ // Hack to support auto printing.
+ var regex = /\bprint\s*\(/g;
+ for (var i = 0, ii = javaScript.length; i < ii; i++) {
+ var js = javaScript[i];
+ if (js && regex.test(js)) {
+ setTimeout(function() {
+ window.print();
+ });
+ return;
+ }
+ }
+ });
+ }
+ });
+
+ // outline depends on pagesRefMap
+ var promises = [pagesPromise, this.animationStartedPromise];
+ Promise.all(promises).then(function() {
+ pdfDocument.getOutline().then(function(outline) {
+ var outlineView = document.getElementById('outlineView');
+ self.outline = new DocumentOutlineView({
+ outline: outline,
+ outlineView: outlineView,
+ linkService: self
+ });
+ document.getElementById('viewOutline').disabled = !outline;
+
+ if (!outline && !outlineView.classList.contains('hidden')) {
+ self.switchSidebarView('thumbs');
+ }
+ if (outline &&
+ self.preferenceSidebarViewOnLoad === SidebarView.OUTLINE) {
+ self.switchSidebarView('outline', true);
+ }
+ });
+ pdfDocument.getAttachments().then(function(attachments) {
+ var attachmentsView = document.getElementById('attachmentsView');
+ self.attachments = new DocumentAttachmentsView({
+ attachments: attachments,
+ attachmentsView: attachmentsView
+ });
+ document.getElementById('viewAttachments').disabled = !attachments;
+
+ if (!attachments && !attachmentsView.classList.contains('hidden')) {
+ self.switchSidebarView('thumbs');
+ }
+ if (attachments &&
+ self.preferenceSidebarViewOnLoad === SidebarView.ATTACHMENTS) {
+ self.switchSidebarView('attachments', true);
+ }
+ });
+ });
+
+ if (self.preferenceSidebarViewOnLoad === SidebarView.THUMBS) {
+ Promise.all([firstPagePromise, onePageRendered]).then(function () {
+ self.switchSidebarView('thumbs', true);
+ });
+ }
+
+ pdfDocument.getMetadata().then(function(data) {
+ var info = data.info, metadata = data.metadata;
+ self.documentInfo = info;
+ self.metadata = metadata;
+
+ // Provides some basic debug information
+ console.log('PDF ' + pdfDocument.fingerprint + ' [' +
+ info.PDFFormatVersion + ' ' + (info.Producer || '-').trim() +
+ ' / ' + (info.Creator || '-').trim() + ']' +
+ ' (PDF.js: ' + (PDFJS.version || '-') +
+ (!PDFJS.disableWebGL ? ' [WebGL]' : '') + ')');
+
+ var pdfTitle;
+ if (metadata && metadata.has('dc:title')) {
+ var title = metadata.get('dc:title');
+ // Ghostscript sometimes return 'Untitled', sets the title to 'Untitled'
+ if (title !== 'Untitled') {
+ pdfTitle = title;
+ }
+ }
+
+ if (!pdfTitle && info && info['Title']) {
+ pdfTitle = info['Title'];
+ }
+
+ if (pdfTitle) {
+ self.setTitle(pdfTitle + ' - ' + document.title);
+ }
+
+ if (info.IsAcroFormPresent) {
+ console.warn('Warning: AcroForm/XFA is not supported');
+ self.fallback(PDFJS.UNSUPPORTED_FEATURES.forms);
+ }
+
+ var versionId = String(info.PDFFormatVersion).slice(-1) | 0;
+ var generatorId = 0;
+ var KNOWN_GENERATORS = ["acrobat distiller", "acrobat pdfwritter",
+ "adobe livecycle", "adobe pdf library", "adobe photoshop", "ghostscript",
+ "tcpdf", "cairo", "dvipdfm", "dvips", "pdftex", "pdfkit", "itext",
+ "prince", "quarkxpress", "mac os x", "microsoft", "openoffice", "oracle",
+ "luradocument", "pdf-xchange", "antenna house", "aspose.cells", "fpdf"];
+ var generatorId = 0;
+ if (info.Producer) {
+ KNOWN_GENERATORS.some(function (generator, s, i) {
+ if (generator.indexOf(s) < 0) {
+ return false;
+ }
+ generatorId = i + 1;
+ return true;
+ }.bind(null, info.Producer.toLowerCase()));
+ }
+ var formType = !info.IsAcroFormPresent ? null : info.IsXFAPresent ?
+ 'xfa' : 'acroform';
+ FirefoxCom.request('reportTelemetry', JSON.stringify({
+ type: 'documentInfo',
+ version: versionId,
+ generator: generatorId,
+ formType: formType
+ }));
+ });
+ },
+
+ setInitialView: function pdfViewSetInitialView(storedHash, scale) {
+ this.isInitialViewSet = true;
+
+ // When opening a new file (when one is already loaded in the viewer):
+ // Reset 'currentPageNumber', since otherwise the page's scale will be wrong
+ // if 'currentPageNumber' is larger than the number of pages in the file.
+ document.getElementById('pageNumber').value =
+ this.pdfViewer.currentPageNumber = 1;
+
+ if (PDFHistory.initialDestination) {
+ this.navigateTo(PDFHistory.initialDestination);
+ PDFHistory.initialDestination = null;
+ } else if (this.initialBookmark) {
+ this.setHash(this.initialBookmark);
+ PDFHistory.push({ hash: this.initialBookmark }, !!this.initialBookmark);
+ this.initialBookmark = null;
+ } else if (storedHash) {
+ this.setHash(storedHash);
+ } else if (scale) {
+ this.setScale(scale, true);
+ this.page = 1;
+ }
+
+ if (this.pdfViewer.currentScale === UNKNOWN_SCALE) {
+ // Scale was not initialized: invalid bookmark or scale was not specified.
+ // Setting the default one.
+ this.setScale(DEFAULT_SCALE, true);
+ }
+ },
+
+ cleanup: function pdfViewCleanup() {
+ this.pdfViewer.cleanup();
+ this.pdfThumbnailViewer.cleanup();
+ this.pdfDocument.cleanup();
+ },
+
+ forceRendering: function pdfViewForceRendering() {
+ this.pdfRenderingQueue.printing = this.printing;
+ this.pdfRenderingQueue.isThumbnailViewEnabled = this.sidebarOpen;
+ this.pdfRenderingQueue.renderHighestPriority();
+ },
+
+ setHash: function pdfViewSetHash(hash) {
+ if (!this.isInitialViewSet) {
+ this.initialBookmark = hash;
+ return;
+ }
+
+ var validFitZoomValues = ['Fit','FitB','FitH','FitBH',
+ 'FitV','FitBV','FitR'];
+
+ if (!hash) {
+ return;
+ }
+
+ if (hash.indexOf('=') >= 0) {
+ var params = this.parseQueryString(hash);
+ // borrowing syntax from "Parameters for Opening PDF Files"
+ if ('nameddest' in params) {
+ PDFHistory.updateNextHashParam(params.nameddest);
+ this.navigateTo(params.nameddest);
+ return;
+ }
+ var pageNumber, dest;
+ if ('page' in params) {
+ pageNumber = (params.page | 0) || 1;
+ }
+ if ('zoom' in params) {
+ var zoomArgs = params.zoom.split(','); // scale,left,top
+ // building destination array
+
+ // If the zoom value, it has to get divided by 100. If it is a string,
+ // it should stay as it is.
+ var zoomArg = zoomArgs[0];
+ var zoomArgNumber = parseFloat(zoomArg);
+ var destName = 'XYZ';
+ if (zoomArgNumber) {
+ zoomArg = zoomArgNumber / 100;
+ } else if (validFitZoomValues.indexOf(zoomArg) >= 0) {
+ destName = zoomArg;
+ }
+ dest = [null, { name: destName },
+ zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
+ zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
+ zoomArg];
+ }
+ if (dest) {
+ this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
+ } else if (pageNumber) {
+ this.page = pageNumber; // simple page
+ }
+ if ('pagemode' in params) {
+ if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
+ params.pagemode === 'attachments') {
+ this.switchSidebarView((params.pagemode === 'bookmarks' ?
+ 'outline' : params.pagemode), true);
+ } else if (params.pagemode === 'none' && this.sidebarOpen) {
+ document.getElementById('sidebarToggle').click();
+ }
+ }
+ } else if (/^\d+$/.test(hash)) { // page number
+ this.page = hash;
+ } else { // named destination
+ PDFHistory.updateNextHashParam(unescape(hash));
+ this.navigateTo(unescape(hash));
+ }
+ },
+
+ switchSidebarView: function pdfViewSwitchSidebarView(view, openSidebar) {
+ if (openSidebar && !this.sidebarOpen) {
+ document.getElementById('sidebarToggle').click();
+ }
+ var thumbsView = document.getElementById('thumbnailView');
+ var outlineView = document.getElementById('outlineView');
+ var attachmentsView = document.getElementById('attachmentsView');
+
+ var thumbsButton = document.getElementById('viewThumbnail');
+ var outlineButton = document.getElementById('viewOutline');
+ var attachmentsButton = document.getElementById('viewAttachments');
+
+ switch (view) {
+ case 'thumbs':
+ var wasAnotherViewVisible = thumbsView.classList.contains('hidden');
+
+ thumbsButton.classList.add('toggled');
+ outlineButton.classList.remove('toggled');
+ attachmentsButton.classList.remove('toggled');
+ thumbsView.classList.remove('hidden');
+ outlineView.classList.add('hidden');
+ attachmentsView.classList.add('hidden');
+
+ this.forceRendering();
+
+ if (wasAnotherViewVisible) {
+ this.pdfThumbnailViewer.ensureThumbnailVisible(this.page);
+ }
+ break;
+
+ case 'outline':
+ thumbsButton.classList.remove('toggled');
+ outlineButton.classList.add('toggled');
+ attachmentsButton.classList.remove('toggled');
+ thumbsView.classList.add('hidden');
+ outlineView.classList.remove('hidden');
+ attachmentsView.classList.add('hidden');
+
+ if (outlineButton.getAttribute('disabled')) {
+ return;
+ }
+ break;
+
+ case 'attachments':
+ thumbsButton.classList.remove('toggled');
+ outlineButton.classList.remove('toggled');
+ attachmentsButton.classList.add('toggled');
+ thumbsView.classList.add('hidden');
+ outlineView.classList.add('hidden');
+ attachmentsView.classList.remove('hidden');
+
+ if (attachmentsButton.getAttribute('disabled')) {
+ return;
+ }
+ break;
+ }
+ },
+
+ // Helper function to parse query string (e.g. ?param1=value&parm2=...).
+ parseQueryString: function pdfViewParseQueryString(query) {
+ var parts = query.split('&');
+ var params = {};
+ for (var i = 0, ii = parts.length; i < ii; ++i) {
+ var param = parts[i].split('=');
+ var key = param[0].toLowerCase();
+ var value = param.length > 1 ? param[1] : null;
+ params[decodeURIComponent(key)] = decodeURIComponent(value);
+ }
+ return params;
+ },
+
+ beforePrint: function pdfViewSetupBeforePrint() {
+ if (!this.supportsPrinting) {
+ var printMessage = mozL10n.get('printing_not_supported', null,
+ 'Warning: Printing is not fully supported by this browser.');
+ this.error(printMessage);
+ return;
+ }
+
+ var alertNotReady = false;
+ var i, ii;
+ if (!this.pagesCount) {
+ alertNotReady = true;
+ } else {
+ for (i = 0, ii = this.pagesCount; i < ii; ++i) {
+ if (!this.pdfViewer.getPageView(i).pdfPage) {
+ alertNotReady = true;
+ break;
+ }
+ }
+ }
+ if (alertNotReady) {
+ var notReadyMessage = mozL10n.get('printing_not_ready', null,
+ 'Warning: The PDF is not fully loaded for printing.');
+ window.alert(notReadyMessage);
+ return;
+ }
+
+ this.printing = true;
+ this.forceRendering();
+
+ var body = document.querySelector('body');
+ body.setAttribute('data-mozPrintCallback', true);
+ for (i = 0, ii = this.pagesCount; i < ii; ++i) {
+ this.pdfViewer.getPageView(i).beforePrint();
+ }
+
+ FirefoxCom.request('reportTelemetry', JSON.stringify({
+ type: 'print'
+ }));
+ },
+
+ afterPrint: function pdfViewSetupAfterPrint() {
+ var div = document.getElementById('printContainer');
+ while (div.hasChildNodes()) {
+ div.removeChild(div.lastChild);
+ }
+
+ this.printing = false;
+ this.forceRendering();
+ },
+
+ setScale: function (value, resetAutoSettings) {
+ this.updateScaleControls = !!resetAutoSettings;
+ this.pdfViewer.currentScaleValue = value;
+ this.updateScaleControls = true;
+ },
+
+ rotatePages: function pdfViewRotatePages(delta) {
+ var pageNumber = this.page;
+
+ this.pageRotation = (this.pageRotation + 360 + delta) % 360;
+ this.pdfViewer.pagesRotation = this.pageRotation;
+ this.pdfThumbnailViewer.pagesRotation = this.pageRotation;
+
+ this.forceRendering();
+
+ this.pdfViewer.scrollPageIntoView(pageNumber);
+ },
+
+ /**
+ * This function flips the page in presentation mode if the user scrolls up
+ * or down with large enough motion and prevents page flipping too often.
+ *
+ * @this {PDFView}
+ * @param {number} mouseScrollDelta The delta value from the mouse event.
+ */
+ mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) {
+ var MOUSE_SCROLL_COOLDOWN_TIME = 50;
+
+ var currentTime = (new Date()).getTime();
+ var storedTime = this.mouseScrollTimeStamp;
+
+ // In case one page has already been flipped there is a cooldown time
+ // which has to expire before next page can be scrolled on to.
+ if (currentTime > storedTime &&
+ currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
+ return;
+ }
+
+ // In case the user decides to scroll to the opposite direction than before
+ // clear the accumulated delta.
+ if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) ||
+ (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) {
+ this.clearMouseScrollState();
+ }
+
+ this.mouseScrollDelta += mouseScrollDelta;
+
+ var PAGE_FLIP_THRESHOLD = 120;
+ if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) {
+
+ var PageFlipDirection = {
+ UP: -1,
+ DOWN: 1
+ };
+
+ // In presentation mode scroll one page at a time.
+ var pageFlipDirection = (this.mouseScrollDelta > 0) ?
+ PageFlipDirection.UP :
+ PageFlipDirection.DOWN;
+ this.clearMouseScrollState();
+ var currentPage = this.page;
+
+ // In case we are already on the first or the last page there is no need
+ // to do anything.
+ if ((currentPage === 1 && pageFlipDirection === PageFlipDirection.UP) ||
+ (currentPage === this.pagesCount &&
+ pageFlipDirection === PageFlipDirection.DOWN)) {
+ return;
+ }
+
+ this.page += pageFlipDirection;
+ this.mouseScrollTimeStamp = currentTime;
+ }
+ },
+
+ /**
+ * This function clears the member attributes used with mouse scrolling in
+ * presentation mode.
+ *
+ * @this {PDFView}
+ */
+ clearMouseScrollState: function pdfViewClearMouseScrollState() {
+ this.mouseScrollTimeStamp = 0;
+ this.mouseScrollDelta = 0;
+ }
+};
+
+
+var THUMBNAIL_SCROLL_MARGIN = -19;
+
+/**
+ * @constructor
+ * @param container
+ * @param id
+ * @param defaultViewport
+ * @param linkService
+ * @param renderingQueue
+ * @param pageSource
+ *
+ * @implements {IRenderableView}
+ */
+var ThumbnailView = function thumbnailView(container, id, defaultViewport,
+ linkService, renderingQueue,
+ pageSource) {
+ var anchor = document.createElement('a');
+ anchor.href = linkService.getAnchorUrl('#page=' + id);
+ anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
+ anchor.onclick = function stopNavigation() {
+ linkService.page = id;
+ return false;
+ };
+
+ this.pdfPage = undefined;
+ this.viewport = defaultViewport;
+ this.pdfPageRotate = defaultViewport.rotation;
+
+ this.rotation = 0;
+ this.pageWidth = this.viewport.width;
+ this.pageHeight = this.viewport.height;
+ this.pageRatio = this.pageWidth / this.pageHeight;
+ this.id = id;
+ this.renderingId = 'thumbnail' + id;
+
+ this.canvasWidth = 98;
+ this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
+ this.scale = (this.canvasWidth / this.pageWidth);
+
+ var div = this.el = document.createElement('div');
+ div.id = 'thumbnailContainer' + id;
+ div.className = 'thumbnail';
+
+ if (id === 1) {
+ // Highlight the thumbnail of the first page when no page number is
+ // specified (or exists in cache) when the document is loaded.
+ div.classList.add('selected');
+ }
+
+ var ring = document.createElement('div');
+ ring.className = 'thumbnailSelectionRing';
+ ring.style.width = this.canvasWidth + 'px';
+ ring.style.height = this.canvasHeight + 'px';
+
+ div.appendChild(ring);
+ anchor.appendChild(div);
+ container.appendChild(anchor);
+
+ this.hasImage = false;
+ this.renderingState = RenderingStates.INITIAL;
+ this.renderingQueue = renderingQueue;
+ this.pageSource = pageSource;
+
+ this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) {
+ this.pdfPage = pdfPage;
+ this.pdfPageRotate = pdfPage.rotate;
+ var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+ this.viewport = pdfPage.getViewport(1, totalRotation);
+ this.update();
+ };
+
+ this.update = function thumbnailViewUpdate(rotation) {
+ if (rotation !== undefined) {
+ this.rotation = rotation;
+ }
+ var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+ this.viewport = this.viewport.clone({
+ scale: 1,
+ rotation: totalRotation
+ });
+ this.pageWidth = this.viewport.width;
+ this.pageHeight = this.viewport.height;
+ this.pageRatio = this.pageWidth / this.pageHeight;
+
+ this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
+ this.scale = (this.canvasWidth / this.pageWidth);
+
+ div.removeAttribute('data-loaded');
+ ring.textContent = '';
+ ring.style.width = this.canvasWidth + 'px';
+ ring.style.height = this.canvasHeight + 'px';
+
+ this.hasImage = false;
+ this.renderingState = RenderingStates.INITIAL;
+ this.resume = null;
+ };
+
+ this.getPageDrawContext = function thumbnailViewGetPageDrawContext() {
+ var canvas = document.createElement('canvas');
+ canvas.id = 'thumbnail' + id;
+
+ canvas.width = this.canvasWidth;
+ canvas.height = this.canvasHeight;
+ canvas.className = 'thumbnailImage';
+ canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
+ {page: id}, 'Thumbnail of Page {{page}}'));
+
+ div.setAttribute('data-loaded', true);
+
+ ring.appendChild(canvas);
+
+ var ctx = canvas.getContext('2d');
+ ctx.save();
+ ctx.fillStyle = 'rgb(255, 255, 255)';
+ ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
+ ctx.restore();
+ return ctx;
+ };
+
+ this.drawingRequired = function thumbnailViewDrawingRequired() {
+ return !this.hasImage;
+ };
+
+ this.draw = function thumbnailViewDraw(callback) {
+ if (!this.pdfPage) {
+ var promise = this.pageSource.getPage(this.id);
+ promise.then(function(pdfPage) {
+ this.setPdfPage(pdfPage);
+ this.draw(callback);
+ }.bind(this));
+ return;
+ }
+
+ if (this.renderingState !== RenderingStates.INITIAL) {
+ console.error('Must be in new state before drawing');
+ }
+
+ this.renderingState = RenderingStates.RUNNING;
+ if (this.hasImage) {
+ callback();
+ return;
+ }
+
+ var self = this;
+ var ctx = this.getPageDrawContext();
+ var drawViewport = this.viewport.clone({ scale: this.scale });
+ var renderContext = {
+ canvasContext: ctx,
+ viewport: drawViewport,
+ continueCallback: function(cont) {
+ if (!self.renderingQueue.isHighestPriority(self)) {
+ self.renderingState = RenderingStates.PAUSED;
+ self.resume = function() {
+ self.renderingState = RenderingStates.RUNNING;
+ cont();
+ };
+ return;
+ }
+ cont();
+ }
+ };
+ this.pdfPage.render(renderContext).promise.then(
+ function pdfPageRenderCallback() {
+ self.renderingState = RenderingStates.FINISHED;
+ callback();
+ },
+ function pdfPageRenderError(error) {
+ self.renderingState = RenderingStates.FINISHED;
+ callback();
+ }
+ );
+ this.hasImage = true;
+ };
+
+ function getTempCanvas(width, height) {
+ var tempCanvas = ThumbnailView.tempImageCache;
+ if (!tempCanvas) {
+ tempCanvas = document.createElement('canvas');
+ ThumbnailView.tempImageCache = tempCanvas;
+ }
+ tempCanvas.width = width;
+ tempCanvas.height = height;
+ return tempCanvas;
+ }
+
+ this.setImage = function thumbnailViewSetImage(img) {
+ if (!this.pdfPage) {
+ var promise = this.pageSource.getPage();
+ promise.then(function(pdfPage) {
+ this.setPdfPage(pdfPage);
+ this.setImage(img);
+ }.bind(this));
+ return;
+ }
+ if (this.hasImage || !img) {
+ return;
+ }
+ this.renderingState = RenderingStates.FINISHED;
+ var ctx = this.getPageDrawContext();
+
+ var reducedImage = img;
+ var reducedWidth = img.width;
+ var reducedHeight = img.height;
+
+ // drawImage does an awful job of rescaling the image, doing it gradually
+ var MAX_SCALE_FACTOR = 2.0;
+ if (Math.max(img.width / ctx.canvas.width,
+ img.height / ctx.canvas.height) > MAX_SCALE_FACTOR) {
+ reducedWidth >>= 1;
+ reducedHeight >>= 1;
+ reducedImage = getTempCanvas(reducedWidth, reducedHeight);
+ var reducedImageCtx = reducedImage.getContext('2d');
+ reducedImageCtx.drawImage(img, 0, 0, img.width, img.height,
+ 0, 0, reducedWidth, reducedHeight);
+ while (Math.max(reducedWidth / ctx.canvas.width,
+ reducedHeight / ctx.canvas.height) > MAX_SCALE_FACTOR) {
+ reducedImageCtx.drawImage(reducedImage,
+ 0, 0, reducedWidth, reducedHeight,
+ 0, 0, reducedWidth >> 1, reducedHeight >> 1);
+ reducedWidth >>= 1;
+ reducedHeight >>= 1;
+ }
+ }
+
+ ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight,
+ 0, 0, ctx.canvas.width, ctx.canvas.height);
+
+ this.hasImage = true;
+ };
+};
+
+ThumbnailView.tempImageCache = null;
+
+/**
+ * @typedef {Object} PDFThumbnailViewerOptions
+ * @property {HTMLDivElement} container - The container for the thumbs elements.
+ * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ */
+
+/**
+ * Simple viewer control to display thumbs for pages.
+ * @class
+ */
+var PDFThumbnailViewer = (function pdfThumbnailViewer() {
+ /**
+ * @constructs
+ * @param {PDFThumbnailViewerOptions} options
+ */
+ function PDFThumbnailViewer(options) {
+ this.container = options.container;
+ this.renderingQueue = options.renderingQueue;
+ this.linkService = options.linkService;
+
+ this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this));
+ this._resetView();
+ }
+
+ PDFThumbnailViewer.prototype = {
+ _scrollUpdated: function PDFThumbnailViewer_scrollUpdated() {
+ this.renderingQueue.renderHighestPriority();
+ },
+
+ getThumbnail: function PDFThumbnailViewer_getThumbnail(index) {
+ return this.thumbnails[index];
+ },
+
+ _getVisibleThumbs: function PDFThumbnailViewer_getVisibleThumbs() {
+ return getVisibleElements(this.container, this.thumbnails);
+ },
+
+ scrollThumbnailIntoView: function (page) {
+ var selected = document.querySelector('.thumbnail.selected');
+ if (selected) {
+ selected.classList.remove('selected');
+ }
+ var thumbnail = document.getElementById('thumbnailContainer' + page);
+ thumbnail.classList.add('selected');
+ var visibleThumbs = this._getVisibleThumbs();
+ var numVisibleThumbs = visibleThumbs.views.length;
+
+ // If the thumbnail isn't currently visible, scroll it into view.
+ if (numVisibleThumbs > 0) {
+ var first = visibleThumbs.first.id;
+ // Account for only one thumbnail being visible.
+ var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first);
+ if (page <= first || page >= last) {
+ scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN });
+ }
+ }
+ },
+
+ get pagesRotation() {
+ return this._pagesRotation;
+ },
+
+ set pagesRotation(rotation) {
+ this._pagesRotation = rotation;
+ for (var i = 0, l = this.thumbnails.length; i < l; i++) {
+ var thumb = this.thumbnails[i];
+ thumb.update(rotation);
+ }
+ },
+
+ cleanup: function PDFThumbnailViewer_cleanup() {
+ ThumbnailView.tempImageCache = null;
+ },
+
+ _resetView: function () {
+ this.thumbnails = [];
+ this._pagesRotation = 0;
+ },
+
+ setDocument: function (pdfDocument) {
+ if (this.pdfDocument) {
+ // cleanup of the elements and views
+ var thumbsView = this.container;
+ while (thumbsView.hasChildNodes()) {
+ thumbsView.removeChild(thumbsView.lastChild);
+ }
+ this._resetView();
+ }
+
+ this.pdfDocument = pdfDocument;
+ if (!pdfDocument) {
+ return Promise.resolve();
+ }
+
+ return pdfDocument.getPage(1).then(function (firstPage) {
+ var pagesCount = pdfDocument.numPages;
+ var viewport = firstPage.getViewport(1.0);
+ for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+ var pageSource = new PDFPageSource(pdfDocument, pageNum);
+ var thumbnail = new ThumbnailView(this.container, pageNum,
+ viewport.clone(), this.linkService,
+ this.renderingQueue, pageSource);
+ this.thumbnails.push(thumbnail);
+ }
+ }.bind(this));
+ },
+
+ ensureThumbnailVisible:
+ function PDFThumbnailViewer_ensureThumbnailVisible(page) {
+ // Ensure that the thumbnail of the current page is visible
+ // when switching from another view.
+ scrollIntoView(document.getElementById('thumbnailContainer' + page));
+ },
+
+ forceRendering: function () {
+ var visibleThumbs = this._getVisibleThumbs();
+ var thumbView = this.renderingQueue.getHighestPriority(visibleThumbs,
+ this.thumbnails,
+ this.scroll.down);
+ if (thumbView) {
+ this.renderingQueue.renderView(thumbView);
+ return true;
+ }
+ return false;
+ }
+ };
+
+ return PDFThumbnailViewer;
+})();
+
+
+var DocumentOutlineView = function documentOutlineView(options) {
+ var outline = options.outline;
+ var outlineView = options.outlineView;
while (outlineView.firstChild) {
outlineView.removeChild(outlineView.firstChild);
}
if (!outline) {
- if (!outlineView.classList.contains('hidden')) {
- PDFView.switchSidebarView('thumbs');
- }
return;
}
+ var linkService = options.linkService;
+
function bindItemLink(domObj, item) {
- domObj.href = PDFView.getDestinationHash(item.dest);
+ domObj.href = linkService.getDestinationHash(item.dest);
domObj.onclick = function documentOutlineViewOnclick(e) {
- PDFView.navigateTo(item.dest);
+ linkService.navigateTo(item.dest);
return false;
};
}
@@ -5430,16 +6140,14 @@ var DocumentOutlineView = function documentOutlineView(outline) {
};
-var DocumentAttachmentsView = function documentAttachmentsView(attachments) {
- var attachmentsView = document.getElementById('attachmentsView');
+var DocumentAttachmentsView = function documentAttachmentsView(options) {
+ var attachments = options.attachments;
+ var attachmentsView = options.attachmentsView;
while (attachmentsView.firstChild) {
attachmentsView.removeChild(attachmentsView.firstChild);
}
if (!attachments) {
- if (!attachmentsView.classList.contains('hidden')) {
- PDFView.switchSidebarView('thumbs');
- }
return;
}
@@ -5470,7 +6178,7 @@ var DocumentAttachmentsView = function documentAttachmentsView(attachments) {
function webViewerLoad(evt) {
- PDFView.initialize().then(webViewerInitialized);
+ PDFViewerApplication.initialize().then(webViewerInitialized);
}
function webViewerInitialized() {
@@ -5479,94 +6187,89 @@ function webViewerInitialized() {
document.getElementById('openFile').setAttribute('hidden', 'true');
document.getElementById('secondaryOpenFile').setAttribute('hidden', 'true');
- // Special debugging flags in the hash section of the URL.
- var hash = document.location.hash.substring(1);
- var hashParams = PDFView.parseQueryString(hash);
- if ('disableWorker' in hashParams) {
- PDFJS.disableWorker = (hashParams['disableWorker'] === 'true');
+ if (PDFViewerApplication.preferencePdfBugEnabled) {
+ // Special debugging flags in the hash section of the URL.
+ var hash = document.location.hash.substring(1);
+ var hashParams = PDFViewerApplication.parseQueryString(hash);
+
+ if ('disableworker' in hashParams) {
+ PDFJS.disableWorker = (hashParams['disableworker'] === 'true');
+ }
+ if ('disablerange' in hashParams) {
+ PDFJS.disableRange = (hashParams['disablerange'] === 'true');
+ }
+ if ('disablestream' in hashParams) {
+ PDFJS.disableStream = (hashParams['disablestream'] === 'true');
+ }
+ if ('disableautofetch' in hashParams) {
+ PDFJS.disableAutoFetch = (hashParams['disableautofetch'] === 'true');
+ }
+ if ('disablefontface' in hashParams) {
+ PDFJS.disableFontFace = (hashParams['disablefontface'] === 'true');
+ }
+ if ('disablehistory' in hashParams) {
+ PDFJS.disableHistory = (hashParams['disablehistory'] === 'true');
+ }
+ if ('webgl' in hashParams) {
+ PDFJS.disableWebGL = (hashParams['webgl'] !== 'true');
+ }
+ if ('useonlycsszoom' in hashParams) {
+ PDFJS.useOnlyCssZoom = (hashParams['useonlycsszoom'] === 'true');
+ }
+ if ('verbosity' in hashParams) {
+ PDFJS.verbosity = hashParams['verbosity'] | 0;
+ }
+ if ('ignorecurrentpositiononzoom' in hashParams) {
+ IGNORE_CURRENT_POSITION_ON_ZOOM =
+ (hashParams['ignorecurrentpositiononzoom'] === 'true');
+ }
+ if ('textlayer' in hashParams) {
+ switch (hashParams['textlayer']) {
+ case 'off':
+ PDFJS.disableTextLayer = true;
+ break;
+ case 'visible':
+ case 'shadow':
+ case 'hover':
+ var viewer = document.getElementById('viewer');
+ viewer.classList.add('textLayer-' + hashParams['textlayer']);
+ break;
+ }
+ }
+ if ('pdfbug' in hashParams) {
+ PDFJS.pdfBug = true;
+ var pdfBug = hashParams['pdfbug'];
+ var enabled = pdfBug.split(',');
+ PDFBug.enable(enabled);
+ PDFBug.init();
+ }
}
- if ('disableRange' in hashParams) {
- PDFJS.disableRange = (hashParams['disableRange'] === 'true');
- }
-
- if ('disableAutoFetch' in hashParams) {
- PDFJS.disableAutoFetch = (hashParams['disableAutoFetch'] === 'true');
- }
-
- if ('disableFontFace' in hashParams) {
- PDFJS.disableFontFace = (hashParams['disableFontFace'] === 'true');
- }
-
- if ('disableHistory' in hashParams) {
- PDFJS.disableHistory = (hashParams['disableHistory'] === 'true');
- }
-
- if ('webgl' in hashParams) {
- PDFJS.disableWebGL = (hashParams['webgl'] !== 'true');
- }
-
- if ('useOnlyCssZoom' in hashParams) {
- PDFJS.useOnlyCssZoom = (hashParams['useOnlyCssZoom'] === 'true');
- }
-
- if ('verbosity' in hashParams) {
- PDFJS.verbosity = hashParams['verbosity'] | 0;
- }
-
- if ('ignoreCurrentPositionOnZoom' in hashParams) {
- IGNORE_CURRENT_POSITION_ON_ZOOM =
- (hashParams['ignoreCurrentPositionOnZoom'] === 'true');
- }
-
-
-
- if (!PDFView.supportsDocumentFonts) {
+ if (!PDFViewerApplication.supportsDocumentFonts) {
PDFJS.disableFontFace = true;
console.warn(mozL10n.get('web_fonts_disabled', null,
'Web fonts are disabled: unable to use embedded PDF fonts.'));
}
- if ('textLayer' in hashParams) {
- switch (hashParams['textLayer']) {
- case 'off':
- PDFJS.disableTextLayer = true;
- break;
- case 'visible':
- case 'shadow':
- case 'hover':
- var viewer = document.getElementById('viewer');
- viewer.classList.add('textLayer-' + hashParams['textLayer']);
- break;
- }
- }
-
- if ('pdfBug' in hashParams && FirefoxCom.requestSync('pdfBugEnabled')) {
- PDFJS.pdfBug = true;
- var pdfBug = hashParams['pdfBug'];
- var enabled = pdfBug.split(',');
- PDFBug.enable(enabled);
- PDFBug.init();
- }
-
- if (!PDFView.supportsPrinting) {
+ if (!PDFViewerApplication.supportsPrinting) {
document.getElementById('print').classList.add('hidden');
document.getElementById('secondaryPrint').classList.add('hidden');
}
- if (!PDFView.supportsFullscreen) {
+ if (!PDFViewerApplication.supportsFullscreen) {
document.getElementById('presentationMode').classList.add('hidden');
document.getElementById('secondaryPresentationMode').
classList.add('hidden');
}
- if (PDFView.supportsIntegratedFind) {
+ if (PDFViewerApplication.supportsIntegratedFind) {
document.getElementById('viewFind').classList.add('hidden');
}
// Listen for unsupported features to trigger the fallback UI.
- PDFJS.UnsupportedManager.listen(PDFView.fallback.bind(PDFView));
+ PDFJS.UnsupportedManager.listen(
+ PDFViewerApplication.fallback.bind(PDFViewerApplication));
// Suppress context menus for some controls
document.getElementById('scaleSelect').oncontextmenu = noContextMenuHandler;
@@ -5587,63 +6290,62 @@ function webViewerInitialized() {
this.classList.toggle('toggled');
outerContainer.classList.add('sidebarMoving');
outerContainer.classList.toggle('sidebarOpen');
- PDFView.sidebarOpen = outerContainer.classList.contains('sidebarOpen');
- PDFView.renderHighestPriority();
+ PDFViewerApplication.sidebarOpen =
+ outerContainer.classList.contains('sidebarOpen');
+ PDFViewerApplication.forceRendering();
});
document.getElementById('viewThumbnail').addEventListener('click',
function() {
- PDFView.switchSidebarView('thumbs');
+ PDFViewerApplication.switchSidebarView('thumbs');
});
document.getElementById('viewOutline').addEventListener('click',
function() {
- PDFView.switchSidebarView('outline');
+ PDFViewerApplication.switchSidebarView('outline');
});
document.getElementById('viewAttachments').addEventListener('click',
function() {
- PDFView.switchSidebarView('attachments');
+ PDFViewerApplication.switchSidebarView('attachments');
});
document.getElementById('previous').addEventListener('click',
function() {
- PDFView.page--;
+ PDFViewerApplication.page--;
});
document.getElementById('next').addEventListener('click',
function() {
- PDFView.page++;
+ PDFViewerApplication.page++;
});
document.getElementById('zoomIn').addEventListener('click',
function() {
- PDFView.zoomIn();
+ PDFViewerApplication.zoomIn();
});
document.getElementById('zoomOut').addEventListener('click',
function() {
- PDFView.zoomOut();
+ PDFViewerApplication.zoomOut();
});
- document.getElementById('pageNumber').addEventListener('click',
- function() {
- this.select();
- });
+ document.getElementById('pageNumber').addEventListener('click', function() {
+ this.select();
+ });
- document.getElementById('pageNumber').addEventListener('change',
- function() {
- // Handle the user inputting a floating point number.
- PDFView.page = (this.value | 0);
+ document.getElementById('pageNumber').addEventListener('change', function() {
+ // Handle the user inputting a floating point number.
+ PDFViewerApplication.page = (this.value | 0);
- if (this.value !== (this.value | 0).toString()) {
- this.value = PDFView.page;
- }
- });
+ if (this.value !== (this.value | 0).toString()) {
+ this.value = PDFViewerApplication.page;
+ }
+ });
document.getElementById('scaleSelect').addEventListener('change',
function() {
- PDFView.setScale(this.value);
+ PDFViewerApplication.setScale(this.value, false);
});
document.getElementById('presentationMode').addEventListener('click',
@@ -5658,118 +6360,128 @@ function webViewerInitialized() {
document.getElementById('download').addEventListener('click',
SecondaryToolbar.downloadClick.bind(SecondaryToolbar));
- PDFView.setTitleUsingUrl(file);
- PDFView.initPassiveLoading();
+ PDFViewerApplication.setTitleUsingUrl(file);
+ PDFViewerApplication.initPassiveLoading();
return;
if (file) {
- PDFView.open(file, 0);
+ PDFViewerApplication.open(file, 0);
}
}
document.addEventListener('DOMContentLoaded', webViewerLoad, true);
+document.addEventListener('pagerendered', function (e) {
+ var pageIndex = e.detail.pageNumber - 1;
+ var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex);
+ var thumbnailView = PDFViewerApplication.pdfThumbnailViewer.
+ getThumbnail(pageIndex);
+ thumbnailView.setImage(pageView.canvas);
+
+ if (pageView.textLayer && pageView.textLayer.textDivs &&
+ pageView.textLayer.textDivs.length > 0 &&
+ !PDFViewerApplication.supportsDocumentColors) {
+ console.error(mozL10n.get('document_colors_disabled', null,
+ 'PDF documents are not allowed to use their own colors: ' +
+ '\'Allow pages to choose their own colors\' ' +
+ 'is deactivated in the browser.'));
+ PDFViewerApplication.fallback();
+ }
+
+ if (pageView.error) {
+ PDFViewerApplication.error(mozL10n.get('rendering_error', null,
+ 'An error occurred while rendering the page.'), pageView.error);
+ }
+
+ FirefoxCom.request('reportTelemetry', JSON.stringify({
+ type: 'pageInfo'
+ }));
+ // It is a good time to report stream and font types
+ PDFViewerApplication.pdfDocument.getStats().then(function (stats) {
+ FirefoxCom.request('reportTelemetry', JSON.stringify({
+ type: 'documentStats',
+ stats: stats
+ }));
+ });
+
+ // If the page is still visible when it has finished rendering,
+ // ensure that the page number input loading indicator is hidden.
+ if ((pageIndex + 1) === PDFViewerApplication.page) {
+ var pageNumberInput = document.getElementById('pageNumber');
+ pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
+ }
+}, true);
+
+window.addEventListener('presentationmodechanged', function (e) {
+ var active = e.detail.active;
+ var switchInProgress = e.detail.switchInProgress;
+ PDFViewerApplication.pdfViewer.presentationModeState =
+ switchInProgress ? PresentationModeState.CHANGING :
+ active ? PresentationModeState.FULLSCREEN : PresentationModeState.NORMAL;
+});
+
function updateViewarea() {
-
- if (!PDFView.initialized) {
+ if (!PDFViewerApplication.initialized) {
return;
}
- var visible = PDFView.getVisiblePages();
- var visiblePages = visible.views;
- if (visiblePages.length === 0) {
+ PDFViewerApplication.pdfViewer.update();
+}
+
+window.addEventListener('updateviewarea', function () {
+ if (!PDFViewerApplication.initialized) {
return;
}
- var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
- 2 * visiblePages.length + 1);
- cache.resize(suggestedCacheSize);
+ var location = PDFViewerApplication.pdfViewer.location;
- PDFView.renderHighestPriority(visible);
-
- var currentId = PDFView.page;
- var firstPage = visible.first;
-
- for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
- i < ii; ++i) {
- var page = visiblePages[i];
-
- if (page.percent < 100) {
- break;
- }
- if (page.id === PDFView.page) {
- stillFullyVisible = true;
- break;
- }
- }
-
- if (!stillFullyVisible) {
- currentId = visiblePages[0].id;
- }
-
- if (!PresentationMode.active) {
- updateViewarea.inProgress = true; // used in "set page"
- PDFView.page = currentId;
- updateViewarea.inProgress = false;
- }
-
- var currentScale = PDFView.currentScale;
- var currentScaleValue = PDFView.currentScaleValue;
- var normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ?
- Math.round(currentScale * 10000) / 100 : currentScaleValue;
-
- var pageNumber = firstPage.id;
- var pdfOpenParams = '#page=' + pageNumber;
- pdfOpenParams += '&zoom=' + normalizedScaleValue;
- var currentPage = PDFView.pages[pageNumber - 1];
- var container = PDFView.container;
- var topLeft = currentPage.getPagePoint((container.scrollLeft - firstPage.x),
- (container.scrollTop - firstPage.y));
- var intLeft = Math.round(topLeft[0]);
- var intTop = Math.round(topLeft[1]);
- pdfOpenParams += ',' + intLeft + ',' + intTop;
-
- if (PresentationMode.active || PresentationMode.switchInProgress) {
- PDFView.currentPosition = null;
- } else {
- PDFView.currentPosition = { page: pageNumber, left: intLeft, top: intTop };
- }
-
- PDFView.store.initializedPromise.then(function() {
- PDFView.store.setMultiple({
+ PDFViewerApplication.store.initializedPromise.then(function() {
+ PDFViewerApplication.store.setMultiple({
'exists': true,
- 'page': pageNumber,
- 'zoom': normalizedScaleValue,
- 'scrollLeft': intLeft,
- 'scrollTop': intTop
+ 'page': location.pageNumber,
+ 'zoom': location.scale,
+ 'scrollLeft': location.left,
+ 'scrollTop': location.top
}).catch(function() {
// unable to write to storage
});
});
- var href = PDFView.getAnchorUrl(pdfOpenParams);
+ var href = PDFViewerApplication.getAnchorUrl(location.pdfOpenParams);
document.getElementById('viewBookmark').href = href;
document.getElementById('secondaryViewBookmark').href = href;
// Update the current bookmark in the browsing history.
- PDFHistory.updateCurrentBookmark(pdfOpenParams, pageNumber);
-}
+ PDFHistory.updateCurrentBookmark(location.pdfOpenParams, location.pageNumber);
+
+ // Show/hide the loading indicator in the page number input element.
+ var pageNumberInput = document.getElementById('pageNumber');
+ var currentPage =
+ PDFViewerApplication.pdfViewer.getPageView(PDFViewerApplication.page - 1);
+
+ if (currentPage.renderingState === RenderingStates.FINISHED) {
+ pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
+ } else {
+ pageNumberInput.classList.add(PAGE_NUMBER_LOADING_INDICATOR);
+ }
+}, true);
window.addEventListener('resize', function webViewerResize(evt) {
- if (PDFView.initialized &&
+ if (PDFViewerApplication.initialized &&
(document.getElementById('pageWidthOption').selected ||
document.getElementById('pageFitOption').selected ||
document.getElementById('pageAutoOption').selected)) {
- PDFView.setScale(document.getElementById('scaleSelect').value);
+ var selectedScale = document.getElementById('scaleSelect').value;
+ PDFViewerApplication.setScale(selectedScale, false);
}
updateViewarea();
// Set the 'max-height' CSS property of the secondary toolbar.
- SecondaryToolbar.setMaxHeight(PDFView.container);
+ SecondaryToolbar.setMaxHeight(document.getElementById('viewerContainer'));
});
window.addEventListener('hashchange', function webViewerHashchange(evt) {
if (PDFHistory.isHashChangeUnlocked) {
- PDFView.setHash(document.location.hash.substring(1));
+ PDFViewerApplication.setHash(document.location.hash.substring(1));
}
});
@@ -5792,7 +6504,7 @@ function selectScaleOption(value) {
window.addEventListener('localized', function localized(evt) {
document.getElementsByTagName('html')[0].dir = mozL10n.getDirection();
- PDFView.animationStartedPromise.then(function() {
+ PDFViewerApplication.animationStartedPromise.then(function() {
// Adjust the width of the zoom box to fit the content.
// Note: If the window is narrow enough that the zoom box is not visible,
// we temporarily show it to be able to adjust its width.
@@ -5811,7 +6523,7 @@ window.addEventListener('localized', function localized(evt) {
}
// Set the 'max-height' CSS property of the secondary toolbar.
- SecondaryToolbar.setMaxHeight(PDFView.container);
+ SecondaryToolbar.setMaxHeight(document.getElementById('viewerContainer'));
});
}, true);
@@ -5822,7 +6534,7 @@ window.addEventListener('scalechange', function scalechange(evt) {
var customScaleOption = document.getElementById('customScaleOption');
customScaleOption.selected = false;
- if (!evt.resetAutoSettings &&
+ if (!PDFViewerApplication.updateScaleControls &&
(document.getElementById('pageWidthOption').selected ||
document.getElementById('pageFitOption').selected ||
document.getElementById('pageAutoOption').selected)) {
@@ -5830,6 +6542,12 @@ window.addEventListener('scalechange', function scalechange(evt) {
return;
}
+ if (evt.presetValue) {
+ selectScaleOption(evt.presetValue);
+ updateViewarea();
+ return;
+ }
+
var predefinedValueFound = selectScaleOption('' + evt.scale);
if (!predefinedValueFound) {
customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%';
@@ -5840,34 +6558,27 @@ window.addEventListener('scalechange', function scalechange(evt) {
window.addEventListener('pagechange', function pagechange(evt) {
var page = evt.pageNumber;
- if (PDFView.previousPageNumber !== page) {
+ if (evt.previousPageNumber !== page) {
document.getElementById('pageNumber').value = page;
- var selected = document.querySelector('.thumbnail.selected');
- if (selected) {
- selected.classList.remove('selected');
- }
- var thumbnail = document.getElementById('thumbnailContainer' + page);
- thumbnail.classList.add('selected');
- var visibleThumbs = PDFView.getVisibleThumbs();
- var numVisibleThumbs = visibleThumbs.views.length;
-
- // If the thumbnail isn't currently visible, scroll it into view.
- if (numVisibleThumbs > 0) {
- var first = visibleThumbs.first.id;
- // Account for only one thumbnail being visible.
- var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first);
- if (page <= first || page >= last) {
- scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN });
- }
- }
+ PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(page);
}
- var numPages = PDFView.pages.length;
+ var numPages = PDFViewerApplication.pagesCount;
document.getElementById('previous').disabled = (page <= 1);
document.getElementById('next').disabled = (page >= numPages);
document.getElementById('firstPage').disabled = (page <= 1);
document.getElementById('lastPage').disabled = (page >= numPages);
+
+ // checking if the this.page was called from the updateViewarea function
+ if (evt.updateInProgress) {
+ return;
+ }
+ // Avoid scrolling the first page during loading
+ if (this.loading && page === 1) {
+ return;
+ }
+ PDFViewerApplication.pdfViewer.scrollPageIntoView(page);
}, true);
function handleMouseWheel(evt) {
@@ -5876,11 +6587,12 @@ function handleMouseWheel(evt) {
evt.wheelDelta / MOUSE_WHEEL_DELTA_FACTOR;
var direction = (ticks < 0) ? 'zoomOut' : 'zoomIn';
- if (evt.ctrlKey) { // Only zoom the pages, not the entire viewer
+ if (PresentationMode.active) {
evt.preventDefault();
- PDFView[direction](Math.abs(ticks));
- } else if (PresentationMode.active) {
- PDFView.mouseScroll(ticks * MOUSE_WHEEL_DELTA_FACTOR);
+ PDFViewerApplication.mouseScroll(ticks * MOUSE_WHEEL_DELTA_FACTOR);
+ } else if (evt.ctrlKey) { // Only zoom the pages, not the entire viewer
+ evt.preventDefault();
+ PDFViewerApplication[direction](Math.abs(ticks));
}
}
@@ -5889,7 +6601,8 @@ window.addEventListener('mousewheel', handleMouseWheel);
window.addEventListener('click', function click(evt) {
if (!PresentationMode.active) {
- if (SecondaryToolbar.opened && PDFView.container.contains(evt.target)) {
+ if (SecondaryToolbar.opened &&
+ PDFViewerApplication.pdfViewer.containsElement(evt.target)) {
SecondaryToolbar.close();
}
} else if (evt.button === 0) {
@@ -5914,16 +6627,22 @@ window.addEventListener('keydown', function keydown(evt) {
// control is selected or not.
if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
// either CTRL or META key with optional SHIFT.
+ var pdfViewer = PDFViewerApplication.pdfViewer;
+ var inPresentationMode =
+ pdfViewer.presentationModeState === PresentationModeState.CHANGING ||
+ pdfViewer.presentationModeState === PresentationModeState.FULLSCREEN;
+
switch (evt.keyCode) {
case 70: // f
- if (!PDFView.supportsIntegratedFind) {
- PDFView.findBar.open();
+ if (!PDFViewerApplication.supportsIntegratedFind) {
+ PDFViewerApplication.findBar.open();
handled = true;
}
break;
case 71: // g
- if (!PDFView.supportsIntegratedFind) {
- PDFView.findBar.dispatchEvent('again', cmd === 5 || cmd === 12);
+ if (!PDFViewerApplication.supportsIntegratedFind) {
+ PDFViewerApplication.findBar.dispatchEvent('again',
+ cmd === 5 || cmd === 12);
handled = true;
}
break;
@@ -5931,23 +6650,29 @@ window.addEventListener('keydown', function keydown(evt) {
case 107: // FF '+' and '='
case 187: // Chrome '+'
case 171: // FF with German keyboard
- PDFView.zoomIn();
+ if (!inPresentationMode) {
+ PDFViewerApplication.zoomIn();
+ }
handled = true;
break;
case 173: // FF/Mac '-'
case 109: // FF '-'
case 189: // Chrome '-'
- PDFView.zoomOut();
+ if (!inPresentationMode) {
+ PDFViewerApplication.zoomOut();
+ }
handled = true;
break;
case 48: // '0'
case 96: // '0' on Numpad of Swedish keyboard
- // keeping it unhandled (to restore page zoom to 100%)
- setTimeout(function () {
- // ... and resetting the scale after browser adjusts its scale
- PDFView.setScale(DEFAULT_SCALE, true);
- });
- handled = false;
+ if (!inPresentationMode) {
+ // keeping it unhandled (to restore page zoom to 100%)
+ setTimeout(function () {
+ // ... and resetting the scale after browser adjusts its scale
+ PDFViewerApplication.setScale(DEFAULT_SCALE, true);
+ });
+ handled = false;
+ }
break;
}
}
@@ -5992,20 +6717,20 @@ window.addEventListener('keydown', function keydown(evt) {
case 33: // pg up
case 8: // backspace
if (!PresentationMode.active &&
- PDFView.currentScaleValue !== 'page-fit') {
+ PDFViewerApplication.currentScaleValue !== 'page-fit') {
break;
}
/* in presentation mode */
/* falls through */
case 37: // left arrow
// horizontal scrolling using arrow keys
- if (PDFView.isHorizontalScrollbarEnabled) {
+ if (PDFViewerApplication.pdfViewer.isHorizontalScrollbarEnabled) {
break;
}
/* falls through */
case 75: // 'k'
case 80: // 'p'
- PDFView.page--;
+ PDFViewerApplication.page--;
handled = true;
break;
case 27: // esc key
@@ -6013,8 +6738,9 @@ window.addEventListener('keydown', function keydown(evt) {
SecondaryToolbar.close();
handled = true;
}
- if (!PDFView.supportsIntegratedFind && PDFView.findBar.opened) {
- PDFView.findBar.close();
+ if (!PDFViewerApplication.supportsIntegratedFind &&
+ PDFViewerApplication.findBar.opened) {
+ PDFViewerApplication.findBar.close();
handled = true;
}
break;
@@ -6022,32 +6748,32 @@ window.addEventListener('keydown', function keydown(evt) {
case 34: // pg down
case 32: // spacebar
if (!PresentationMode.active &&
- PDFView.currentScaleValue !== 'page-fit') {
+ PDFViewerApplication.currentScaleValue !== 'page-fit') {
break;
}
/* falls through */
case 39: // right arrow
// horizontal scrolling using arrow keys
- if (PDFView.isHorizontalScrollbarEnabled) {
+ if (PDFViewerApplication.pdfViewer.isHorizontalScrollbarEnabled) {
break;
}
/* falls through */
case 74: // 'j'
case 78: // 'n'
- PDFView.page++;
+ PDFViewerApplication.page++;
handled = true;
break;
case 36: // home
- if (PresentationMode.active || PDFView.page > 1) {
- PDFView.page = 1;
+ if (PresentationMode.active || PDFViewerApplication.page > 1) {
+ PDFViewerApplication.page = 1;
handled = true;
}
break;
case 35: // end
- if (PresentationMode.active || (PDFView.pdfDocument &&
- PDFView.page < PDFView.pdfDocument.numPages)) {
- PDFView.page = PDFView.pdfDocument.numPages;
+ if (PresentationMode.active || (PDFViewerApplication.pdfDocument &&
+ PDFViewerApplication.page < PDFViewerApplication.pagesCount)) {
+ PDFViewerApplication.page = PDFViewerApplication.pagesCount;
handled = true;
}
break;
@@ -6058,7 +6784,7 @@ window.addEventListener('keydown', function keydown(evt) {
}
break;
case 82: // 'r'
- PDFView.rotatePages(90);
+ PDFViewerApplication.rotatePages(90);
break;
}
}
@@ -6067,15 +6793,15 @@ window.addEventListener('keydown', function keydown(evt) {
switch (evt.keyCode) {
case 32: // spacebar
if (!PresentationMode.active &&
- PDFView.currentScaleValue !== 'page-fit') {
+ PDFViewerApplication.currentScaleValue !== 'page-fit') {
break;
}
- PDFView.page--;
+ PDFViewerApplication.page--;
handled = true;
break;
case 82: // 'r'
- PDFView.rotatePages(-90);
+ PDFViewerApplication.rotatePages(-90);
break;
}
}
@@ -6084,17 +6810,17 @@ window.addEventListener('keydown', function keydown(evt) {
// 33=Page Up 34=Page Down 35=End 36=Home
// 37=Left 38=Up 39=Right 40=Down
if (evt.keyCode >= 33 && evt.keyCode <= 40 &&
- !PDFView.container.contains(curElement)) {
+ !PDFViewerApplication.pdfViewer.containsElement(curElement)) {
// The page container is not focused, but a page navigation key has been
// pressed. Change the focus to the viewer container to make sure that
// navigation by keyboard works as expected.
- PDFView.container.focus();
+ PDFViewerApplication.pdfViewer.focus();
}
// 32=Spacebar
if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') {
// Workaround for issue in Firefox, that prevents scroll keys from
// working when elements with 'tabindex' are focused. (#3498)
- PDFView.container.blur();
+ PDFViewerApplication.pdfViewer.blur();
}
}
@@ -6117,22 +6843,23 @@ window.addEventListener('keydown', function keydown(evt) {
if (handled) {
evt.preventDefault();
- PDFView.clearMouseScrollState();
+ PDFViewerApplication.clearMouseScrollState();
}
});
window.addEventListener('beforeprint', function beforePrint(evt) {
- PDFView.beforePrint();
+ PDFViewerApplication.beforePrint();
});
window.addEventListener('afterprint', function afterPrint(evt) {
- PDFView.afterPrint();
+ PDFViewerApplication.afterPrint();
});
(function animationStartedClosure() {
// The offsetParent is not set until the pdf.js iframe or object is visible.
// Waiting for first animation.
- PDFView.animationStartedPromise = new Promise(function (resolve) {
+ PDFViewerApplication.animationStartedPromise = new Promise(
+ function (resolve) {
window.requestAnimationFrame(resolve);
});
})();
diff --git a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
index d9e910337143..9d84c904302d 100644
--- a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
+++ b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
@@ -97,7 +97,7 @@ quit-button.tooltiptext.linux2 = Quit %1$S (%2$S)
# %2$S is the keyboard shortcut
quit-button.tooltiptext.mac = Quit %1$S (%2$S)
-# LOCALIZATION NOTE(loop-call-button2.label2): This is a brand name, request
+# LOCALIZATION NOTE(loop-call-button3.label): This is a brand name, request
# approval before you change it.
loop-call-button3.label = Hello
loop-call-button2.tooltiptext = Start a conversation
diff --git a/browser/themes/linux/browser.css b/browser/themes/linux/browser.css
index 163ff92c35cd..7100cb7af1cd 100644
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -2068,15 +2068,6 @@ toolbarbutton.chevron > .toolbarbutton-icon {
/* Social toolbar item */
-#social-provider-button {
- -moz-image-region: rect(0, 16px, 16px, 0);
- list-style-image: url(chrome://browser/skin/social/services-16.png);
-}
-
-#social-provider-button > .toolbarbutton-menu-dropmarker {
- display: none;
-}
-
.popup-notification-icon[popupid="servicesInstall"] {
list-style-image: url(chrome://browser/skin/social/services-64.png);
}
diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css
index 896e89bc7f96..5c1b79936ab5 100644
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -4366,64 +4366,6 @@ menulist.translate-infobar-element > .menulist-dropmarker {
border-radius: 1px;
}
-/* === social toolbar button === */
-
-#social-toolbar-item > .toolbarbutton-1 {
- margin-left: 0;
- margin-right: 0;
- border-top-left-radius: 0;
- border-bottom-left-radius: 0;
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
-}
-
-#social-toolbar-item > .toolbarbutton-1:-moz-locale-dir(ltr) {
- -moz-border-end-width: 0;
-}
-#social-toolbar-item > .toolbarbutton-1:last-child:-moz-locale-dir(ltr) {
- -moz-border-end-width: 1px;
-}
-#social-toolbar-item > .toolbarbutton-1:-moz-locale-dir(rtl) {
- -moz-border-start-width: 0;
-}
-#social-toolbar-item > .toolbarbutton-1:first-child:-moz-locale-dir(rtl) {
- -moz-border-start-width: 1px;
-}
-
-#social-toolbar-item > .toolbarbutton-1:-moz-locale-dir(ltr):first-child,
-#social-toolbar-item > .toolbarbutton-1:-moz-locale-dir(rtl):last-child {
- margin-left: 4px;
- border-top-left-radius: 3px;
- border-bottom-left-radius: 3px;
-}
-#social-toolbar-item > .toolbarbutton-1:-moz-locale-dir(rtl):first-child,
-#social-toolbar-item > .toolbarbutton-1:-moz-locale-dir(ltr):last-child {
- margin-right: 4px;
- border-top-right-radius: 3px;
- border-bottom-right-radius: 3px;
-}
-
-#social-toolbar-item > toolbaritem {
- margin: 0;
-}
-
-#social-provider-button {
- list-style-image: url(chrome://browser/skin/social/services-16.png);
-}
-
-@media (min-resolution: 2dppx) {
- #social-provider-button {
- list-style-image: url(chrome://browser/skin/social/services-16@2x.png);
- }
- #social-provider-button > .toolbarbutton-icon {
- width: 16px;
- }
-}
-
-#social-provider-button > .toolbarbutton-menu-dropmarker {
- display: none;
-}
-
.popup-notification-icon[popupid="servicesInstall"] {
list-style-image: url(chrome://browser/skin/social/services-64.png);
}
diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css
index 9a59218b7b12..d1de83a6193a 100644
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -623,7 +623,7 @@ toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
-moz-padding-end: 5px;
}
-#nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#social-provider-button):not(#PanelUI-menu-button) {
+#nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#PanelUI-menu-button) {
padding-left: 5px;
padding-right: 5px;
}
@@ -698,7 +698,7 @@ toolbarbutton[sdk-button="true"][cui-areatype="toolbar"] > .toolbarbutton-icon {
width: 32px;
}
-#nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#social-provider-button):not(#PanelUI-menu-button) > .toolbarbutton-icon,
+#nav-bar .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button):not(#PanelUI-menu-button) > .toolbarbutton-icon,
#nav-bar .toolbarbutton-1[type=menu] > .toolbarbutton-text /* hack for add-ons that forcefully display the label */ {
-moz-padding-end: 17px;
}
@@ -2677,15 +2677,6 @@ notification[value="translation"] {
/* Social toolbar item */
-#social-provider-button {
- -moz-image-region: rect(0, 16px, 16px, 0);
- list-style-image: url(chrome://browser/skin/social/services-16.png);
-}
-
-#social-provider-button > .toolbarbutton-menu-dropmarker {
- display: none;
-}
-
#switch-to-metro-button[cui-areatype="toolbar"] {
list-style-image: url(chrome://browser/skin/Metro_Glyph.png);
}
diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java
index 3d87891c2bf5..bfe9e6eebe84 100644
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1788,7 +1788,7 @@ public class BrowserApp extends GeckoApp
mTabsPanel.prepareTabsAnimation(mMainLayoutAnimator);
mBrowserToolbar.triggerTabsPanelTransition(mMainLayoutAnimator, areTabsShown());
- // If the tabs layout is animating onto the screen, pin the dynamic
+ // If the tabs panel is animating onto the screen, pin the dynamic
// toolbar.
if (mDynamicToolbar.isEnabled()) {
if (width > 0 && height > 0) {
@@ -2376,7 +2376,7 @@ public class BrowserApp extends GeckoApp
}
/**
- * Hides certain UI elements (e.g. button toast, tabs tray) when the
+ * Hides certain UI elements (e.g. button toast, tabs panel) when the
* user touches the main layout.
*/
private class HideOnTouchListener implements TouchEventInterceptor {
@@ -2412,7 +2412,7 @@ public class BrowserApp extends GeckoApp
return false;
}
- // If the tab tray is showing, hide the tab tray and don't send the event to content.
+ // If the tabs panel is showing, hide the tab panel and don't send the event to content.
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && autoHideTabs()) {
mIsHidingTabs = true;
return true;
diff --git a/mobile/android/base/RemoteTabsExpandableListAdapter.java b/mobile/android/base/RemoteTabsExpandableListAdapter.java
index 0deccfa1dfaf..da4060513b6f 100644
--- a/mobile/android/base/RemoteTabsExpandableListAdapter.java
+++ b/mobile/android/base/RemoteTabsExpandableListAdapter.java
@@ -128,7 +128,7 @@ public class RemoteTabsExpandableListAdapter extends BaseExpandableListAdapter {
lastModifiedView.setText(TabsAccessor.getLastSyncedString(context, now, client.lastModified));
// These views exists only in some of our group views: they are present
- // for the home panel groups and not for the tabs tray groups.
+ // for the home panel groups and not for the tabs panel groups.
// Therefore, we must handle null.
final ImageView deviceTypeView = (ImageView) view.findViewById(R.id.device_type);
if (deviceTypeView != null) {
@@ -174,7 +174,7 @@ public class RemoteTabsExpandableListAdapter extends BaseExpandableListAdapter {
final RemoteTab tab = client.tabs.get(childPosition);
// The view is a TwoLinePageRow only for some of our child views: it's
- // present for the home panel children and not for the tabs tray
+ // present for the home panel children and not for the tabs panel
// children. Therefore, we must handle one case manually.
if (view instanceof TwoLinePageRow) {
((TwoLinePageRow) view).update(tab.title, tab.url);
diff --git a/mobile/android/base/resources/layout-large-land-v11/tabs_panel_sidebar.xml b/mobile/android/base/resources/layout-large-land-v11/tabs_panel_sidebar.xml
index 8a892389e2e4..d3dd091c3430 100644
--- a/mobile/android/base/resources/layout-large-land-v11/tabs_panel_sidebar.xml
+++ b/mobile/android/base/resources/layout-large-land-v11/tabs_panel_sidebar.xml
@@ -33,7 +33,7 @@
-
-
-
-
diff --git a/mobile/android/base/resources/values-large-land-v11/styles.xml b/mobile/android/base/resources/values-large-land-v11/styles.xml
index b4c0aaa00e23..3850b6489e87 100644
--- a/mobile/android/base/resources/values-large-land-v11/styles.xml
+++ b/mobile/android/base/resources/values-large-land-v11/styles.xml
@@ -5,7 +5,7 @@
-
diff --git a/mobile/android/base/resources/values-large-v11/styles.xml b/mobile/android/base/resources/values-large-v11/styles.xml
index 36ecfd8a6a61..71398d37b6e8 100644
--- a/mobile/android/base/resources/values-large-v11/styles.xml
+++ b/mobile/android/base/resources/values-large-v11/styles.xml
@@ -50,7 +50,7 @@
- horizontal
-
diff --git a/mobile/android/base/resources/values-v11/styles.xml b/mobile/android/base/resources/values-v11/styles.xml
index a15b1e03e38f..8a99ada2a553 100644
--- a/mobile/android/base/resources/values-v11/styles.xml
+++ b/mobile/android/base/resources/values-v11/styles.xml
@@ -51,8 +51,8 @@
- showHome|homeAsUp|showTitle
-
-
diff --git a/mobile/android/base/resources/values/attrs.xml b/mobile/android/base/resources/values/attrs.xml
index a61dcb516ecd..3e5926556799 100644
--- a/mobile/android/base/resources/values/attrs.xml
+++ b/mobile/android/base/resources/values/attrs.xml
@@ -98,7 +98,7 @@
-
+
diff --git a/mobile/android/base/resources/values/dimens.xml b/mobile/android/base/resources/values/dimens.xml
index 985d23dfbd8f..1ef95e631ae5 100644
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -109,7 +109,7 @@
100dp
18dp
1dp
- 156dp
+ 156dp
47dp
58dp
11dp
diff --git a/mobile/android/base/resources/values/styles.xml b/mobile/android/base/resources/values/styles.xml
index 83237f65401b..6f096905ebba 100644
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -480,13 +480,13 @@
- 0.0
-
-
-
@@ -615,15 +615,15 @@
- true
-
-
-
-
diff --git a/mobile/android/base/tabs/PrivateTabsPanel.java b/mobile/android/base/tabs/PrivateTabsPanel.java
index aa20ee4b3ef1..e874c0c8a7ff 100644
--- a/mobile/android/base/tabs/PrivateTabsPanel.java
+++ b/mobile/android/base/tabs/PrivateTabsPanel.java
@@ -33,7 +33,7 @@ class PrivateTabsPanel extends FrameLayout implements CloseAllPanelView {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.private_tabs_panel, this);
- tabsLayout = (TabsLayout) findViewById(R.id.private_tabs_tray);
+ tabsLayout = (TabsLayout) findViewById(R.id.private_tabs_layout);
final View emptyView = findViewById(R.id.private_tabs_empty);
tabsLayout.setEmptyView(emptyView);
diff --git a/mobile/android/base/tabs/TabsGridLayout.java b/mobile/android/base/tabs/TabsGridLayout.java
index 37835c065ccf..a35405f2ecc4 100644
--- a/mobile/android/base/tabs/TabsGridLayout.java
+++ b/mobile/android/base/tabs/TabsGridLayout.java
@@ -47,8 +47,8 @@ class TabsGridLayout extends GridView
super(context, attrs, R.attr.tabGridLayoutViewStyle);
mContext = context;
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsTray);
- mIsPrivate = (a.getInt(R.styleable.TabsTray_tabs, 0x0) == 1);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
+ mIsPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
a.recycle();
mTabsAdapter = new TabsGridLayoutAdapter(mContext);
diff --git a/mobile/android/base/tabs/TabsListLayout.java b/mobile/android/base/tabs/TabsListLayout.java
index 326b6c41eb9d..86034515ce62 100644
--- a/mobile/android/base/tabs/TabsListLayout.java
+++ b/mobile/android/base/tabs/TabsListLayout.java
@@ -65,8 +65,8 @@ class TabsListLayout extends TwoWayView
setItemsCanFocus(true);
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsTray);
- mIsPrivate = (a.getInt(R.styleable.TabsTray_tabs, 0x0) == 1);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabsLayout);
+ mIsPrivate = (a.getInt(R.styleable.TabsLayout_tabs, 0x0) == 1);
a.recycle();
mTabsAdapter = new TabsListLayoutAdapter(mContext);
diff --git a/mobile/android/base/tabs/TabsPanel.java b/mobile/android/base/tabs/TabsPanel.java
index 5d279cc53970..3a0bd4ef7ca7 100644
--- a/mobile/android/base/tabs/TabsPanel.java
+++ b/mobile/android/base/tabs/TabsPanel.java
@@ -83,7 +83,7 @@ public class TabsPanel extends LinearLayout
private final GeckoApp mActivity;
private final LightweightTheme mTheme;
private RelativeLayout mHeader;
- private PanelViewContainer mPanelsContainer;
+ private TabsLayoutContainer mTabsContainer;
private PanelView mPanel;
private PanelView mPanelNormal;
private PanelView mPanelPrivate;
@@ -152,7 +152,7 @@ public class TabsPanel extends LinearLayout
private void initialize() {
mHeader = (RelativeLayout) findViewById(R.id.tabs_panel_header);
- mPanelsContainer = (PanelViewContainer) findViewById(R.id.tabs_container);
+ mTabsContainer = (TabsLayoutContainer) findViewById(R.id.tabs_container);
mPanelNormal = (PanelView) findViewById(R.id.normal_tabs);
mPanelNormal.setTabsPanel(this);
@@ -279,19 +279,19 @@ public class TabsPanel extends LinearLayout
return mActivity.onOptionsItemSelected(item);
}
- private static int getPanelsContainerHeight(PanelViewContainer panelsContainer) {
- Resources resources = panelsContainer.getContext().getResources();
+ private static int getTabContainerHeight(TabsLayoutContainer tabsContainer) {
+ Resources resources = tabsContainer.getContext().getResources();
- PanelView panelView = panelsContainer.getCurrentPanelView();
+ PanelView panelView = tabsContainer.getCurrentPanelView();
if (panelView != null && !panelView.shouldExpand()) {
- return resources.getDimensionPixelSize(R.dimen.tabs_tray_horizontal_height);
+ return resources.getDimensionPixelSize(R.dimen.tabs_layout_horizontal_height);
}
int actionBarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height);
int screenHeight = resources.getDisplayMetrics().heightPixels;
Rect windowRect = new Rect();
- panelsContainer.getWindowVisibleDisplayFrame(windowRect);
+ tabsContainer.getWindowVisibleDisplayFrame(windowRect);
int windowHeight = windowRect.bottom - windowRect.top;
// The web content area should have at least 1.5x the height of the action bar.
@@ -338,9 +338,8 @@ public class TabsPanel extends LinearLayout
onLightweightThemeChanged();
}
- // Panel View Container holds the ListView
- static class PanelViewContainer extends FrameLayout {
- public PanelViewContainer(Context context, AttributeSet attrs) {
+ static class TabsLayoutContainer extends FrameLayout {
+ public TabsLayoutContainer(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -361,7 +360,7 @@ public class TabsPanel extends LinearLayout
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!GeckoAppShell.getGeckoInterface().hasTabsSideBar()) {
- int heightSpec = MeasureSpec.makeMeasureSpec(getPanelsContainerHeight(PanelViewContainer.this), MeasureSpec.EXACTLY);
+ int heightSpec = MeasureSpec.makeMeasureSpec(getTabContainerHeight(TabsLayoutContainer.this), MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightSpec);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -483,7 +482,7 @@ public class TabsPanel extends LinearLayout
dispatchLayoutChange(getWidth(), getHeight());
} else {
int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height);
- int height = actionBarHeight + getPanelsContainerHeight(mPanelsContainer);
+ int height = actionBarHeight + getTabContainerHeight(mTabsContainer);
dispatchLayoutChange(getWidth(), height);
}
mHeaderVisible = true;
@@ -502,7 +501,7 @@ public class TabsPanel extends LinearLayout
public void refresh() {
removeAllViews();
- LayoutInflater.from(mContext).inflate(R.layout.tabs_panel, this);
+ inflateLayout(mContext);
initialize();
if (mVisible)
@@ -541,13 +540,13 @@ public class TabsPanel extends LinearLayout
final int tabsPanelWidth = getWidth();
if (mVisible) {
ViewHelper.setTranslationX(mHeader, -tabsPanelWidth);
- ViewHelper.setTranslationX(mPanelsContainer, -tabsPanelWidth);
+ ViewHelper.setTranslationX(mTabsContainer, -tabsPanelWidth);
// The footer view is only present on the sidebar, v11+.
ViewHelper.setTranslationX(mFooter, -tabsPanelWidth);
}
final int translationX = (mVisible ? 0 : -tabsPanelWidth);
- animator.attach(mPanelsContainer, PropertyAnimator.Property.TRANSLATION_X, translationX);
+ animator.attach(mTabsContainer, PropertyAnimator.Property.TRANSLATION_X, translationX);
animator.attach(mHeader, PropertyAnimator.Property.TRANSLATION_X, translationX);
animator.attach(mFooter, PropertyAnimator.Property.TRANSLATION_X, translationX);
@@ -557,16 +556,16 @@ public class TabsPanel extends LinearLayout
final int translationY = (mVisible ? 0 : -toolbarHeight);
if (mVisible) {
ViewHelper.setTranslationY(mHeader, -toolbarHeight);
- ViewHelper.setTranslationY(mPanelsContainer, -toolbarHeight);
- ViewHelper.setAlpha(mPanelsContainer, 0.0f);
+ ViewHelper.setTranslationY(mTabsContainer, -toolbarHeight);
+ ViewHelper.setAlpha(mTabsContainer, 0.0f);
}
- animator.attach(mPanelsContainer, PropertyAnimator.Property.ALPHA, mVisible ? 1.0f : 0.0f);
- animator.attach(mPanelsContainer, PropertyAnimator.Property.TRANSLATION_Y, translationY);
+ animator.attach(mTabsContainer, PropertyAnimator.Property.ALPHA, mVisible ? 1.0f : 0.0f);
+ animator.attach(mTabsContainer, PropertyAnimator.Property.TRANSLATION_Y, translationY);
animator.attach(mHeader, PropertyAnimator.Property.TRANSLATION_Y, translationY);
}
mHeader.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- mPanelsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mTabsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
public void finishTabsAnimation() {
@@ -575,10 +574,10 @@ public class TabsPanel extends LinearLayout
}
mHeader.setLayerType(View.LAYER_TYPE_NONE, null);
- mPanelsContainer.setLayerType(View.LAYER_TYPE_NONE, null);
+ mTabsContainer.setLayerType(View.LAYER_TYPE_NONE, null);
- // If the tray is now hidden, call hide() on current panel and unset it as the current panel
- // to avoid hide() being called again when the tray is opened next.
+ // If the tabs panel is now hidden, call hide() on current panel and unset it as the current panel
+ // to avoid hide() being called again when the layout is opened next.
if (!mVisible && mPanel != null) {
mPanel.hide();
mPanel = null;
diff --git a/mobile/android/base/tests/BaseTest.java b/mobile/android/base/tests/BaseTest.java
index 6d4f018d8de7..4fb0e9d44b0d 100644
--- a/mobile/android/base/tests/BaseTest.java
+++ b/mobile/android/base/tests/BaseTest.java
@@ -25,6 +25,7 @@ import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoThread;
import org.mozilla.gecko.GeckoThread.LaunchState;
+import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R;
import org.mozilla.gecko.RobocopUtils;
import org.mozilla.gecko.Tab;
@@ -69,6 +70,8 @@ abstract class BaseTest extends BaseRobocopTest {
private static final int GECKO_READY_WAIT_MS = 180000;
public static final int MAX_WAIT_BLOCK_FOR_EVENT_DATA_MS = 90000;
+ private static final String URL_HTTP_PREFIX = "http://";
+
private Activity mActivity;
private int mPreferenceRequestID = 0;
protected Solo mSolo;
@@ -527,7 +530,25 @@ abstract class BaseTest extends BaseRobocopTest {
}
}
- public final void verifyPageTitle(String title) {
+ public final void verifyPageTitle(final String title, String url) {
+ // We are asserting visible state - we shouldn't know if the title is null.
+ mAsserter.isnot(title, null, "The title argument is not null");
+ mAsserter.isnot(url, null, "The url argument is not null");
+
+ // TODO: We should also check the title bar preference.
+ final String expected;
+ if (!NewTabletUI.isEnabled(mActivity)) {
+ expected = title;
+ } else {
+ if (StringHelper.ABOUT_HOME_URL.equals(url)) {
+ expected = StringHelper.ABOUT_HOME_TITLE;
+ } else if (url.startsWith(URL_HTTP_PREFIX)) {
+ expected = url.substring(URL_HTTP_PREFIX.length());
+ } else {
+ expected = url;
+ }
+ }
+
final TextView urlBarTitle = (TextView) mSolo.getView(R.id.url_bar_title);
String pageTitle = null;
if (urlBarTitle != null) {
@@ -536,7 +557,7 @@ abstract class BaseTest extends BaseRobocopTest {
waitForCondition(new VerifyTextViewText(urlBarTitle, title), MAX_WAIT_VERIFY_PAGE_TITLE_MS);
pageTitle = urlBarTitle.getText().toString();
}
- mAsserter.is(pageTitle, title, "Page title is correct");
+ mAsserter.is(pageTitle, expected, "Page title is correct");
}
public final void verifyTabCount(int expectedTabCount) {
@@ -619,23 +640,23 @@ abstract class BaseTest extends BaseRobocopTest {
/**
* Gets the AdapterView of the tabs list.
*
- * @return List view in the tabs tray
+ * @return List view in the tabs panel
*/
- private final AdapterView getTabsList() {
+ private final AdapterView getTabsLayout() {
Element tabs = mDriver.findElement(getActivity(), R.id.tabs);
tabs.click();
return (AdapterView) getActivity().findViewById(R.id.normal_tabs);
}
/**
- * Gets the view in the tabs tray at the specified index.
+ * Gets the view in the tabs panel at the specified index.
*
* @return View at index
*/
private View getTabViewAt(final int index) {
final View[] childView = { null };
- final AdapterView view = getTabsList();
+ final AdapterView view = getTabsLayout();
runOnUiThreadSync(new Runnable() {
@Override
diff --git a/mobile/android/base/tests/SessionTest.java b/mobile/android/base/tests/SessionTest.java
index c1e14b6673f1..e2389efdcc38 100644
--- a/mobile/android/base/tests/SessionTest.java
+++ b/mobile/android/base/tests/SessionTest.java
@@ -197,7 +197,7 @@ public abstract class SessionTest extends BaseTest {
verifyUrl(page.url);
} else {
waitForText(page.title);
- verifyPageTitle(page.title);
+ verifyPageTitle(page.title, page.url);
}
}
diff --git a/mobile/android/base/tests/components/ToolbarComponent.java b/mobile/android/base/tests/components/ToolbarComponent.java
index 4642a2b39484..2aa79eb42d33 100644
--- a/mobile/android/base/tests/components/ToolbarComponent.java
+++ b/mobile/android/base/tests/components/ToolbarComponent.java
@@ -9,9 +9,12 @@ import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertFalse;
import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertNotNull;
import static org.mozilla.gecko.tests.helpers.AssertionHelper.fAssertTrue;
+import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R;
+import org.mozilla.gecko.tests.StringHelper;
import org.mozilla.gecko.tests.UITestContext;
import org.mozilla.gecko.tests.helpers.DeviceHelper;
+import org.mozilla.gecko.tests.helpers.NavigationHelper;
import org.mozilla.gecko.tests.helpers.WaitHelper;
import android.view.View;
@@ -26,6 +29,9 @@ import com.jayway.android.robotium.solo.Solo;
* A class representing any interactions that take place on the Toolbar.
*/
public class ToolbarComponent extends BaseComponent {
+
+ private static final String URL_HTTP_PREFIX = "http://";
+
public ToolbarComponent(final UITestContext testContext) {
super(testContext);
}
@@ -40,7 +46,26 @@ public class ToolbarComponent extends BaseComponent {
return this;
}
- public ToolbarComponent assertTitle(final String expected) {
+ public ToolbarComponent assertTitle(final String title, final String url) {
+ // We are asserting visible state - we shouldn't know if the title is null.
+ fAssertNotNull("The title argument is not null", title);
+ fAssertNotNull("The url argument is not null", url);
+
+ // TODO: We should also check the title bar preference.
+ final String expected;
+ if (!NewTabletUI.isEnabled(mActivity)) {
+ expected = title;
+ } else {
+ final String absoluteURL = NavigationHelper.adjustUrl(url);
+ if (StringHelper.ABOUT_HOME_URL.equals(absoluteURL)) {
+ expected = StringHelper.ABOUT_HOME_TITLE;
+ } else if (absoluteURL.startsWith(URL_HTTP_PREFIX)) {
+ expected = absoluteURL.substring(URL_HTTP_PREFIX.length());
+ } else {
+ expected = absoluteURL;
+ }
+ }
+
fAssertEquals("The Toolbar title is " + expected, expected, getTitle());
return this;
}
@@ -91,7 +116,7 @@ public class ToolbarComponent extends BaseComponent {
return (ImageButton) getToolbarView().findViewById(R.id.edit_cancel);
}
- private CharSequence getTitle() {
+ private String getTitle() {
return getTitleHelper(true);
}
@@ -100,16 +125,16 @@ public class ToolbarComponent extends BaseComponent {
* may return a value that may never be visible to the user. Callers likely want to use
* {@link assertTitle} instead.
*/
- public CharSequence getPotentiallyInconsistentTitle() {
+ public String getPotentiallyInconsistentTitle() {
return getTitleHelper(false);
}
- private CharSequence getTitleHelper(final boolean shouldAssertNotEditing) {
+ private String getTitleHelper(final boolean shouldAssertNotEditing) {
if (shouldAssertNotEditing) {
assertIsNotEditing();
}
- return getUrlTitleText().getText();
+ return getUrlTitleText().getText().toString();
}
private boolean isEditing() {
diff --git a/mobile/android/base/tests/helpers/NavigationHelper.java b/mobile/android/base/tests/helpers/NavigationHelper.java
index 8d7f1f0b73ab..eb80f71faca1 100644
--- a/mobile/android/base/tests/helpers/NavigationHelper.java
+++ b/mobile/android/base/tests/helpers/NavigationHelper.java
@@ -44,7 +44,7 @@ final public class NavigationHelper {
/**
* Returns a new URL with the docshell HTTP server host prefix.
*/
- private static String adjustUrl(final String url) {
+ public static String adjustUrl(final String url) {
fAssertNotNull("url is not null", url);
if (url.startsWith("about:") || url.startsWith("chrome:")) {
diff --git a/mobile/android/base/tests/robocop_autophone.ini b/mobile/android/base/tests/robocop_autophone.ini
index 57bc20ff76d5..df90138c1490 100644
--- a/mobile/android/base/tests/robocop_autophone.ini
+++ b/mobile/android/base/tests/robocop_autophone.ini
@@ -66,7 +66,7 @@
#[testTabHistory]
# fails on gs2, nexus one, lg revolution, nexus s
-#[testTabsTrayMenu]
+#[testTabsLayoutMenu]
# fails on gs2, nexus one, lg revolution, nexus s
#[testThumbnails]
diff --git a/mobile/android/base/tests/testAboutHomeVisibility.java b/mobile/android/base/tests/testAboutHomeVisibility.java
index 3f408c119701..979eb412a60f 100644
--- a/mobile/android/base/tests/testAboutHomeVisibility.java
+++ b/mobile/android/base/tests/testAboutHomeVisibility.java
@@ -13,18 +13,20 @@ public class testAboutHomeVisibility extends UITest {
GeckoHelper.blockForReady();
// Check initial state on about:home.
- mToolbar.assertTitle(StringHelper.ABOUT_HOME_TITLE);
+ mToolbar.assertTitle(StringHelper.ABOUT_HOME_TITLE, StringHelper.ABOUT_HOME_URL);
mAboutHome.assertVisible()
.assertCurrentPanel(PanelType.TOP_SITES);
// Go to blank 01.
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
- mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
+ mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE,
+ StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
mAboutHome.assertNotVisible();
// Go to blank 02.
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
- mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
+ mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE,
+ StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
mAboutHome.assertNotVisible();
// Enter editing mode, where the about:home UI should be visible.
@@ -38,7 +40,7 @@ public class testAboutHomeVisibility extends UITest {
// Loading about:home should show about:home again.
NavigationHelper.enterAndLoadUrl(StringHelper.ABOUT_HOME_URL);
- mToolbar.assertTitle(StringHelper.ABOUT_HOME_TITLE);
+ mToolbar.assertTitle(StringHelper.ABOUT_HOME_TITLE, StringHelper.ABOUT_HOME_URL);
mAboutHome.assertVisible()
.assertCurrentPanel(PanelType.TOP_SITES);
@@ -49,7 +51,5 @@ public class testAboutHomeVisibility extends UITest {
mAboutHome.navigateToBuiltinPanelType(PanelType.HISTORY)
.assertVisible()
.assertCurrentPanel(PanelType.HISTORY);
-
- // TODO: Type in a url and assert the go button is visible.
}
}
diff --git a/mobile/android/base/tests/testAboutPage.java b/mobile/android/base/tests/testAboutPage.java
index cd47be2703e8..f71dca1f20e0 100644
--- a/mobile/android/base/tests/testAboutPage.java
+++ b/mobile/android/base/tests/testAboutPage.java
@@ -2,34 +2,44 @@ package org.mozilla.gecko.tests;
import org.mozilla.gecko.Actions;
import org.mozilla.gecko.Element;
+import org.mozilla.gecko.NewTabletUI;
import org.mozilla.gecko.R;
+import android.app.Activity;
+
/* Tests related to the about: page:
* - check that about: loads from the URL bar
* - check that about: loads from Settings/About...
*/
public class testAboutPage extends PixelTest {
- private void ensureTitleMatches(final String regex) {
- Element urlBarTitle = mDriver.findElement(getActivity(), R.id.url_bar_title);
+ /**
+ * Ensures the page title matches the given regex (as opposed to String equality).
+ */
+ private void ensureTitleMatches(final String titleRegex, final String urlRegex) {
+ final Activity activity = getActivity();
+ final Element urlBarTitle = mDriver.findElement(activity, R.id.url_bar_title);
+
+ // TODO: We should also be testing what the page title preference value is.
+ final String expectedTitle = NewTabletUI.isEnabled(activity) ? urlRegex : titleRegex;
mAsserter.isnot(urlBarTitle, null, "Got the URL bar title");
- assertMatches(urlBarTitle.getText(), regex, "page title match");
+ assertMatches(urlBarTitle.getText(), expectedTitle, "page title match");
}
public void testAboutPage() {
blockForGeckoReady();
// Load the about: page and verify its title.
- String url = "about:";
+ String url = StringHelper.ABOUT_SCHEME;
loadAndPaint(url);
- ensureTitleMatches(StringHelper.ABOUT_LABEL);
+ ensureTitleMatches(StringHelper.ABOUT_LABEL, url);
// Open a new page to remove the about: page from the current tab.
url = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
inputAndLoadUrl(url);
// At this point the page title should have been set.
- ensureTitleMatches(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
+ verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE, url);
// Set up listeners to catch the page load we're about to do.
Actions.EventExpecter tabEventExpecter = mActions.expectGeckoEvent("Tab:Added");
@@ -45,6 +55,6 @@ public class testAboutPage extends PixelTest {
contentEventExpecter.unregisterListener();
// Grab the title to make sure the about: page was loaded.
- ensureTitleMatches(StringHelper.ABOUT_LABEL);
+ ensureTitleMatches(StringHelper.ABOUT_LABEL, StringHelper.ABOUT_SCHEME);
}
}
diff --git a/mobile/android/base/tests/testAddSearchEngine.java b/mobile/android/base/tests/testAddSearchEngine.java
index d218fedd3a9b..4b64f2efd695 100644
--- a/mobile/android/base/tests/testAddSearchEngine.java
+++ b/mobile/android/base/tests/testAddSearchEngine.java
@@ -61,7 +61,7 @@ public class testAddSearchEngine extends AboutHomeTest {
// Load the page for the search engine to add.
inputAndLoadUrl(searchEngineURL);
waitForText(StringHelper.ROBOCOP_SEARCH_TITLE);
- verifyPageTitle(StringHelper.ROBOCOP_SEARCH_TITLE);
+ verifyPageTitle(StringHelper.ROBOCOP_SEARCH_TITLE, searchEngineURL);
// Used to long-tap on the search input box for the search engine to add.
getInstrumentation().waitForIdleSync();
@@ -99,7 +99,7 @@ public class testAddSearchEngine extends AboutHomeTest {
mAsserter.dumpLog("Search Engines list = " + searchEngines.toString());
mAsserter.is(searchEngines.size(), initialNumSearchEngines + 1, "Checking the number of Search Engines has increased");
-
+
// Verify that the number of displayed searchengines is the same as the one received through the SearchEngines:Data event.
verifyDisplayedSearchEnginesCount(initialNumSearchEngines + 1);
searchEngineDataEventExpector.unregisterListener();
@@ -151,7 +151,7 @@ public class testAddSearchEngine extends AboutHomeTest {
return (adapter.getCount() == expectedCount);
}
}, MAX_WAIT_TEST_MS);
-
+
// Exit about:home
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
diff --git a/mobile/android/base/tests/testAddonManager.java b/mobile/android/base/tests/testAddonManager.java
index d4033cc374a4..8336dfb26930 100644
--- a/mobile/android/base/tests/testAddonManager.java
+++ b/mobile/android/base/tests/testAddonManager.java
@@ -17,7 +17,7 @@ public class testAddonManager extends PixelTest {
public void testAddonManager() {
Actions.EventExpecter tabEventExpecter;
Actions.EventExpecter contentEventExpecter;
- String url = StringHelper.ABOUT_ADDONS_URL;
+ final String aboutAddonsURL = StringHelper.ABOUT_ADDONS_URL;
blockForGeckoReady();
@@ -36,21 +36,22 @@ public class testAddonManager extends PixelTest {
contentEventExpecter.unregisterListener();
// Verify the url
- verifyPageTitle(StringHelper.ADDONS_LABEL);
+ verifyPageTitle(StringHelper.ADDONS_LABEL, aboutAddonsURL);
// Close the Add-on Manager
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
// Load the about:addons page and verify it was loaded
- loadAndPaint(url);
- verifyPageTitle(StringHelper.ADDONS_LABEL);
+ loadAndPaint(aboutAddonsURL);
+ verifyPageTitle(StringHelper.ADDONS_LABEL, aboutAddonsURL);
// Setup wait for tab to spawn and load
tabEventExpecter = mActions.expectGeckoEvent("Tab:Added");
contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded");
// Open a new tab
- addTab(getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL));
+ final String blankURL = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
+ addTab(blankURL);
// Wait for the new tab and page to load
tabEventExpecter.blockForEvent();
@@ -63,7 +64,7 @@ public class testAddonManager extends PixelTest {
verifyTabCount(2);
// Verify the page was opened
- verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
+ verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE, blankURL);
// Addons Manager is not opened 2 separate times when opened from the menu
selectMenuItem(StringHelper.ADDONS_LABEL);
diff --git a/mobile/android/base/tests/testAppMenuPathways.java b/mobile/android/base/tests/testAppMenuPathways.java
index 42c369ebcb48..d1f4485186d0 100644
--- a/mobile/android/base/tests/testAppMenuPathways.java
+++ b/mobile/android/base/tests/testAppMenuPathways.java
@@ -48,7 +48,8 @@ public class testAppMenuPathways extends UITest {
// The above mock video playback test changes Java state, but not the associated JS state.
// Navigate to a new page so that the Java state is cleared.
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
- mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
+ mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE,
+ StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
// Test save as pdf functionality.
// The following call doesn't wait for the resulting pdf but checks that no exception are thrown.
diff --git a/mobile/android/base/tests/testBookmark.java b/mobile/android/base/tests/testBookmark.java
index 80bb4cc0bf64..8225aac5ff8b 100644
--- a/mobile/android/base/tests/testBookmark.java
+++ b/mobile/android/base/tests/testBookmark.java
@@ -32,7 +32,8 @@ public class testBookmark extends AboutHomeTest {
isBookmarkDisplayed(BOOKMARK_URL);
loadBookmark(BOOKMARK_URL);
waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
- verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
+ verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE,
+ StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
mDatabaseHelper.deleteBookmark(BOOKMARK_URL);
waitForBookmarked(false);
diff --git a/mobile/android/base/tests/testBookmarkFolders.java b/mobile/android/base/tests/testBookmarkFolders.java
index b584633bdd65..ec65c68e0260 100644
--- a/mobile/android/base/tests/testBookmarkFolders.java
+++ b/mobile/android/base/tests/testBookmarkFolders.java
@@ -52,7 +52,7 @@ public class testBookmarkFolders extends AboutHomeTest {
// Open the bookmark from a bookmark folder hierarchy
loadBookmark(DESKTOP_BOOKMARK_URL);
waitForText(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
- verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
+ verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE, DESKTOP_BOOKMARK_URL);
openAboutHomeTab(AboutHomeTabs.BOOKMARKS);
// Check that folders don't have a context menu
diff --git a/mobile/android/base/tests/testBookmarkKeyword.java b/mobile/android/base/tests/testBookmarkKeyword.java
index f17c36d47a2a..f7cd6cca6786 100644
--- a/mobile/android/base/tests/testBookmarkKeyword.java
+++ b/mobile/android/base/tests/testBookmarkKeyword.java
@@ -19,7 +19,8 @@ public class testBookmarkKeyword extends AboutHomeTest {
waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
// Make sure the title of the page appeared.
- verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
+ verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE,
+ StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
// Delete the bookmark to clean up.
mDatabaseHelper.deleteBookmark(url);
diff --git a/mobile/android/base/tests/testBookmarklets.java b/mobile/android/base/tests/testBookmarklets.java
index c191625b54b9..637cf5225009 100644
--- a/mobile/android/base/tests/testBookmarklets.java
+++ b/mobile/android/base/tests/testBookmarklets.java
@@ -18,7 +18,9 @@ public class testBookmarklets extends AboutHomeTest {
// load a standard page so bookmarklets work
inputAndLoadUrl(url);
- verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE); // Waiting for page title to ensure the page is loaded
+ // Waiting for page title to ensure the page is loaded
+ verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE,
+ StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
// verify that user-entered bookmarklets do *not* work
enterUrl(js);
diff --git a/mobile/android/base/tests/testClearPrivateData.java b/mobile/android/base/tests/testClearPrivateData.java
index cc63d4a29666..42a95d814466 100644
--- a/mobile/android/base/tests/testClearPrivateData.java
+++ b/mobile/android/base/tests/testClearPrivateData.java
@@ -30,14 +30,14 @@ public class testClearPrivateData extends PixelTest {
String blank2 = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
String title = StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE;
inputAndLoadUrl(blank1);
- verifyPageTitle(title);
+ verifyPageTitle(title, blank1);
mDatabaseHelper.addOrUpdateMobileBookmark(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE, blank2);
// Checking that the history list is not empty
verifyHistoryCount(1);
//clear and check for device
- checkDevice(title);
+ checkDevice(title, blank1);
// Checking that history list is empty
verifyHistoryCount(0);
@@ -65,7 +65,7 @@ public class testClearPrivateData extends PixelTest {
checkOption(shareStrings[3], "Cancel");
loadCheckDismiss(shareStrings[2], url, shareStrings[0]);
checkOption(shareStrings[2], "Cancel");
- checkDevice(titleGeolocation);
+ checkDevice(titleGeolocation, url);
}
public void clearPassword(){
@@ -75,24 +75,20 @@ public class testClearPrivateData extends PixelTest {
loadCheckDismiss(passwordStrings[1], loginUrl, passwordStrings[0]);
checkOption(passwordStrings[1], "Clear");
loadCheckDismiss(passwordStrings[2], loginUrl, passwordStrings[0]);
- checkDevice(title);
+ checkDevice(title, getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL));
}
// clear private data and verify the device type because for phone there is an extra back action to exit the settings menu
- public void checkDevice(String title) {
+ public void checkDevice(final String title, final String url) {
clearPrivateData();
if (mDevice.type.equals("phone")) {
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
mAsserter.ok(waitForText(StringHelper.PRIVACY_SECTION_LABEL), "waiting to perform one back", "one back");
- mActions.sendSpecialKey(Actions.SpecialKey.BACK);
- verifyPageTitle(title);
- }
- else {
- mActions.sendSpecialKey(Actions.SpecialKey.BACK);
- verifyPageTitle(title);
}
+ mActions.sendSpecialKey(Actions.SpecialKey.BACK);
+ verifyPageTitle(title, url);
}
-
+
// Load a URL, verify that the doorhanger appears and dismiss it
public void loadCheckDismiss(String option, String url, String message) {
inputAndLoadUrl(url);
diff --git a/mobile/android/base/tests/testHistory.java b/mobile/android/base/tests/testHistory.java
index 3b929b7d1099..53187cd2deaf 100644
--- a/mobile/android/base/tests/testHistory.java
+++ b/mobile/android/base/tests/testHistory.java
@@ -17,11 +17,11 @@ public class testHistory extends AboutHomeTest {
String url3 = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_03_URL);
inputAndLoadUrl(url);
- verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
+ verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_URL, url);
inputAndLoadUrl(url2);
- verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
+ verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_URL, url2);
inputAndLoadUrl(url3);
- verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_03_URL);
+ verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_03_URL, url3);
openAboutHomeTab(AboutHomeTabs.HISTORY);
@@ -62,7 +62,7 @@ public class testHistory extends AboutHomeTest {
// The first item here (since it was just visited) should be a "Switch to tab" item
// i.e. don't expect a DOMContentLoaded event
- verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_03_URL);
+ verifyPageTitle(StringHelper.ROBOCOP_BLANK_PAGE_03_URL, StringHelper.ROBOCOP_BLANK_PAGE_03_URL);
verifyUrl(url3);
}
}
diff --git a/mobile/android/base/tests/testInputConnection.java b/mobile/android/base/tests/testInputConnection.java
index 96f365fc1b64..e7d3bc739480 100644
--- a/mobile/android/base/tests/testInputConnection.java
+++ b/mobile/android/base/tests/testInputConnection.java
@@ -24,8 +24,9 @@ public class testInputConnection extends UITest {
public void testInputConnection() throws InterruptedException {
GeckoHelper.blockForReady();
- NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_INPUT_URL + "#" + INITIAL_TEXT);
- mToolbar.assertTitle(StringHelper.ROBOCOP_INPUT_TITLE);
+ final String url = StringHelper.ROBOCOP_INPUT_URL + "#" + INITIAL_TEXT;
+ NavigationHelper.enterAndLoadUrl(url);
+ mToolbar.assertTitle(StringHelper.ROBOCOP_INPUT_TITLE, url);
mGeckoView.mTextInput
.waitForInputConnection()
diff --git a/mobile/android/base/tests/testPictureLinkContextMenu.java b/mobile/android/base/tests/testPictureLinkContextMenu.java
index 0e6f77ba92b3..1801936341d8 100644
--- a/mobile/android/base/tests/testPictureLinkContextMenu.java
+++ b/mobile/android/base/tests/testPictureLinkContextMenu.java
@@ -18,7 +18,7 @@ public class testPictureLinkContextMenu extends ContentContextMenuTest {
PICTURE_PAGE_URL=getAbsoluteUrl(StringHelper.ROBOCOP_PICTURE_LINK_URL);
BLANK_PAGE_URL=getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
loadAndPaint(PICTURE_PAGE_URL);
- verifyPageTitle(PICTURE_PAGE_TITLE);
+ verifyPageTitle(PICTURE_PAGE_TITLE, PICTURE_PAGE_URL);
switchTabs(imageTitle);
verifyContextMenuItems(photoMenuItems);
diff --git a/mobile/android/base/tests/testReaderMode.java b/mobile/android/base/tests/testReaderMode.java
index 491bc82b81ef..3b10e4609ced 100644
--- a/mobile/android/base/tests/testReaderMode.java
+++ b/mobile/android/base/tests/testReaderMode.java
@@ -73,7 +73,7 @@ public class testReaderMode extends AboutHomeTest {
contentPageShowExpecter.unregisterListener();
paintExpecter.blockUntilClear(EVENT_CLEAR_DELAY_MS);
paintExpecter.unregisterListener();
- verifyPageTitle(StringHelper.ROBOCOP_TEXT_PAGE_TITLE);
+ verifyPageTitle(StringHelper.ROBOCOP_TEXT_PAGE_TITLE, StringHelper.ROBOCOP_TEXT_PAGE_URL);
// Open the share menu for the reader toolbar
height = mDriver.getGeckoTop() + mDriver.getGeckoHeight() - 10;
@@ -134,7 +134,7 @@ public class testReaderMode extends AboutHomeTest {
mSolo.clickOnView(child);
contentEventExpecter.blockForEvent();
contentEventExpecter.unregisterListener();
- verifyPageTitle(StringHelper.ROBOCOP_TEXT_PAGE_TITLE);
+ verifyPageTitle(StringHelper.ROBOCOP_TEXT_PAGE_TITLE, StringHelper.ROBOCOP_TEXT_PAGE_URL);
// Verify that we are in reader mode and remove the page from Reading List
height = mDriver.getGeckoTop() + mDriver.getGeckoHeight() - 10;
@@ -142,7 +142,7 @@ public class testReaderMode extends AboutHomeTest {
mAsserter.dumpLog("Long Clicking at width = " + String.valueOf(width) + " and height = " + String.valueOf(height));
mSolo.clickOnScreen(width,height);
mAsserter.ok(mSolo.waitForText("Page removed from your Reading List"), "Waiting for the page to removed from your Reading List", "The page is removed from your Reading List");
- verifyPageTitle(StringHelper.ROBOCOP_TEXT_PAGE_TITLE);
+ verifyPageTitle(StringHelper.ROBOCOP_TEXT_PAGE_TITLE, StringHelper.ROBOCOP_TEXT_PAGE_URL);
//Check if the Reading List is empty
openAboutHomeTab(AboutHomeTabs.READING_LIST);
diff --git a/mobile/android/base/tests/testSelectionHandler.java b/mobile/android/base/tests/testSelectionHandler.java
index 5684bafd582e..14206a362d6c 100644
--- a/mobile/android/base/tests/testSelectionHandler.java
+++ b/mobile/android/base/tests/testSelectionHandler.java
@@ -16,8 +16,9 @@ public class testSelectionHandler extends UITest {
GeckoHelper.blockForReady();
Actions.EventExpecter robocopTestExpecter = getActions().expectGeckoEvent("Robocop:testSelectionHandler");
- NavigationHelper.enterAndLoadUrl("chrome://roboextender/content/testSelectionHandler.html");
- mToolbar.assertTitle(StringHelper.ROBOCOP_SELECTION_HANDLER_TITLE);
+ final String url = "chrome://roboextender/content/testSelectionHandler.html";
+ NavigationHelper.enterAndLoadUrl(url);
+ mToolbar.assertTitle(StringHelper.ROBOCOP_SELECTION_HANDLER_TITLE, url);
while (!test(robocopTestExpecter)) {
// do nothing
diff --git a/mobile/android/base/tests/testSessionHistory.java b/mobile/android/base/tests/testSessionHistory.java
index e1e3c2583719..ad13c65f273c 100644
--- a/mobile/android/base/tests/testSessionHistory.java
+++ b/mobile/android/base/tests/testSessionHistory.java
@@ -10,25 +10,32 @@ public class testSessionHistory extends UITest {
public void testSessionHistory() {
GeckoHelper.blockForReady();
- NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
- mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
+ String url = StringHelper.ROBOCOP_BLANK_PAGE_01_URL;
+ NavigationHelper.enterAndLoadUrl(url);
+ mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE, url);
- NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
- mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
+ url = StringHelper.ROBOCOP_BLANK_PAGE_02_URL;
+ NavigationHelper.enterAndLoadUrl(url);
+ mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE, url);
- NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_BLANK_PAGE_03_URL);
- mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_03_TITLE);
+ url = StringHelper.ROBOCOP_BLANK_PAGE_03_URL;
+ NavigationHelper.enterAndLoadUrl(url);
+ mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_03_TITLE, url);
NavigationHelper.goBack();
- mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
+ mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE,
+ StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
NavigationHelper.goBack();
- mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
+ mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE,
+ StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
NavigationHelper.goForward();
- mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
+ mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE,
+ StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
NavigationHelper.reload();
- mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE);
+ mToolbar.assertTitle(StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE,
+ StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
}
}
diff --git a/mobile/android/base/tests/testShareLink.java b/mobile/android/base/tests/testShareLink.java
index 0c98535592fc..9c1c8efefd22 100644
--- a/mobile/android/base/tests/testShareLink.java
+++ b/mobile/android/base/tests/testShareLink.java
@@ -37,7 +37,7 @@ public class testShareLink extends AboutHomeTest {
openAboutHomeTab(AboutHomeTabs.READING_LIST);
inputAndLoadUrl(url);
- verifyPageTitle(urlTitle); // Waiting for page title to ensure the page is loaded
+ verifyPageTitle(urlTitle, url); // Waiting for page title to ensure the page is loaded
selectMenuItem(StringHelper.SHARE_LABEL);
if (Build.VERSION.SDK_INT >= 14) {
@@ -248,7 +248,7 @@ public class testShareLink extends AboutHomeTest {
public boolean test() {
ArrayList views = mSolo.getCurrentViews();
for (View view : views) {
- // List may be displayed in different view formats.
+ // List may be displayed in different view formats.
// On JB, GridView is common; on ICS-, ListView is common.
if (view instanceof ListView ||
view instanceof GridView) {
diff --git a/mobile/android/base/tests/testTitleBar.java b/mobile/android/base/tests/testTitleBar.java
index 37f1fce77226..4f1eced01f90 100644
--- a/mobile/android/base/tests/testTitleBar.java
+++ b/mobile/android/base/tests/testTitleBar.java
@@ -1,5 +1,7 @@
package org.mozilla.gecko.tests;
+
import org.mozilla.gecko.Actions;
+import org.mozilla.gecko.NewTabletUI;
/**
* This patch tests the option that shows the full URL and title in the URL Bar
@@ -7,6 +9,11 @@ import org.mozilla.gecko.Actions;
public class testTitleBar extends PixelTest {
public void testTitleBar() {
+ // Because there is no title bar option on new tablet, we don't need to run this test.
+ if (NewTabletUI.isEnabled(getActivity())) {
+ return;
+ }
+
blockForGeckoReady();
checkOption();
}
@@ -18,7 +25,7 @@ public class testTitleBar extends PixelTest {
// Loading a page
inputAndLoadUrl(blank1);
- verifyPageTitle(title);
+ verifyPageTitle(title, blank1);
// Ensure the full URL is displayed in the URL Bar
selectOption(StringHelper.SHOW_PAGE_ADDRESS_LABEL);
@@ -28,7 +35,7 @@ public class testTitleBar extends PixelTest {
// Ensure the title is displayed in the URL Bar
selectOption(StringHelper.SHOW_PAGE_TITLE_LABEL);
inputAndLoadUrl(blank1);
- verifyPageTitle(title);
+ verifyPageTitle(title, blank1);
}
// Entering settings, changing the options: show title/page address option and verifing the device type because for phone there is an extra back action to exit the settings menu
diff --git a/mobile/android/base/toolbar/TabCounter.java b/mobile/android/base/toolbar/TabCounter.java
index 770edc373ff5..794fa8df02cd 100644
--- a/mobile/android/base/toolbar/TabCounter.java
+++ b/mobile/android/base/toolbar/TabCounter.java
@@ -36,6 +36,8 @@ public class TabCounter extends ThemedTextSwitcher
private final int mLayoutId;
private int mCount;
+ public static final int MAX_VISIBLE_TABS = 99;
+ public static final String SO_MANY_TABS_OPEN = "∞";
private enum FadeMode {
FADE_IN,
@@ -84,6 +86,12 @@ public class TabCounter extends ThemedTextSwitcher
return;
}
+ // don't animate if there are still over MAX_VISIBLE_TABS tabs open
+ if (mCount > MAX_VISIBLE_TABS && count > MAX_VISIBLE_TABS) {
+ mCount = count;
+ return;
+ }
+
if (count < mCount) {
setInAnimation(mFlipInBackward);
setOutAnimation(mFlipOutForward);
@@ -97,14 +105,21 @@ public class TabCounter extends ThemedTextSwitcher
setDisplayedChild(0);
// Set In value, trigger animation to Out value
- setCurrentText(String.valueOf(mCount));
- setText(String.valueOf(count));
+ setCurrentText(formatForDisplay(mCount));
+ setText(formatForDisplay(count));
mCount = count;
}
+ private String formatForDisplay(int count) {
+ if (count > MAX_VISIBLE_TABS) {
+ return SO_MANY_TABS_OPEN;
+ }
+ return String.valueOf(count);
+ }
+
void setCount(int count) {
- setCurrentText(String.valueOf(count));
+ setCurrentText(formatForDisplay(count));
mCount = count;
}
diff --git a/mobile/android/base/widget/IconTabWidget.java b/mobile/android/base/widget/IconTabWidget.java
index 0e8ff1ce9762..579f819f4de6 100644
--- a/mobile/android/base/widget/IconTabWidget.java
+++ b/mobile/android/base/widget/IconTabWidget.java
@@ -83,7 +83,7 @@ public class IconTabWidget extends TabWidget {
if (!mIsIcon) {
return null;
}
- // We can have multiple views in the tray for each child. This finds the
+ // We can have multiple views in the tabs panel for each child. This finds the
// first view corresponding to the given tab. This varies by Android
// version. The first view should always be our ImageButton, but let's
// be safe.
@@ -98,7 +98,7 @@ public class IconTabWidget extends TabWidget {
if (!mIsIcon) {
return;
}
- // We can have multiple views in the tray for each child. This finds the
+ // We can have multiple views in the tabs panel for each child. This finds the
// first view corresponding to the given tab. This varies by Android
// version. The first view should always be our ImageButton, but let's
// be safe.
diff --git a/mobile/android/chrome/content/WebcompatReporter.js b/mobile/android/chrome/content/WebcompatReporter.js
new file mode 100644
index 000000000000..264553e62898
--- /dev/null
+++ b/mobile/android/chrome/content/WebcompatReporter.js
@@ -0,0 +1,87 @@
+/* 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/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var WebcompatReporter = {
+ menuItem: null,
+ menuItemEnabled: null,
+ init: function() {
+ Services.obs.addObserver(this, "DesktopMode:Change", false);
+ Services.obs.addObserver(this, "content-page-shown", false);
+ this.addMenuItem();
+ },
+
+ uninit: function() {
+ Services.obs.removeObserver(this, "DesktopMode:Change");
+
+ if (this.menuItem) {
+ NativeWindow.menu.remove(this.menuItem);
+ this.menuItem = null;
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ if (topic === "content-page-shown") {
+ let currentURI = subject.documentURI;
+ if (!this.menuItemEnabled && this.isReportableUrl(currentURI)) {
+ NativeWindow.menu.update(this.menuItem, {enabled: true});
+ this.menuItemEnabled = true;
+ } else if (this.menuItemEnabled && !this.isReportableUrl(currentURI)) {
+ NativeWindow.menu.update(this.menuItem, {enabled: false});
+ this.menuItemEnabled = false;
+ }
+ } else if (topic === "DesktopMode:Change") {
+ let args = JSON.parse(data);
+ let tab = BrowserApp.getTabForId(args.tabId);
+ if (args.desktopMode && tab !== null) {
+ this.reportDesktopModePrompt();
+ }
+ }
+ },
+
+ addMenuItem: function() {
+ this.menuItem = NativeWindow.menu.add({
+ name: this.strings.GetStringFromName("webcompat.menu.name"),
+ callback: () => {
+ let currentURI = BrowserApp.selectedTab.browser.currentURI.spec;
+ this.reportIssue(currentURI);
+ },
+ enabled: false,
+ });
+ },
+
+ isReportableUrl: function(url) {
+ return url !== null && !(url.startsWith("about") ||
+ url.startsWith("chrome") ||
+ url.startsWith("file") ||
+ url.startsWith("resource"));
+ },
+
+ reportDesktopModePrompt: function() {
+ let currentURI = BrowserApp.selectedTab.browser.currentURI.spec;
+ let message = this.strings.GetStringFromName("webcompat.reportDesktopMode.message");
+ let options = {
+ button: {
+ label: this.strings.GetStringFromName("webcompat.reportDesktopModeYes.label"),
+ callback: () => this.reportIssue(currentURI)
+ }
+ };
+ NativeWindow.toast.show(message, "long", options);
+ },
+
+ reportIssue: function(url) {
+ let webcompatURL = new URL("http://webcompat.com/");
+ webcompatURL.searchParams.append("open", "1");
+ webcompatURL.searchParams.append("url", url);
+ BrowserApp.addTab(webcompatURL.href);
+ }
+};
+
+XPCOMUtils.defineLazyGetter(WebcompatReporter, "strings", function() {
+ return Services.strings.createBundle("chrome://browser/locale/webcompatReporter.properties");
+});
diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js
index 169febfc3bb1..08ed579ea878 100644
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -112,6 +112,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "SharedPreferences",
["Linkifier", "chrome://browser/content/Linkify.js"],
["ZoomHelper", "chrome://browser/content/ZoomHelper.js"],
["CastingApps", "chrome://browser/content/CastingApps.js"],
+#ifdef NIGHTLY_BUILD
+ ["WebcompatReporter", "chrome://browser/content/WebcompatReporter.js"],
+#endif
].forEach(function (aScript) {
let [name, script] = aScript;
XPCOMUtils.defineLazyGetter(window, name, function() {
@@ -334,6 +337,9 @@ var BrowserApp = {
// Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
SafeBrowsing.init();
}, Ci.nsIThread.DISPATCH_NORMAL);
+#endif
+#ifdef NIGHTLY_BUILD
+ WebcompatReporter.init();
#endif
} catch(ex) { console.log(ex); }
}, false);
@@ -858,6 +864,9 @@ var BrowserApp = {
CastingApps.uninit();
Distribution.uninit();
Tabs.uninit();
+#ifdef NIGHTLY_BUILD
+ WebcompatReporter.uninit();
+#endif
},
// This function returns false during periods where the browser displayed document is
diff --git a/mobile/android/chrome/jar.mn b/mobile/android/chrome/jar.mn
index bcb130999b3b..652cad785bc0 100644
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -62,6 +62,9 @@ chrome.jar:
content/aboutDevices.xhtml (content/aboutDevices.xhtml)
content/aboutDevices.js (content/aboutDevices.js)
#endif
+#ifdef NIGHTLY_BUILD
+ content/WebcompatReporter.js (content/WebcompatReporter.js)
+#endif
% content branding %content/branding/
diff --git a/mobile/android/locales/en-US/chrome/webcompatReporter.properties b/mobile/android/locales/en-US/chrome/webcompatReporter.properties
new file mode 100644
index 000000000000..cce28981dd4c
--- /dev/null
+++ b/mobile/android/locales/en-US/chrome/webcompatReporter.properties
@@ -0,0 +1,12 @@
+# 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/.
+
+# LOCALIZATION NOTE (webcompat.menu.name): A "site issue" is a bug, display,
+# or functionality problem with a webpage in the browser.
+webcompat.menu.name=Report Site Issue
+
+# LOCALIZATION NOTE (webcompat.reportDesktopMode.message): A " site issue" is a
+# bug, display, or functionality problem with a webpage in the browser.
+webcompat.reportDesktopMode.message=Report site issue?
+webcompat.reportDesktopModeYes.label=Report
diff --git a/mobile/android/locales/jar.mn b/mobile/android/locales/jar.mn
index 51fe85d9c372..d7d8e5b6a113 100644
--- a/mobile/android/locales/jar.mn
+++ b/mobile/android/locales/jar.mn
@@ -39,6 +39,9 @@
locale/@AB_CD@/browser/payments.properties (%chrome/payments.properties)
locale/@AB_CD@/browser/handling.properties (%chrome/handling.properties)
locale/@AB_CD@/browser/webapp.properties (%chrome/webapp.properties)
+#ifdef NIGHTLY_BUILD
+ locale/@AB_CD@/browser/webcompatReporter.properties (%chrome/webcompatReporter.properties)
+#endif
# overrides for toolkit l10n, also for en-US
relativesrcdir toolkit/locales:
diff --git a/mobile/android/modules/TabMirror.jsm b/mobile/android/modules/TabMirror.jsm
index 54784c73f18a..59412147f387 100644
--- a/mobile/android/modules/TabMirror.jsm
+++ b/mobile/android/modules/TabMirror.jsm
@@ -5,171 +5,33 @@
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Messaging.jsm");
+
+const CONFIG = { iceServers: [{ "url": "stun:stun.services.mozilla.com" }] };
+
+let log = Cu.import("resource://gre/modules/AndroidLog.jsm",
+ {}).AndroidLog.d.bind(null, "TabMirror");
+
+let failure = function(x) {
+ log("ERROR: " + JSON.stringify(x));
+};
let TabMirror = function(deviceId, window) {
- let out_queue = [];
- let in_queue = [];
- let DEBUG = false;
- let log = Cu.import("resource://gre/modules/AndroidLog.jsm",
- {}).AndroidLog.d.bind(null, "TabMirror");
+ this.deviceId = deviceId;
+ // Save mozRTCSessionDescription and mozRTCIceCandidate for later when the window object is not available.
+ this.RTCSessionDescription = window.mozRTCSessionDescription;
+ this.RTCIceCandidate = window.mozRTCIceCandidate;
- let RTCPeerConnection = window.mozRTCPeerConnection;
- let RTCSessionDescription = window.mozRTCSessionDescription;
- let RTCIceCandidate = window.mozRTCIceCandidate;
- let getUserMedia = window.navigator.mozGetUserMedia.bind(window.navigator);
+ Services.obs.addObserver((aSubject, aTopic, aData) => this._processMessage(aData), "MediaPlayer:Response", false);
- Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
- in_queue.push(aData);
- }, "MediaPlayer:Response", false);
+ this._pc = new window.mozRTCPeerConnection(CONFIG, {});
- let poll_timeout = 1000; // ms
-
- let audio_stream = undefined;
- let video_stream = undefined;
- let pc = undefined;
-
- let poll_success = function(msg) {
- if (!msg) {
- poll_error();
- return;
- }
-
- let js;
- try {
- js = JSON.parse(msg);
- } catch(ex) {
- log("ex: " + ex);
- }
-
- let sdp = js.body;
-
- if (sdp) {
- if (sdp.sdp) {
- if (sdp.type === "offer") {
- process_offer(sdp);
- } else if (sdp.type === "answer") {
- process_answer(sdp);
- }
- } else {
- process_ice_candidate(sdp);
- }
- }
-
- window.setTimeout(poll, poll_timeout);
- };
-
- let poll_error = function (msg) {
- window.setTimeout(poll, poll_timeout);
- };
-
- let poll = function () {
- if (in_queue) {
- poll_success(in_queue.pop());
- }
- };
-
- let failure = function(x) {
- log("ERROR: " + JSON.stringify(x));
- };
-
-
- // Signaling methods
- let send_sdpdescription= function(sdp) {
- let msg = {
- body: sdp
- };
-
- sendMessage(JSON.stringify(msg));
- };
-
-
- let deobjify = function(x) {
- return JSON.parse(JSON.stringify(x));
- };
-
-
- let process_offer = function(sdp) {
- pc.setRemoteDescription(new RTCSessionDescription(sdp),
- set_remote_offer_success, failure);
- };
-
- let process_answer = function(sdp) {
- pc.setRemoteDescription(new RTCSessionDescription(sdp),
- set_remote_answer_success, failure);
- };
-
- let process_ice_candidate = function(msg) {
- pc.addIceCandidate(new RTCIceCandidate(msg));
- };
-
- let set_remote_offer_success = function() {
- pc.createAnswer(create_answer_success, failure);
- };
-
- let set_remote_answer_success= function() {
- };
-
- let set_local_success_offer = function(sdp) {
- send_sdpdescription(sdp);
- };
-
- let set_local_success_answer = function(sdp) {
- send_sdpdescription(sdp);
- };
-
- let filter_nonrelay_candidates = function(sdp) {
- let lines = sdp.sdp.split("\r\n");
- let lines2 = lines.filter(function(x) {
- if (!/candidate/.exec(x))
- return true;
- if (/relay/.exec(x))
- return true;
-
- return false;
- });
-
- sdp.sdp = lines2.join("\r\n");
- };
-
- let create_offer_success = function(sdp) {
- pc.setLocalDescription(sdp,
- function() {
- set_local_success_offer(sdp);
- },
- failure);
- };
-
- let create_answer_success = function(sdp) {
- pc.setLocalDescription(sdp,
- function() {
- set_local_success_answer(sdp);
- },
- failure);
- };
-
- let on_ice_candidate = function (candidate) {
- send_sdpdescription(candidate.candidate);
- };
-
- let ready = function() {
- pc.createOffer(create_offer_success, failure);
- poll();
- };
-
-
- let config = {
- iceServers: [{ "url": "stun:stun.services.mozilla.com" }]
- };
-
- pc = new RTCPeerConnection(config, {});
-
- if (!pc) {
- log("Failure creating Webrtc object");
- return;
+ if (!this._pc) {
+ throw "Failure creating Webrtc object";
}
- pc.onicecandidate = on_ice_candidate;
+ this._pc.onicecandidate = this._onIceCandidate.bind(this);
let windowId = window.BrowserApp.selectedBrowser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
let viewport = window.BrowserApp.selectedTab.getViewport();
@@ -203,41 +65,95 @@ let TabMirror = function(deviceId, window) {
}
};
- let gUM_success = function(stream){
- pc.addStream(stream);
- ready();
- };
+ window.navigator.mozGetUserMedia(constraints, this._onGumSuccess.bind(this), this._onGumFailure.bind(this));
+};
- let gUM_failure = function() {
- log("Could not get video stream");
- };
+TabMirror.prototype = {
- getUserMedia( constraints, gUM_success, gUM_failure);
+ _processMessage: function(data) {
- function sendMessage(msg) {
- let obj = {
- type: "MediaPlayer:Message",
- id: deviceId,
- data: msg
- };
-
- if (deviceId) {
- Services.androidBridge.handleGeckoMessage(obj);
+ if (!data) {
+ return;
}
- }
- return {
- stop: function() {
+ let msg = JSON.parse(data);
+
+ if (!msg) {
+ return;
+ }
+
+ if (msg.sdp) {
+ if (msg.type === "answer") {
+ this._processAnswer(msg);
+ } else {
+ log("Unandled sdp message type: " + msg.type);
+ }
+ } else {
+ this._processIceCandidate(msg);
+ }
+ },
+
+ // Signaling methods
+ _processAnswer: function(msg) {
+ this._pc.setRemoteDescription(new this.RTCSessionDescription(msg),
+ this._setRemoteAnswerSuccess.bind(this), failure);
+ },
+
+ _processIceCandidate: function(msg) {
+ // WebRTC generates a warning if the success and fail callbacks are not passed in.
+ this._pc.addIceCandidate(new this.RTCIceCandidate(msg), () => log("Ice Candiated added successfuly"), () => log("Failed to add Ice Candidate"));
+ },
+
+ _setRemoteAnswerSuccess: function() {
+ },
+
+ _setLocalSuccessOffer: function(sdp) {
+ this._sendMessage(sdp);
+ },
+
+ _createOfferSuccess: function(sdp) {
+ this._pc.setLocalDescription(sdp, () => this._setLocalSuccessOffer(sdp), failure);
+ },
+
+ _onIceCandidate: function (msg) {
+ log("NEW Ice Candidate: " + JSON.stringify(msg.candidate));
+ this._sendMessage(msg.candidate);
+ },
+
+ _ready: function() {
+ this._pc.createOffer(this._createOfferSuccess.bind(this), failure);
+ },
+
+ _onGumSuccess: function(stream){
+ this._pc.addStream(stream);
+ this._ready();
+ },
+
+ _onGumFailure: function() {
+ log("Could not get video stream");
+ this._pc.close();
+ },
+
+ _sendMessage: function(msg) {
+ if (this.deviceId) {
+ let obj = {
+ type: "MediaPlayer:Message",
+ id: this.deviceId,
+ data: JSON.stringify(msg)
+ };
+ Messaging.sendRequest(obj);
+ }
+ },
+
+ stop: function() {
+ if (this.deviceId) {
let obj = {
type: "MediaPlayer:End",
- id: deviceId
+ id: this.deviceId
};
-
- if (deviceId) {
- Services.androidBridge.handleGeckoMessage(obj);
- }
+ Services.androidBridge.handleGeckoMessage(obj);
}
- }
+ },
};