Skip to content

Commit

Permalink
Improve scrolling performance in OOP PDF
Browse files Browse the repository at this point in the history
This increases the performance of scrolling in OOP PDF by not sending viewport
changes via postMessage every time a scroll event occurs on the page. Instead,
scroll events are sent along with pepper DidChangeView messages, which is a
much more responsive way of updating the scroll position.

Unfortunately this introduces some issues coordinating zoom and scroll events
on the page. When a zoom happens in the container page, a scroll message
might be sent to the plugin via DidChangeView. Then we send a zoom change
message via postMessage. The result is that the zooming and scrolling
happen asynchronously and result in a flickering effect.

To avoid this, we first notify the plugin that we are about to zoom which
causes it to stop reacting to scroll messages. After we have finished zooming
we notify the plugin again so that it can continue reacting to scroll messages.

BUG=386920

Review URL: https://codereview.chromium.org/347763007

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@279910 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
raymes@chromium.org committed Jun 26, 2014
1 parent c0da00f commit 499e956
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 142 deletions.
54 changes: 41 additions & 13 deletions chrome/browser/resources/pdf/pdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ function PDFViewer() {
// Create the viewport.
this.viewport_ = new Viewport(window,
this.sizer_,
this.viewportChangedCallback_.bind(this),
this.viewportChanged_.bind(this),
this.beforeZoom_.bind(this),
this.afterZoom_.bind(this),
getScrollbarWidth());

// Create the plugin object dynamically so we can set its src. The plugin
Expand Down Expand Up @@ -109,6 +111,15 @@ function PDFViewer() {
this.plugin_.setAttribute('full-frame', '');
document.body.appendChild(this.plugin_);

// TODO(raymes): Remove this spurious message once crbug.com/388606 is fixed.
// This is a hack to initialize pepper sync scripting and avoid re-entrancy.
this.plugin_.postMessage({
type: 'viewport',
zoom: 1,
xOffset: 0,
yOffset: 0
});

// Setup the button event listeners.
$('fit-to-width-button').addEventListener('click',
this.viewport_.fitToWidth.bind(this.viewport_));
Expand Down Expand Up @@ -353,9 +364,36 @@ PDFViewer.prototype = {

/**
* @private
* A callback that's called when the viewport changes.
* A callback that's called before the zoom changes. Notify the plugin to stop
* reacting to scroll events while zoom is taking place to avoid flickering.
*/
beforeZoom_: function() {
this.plugin_.postMessage({
type: 'stopScrolling'
});
},

/**
* @private
* A callback that's called after the zoom changes. Notify the plugin of the
* zoom change and to continue reacting to scroll events.
*/
afterZoom_: function() {
var position = this.viewport_.position;
var zoom = this.viewport_.zoom;
this.plugin_.postMessage({
type: 'viewport',
zoom: zoom,
xOffset: position.x,
yOffset: position.y
});
},

/**
* @private
* A callback that's called after the viewport changes.
*/
viewportChangedCallback_: function() {
viewportChanged_: function() {
if (!this.documentDimensions_)
return;

Expand Down Expand Up @@ -391,16 +429,6 @@ PDFViewer.prototype = {
this.pageIndicator_.style.visibility = 'hidden';
}

var position = this.viewport_.position;
var zoom = this.viewport_.zoom;
// Notify the plugin of the viewport change.
this.plugin_.postMessage({
type: 'viewport',
zoom: zoom,
xOffset: position.x,
yOffset: position.y
});

var visiblePageDimensions = this.viewport_.getPageScreenRect(visiblePage);
var size = this.viewport_.size;
this.sendScriptingMessage_({
Expand Down
181 changes: 122 additions & 59 deletions chrome/browser/resources/pdf/viewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,22 @@ function getIntersectionArea(rect1, rect2) {
* @param {Object} sizer is the element which represents the size of the
* document in the viewport
* @param {Function} viewportChangedCallback is run when the viewport changes
* @param {Function} beforeZoomCallback is run before a change in zoom
* @param {Function} afterZoomCallback is run after a change in zoom
* @param {number} scrollbarWidth the width of scrollbars on the page
*/
function Viewport(window,
sizer,
viewportChangedCallback,
beforeZoomCallback,
afterZoomCallback,
scrollbarWidth) {
this.window_ = window;
this.sizer_ = sizer;
this.viewportChangedCallback_ = viewportChangedCallback;
this.beforeZoomCallback_ = beforeZoomCallback;
this.afterZoomCallback_ = afterZoomCallback;
this.allowedToChangeZoom_ = false;
this.zoom_ = 1;
this.documentDimensions_ = null;
this.pageDimensions_ = [];
Expand Down Expand Up @@ -83,6 +90,12 @@ Viewport.prototype = {
* respectively.
*/
documentNeedsScrollbars_: function(zoom) {
if (!this.documentDimensions_) {
return {
horizontal: false,
vertical: false
};
}
var documentWidth = this.documentDimensions_.width * zoom;
var documentHeight = this.documentDimensions_.height * zoom;
return {
Expand Down Expand Up @@ -173,12 +186,29 @@ Viewport.prototype = {
return this.zoom_;
},

/**
* @private
* Used to wrap a function that might perform zooming on the viewport. This is
* required so that we can notify the plugin that zooming is in progress
* so that while zooming is taking place it can stop reacting to scroll events
* from the viewport. This is to avoid flickering.
*/
mightZoom_: function(f) {
this.beforeZoomCallback_();
this.allowedToChangeZoom_ = true;
f();
this.allowedToChangeZoom_ = false;
this.afterZoomCallback_();
},

/**
* @private
* Sets the zoom of the viewport.
* @param {number} newZoom the zoom level to zoom to.
*/
setZoom_: function(newZoom) {
if (!this.allowedToChangeZoom_)
throw 'Called Viewport.setZoom_ without calling Viewport.mightZoom_.';
var oldZoom = this.zoom_;
this.zoom_ = newZoom;
// Record the scroll position (relative to the middle of the window).
Expand All @@ -193,6 +223,16 @@ Viewport.prototype = {
currentScrollPos[1] * newZoom - this.window_.innerHeight / 2);
},

/**
* @private
* Sets the zoom for testing purposes.
*/
setZoomForTest_: function(newZoom) {
this.mightZoom_(function() {
this.setZoom_(newZoom);
}.bind(this));
},

/**
* @type {number} the width of scrollbars in the viewport in pixels.
*/
Expand Down Expand Up @@ -333,98 +373,113 @@ Viewport.prototype = {
* Zoom the viewport so that the page-width consumes the entire viewport.
*/
fitToWidth: function() {
this.fittingType_ = Viewport.FittingType.FIT_TO_WIDTH;
if (!this.documentDimensions_)
return;
// Track the last y-position so we stay at the same position after zooming.
var oldY = this.window_.pageYOffset / this.zoom_;
// When computing fit-to-width, the maximum width of a page in the document
// is used, which is equal to the size of the document width.
this.setZoom_(this.computeFittingZoom_(this.documentDimensions_, true));
var page = this.getMostVisiblePage();
this.window_.scrollTo(0, oldY * this.zoom_);
this.updateViewport_();
this.mightZoom_(function() {
this.fittingType_ = Viewport.FittingType.FIT_TO_WIDTH;
if (!this.documentDimensions_)
return;
// Track the last y-position to stay at the same position after zooming.
var oldY = this.window_.pageYOffset / this.zoom_;
// When computing fit-to-width, the maximum width of a page in the
// document is used, which is equal to the size of the document width.
this.setZoom_(this.computeFittingZoom_(this.documentDimensions_, true));
var page = this.getMostVisiblePage();
this.window_.scrollTo(0, oldY * this.zoom_);
this.updateViewport_();
}.bind(this));
},

/**
* Zoom the viewport so that a page consumes the entire viewport. Also scrolls
* to the top of the most visible page.
*/
fitToPage: function() {
this.fittingType_ = Viewport.FittingType.FIT_TO_PAGE;
if (!this.documentDimensions_)
return;
var page = this.getMostVisiblePage();
this.setZoom_(this.computeFittingZoom_(this.pageDimensions_[page], false));
// Center the document in the page by scrolling by the amount of empty
// space to the left of the document.
var xOffset =
(this.documentDimensions_.width - this.pageDimensions_[page].width) *
this.zoom_ / 2;
this.window_.scrollTo(xOffset,
this.pageDimensions_[page].y * this.zoom_);
this.updateViewport_();
this.mightZoom_(function() {
this.fittingType_ = Viewport.FittingType.FIT_TO_PAGE;
if (!this.documentDimensions_)
return;
var page = this.getMostVisiblePage();
this.setZoom_(this.computeFittingZoom_(
this.pageDimensions_[page], false));
// Center the document in the page by scrolling by the amount of empty
// space to the left of the document.
var xOffset =
(this.documentDimensions_.width - this.pageDimensions_[page].width) *
this.zoom_ / 2;
this.window_.scrollTo(xOffset,
this.pageDimensions_[page].y * this.zoom_);
this.updateViewport_();
}.bind(this));
},

/**
* Zoom out to the next predefined zoom level.
*/
zoomOut: function() {
this.fittingType_ = Viewport.FittingType.NONE;
var nextZoom = Viewport.ZOOM_FACTORS[0];
for (var i = 0; i < Viewport.ZOOM_FACTORS.length; i++) {
if (Viewport.ZOOM_FACTORS[i] < this.zoom_)
nextZoom = Viewport.ZOOM_FACTORS[i];
}
this.setZoom_(nextZoom);
this.updateViewport_();
this.mightZoom_(function() {
this.fittingType_ = Viewport.FittingType.NONE;
var nextZoom = Viewport.ZOOM_FACTORS[0];
for (var i = 0; i < Viewport.ZOOM_FACTORS.length; i++) {
if (Viewport.ZOOM_FACTORS[i] < this.zoom_)
nextZoom = Viewport.ZOOM_FACTORS[i];
}
this.setZoom_(nextZoom);
this.updateViewport_();
}.bind(this));
},

/**
* Zoom in to the next predefined zoom level.
*/
zoomIn: function() {
this.fittingType_ = Viewport.FittingType.NONE;
var nextZoom = Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1];
for (var i = Viewport.ZOOM_FACTORS.length - 1; i >= 0; i--) {
if (Viewport.ZOOM_FACTORS[i] > this.zoom_)
nextZoom = Viewport.ZOOM_FACTORS[i];
}
this.setZoom_(nextZoom);
this.updateViewport_();
this.mightZoom_(function() {
this.fittingType_ = Viewport.FittingType.NONE;
var nextZoom = Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1];
for (var i = Viewport.ZOOM_FACTORS.length - 1; i >= 0; i--) {
if (Viewport.ZOOM_FACTORS[i] > this.zoom_)
nextZoom = Viewport.ZOOM_FACTORS[i];
}
this.setZoom_(nextZoom);
this.updateViewport_();
}.bind(this));
},

/**
* Go to the given page index.
* @param {number} page the index of the page to go to.
*/
goToPage: function(page) {
if (this.pageDimensions_.length == 0)
return;
if (page < 0)
page = 0;
if (page >= this.pageDimensions_.length)
page = this.pageDimensions_.length - 1;
var dimensions = this.pageDimensions_[page];
this.window_.scrollTo(dimensions.x * this.zoom_, dimensions.y * this.zoom_);
this.mightZoom_(function() {
if (this.pageDimensions_.length == 0)
return;
if (page < 0)
page = 0;
if (page >= this.pageDimensions_.length)
page = this.pageDimensions_.length - 1;
var dimensions = this.pageDimensions_[page];
this.window_.scrollTo(dimensions.x * this.zoom_,
dimensions.y * this.zoom_);
this.updateViewport_();
}.bind(this));
},

/**
* Set the dimensions of the document.
* @param {Object} documentDimensions the dimensions of the document
*/
setDocumentDimensions: function(documentDimensions) {
var initialDimensions = !this.documentDimensions_;
this.documentDimensions_ = documentDimensions;
this.pageDimensions_ = this.documentDimensions_.pageDimensions;
if (initialDimensions) {
this.setZoom_(this.computeFittingZoom_(this.documentDimensions_, true));
if (this.zoom_ > 1)
this.setZoom_(1);
this.window_.scrollTo(0, 0);
}
this.contentSizeChanged_();
this.resize_();
this.mightZoom_(function() {
var initialDimensions = !this.documentDimensions_;
this.documentDimensions_ = documentDimensions;
this.pageDimensions_ = this.documentDimensions_.pageDimensions;
if (initialDimensions) {
this.setZoom_(this.computeFittingZoom_(this.documentDimensions_, true));
if (this.zoom_ > 1)
this.setZoom_(1);
this.window_.scrollTo(0, 0);
}
this.contentSizeChanged_();
this.resize_();
}.bind(this));
},

/**
Expand All @@ -434,6 +489,14 @@ Viewport.prototype = {
* @return {Object} a rect representing the page in screen coordinates.
*/
getPageScreenRect: function(page) {
if (!this.documentDimensions_) {
return {
x: 0,
y: 0,
width: 0,
height: 0
};
}
if (page >= this.pageDimensions_.length)
page = this.pageDimensions_.length - 1;

Expand Down
2 changes: 1 addition & 1 deletion chrome/test/data/pdf/basic_plugin_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var tests = [
// Verify that the initial zoom is less than or equal to 100%.
chrome.test.assertTrue(viewer.viewport.zoom <= 1);

viewer.viewport.zoom = 1;
viewer.viewport.setZoomForTest_(1);
var sizer = document.getElementById('sizer');
chrome.test.assertEq(826, sizer.offsetWidth);
chrome.test.assertEq(1066, sizer.offsetHeight);
Expand Down
Loading

0 comments on commit 499e956

Please sign in to comment.