/toolkit/content/tests/fennec-tile-testapp/chrome/content/BrowserView.js
http://github.com/zpao/v8monkey · JavaScript · 728 lines · 372 code · 143 blank · 213 comment · 34 complexity · 833e7700b16f76157ea102083a43ca1a MD5 · raw file
- // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
- /*
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Mobile Browser.
- *
- * The Initial Developer of the Original Code is
- * Mozilla Corporation.
- * Portions created by the Initial Developer are Copyright (C) 2008
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Roy Frostig <rfrostig@mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
- let Ci = Components.interfaces;
- // --- REMOVE ---
- let noop = function() {};
- let endl = '\n';
- // --------------
- function BrowserView(container, visibleRect) {
- bindAll(this);
- this.init(container, visibleRect);
- }
- /**
- * A BrowserView maintains state of the viewport (browser, zoom level,
- * dimensions) and the visible rectangle into the viewport, for every
- * browser it is given (cf setBrowser()). In updates to the viewport state,
- * a BrowserView (using its TileManager) renders parts of the page quasi-
- * intelligently, with guarantees of having rendered and appended all of the
- * visible browser content (aka the "critical rectangle").
- *
- * State is characterized in large part by two rectangles (and an implicit third):
- * - Viewport: Always rooted at the origin, ie with (left, top) at (0, 0). The
- * width and height (right and bottom) of this rectangle are that of the
- * current viewport, which corresponds more or less to the transformed
- * browser content (scaled by zoom level).
- * - Visible: Corresponds to the client's viewing rectangle in viewport
- * coordinates. Has (top, left) corresponding to position, and width & height
- * corresponding to the clients viewing dimensions. Take note that the top
- * and left of the visible rect are per-browser state, but that the width
- * and height persist across setBrowser() calls. This is best explained by
- * a simple example: user views browser A, pans to position (x0, y0), switches
- * to browser B, where she finds herself at position (x1, y1), tilts her
- * device so that visible rectangle's width and height change, and switches
- * back to browser A. She expects to come back to position (x0, y0), but her
- * device remains tilted.
- * - Critical (the implicit one): The critical rectangle is the (possibly null)
- * intersection of the visible and viewport rectangles. That is, it is that
- * region of the viewport which is visible to the user. We care about this
- * because it tells us which region must be rendered as soon as it is dirtied.
- * The critical rectangle is mostly state that we do not keep in BrowserView
- * but that our TileManager maintains.
- *
- * Example rectangle state configurations:
- *
- *
- * +-------------------------------+
- * |A |
- * | |
- * | |
- * | |
- * | +----------------+ |
- * | |B,C | |
- * | | | |
- * | | | |
- * | | | |
- * | +----------------+ |
- * | |
- * | |
- * | |
- * | |
- * | |
- * +-------------------------------+
- *
- *
- * A = viewport ; at (0, 0)
- * B = visible ; at (x, y) where x > 0, y > 0
- * C = critical ; at (x, y)
- *
- *
- *
- * +-------------------------------+
- * |A |
- * | |
- * | |
- * | |
- * +----+-----------+ |
- * |B .C | |
- * | . | |
- * | . | |
- * | . | |
- * +----+-----------+ |
- * | |
- * | |
- * | |
- * | |
- * | |
- * +-------------------------------+
- *
- *
- * A = viewport ; at (0, 0)
- * B = visible ; at (x, y) where x < 0, y > 0
- * C = critical ; at (0, y)
- *
- *
- * Maintaining per-browser state is a little bit of a hack involving attaching
- * an object as the obfuscated dynamic JS property of the browser object, that
- * hopefully no one but us will touch. See getViewportStateFromBrowser() for
- * the property name.
- */
- BrowserView.prototype = (
- function() {
- // -----------------------------------------------------------
- // Privates
- //
- const kZoomLevelMin = 0.2;
- const kZoomLevelMax = 4.0;
- const kZoomLevelPrecision = 10000;
- function visibleRectToCriticalRect(visibleRect, browserViewportState) {
- return visibleRect.intersect(browserViewportState.viewportRect);
- }
- function clampZoomLevel(zl) {
- let bounded = Math.min(Math.max(kZoomLevelMin, zl), kZoomLevelMax);
- return Math.round(bounded * kZoomLevelPrecision) / kZoomLevelPrecision;
- }
- function pageZoomLevel(visibleRect, browserW, browserH) {
- return clampZoomLevel(visibleRect.width / browserW);
- }
- function seenBrowser(browser) {
- return !!(browser.__BrowserView__vps);
- }
- function initBrowserState(browser, visibleRect) {
- let [browserW, browserH] = getBrowserDimensions(browser);
- let zoomLevel = pageZoomLevel(visibleRect, browserW, browserH);
- let viewportRect = (new wsRect(0, 0, browserW, browserH)).scale(zoomLevel, zoomLevel);
- dump('--- initing browser to ---' + endl);
- browser.__BrowserView__vps = new BrowserView.BrowserViewportState(viewportRect,
- visibleRect.x,
- visibleRect.y,
- zoomLevel);
- dump(browser.__BrowserView__vps.toString() + endl);
- dump('--------------------------' + endl);
- }
- function getViewportStateFromBrowser(browser) {
- return browser.__BrowserView__vps;
- }
- function getBrowserDimensions(browser) {
- return [browser.scrollWidth, browser.scrollHeight];
- }
- function getContentScrollValues(browser) {
- let cwu = getBrowserDOMWindowUtils(browser);
- let scrollX = {};
- let scrollY = {};
- cwu.getScrollXY(false, scrollX, scrollY);
- return [scrollX.value, scrollY.value];
- }
- function getBrowserDOMWindowUtils(browser) {
- return browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- }
- function getNewBatchOperationState() {
- return {
- viewportSizeChanged: false,
- dirtyAll: false
- };
- }
- function clampViewportWH(width, height, visibleRect) {
- let minW = visibleRect.width;
- let minH = visibleRect.height;
- return [Math.max(width, minW), Math.max(height, minH)];
- }
- function initContainer(container, visibleRect) {
- container.style.width = visibleRect.width + 'px';
- container.style.height = visibleRect.height + 'px';
- container.style.overflow = '-moz-hidden-unscrollable';
- }
- function resizeContainerToViewport(container, viewportRect) {
- container.style.width = viewportRect.width + 'px';
- container.style.height = viewportRect.height + 'px';
- }
- // !!! --- RESIZE HACK BEGIN -----
- function simulateMozAfterSizeChange(browser, width, height) {
- let ev = document.createElement("MouseEvents");
- ev.initEvent("FakeMozAfterSizeChange", false, false, window, 0, width, height);
- browser.dispatchEvent(ev);
- }
- // !!! --- RESIZE HACK END -------
- // --- Change of coordinates functions --- //
- // The following returned object becomes BrowserView.prototype
- return {
- // -----------------------------------------------------------
- // Public instance methods
- //
- init: function init(container, visibleRect) {
- this._batchOps = [];
- this._container = container;
- this._browserViewportState = null;
- this._renderMode = 0;
- this._tileManager = new TileManager(this._appendTile, this._removeTile, this);
- this.setVisibleRect(visibleRect);
- // !!! --- RESIZE HACK BEGIN -----
- // remove this eventually
- this._resizeHack = {
- maxSeenW: 0,
- maxSeenH: 0
- };
- // !!! --- RESIZE HACK END -------
- },
- setVisibleRect: function setVisibleRect(r) {
- let bvs = this._browserViewportState;
- let vr = this._visibleRect;
- if (!vr)
- this._visibleRect = vr = r.clone();
- else
- vr.copyFrom(r);
- if (bvs) {
- bvs.visibleX = vr.left;
- bvs.visibleY = vr.top;
- // reclamp minimally to the new visible rect
- //this.setViewportDimensions(bvs.viewportRect.right, bvs.viewportRect.bottom);
- } else
- this._viewportChanged(false, false);
- },
- getVisibleRect: function getVisibleRect() {
- return this._visibleRect.clone();
- },
- getVisibleRectX: function getVisibleRectX() { return this._visibleRect.x; },
- getVisibleRectY: function getVisibleRectY() { return this._visibleRect.y; },
- getVisibleRectWidth: function getVisibleRectWidth() { return this._visibleRect.width; },
- getVisibleRectHeight: function getVisibleRectHeight() { return this._visibleRect.height; },
- setViewportDimensions: function setViewportDimensions(width, height, causedByZoom) {
- let bvs = this._browserViewportState;
- let vis = this._visibleRect;
- if (!bvs)
- return;
- //[width, height] = clampViewportWH(width, height, vis);
- bvs.viewportRect.right = width;
- bvs.viewportRect.bottom = height;
- // XXX we might not want the user's page to disappear from under them
- // at this point, which could happen if the container gets resized such
- // that visible rect becomes entirely outside of viewport rect. might
- // be wise to define what UX should be in this case, like a move occurs.
- // then again, we could also argue this is the responsibility of the
- // caller who would do such a thing...
- this._viewportChanged(true, !!causedByZoom);
- },
- setZoomLevel: function setZoomLevel(zl) {
- let bvs = this._browserViewportState;
- if (!bvs)
- return;
- let newZL = clampZoomLevel(zl);
- if (newZL != bvs.zoomLevel) {
- let browserW = this.viewportToBrowser(bvs.viewportRect.right);
- let browserH = this.viewportToBrowser(bvs.viewportRect.bottom);
- bvs.zoomLevel = newZL; // side-effect: now scale factor in transformations is newZL
- this.setViewportDimensions(this.browserToViewport(browserW),
- this.browserToViewport(browserH));
- }
- },
- getZoomLevel: function getZoomLevel() {
- let bvs = this._browserViewportState;
- if (!bvs)
- return undefined;
- return bvs.zoomLevel;
- },
- beginBatchOperation: function beginBatchOperation() {
- this._batchOps.push(getNewBatchOperationState());
- this.pauseRendering();
- },
- commitBatchOperation: function commitBatchOperation() {
- let bops = this._batchOps;
- if (bops.length == 0)
- return;
- let opState = bops.pop();
- this._viewportChanged(opState.viewportSizeChanged, opState.dirtyAll);
- this.resumeRendering();
- },
- discardBatchOperation: function discardBatchOperation() {
- let bops = this._batchOps;
- bops.pop();
- this.resumeRendering();
- },
- discardAllBatchOperations: function discardAllBatchOperations() {
- let bops = this._batchOps;
- while (bops.length > 0)
- this.discardBatchOperation();
- },
- moveVisibleBy: function moveVisibleBy(dx, dy) {
- let vr = this._visibleRect;
- let vs = this._browserViewportState;
- this.onBeforeVisibleMove(dx, dy);
- this.onAfterVisibleMove(dx, dy);
- },
- moveVisibleTo: function moveVisibleTo(x, y) {
- let visibleRect = this._visibleRect;
- let dx = x - visibleRect.x;
- let dy = y - visibleRect.y;
- this.moveBy(dx, dy);
- },
- /**
- * Calls to this function need to be one-to-one with calls to
- * resumeRendering()
- */
- pauseRendering: function pauseRendering() {
- this._renderMode++;
- },
- /**
- * Calls to this function need to be one-to-one with calls to
- * pauseRendering()
- */
- resumeRendering: function resumeRendering(renderNow) {
- if (this._renderMode > 0)
- this._renderMode--;
- if (renderNow || this._renderMode == 0)
- this._tileManager.criticalRectPaint();
- },
- isRendering: function isRendering() {
- return (this._renderMode == 0);
- },
- /**
- * @param dx Guess delta to destination x coordinate
- * @param dy Guess delta to destination y coordinate
- */
- onBeforeVisibleMove: function onBeforeVisibleMove(dx, dy) {
- let vs = this._browserViewportState;
- let vr = this._visibleRect;
- let destCR = visibleRectToCriticalRect(vr.clone().translate(dx, dy), vs);
- this._tileManager.beginCriticalMove(destCR);
- },
- /**
- * @param dx Actual delta to destination x coordinate
- * @param dy Actual delta to destination y coordinate
- */
- onAfterVisibleMove: function onAfterVisibleMove(dx, dy) {
- let vs = this._browserViewportState;
- let vr = this._visibleRect;
- vr.translate(dx, dy);
- vs.visibleX = vr.left;
- vs.visibleY = vr.top;
- let cr = visibleRectToCriticalRect(vr, vs);
- this._tileManager.endCriticalMove(cr, this.isRendering());
- },
- setBrowser: function setBrowser(browser, skipZoom) {
- let currentBrowser = this._browser;
- let browserChanged = (currentBrowser !== browser);
- if (currentBrowser) {
- currentBrowser.removeEventListener("MozAfterPaint", this.handleMozAfterPaint, false);
- // !!! --- RESIZE HACK BEGIN -----
- // change to the real event type and perhaps refactor the handler function name
- currentBrowser.removeEventListener("FakeMozAfterSizeChange", this.handleMozAfterSizeChange, false);
- // !!! --- RESIZE HACK END -------
- this.discardAllBatchOperations();
- currentBrowser.setAttribute("type", "content");
- currentBrowser.docShell.isOffScreenBrowser = false;
- }
- this._restoreBrowser(browser);
- browser.setAttribute("type", "content-primary");
- this.beginBatchOperation();
- browser.addEventListener("MozAfterPaint", this.handleMozAfterPaint, false);
- // !!! --- RESIZE HACK BEGIN -----
- // change to the real event type and perhaps refactor the handler function name
- browser.addEventListener("FakeMozAfterSizeChange", this.handleMozAfterSizeChange, false);
- // !!! --- RESIZE HACK END -------
- if (!skipZoom) {
- browser.docShell.isOffScreenBrowser = true;
- this.zoomToPage();
- }
- this._viewportChanged(browserChanged, browserChanged);
- this.commitBatchOperation();
- },
- handleMozAfterPaint: function handleMozAfterPaint(ev) {
- let browser = this._browser;
- let tm = this._tileManager;
- let vs = this._browserViewportState;
- let [scrollX, scrollY] = getContentScrollValues(browser);
- let clientRects = ev.clientRects;
- // !!! --- RESIZE HACK BEGIN -----
- // remove this, cf explanation in loop below
- let hack = this._resizeHack;
- let hackSizeChanged = false;
- // !!! --- RESIZE HACK END -------
- let rects = [];
- // loop backwards to avoid xpconnect penalty for .length
- for (let i = clientRects.length - 1; i >= 0; --i) {
- let e = clientRects.item(i);
- let r = new wsRect(e.left + scrollX,
- e.top + scrollY,
- e.width, e.height);
- this.browserToViewportRect(r);
- r.round();
- if (r.right < 0 || r.bottom < 0)
- continue;
- // !!! --- RESIZE HACK BEGIN -----
- // remove this. this is where we make 'lazy' calculations
- // that hint at a browser size change and fake the size change
- // event dispach
- if (r.right > hack.maxW) {
- hack.maxW = rect.right;
- hackSizeChanged = true;
- }
- if (r.bottom > hack.maxH) {
- hack.maxH = rect.bottom;
- hackSizeChanged = true;
- }
- // !!! --- RESIZE HACK END -------
- r.restrictTo(vs.viewportRect);
- rects.push(r);
- }
- // !!! --- RESIZE HACK BEGIN -----
- // remove this, cf explanation in loop above
- if (hackSizeChanged)
- simulateMozAfterSizeChange(browser, hack.maxW, hack.maxH);
- // !!! --- RESIZE HACK END -------
- tm.dirtyRects(rects, this.isRendering());
- },
- handleMozAfterSizeChange: function handleMozAfterPaint(ev) {
- // !!! --- RESIZE HACK BEGIN -----
- // get the correct properties off of the event, these are wrong because
- // we're using a MouseEvent since it has an X and Y prop of some sort and
- // we piggyback on that.
- let w = ev.screenX;
- let h = ev.screenY;
- // !!! --- RESIZE HACK END -------
- this.setViewportDimensions(w, h);
- },
- zoomToPage: function zoomToPage() {
- let browser = this._browser;
- if (!browser)
- return;
- let [w, h] = getBrowserDimensions(browser);
- this.setZoomLevel(pageZoomLevel(this._visibleRect, w, h));
- },
- zoom: function zoom(aDirection) {
- if (aDirection == 0)
- return;
- var zoomDelta = 0.05; // 1/20
- if (aDirection >= 0)
- zoomDelta *= -1;
- this.zoomLevel = this._zoomLevel + zoomDelta;
- },
- viewportToBrowser: function viewportToBrowser(x) {
- let bvs = this._browserViewportState;
- if (!bvs)
- throw "No browser is set";
- return x / bvs.zoomLevel;
- },
- browserToViewport: function browserToViewport(x) {
- let bvs = this._browserViewportState;
- if (!bvs)
- throw "No browser is set";
- return x * bvs.zoomLevel;
- },
- viewportToBrowserRect: function viewportToBrowserRect(rect) {
- let f = this.viewportToBrowser(1.0);
- return rect.scale(f, f);
- },
- browserToViewportRect: function browserToViewportRect(rect) {
- let f = this.browserToViewport(1.0);
- return rect.scale(f, f);
- },
- browserToViewportCanvasContext: function browserToViewportCanvasContext(ctx) {
- let f = this.browserToViewport(1.0);
- ctx.scale(f, f);
- },
- // -----------------------------------------------------------
- // Private instance methods
- //
- _restoreBrowser: function _restoreBrowser(browser) {
- let vr = this._visibleRect;
- if (!seenBrowser(browser))
- initBrowserState(browser, vr);
- let bvs = getViewportStateFromBrowser(browser);
- this._contentWindow = browser.contentWindow;
- this._browser = browser;
- this._browserViewportState = bvs;
- vr.left = bvs.visibleX;
- vr.top = bvs.visibleY;
- this._tileManager.setBrowser(browser);
- },
- _viewportChanged: function _viewportChanged(viewportSizeChanged, dirtyAll) {
- let bops = this._batchOps;
- if (bops.length > 0) {
- let opState = bops[bops.length - 1];
- if (viewportSizeChanged)
- opState.viewportSizeChanged = viewportSizeChanged;
- if (dirtyAll)
- opState.dirtyAll = dirtyAll;
- return;
- }
- let bvs = this._browserViewportState;
- let vis = this._visibleRect;
- // !!! --- RESIZE HACK BEGIN -----
- // We want to uncomment this for perf, but we can't with the hack in place
- // because the mozAfterPaint gives us rects that we use to create the
- // fake mozAfterResize event, so we can't just clear things.
- /*
- if (dirtyAll) {
- // We're about to mark the entire viewport dirty, so we can clear any
- // queued afterPaint events that will cause redundant draws
- getBrowserDOMWindowUtils(this._browser).clearMozAfterPaintEvents();
- }
- */
- // !!! --- RESIZE HACK END -------
- if (bvs) {
- resizeContainerToViewport(this._container, bvs.viewportRect);
- this._tileManager.viewportChangeHandler(bvs.viewportRect,
- visibleRectToCriticalRect(vis, bvs),
- viewportSizeChanged,
- dirtyAll);
- }
- },
- _appendTile: function _appendTile(tile) {
- let canvas = tile.getContentImage();
- /*
- canvas.style.position = "absolute";
- canvas.style.left = tile.x + "px";
- canvas.style.top = tile.y + "px";
- */
- canvas.setAttribute("style", "position: absolute; left: " + tile.boundRect.left + "px; " + "top: " + tile.boundRect.top + "px;");
- this._container.appendChild(canvas);
- //dump('++ ' + tile.toString(true) + endl);
- },
- _removeTile: function _removeTile(tile) {
- let canvas = tile.getContentImage();
- this._container.removeChild(canvas);
- //dump('-- ' + tile.toString(true) + endl);
- }
- };
- }
- )();
- // -----------------------------------------------------------
- // Helper structures
- //
- BrowserView.BrowserViewportState = function(viewportRect,
- visibleX,
- visibleY,
- zoomLevel) {
- this.init(viewportRect, visibleX, visibleY, zoomLevel);
- };
- BrowserView.BrowserViewportState.prototype = {
- init: function init(viewportRect, visibleX, visibleY, zoomLevel) {
- this.viewportRect = viewportRect;
- this.visibleX = visibleX;
- this.visibleY = visibleY;
- this.zoomLevel = zoomLevel;
- },
- clone: function clone() {
- return new BrowserView.BrowserViewportState(this.viewportRect,
- this.visibleX,
- this.visibleY,
- this.zoomLevel);
- },
- toString: function toString() {
- let props = ['\tviewportRect=' + this.viewportRect.toString(),
- '\tvisibleX=' + this.visibleX,
- '\tvisibleY=' + this.visibleY,
- '\tzoomLevel=' + this.zoomLevel];
- return '[BrowserViewportState] {\n' + props.join(',\n') + '\n}';
- }
- };