Bug 520872: Make zooming transition better [r=mark.finkle]

This commit is contained in:
Benjamin Stover 2009-10-21 19:11:38 -04:00
Родитель e34f62af05
Коммит ad42ee2cab
4 изменённых файлов: 162 добавлений и 86 удалений

Просмотреть файл

@ -232,6 +232,7 @@ BrowserView.prototype = {
this._browserViewportState = null; this._browserViewportState = null;
this._contentWindow = null; this._contentWindow = null;
this._renderMode = 0; this._renderMode = 0;
this._offscreenDepth = 0;
let cacheSize = kBrowserViewCacheSize; let cacheSize = kBrowserViewCacheSize;
try { try {
@ -300,6 +301,28 @@ BrowserView.prototype = {
return bvs.zoomLevel; return bvs.zoomLevel;
}, },
beginOffscreenOperation: function beginOffscreenOperation() {
if (this._offscreenDepth == 0) {
let vis = this.getVisibleRect();
let canvas = document.getElementById("view-buffer");
canvas.width = vis.width;
canvas.height = vis.height;
this.renderToCanvas(canvas, vis.width, vis.height, vis);
canvas.style.display = "block";
this.pauseRendering();
}
this._offscreenDepth++;
},
commitOffscreenOperation: function commitOffscreenOperation() {
this._offscreenDepth--;
if (this._offscreenDepth == 0) {
this.resumeRendering();
let canvas = document.getElementById("view-buffer");
canvas.style.display = "none";
}
},
beginBatchOperation: function beginBatchOperation() { beginBatchOperation: function beginBatchOperation() {
this._batchOps.push(BrowserView.Util.getNewBatchOperationState()); this._batchOps.push(BrowserView.Util.getNewBatchOperationState());
this.pauseRendering(); this.pauseRendering();
@ -504,10 +527,13 @@ BrowserView.prototype = {
}, },
zoomToPage: function zoomToPage() { zoomToPage: function zoomToPage() {
let browser = this._browser; this.setZoomLevel(this.getZoomForPage());
},
getZoomForPage: function getZoomForPage() {
let browser = this._browser;
if (!browser) if (!browser)
return; return 0;
var windowUtils = browser.contentWindow var windowUtils = browser.contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor) .QueryInterface(Ci.nsIInterfaceRequestor)
@ -515,19 +541,15 @@ BrowserView.prototype = {
var handheldFriendly = windowUtils.getDocumentMetadata("HandheldFriendly"); var handheldFriendly = windowUtils.getDocumentMetadata("HandheldFriendly");
if (handheldFriendly == "true") { if (handheldFriendly == "true") {
browser.className = "browser-handheld"; return 1;
this.setZoomLevel(1);
browser.markupDocumentViewer.textZoom = 1;
} else { } else {
browser.className = "browser";
let [w, h] = BrowserView.Util.getBrowserDimensions(browser); let [w, h] = BrowserView.Util.getBrowserDimensions(browser);
this.setZoomLevel(BrowserView.Util.pageZoomLevel(this.getVisibleRect(), w, h)); return BrowserView.Util.pageZoomLevel(this.getVisibleRect(), w, h);
} }
}, },
zoom: function zoom(aDirection) { zoom: function zoom(aDirection) {
let bvs = this._browserViewportState; let bvs = this._browserViewportState;
if (!bvs) if (!bvs)
throw "No browser is set"; throw "No browser is set";
@ -574,7 +596,6 @@ BrowserView.prototype = {
viewportToBrowser: function viewportToBrowser(x) { viewportToBrowser: function viewportToBrowser(x) {
let bvs = this._browserViewportState; let bvs = this._browserViewportState;
if (!bvs) if (!bvs)
throw "No browser is set"; throw "No browser is set";
@ -583,7 +604,6 @@ BrowserView.prototype = {
browserToViewport: function browserToViewport(x) { browserToViewport: function browserToViewport(x) {
let bvs = this._browserViewportState; let bvs = this._browserViewportState;
if (!bvs) if (!bvs)
throw "No browser is set"; throw "No browser is set";
@ -617,7 +637,6 @@ BrowserView.prototype = {
_viewportChanged: function _viewportChanged(viewportSizeChanged, dirtyAll) { _viewportChanged: function _viewportChanged(viewportSizeChanged, dirtyAll) {
let bops = this._batchOps; let bops = this._batchOps;
if (bops.length > 0) { if (bops.length > 0) {
let opState = bops[bops.length - 1]; let opState = bops[bops.length - 1];
@ -631,7 +650,6 @@ BrowserView.prototype = {
} }
let bvs = this._browserViewportState; let bvs = this._browserViewportState;
if (bvs) { if (bvs) {
BrowserView.Util.resizeContainerToViewport(this._container, bvs.viewportRect); BrowserView.Util.resizeContainerToViewport(this._container, bvs.viewportRect);

Просмотреть файл

@ -343,5 +343,14 @@ Rect.prototype = {
this.right = f.call(this, this.right); this.right = f.call(this, this.right);
this.bottom = f.call(this, this.bottom); this.bottom = f.call(this, this.bottom);
return this; return this;
},
/** Ensure this rectangle is inside the other, if possible. Preserves w, h. */
translateInside: function translateInside(other) {
let offsetX = (this.left < other.left ? other.left - this.left :
(this.right > other.right ? this.right - other.right : 0));
let offsetY = (this.top < other.top ? other.top - this.top :
(this.bottom > other.bottom ? this.bottom - other.bottom : 0));
return this.translate(offsetX, offsetY);
} }
}; };

Просмотреть файл

@ -603,6 +603,7 @@ var Browser = {
scrollContentToTop: function scrollContentToTop() { scrollContentToTop: function scrollContentToTop() {
this.contentScrollboxScroller.scrollTo(0, 0); this.contentScrollboxScroller.scrollTo(0, 0);
this.pageScrollboxScroller.scrollTo(0, 0);
this._browserView.onAfterVisibleMove(); this._browserView.onAfterVisibleMove();
}, },
@ -613,6 +614,14 @@ var Browser = {
this._browserView.onAfterVisibleMove(); this._browserView.onAfterVisibleMove();
}, },
hideTitlebar: function hideTitlebar() {
let container = this.contentScrollbox;
let rect = container.getBoundingClientRect();
this.pageScrollboxScroller.scrollBy(0, Math.round(rect.top));
this.tryUnfloatToolbar();
this._browserView.onAfterVisibleMove();
},
/** /**
* Return the currently active <browser> object * Return the currently active <browser> object
*/ */
@ -954,7 +963,7 @@ var Browser = {
return true; return true;
let [leftvis, ritevis, leftw, ritew] = Browser.computeSidebarVisibility(dx, dy); let [leftvis, ritevis, leftw, ritew] = Browser.computeSidebarVisibility(dx, dy);
if (leftvis <= 0.002 && ritevis <= 0.002) { if (leftvis == 0 && ritevis == 0) {
BrowserUI.unlockToolbar(); BrowserUI.unlockToolbar();
this.floatedWhileDragging = false; this.floatedWhileDragging = false;
return true; return true;
@ -973,78 +982,101 @@ var Browser = {
// needed then uncomment this line. // needed then uncomment this line.
}, },
/**
* Returns a good zoom rectangle for given element.
* @param y Where the user clicked in browser coordinates. For long elements
* (like news columns), this keeps the clicked spot in the viewport.
* @return Rectangle in current viewport coordinates, null if nothing works.
*/
_getZoomRectForElement: function _getZoomRectForElement(element, y) {
if (element == null)
return null;
const margin = 15;
let bv = Browser._browserView;
let vis = bv.getVisibleRect();
let elRect = bv.browserToViewportRect(Browser.getBoundingContentRect(element));
y = bv.browserToViewport(y);
let zoomLevel = BrowserView.Util.clampZoomLevel(bv.getZoomLevel() * vis.width / (elRect.width + margin * 2));
let zoomRatio = bv.getZoomLevel() / zoomLevel;
// Don't zoom in a marginal amount
// > 2/3 means operation increases the zoom level by less than 1.5
if (zoomRatio >= .6666)
return null;
let newVisW = vis.width * zoomRatio, newVisH = vis.height * zoomRatio;
let result = new Rect(elRect.center().x - newVisW / 2, y - newVisH / 2, newVisW, newVisH).expandToIntegers();
// Make sure rectangle doesn't poke out of viewport
return result.translateInside(bv._browserViewportState.viewportRect);
},
/**
* Find a good zoom rectangle for point specified in browser coordinates.
* @return Point in viewport coordinates, null if the zoom is too big.
*/
_getZoomRectForPoint: function _getZoomRectForPoint(x, y, zoomLevel) {
let bv = Browser._browserView;
let vis = bv.getVisibleRect();
x = bv.browserToViewport(x);
y = bv.browserToViewport(y);
if (zoomLevel >= 4)
return null;
let zoomRatio = zoomLevel / bv.getZoomLevel();
let newVisW = vis.width / zoomRatio, newVisH = vis.height / zoomRatio;
let result = new Rect(x - newVisW / 2, y - newVisH / 2, newVisW, newVisH);
// Make sure rectangle doesn't poke out of viewport
return result.translateInside(bv._browserViewportState.viewportRect);
},
setVisibleRect: function setVisibleRect(rect) {
let bv = Browser._browserView;
let vis = bv.getVisibleRect();
let zoomRatio = vis.width / rect.width;
let zoomLevel = bv.getZoomLevel() * zoomRatio;
let scrollX = rect.left * zoomRatio;
let scrollY = rect.top * zoomRatio;
bv.beginOffscreenOperation();
Browser.hideSidebars();
Browser.hideTitlebar();
bv.setZoomLevel(zoomLevel);
Browser.contentScrollboxScroller.scrollTo(scrollX, scrollY);
bv.onAfterVisibleMove();
bv.renderNow();
bv.commitOffscreenOperation();
},
zoomToPoint: function zoomToPoint(cX, cY) { zoomToPoint: function zoomToPoint(cX, cY) {
const margin = 15; const margin = 15;
let [elementX, elementY] = Browser.transformClientToBrowser(cX, cY); let [elementX, elementY] = Browser.transformClientToBrowser(cX, cY);
let aElement = Browser.elementFromPoint(elementX, elementY); let element = Browser.elementFromPoint(elementX, elementY);
let zoomRect = this._getZoomRectForElement(element, elementY);
let bv = Browser._browserView; if (zoomRect == null)
let scroller = Browser.contentScrollboxScroller;
let elRect = Browser.getBoundingContentRect(aElement);
let elWidth = elRect.width;
let vis = bv.getVisibleRect();
let vrWidth = vis.width;
/* Try to set zoom-level such that once zoomed element is as wide
* as the visible rect */
let zoomLevel = BrowserView.Util.clampZoomLevel((vrWidth - (2 * margin)) / elWidth);
let oldZoomLevel = bv.getZoomLevel();
//dump("element width: " + elWidth + "\n");
//dump("old zoom level: " + oldZoomLevel + "\n");
//dump("new zoom level: " + zoomLevel + "\n");
/* If the new zoom level we've calculated is less than or equal to
* the current zoom level, return early and don't progress further.
* If the new zoom level is higher, continue to zoom inward.
*/
if (oldZoomLevel >= zoomLevel)
return false; return false;
bv.beginBatchOperation(); this.setVisibleRect(zoomRect);
/* XXX
* This isn't ideal.
*/
this.hideSidebars();
bv.setZoomLevel(zoomLevel);
bv.forceContainerResize();
//Browser.forceChromeReflow();
let dx = Math.round(bv.browserToViewport(elRect.left) - margin - vis.left);
let dy = Math.round(bv.browserToViewport(elementY) - (window.innerHeight / 2) - vis.top);
Browser.contentScrollbox.customDragger.dragMove(dx, dy, scroller);
bv.commitBatchOperation();
return true; return true;
}, },
zoomFromPoint: function zoomFromPoint(cX, cY) { zoomFromPoint: function zoomFromPoint(cX, cY) {
let [elementX, elementY] = this.transformClientToBrowser(cX, cY); let bv = this._browserView;
let bv = Browser._browserView; let zoomLevel = bv.getZoomForPage();
if (bv.getZoomLevel() != zoomLevel) {
bv.beginBatchOperation(); let [elementX, elementY] = this.transformClientToBrowser(cX, cY);
let zoomRect = this._getZoomRectForPoint(elementX, elementY, zoomLevel);
bv.zoomToPage(); this.setVisibleRect(zoomRect);
}
bv.forceContainerResize();
let dy = Math.round(bv.browserToViewport(elementY) - (window.innerHeight / 2) - bv.getVisibleRect().top);
this.contentScrollbox.customDragger.dragMove(0, dy, this.contentScrollboxScroller);
Browser.forceChromeReflow();
this.hideSidebars();
bv.commitBatchOperation();
}, },
getBoundingContentRect: function getBoundingContentRect(contentElem) { getBoundingContentRect: function getBoundingContentRect(contentElem) {
@ -1053,18 +1085,18 @@ var Browser = {
if (!browser) if (!browser)
return null; return null;
let scrollX = { value: 0 }; let offset = BrowserView.Util.getContentScrollOffset(browser);
let scrollY = { value: 0 };
let cwu = BrowserView.Util.getBrowserDOMWindowUtils(browser);
cwu.getScrollXY(false, scrollX, scrollY);
let r = contentElem.getBoundingClientRect(); let r = contentElem.getBoundingClientRect();
//dump('getBoundingContentRect: clientRect is at ' + r.left + ', ' + r.top + '; scrolls are ' + scrollX.value + ', ' + scrollY.value + '\n'); // step out of iframes and frames, offsetting scroll values
for (let frame = contentElem.ownerDocument.defaultView; frame != browser.contentWindow; frame = frame.parent) {
// adjust client coordinates' origin to be top left of iframe viewport
let rect = frame.frameElement.getBoundingClientRect();
offset.add(rect.left, rect.top);
}
return new Rect(r.left + scrollX.value, return new Rect(r.left + offset.x, r.top + offset.y, r.width, r.height);
r.top + scrollY.value,
r.width, r.height);
}, },
/** /**
@ -2446,6 +2478,18 @@ Tab.prototype = {
}, },
endLoading: function() { endLoading: function() {
// Determine at what resolution the browser is rendered based on meta tag
let browser = this._browser;
let windowUtils = browser.contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let handheldFriendly = windowUtils.getDocumentMetadata("HandheldFriendly");
if (handheldFriendly == "true") {
browser.className = "browser-handheld";
} else {
browser.className = "browser";
}
//if (!this._loading) //if (!this._loading)
// dump("!!! Already finished loading this tab, please file a bug\n"); // dump("!!! Already finished loading this tab, please file a bug\n");
this.setIcon(this._browser.mIconURL); this.setIcon(this._browser.mIconURL);

Просмотреть файл

@ -226,10 +226,15 @@
<notificationbox id="notifications"/> <notificationbox id="notifications"/>
<!-- Content viewport --> <!-- Content viewport -->
<scrollbox id="content-scrollbox" style="overflow: hidden;" class="window-width window-height"> <stack id="tile-stack" class="window-width window-height">
<!-- Content viewport --> <scrollbox id="content-scrollbox" style="overflow: hidden;" class="window-width window-height">
<html:div id="tile-container" style="overflow: hidden;"/> <!-- Content viewport -->
</scrollbox> <html:div id="tile-container" style="overflow: hidden;"/>
</scrollbox>
<html:canvas id="view-buffer" style="display: none;" moz-opaque="true">
</html:canvas>
</stack>
</vbox> </vbox>
</scrollbox> </scrollbox>