michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: %findBarDTD; michael@0: ]> michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: binding. michael@0: this.removeEventListener("pageshow", this.onPageShow, true); michael@0: this.removeEventListener("pagehide", this.onPageHide, true); michael@0: this.removeEventListener("DOMPopupBlocked", this.onPopupBlocked, true); michael@0: michael@0: this.setAttribute("autoscrollpopup", "autoscrollerid"); michael@0: ]]> michael@0: michael@0: michael@0: [] michael@0: michael@0: michael@0: null michael@0: michael@0: michael@0: null michael@0: michael@0: null michael@0: michael@0: michael@0: Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: null michael@0: null michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: 0 michael@0: 0 michael@0: michael@0: michael@0: michael@0: michael@0: 0 michael@0: 0 michael@0: michael@0: michael@0: michael@0: michael@0: 0 michael@0: michael@0: michael@0: 1 michael@0: 1 michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: false michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: null michael@0: michael@0: michael@0: michael@0: null michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: true michael@0: michael@0: michael@0: michael@0: michael@0: ({}) michael@0: michael@0: michael@0: michael@0: michael@0: = this.kDieTime) michael@0: this._die(); michael@0: else michael@0: // This doesn't need to be exact, just be sure to clean up at some point. michael@0: this._timeout = setTimeout(this._dieIfOld.bind(this), this.kDieTime); michael@0: }, michael@0: michael@0: /** Cleanup after ourselves. */ michael@0: _die: function() { michael@0: let timeout = this._timeout; michael@0: if (timeout) { michael@0: clearTimeout(timeout); michael@0: this._timeout = null; michael@0: } michael@0: michael@0: if (this._contentView && Math.abs(this._pixelsPannedSinceRefresh) > 0) michael@0: this._updateCacheViewport(); michael@0: michael@0: // We expect contentViews to contain our ID. If not, something bad michael@0: // happened. michael@0: delete this.self._contentViews[this._id]; michael@0: }, michael@0: michael@0: /** michael@0: * Given the cache size and the viewport size, this determines where the cache michael@0: * should start relative to the scroll position. This adjusts the position based michael@0: * on which direction the user is panning, so that we use our cache as michael@0: * effectively as possible. michael@0: * michael@0: * @param aDirection Negative means user is panning to the left or above michael@0: * Zero means user did not pan michael@0: * Positive means user is panning to the right or below michael@0: * @param aViewportSize The width or height of the viewport michael@0: * @param aCacheSize The width or height of the displayport michael@0: */ michael@0: _getRelativeCacheStart: function(aDirection, aViewportSize, aCacheSize) { michael@0: // Remember that this is relative to the viewport scroll position. michael@0: // Let's assume we are thinking about the y-axis. michael@0: // The extreme cases: michael@0: // |0| would mean that there is no content available above michael@0: // |aViewportSize - aCacheSize| would mean no content available below michael@0: // michael@0: // Taking the average of the extremes puts equal amounts of cache on the michael@0: // top and bottom of the viewport. If we think of this like a weighted michael@0: // average, .5 is the sweet spot where equals amounts of content are michael@0: // above and below the visible area. michael@0: // michael@0: // This weight is therefore how much of the cache is above (or to the michael@0: // left) the visible area. michael@0: let cachedAbove = .5; michael@0: michael@0: // If panning down, leave only 25% of the non-visible cache above. michael@0: if (aDirection > 0) michael@0: cachedAbove = .25; michael@0: michael@0: // If panning up, Leave 75% of the non-visible cache above. michael@0: if (aDirection < 0) michael@0: cachedAbove = .75; michael@0: michael@0: return (aViewportSize - aCacheSize) * cachedAbove; michael@0: }, michael@0: michael@0: /** Determine size of the pixel cache. */ michael@0: _getCacheSize: function(viewportSize) { michael@0: let self = this.self; michael@0: let contentView = this._contentView; michael@0: michael@0: let cacheWidth = self._cacheRatioWidth * viewportSize.width; michael@0: let cacheHeight = self._cacheRatioHeight * viewportSize.height; michael@0: let contentSize = this._getContentSize(); michael@0: let contentWidth = contentSize.width; michael@0: let contentHeight = contentSize.height; michael@0: michael@0: // There are common cases, such as long skinny pages, where our cache size is michael@0: // bigger than our content size. In those cases, we take that sliver of leftover michael@0: // space and apply it to the other dimension. michael@0: if (contentWidth < cacheWidth) { michael@0: cacheHeight += (cacheWidth - contentWidth) * cacheHeight / cacheWidth; michael@0: cacheWidth = contentWidth; michael@0: } else if (contentHeight < cacheHeight) { michael@0: cacheWidth += (cacheHeight - contentHeight) * cacheWidth / cacheHeight; michael@0: cacheHeight = contentHeight; michael@0: } michael@0: michael@0: return { width: cacheWidth, height: cacheHeight }; michael@0: }, michael@0: michael@0: _sendDisplayportUpdate: function(scrollX, scrollY) { michael@0: let self = this.self; michael@0: if (!self.active) michael@0: return; michael@0: michael@0: let contentView = this._contentView; michael@0: let viewportSize = this._getViewportSize(); michael@0: let cacheSize = this._getCacheSize(viewportSize); michael@0: let cacheX = this._getRelativeCacheStart(this._pixelsPannedSinceRefresh.x, viewportSize.width, cacheSize.width) + contentView.scrollX; michael@0: let cacheY = this._getRelativeCacheStart(this._pixelsPannedSinceRefresh.y, viewportSize.height, cacheSize.height) + contentView.scrollY; michael@0: let contentSize = this._getContentSize(); michael@0: michael@0: // Use our pixels efficiently and don't try to cache things outside of content michael@0: // boundaries (The left bound can be negative because of RTL). michael@0: michael@0: let rootScale = self.scale; michael@0: let leftBound = self._contentDocumentLeft * rootScale; michael@0: let bounds = new Rect(leftBound, 0, contentSize.width, contentSize.height); michael@0: let displayport = new Rect(cacheX, cacheY, cacheSize.width, cacheSize.height); michael@0: displayport.translateInside(bounds); michael@0: michael@0: self.messageManager.sendAsyncMessage("Content:SetCacheViewport", { michael@0: scrollX: Math.round(scrollX) / rootScale, michael@0: scrollY: Math.round(scrollY) / rootScale, michael@0: x: Math.round(displayport.x) / rootScale, michael@0: y: Math.round(displayport.y) / rootScale, michael@0: w: Math.round(displayport.width) / rootScale, michael@0: h: Math.round(displayport.height) / rootScale, michael@0: scale: rootScale, michael@0: id: contentView.id michael@0: }); michael@0: michael@0: this._pixelsPannedSinceRefresh.x = 0; michael@0: this._pixelsPannedSinceRefresh.y = 0; michael@0: }, michael@0: michael@0: _updateCSSViewport: function() { michael@0: let contentView = this._contentView; michael@0: this._sendDisplayportUpdate(contentView.scrollX, michael@0: contentView.scrollY); michael@0: }, michael@0: michael@0: /** michael@0: * The cache viewport is what parts of content is cached in the parent process for michael@0: * fast scrolling. This syncs that up with the current projection viewport. michael@0: */ michael@0: _updateCacheViewport: function() { michael@0: // Do not update scroll values for content. michael@0: if (this.isRoot()) michael@0: this._sendDisplayportUpdate(-1, -1); michael@0: else { michael@0: let contentView = this._contentView; michael@0: this._sendDisplayportUpdate(contentView.scrollX, michael@0: contentView.scrollY); michael@0: } michael@0: }, michael@0: michael@0: _getContentSize: function() { michael@0: let self = this.self; michael@0: return { width: this._contentView.contentWidth, michael@0: height: this._contentView.contentHeight }; michael@0: }, michael@0: michael@0: _getViewportSize: function() { michael@0: let self = this.self; michael@0: if (this.isRoot()) { michael@0: let bcr = self.getBoundingClientRect(); michael@0: return { width: bcr.width, height: bcr.height }; michael@0: } else { michael@0: return { width: this._contentView.viewportWidth, michael@0: height: this._contentView.viewportHeight }; michael@0: } michael@0: }, michael@0: michael@0: init: function(contentView) { michael@0: let self = this.self; michael@0: michael@0: this._contentView = contentView; michael@0: this._id = contentView.id; michael@0: this._scale = 1; michael@0: self._contentViews[this._id] = this; michael@0: michael@0: if (!this.isRoot()) { michael@0: // Non-root content views are short lived. michael@0: this._timeout = setTimeout(this._dieIfOld.bind(this), this.kDieTime); michael@0: // This iframe may not have a display port yet, so build up a cache michael@0: // immediately. michael@0: this._updateCacheViewport(); michael@0: } michael@0: }, michael@0: michael@0: isRoot: function() { michael@0: return this.self._contentViewManager.rootContentView == this._contentView; michael@0: }, michael@0: michael@0: scrollBy: function(x, y) { michael@0: let self = this.self; michael@0: michael@0: // Bounding content rectangle is in device pixels michael@0: let contentView = this._contentView; michael@0: let viewportSize = this._getViewportSize(); michael@0: let contentSize = this._getContentSize(); michael@0: // Calculate document dimensions in device pixels michael@0: let scrollRangeX = contentSize.width - viewportSize.width; michael@0: let scrollRangeY = contentSize.height - viewportSize.height; michael@0: michael@0: let leftOffset = self._contentDocumentLeft * this._scale; michael@0: x = Math.floor(Math.max(leftOffset, Math.min(scrollRangeX + leftOffset, contentView.scrollX + x))) - contentView.scrollX; michael@0: y = Math.floor(Math.max(0, Math.min(scrollRangeY, contentView.scrollY + y))) - contentView.scrollY; michael@0: michael@0: if (x == 0 && y == 0) michael@0: return; michael@0: michael@0: contentView.scrollBy(x, y); michael@0: michael@0: this._lastPanTime = Date.now(); michael@0: michael@0: this._pixelsPannedSinceRefresh.x += x; michael@0: this._pixelsPannedSinceRefresh.y += y; michael@0: if (Math.abs(this._pixelsPannedSinceRefresh.x) > 20 || michael@0: Math.abs(this._pixelsPannedSinceRefresh.y) > 20) michael@0: this._updateCacheViewport(); michael@0: }, michael@0: michael@0: scrollTo: function(x, y) { michael@0: let contentView = this._contentView; michael@0: this.scrollBy(x - contentView.scrollX, y - contentView.scrollY); michael@0: }, michael@0: michael@0: _setScale: function _setScale(scale) { michael@0: this._scale = scale; michael@0: this._contentView.setScale(scale, scale); michael@0: }, michael@0: michael@0: getPosition: function() { michael@0: let contentView = this._contentView; michael@0: return { x: contentView.scrollX, y: contentView.scrollY }; michael@0: } michael@0: }) michael@0: ]]> michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: null michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: michael@0: