Bug 462425: Convert Fennec to use WidgetStack framework for panning, r=stuart

This commit is contained in:
Mark Finkle 2008-11-22 00:12:25 -05:00
parent 4f628ba099
commit 196c91951a
14 changed files with 4129 additions and 2064 deletions

View File

@ -0,0 +1,573 @@
// -*- 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):
* Stuart Parmenter <stuart@mozilla.com>
* Brad Lassey <blassey@mozilla.com>
* Mark Finkle <mfinkle@mozilla.com>
* Gavin Sharp <gavin.sharp@gmail.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 ***** */
function CanvasBrowser(canvas) {
this._canvas = canvas;
this._zoomLevel = 1;
this._browser = null;
this._pageX = 0;
this._pageY = 0;
}
CanvasBrowser.prototype = {
_canvas: null,
_zoomLevel: 1,
_browser: null,
_pageX: 0,
_pageY: 0,
_screenX: 0,
_screenY: 0,
get viewportDimensions() {
var rect = this._canvas.getBoundingClientRect();
return [rect.width, rect.height];
},
get _effectiveViewportDimensions() {
var [w, h] = this.viewportDimensions;
return [this._screenToPage(w), this._screenToPage(h)];
},
get _effectiveCanvasDimensions() {
let canvasRect = this._canvas.getBoundingClientRect();
return [this._screenToPage(canvasRect.width),
this._screenToPage(canvasRect.height)];
},
setCurrentBrowser: function(browser) {
let currentBrowser = this._browser;
if (currentBrowser) {
// stop monitor paint events for this browser
currentBrowser.removeEventListener("MozAfterPaint", this._paintHandler, false);
currentBrowser.setAttribute("type", "content");
}
browser.setAttribute("type", "content-primary");
// start monitoring paint events for this browser
var self = this;
this._paintHandler = function(ev) { self._handleMozAfterPaint(ev); }
browser.addEventListener("MozAfterPaint", this._paintHandler, false);
this._browser = browser;
self.zoomToPage();
},
startLoading: function() {
// Clear the whole canvas
// we clear the whole canvas because the browser's width or height
// could be less than the area we end up actually drawing.
var ctx = this._canvas.getContext("2d");
ctx.fillStyle = "rgb(255,255,255)";
ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
ctx.fillStyle = "rgb(0,0,0)";
ctx.fillText("Loading...", 20, 20);
this._resizeInterval = setInterval(function(self) { self.zoomToPage(); }, 1000, this);
},
endLoading: function() {
clearInterval(this._resizeInterval);
this.zoomToPage();
},
viewportHandler: function(bounds, oldBounds) {
let pageBounds = bounds.clone();
pageBounds.top = Math.floor(this._screenToPage(bounds.top));
pageBounds.left = Math.floor(this._screenToPage(bounds.left));
pageBounds.bottom = Math.ceil(this._screenToPage(bounds.bottom));
pageBounds.right = Math.ceil(this._screenToPage(bounds.right));
if (0) {
if (true /*!oldBounds*/) {
this._pageX = pageBounds.x;
this._pageY = pageBounds.y;
var ctx = this._canvas.getContext("2d");
ctx.save();
ctx.scale(this._zoomLevel, this._zoomLevel);
try {
dump("drawWindow: " + pageBounds.x + " " + pageBounds.y + " " + pageBounds.width + " " + pageBounds.height + "\n");
ctx.drawWindow(this._browser.contentWindow,
pageBounds.x, pageBounds.y, pageBounds.width, pageBounds.height,
"white",
ctx.DRAWWINDOW_DO_NOT_FLUSH);
} catch (e) {
dump("DRAWWINDOW FAILED\n");
}
ctx.restore();
return;
}
}
if (!oldBounds) {
// no old bounds means we resized the viewport, so redraw everything
this._screenX = bounds.x;
this._screenY = bounds.y;
this._pageX = pageBounds.x;
this._pageY = pageBounds.y;
return this._redrawRect(pageBounds.x, pageBounds.y,
pageBounds.width, pageBounds.height);
}
let dx = this._screenX - bounds.x;
let dy = this._screenY - bounds.y;
let [soffX, soffY] = this._canvasPageOffset;
this._screenX = bounds.x;
this._screenY = bounds.y;
this._pageX = pageBounds.x;
this._pageY = pageBounds.y;
let [offX, offY] = this._drawOffset;
// take in to account the canvas offset when we blit
let [eoffX, eoffY] = this._canvasPageOffset;
let [coffX, coffY] = [Math.floor(this._pageToScreen(eoffX - soffX)),
Math.floor(this._pageToScreen(eoffY - soffY))];
//dump("viewportHandler: " + bounds.toSource() + " " + oldBounds.toSource() + "\n");
//dump("page offset " + eoffX + " " + eoffY + "\n");
// deal with repainting
let srcRect = { x: 0, y: 0,
width: this._canvas.width, height: this._canvas.height };
let dstRect = { x: dx - coffX, y: dy - coffY,
width: this._canvas.width, height: this._canvas.height };
// we don't need to do anything if the source and destination are the same
if (srcRect.x == dstRect.x && srcRect.y == dstRect.y &&
srcRect.width == dstRect.width && srcRect.height == dstRect.height) {
dump("avoiding dumb paint\n");
return;
}
// blit what we can
var ctx = this._canvas.getContext("2d");
ctx.drawImage(this._canvas,
srcRect.x, srcRect.y,
srcRect.width, srcRect.height,
dstRect.x, dstRect.y,
dstRect.width, dstRect.height);
//dump("blitting " + srcRect.toSource() + " to " + dstRect.toSource() + "\n");
// redraw the rest
var rgn = Cc["@mozilla.org/gfx/region;1"].createInstance(Ci.nsIScriptableRegion);
rgn.setToRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height);
rgn.subtractRect(dstRect.x, dstRect.y, dstRect.width, dstRect.height);
let outX = {}; let outY = {}; let outW = {}; let outH = {};
rgn.getBoundingBox(outX, outY, outW, outH);
dstRect = { x: outX.value, y: outY.value, width: outW.value, height: outH.value };
if (dstRect.width > 0 && dstRect.height > 0) {
dstRect.width += 1;
dstRect.height += 1;
//dump("redrawing: offset " + dstRect.x + " " + dstRect.y + "\n");
ctx.save();
ctx.translate(dstRect.x, dstRect.y);
ctx.scale(this._zoomLevel, this._zoomLevel);
var [offX, offY] = this._drawOffset;
let scaledRect = { x: offX + this._screenToPage(dstRect.x),
y: offY + this._screenToPage(dstRect.y),
width: this._screenToPage(dstRect.width),
height: this._screenToPage(dstRect.height) };
//dump(" rect " + scaledRect.toSource() + "\n");
ctx.drawWindow(this._browser.contentWindow,
scaledRect.x, scaledRect.y,
scaledRect.width, scaledRect.height,
"white",
ctx.DRAWWINDOW_DO_NOT_FLUSH);
// for testing
//ctx.fillStyle = "rgba(255,0,0,0.5)";
//ctx.fillRect(0, 0, scaledRect.width, scaledRect.height);
ctx.restore();
}
},
get _canvasPageOffset() {
/*
let [canvasW, canvasH] = this._effectiveCanvasDimensions;
let [viewportW, viewportH] = this._effectiveViewportDimensions;
let offscreenCanvasW = (canvasW - viewportW);
let offscreenCanvasH = (canvasH - viewportH);
let [contentWidth, contentHeight] = this._contentAreaDimensions;
let [pageX, pageY] = this._pageOffset();
let left = Math.max(-pageX, -(offscreenCanvasW / 2));
let rightMost = (contentWidth - canvasW);
if (left > rightMost && rightMost > 0)
left = rightMost;
let top = Math.max(-pageY, -(offscreenCanvasH / 2));
let bottomMost = (contentHeight - canvasH);
if (top > bottomMost && bottomMost > 0)
top = bottomMost;
return [left, top];
*/
return [0, 0];
},
get _drawOffset() {
let [offX, offY] = this._canvasPageOffset;
let [pageX, pageY] = this._pageOffset();
//dump(offX + " " + offY + " " + pageX + " " + pageY + "\n");
return [pageX + offX, pageY + offY];
},
_handleMozAfterPaint: function(aEvent) {
let cwin = this._browser.contentWindow;
for (let i = 0; i < aEvent.clientRects.length; i++) {
let e = aEvent.clientRects.item(i);
//dump(Math.floor(e.left + cwin.scrollX),
// Math.floor(e.top + cwin.scrollY),
// Math.ceil(e.width), Math.ceil(e.height));
this._redrawRect(Math.floor(e.left + cwin.scrollX),
Math.floor(e.top + cwin.scrollY),
Math.ceil(e.width), Math.ceil(e.height));
}
},
_redrawRect: function(x, y, width, height) {
function intersect(r1, r2) {
let xmost1 = r1.x + r1.width;
let ymost1 = r1.y + r1.height;
let xmost2 = r2.x + r2.width;
let ymost2 = r2.y + r2.height;
let x = Math.max(r1.x, r2.x);
let y = Math.max(r1.y, r2.y);
let temp = Math.min(xmost1, xmost2);
if (temp <= x)
return null;
let width = temp - x;
temp = Math.min(ymost1, ymost2);
if (temp <= y)
return null;
let height = temp - y;
return { x: x,
y: y,
width: width,
height: height };
}
let r1 = { x : x,
y : y,
width : width,
height: height };
// check to see if the input coordinates are inside the visiable destination
let [canvasW, canvasH] = this._effectiveCanvasDimensions;
let r2 = { x : this._pageX,
y : this._pageY,
width : canvasW,
height: canvasH };
let dest = intersect(r1, r2);
if (!dest)
return;
//dump(dest.toSource() + "\n");
var ctx = this._canvas.getContext("2d");
ctx.save();
ctx.scale(this._zoomLevel, this._zoomLevel);
var [offX, offY] = this._drawOffset;
//dump("offx, offy: " + offX + " " + offY + "\n");
ctx.translate(dest.x - offX, dest.y - offY);
//dump("drawWindow#2: " + dest.x + " " + dest.y + " " + dest.width + " " + dest.height + " @ " + (dest.x - offX) + " " + (dest.y - offY) + "\n");
ctx.drawWindow(this._browser.contentWindow,
dest.x, dest.y,
dest.width, dest.height,
"white",
ctx.DRAWWINDOW_DO_NOT_FLUSH);
ctx.restore();
},
_clampZoomLevel: function(aZoomLevel) {
const min = 0.2;
const max = 2.0;
return Math.min(Math.max(min, aZoomLevel), max);
},
set zoomLevel(val) {
this._zoomLevel = this._clampZoomLevel(val);
Browser.updateViewportSize();
},
get zoomLevel() {
return this._zoomLevel;
},
zoom: function(aDirection) {
if (aDirection == 0)
return;
var zoomDelta = 0.05; // 1/20
if (aDirection >= 0)
zoomDelta *= -1;
this.zoomLevel = this._zoomLevel + zoomDelta;
},
zoomToPage: function() {
//dump("zoom to page\n");
// Adjust the zoomLevel to fit the page contents in our window
// width
let [contentW, ] = this._contentAreaDimensions;
let [viewportW, ] = this.viewportDimensions;
if (contentW > viewportW)
this.zoomLevel = viewportW / contentW;
},
zoomToElement: function(aElement) {
const margin = 15;
// XXX The widget stack code doesn't do what we want when you change
// the viewport bounds to something smaller than your current position
// so pan back to 0,0 before we resize and then pan to our destination
ws.panTo(0, 0);
// scale to the element's width
let [viewportW, ] = this.viewportDimensions;
let elRect = this._getPagePosition(aElement);
let zoomLevel = viewportW / (elRect.width + (2 * margin));
this.zoomLevel = Math.min(zoomLevel, 10);
// pan to the element
ws.panTo(Math.floor(Math.max(this._pageToScreen(elRect.x) - margin, 0)),
Math.floor(Math.max(this._pageToScreen(elRect.y) - margin, 0)));
},
zoomFromElement: function(aElement) {
let elRect = this._getPagePosition(aElement);
// XXX The widget stack code doesn't do what we want when you change
// the viewport bounds to something smaller than your current position
// so pan back to 0,0 before we resize and then pan to our destination
ws.panTo(0, 0);
// pan to the element
// don't bother with x since we're zooming all the way out
this.zoomToPage();
// XXX have this center the element on the page
ws.panTo(0, Math.floor(Math.max(0, this._pageToScreen(elRect.y))));
},
/**
* Retrieve the content element for a given point in client coordinates
* (relative to the top left corner of the chrome window).
*/
elementFromPoint: function(aX, aY) {
let [x, y] = this._clientToContentCoords(aX, aY);
let cwu = this._browser.contentWindow
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
let element = cwu.elementFromPoint(x, y,
true, /* ignore root scroll frame*/
false); /* don't flush layout */
return element;
},
/**
* Retrieve the page position for a given element
* (relative to the document origin).
*/
_getPagePosition: function(aElement) {
let r = aElement.getBoundingClientRect();
let cwin = this._browser.contentWindow;
let retVal = {
width: r.width,
height: r.height,
x: r.left + cwin.scrollX,
y: r.top + cwin.scrollY
};
return retVal;
},
_pageOffset: function() {
// return [this._screenToPage(ws._viewport.viewportInnerBounds.x),
// this._screenToPage(ws._viewport.viewportInnerBounds.y)];
return [this._pageX, this._pageY];
},
/* Given a set of client coordinates (relative to the app window),
* returns the content coordinates relative to the viewport.
*/
_clientToContentCoords: function(aClientX, aClientY) {
// Determine position relative to the document origin
// Need to adjust for the deckbrowser not being at 0,0
// (e.g. due to other browser UI)
let browserRect = this._canvas.getBoundingClientRect();
let clickOffsetX = this._screenToPage(aClientX - browserRect.left) + this._pageX;
let clickOffsetY = this._screenToPage(aClientY - browserRect.top) + this._pageY;
// Take scroll offset into account to return coordinates relative to the viewport
let cwin = this._browser.contentWindow;
return [clickOffsetX - cwin.scrollX,
clickOffsetY - cwin.scrollY];
},
get _contentAreaDimensions() {
var cdoc = this._browser.contentDocument;
// Return the document width/height for XUL documents (which is
// essentially the same as the viewport width/height).
if (cdoc instanceof XULDocument)
return [cdoc.width, cdoc.height];
if (cdoc instanceof SVGDocument) {
let rect = cdoc.rootElement.getBoundingClientRect();
return [rect.width, rect.height];
}
// These might not exist yet depending on page load state
var body = cdoc.body || {};
var html = cdoc.documentElement || {};
var w = Math.max(body.scrollWidth, html.scrollWidth);
var h = Math.max(body.scrollHeight, html.scrollHeight);
if (isNaN(w) || isNaN(h) || w == 0 || h == 0)
return [this._canvas.width, this._canvas.height];
return [w, h];
},
_screenToPage: function(aValue) {
return aValue / this._zoomLevel;
},
_pageToScreen: function(aValue) {
return aValue * this._zoomLevel;
},
/* ensures that a given content element is visible */
ensureElementIsVisible: function(aElement) {
let elRect = this._getPagePosition(aElement);
let [viewportW, viewportH] = this._effectiveViewportDimensions;
let curRect = {
x: this._pageX,
y: this._pageY,
width: viewportW,
height: viewportH
};
// Adjust for part of our viewport being offscreen
// XXX this assumes that the browser is meant to be fullscreen
let browserRect = this._currentBrowser.getBoundingClientRect();
curRect.height -= this._screenToPage(Math.abs(browserRect.top));
if (browserRect.top < 0)
curRect.y -= this._screenToPage(browserRect.top);
curRect.width -= this._screenToPage(Math.abs(browserRect.left));
if (browserRect.left < 0)
curRect.x -= this._screenToPage(browserRect.left);
let newx = curRect.x;
let newy = curRect.y;
if (elRect.x + elRect.width > curRect.x + curRect.width) {
newx = curRect.x + ((elRect.x + elRect.width)-(curRect.x + curRect.width));
} else if (elRect.x < curRect.x) {
newx = elRect.x;
}
if (elRect.y + elRect.height > curRect.y + curRect.height) {
newy = curRect.y + ((elRect.y + elRect.height)-(curRect.y + curRect.height));
} else if (elRect.y < curRect.y) {
newy = elRect.y;
}
this.panTo(newx, newy);
},
/* Pans directly to a given content element */
panToElement: function(aElement) {
var elRect = this._getPagePosition(aElement);
this.panTo(elRect.x, elRect.y);
},
panTo: function(x, y) {
ws.panTo(x, y);
}
};

View File

@ -0,0 +1,646 @@
// -*- 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):
* Stuart Parmenter <stuart@mozilla.com>
* Brad Lassey <blassey@mozilla.com>
* Mark Finkle <mfinkle@mozilla.com>
* Gavin Sharp <gavin.sharp@gmail.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 ***** */
/**
* Everything that is registed in _modules gets called with each event that the
* InputHandler is registered to listen for.
*
* When one of the handlers decides it wants to handle the event, it should call
* grab() on its owner which will cause it to receive all of the events until it
* calls ungrab(). Calling grab will notify the other handlers via a
* cancelPending() notification. This tells them to stop what they're doing and
* give up hope for being the one to process the events.
*/
function InputHandler() {
let stack = document.getElementById("browser-container");
stack.addEventListener("DOMMouseScroll", this, true);
let content = document.getElementById("canvas");
content.addEventListener("mouseout", this, true);
content.addEventListener("mousedown", this, true);
content.addEventListener("mouseup", this, true);
content.addEventListener("mousemove", this, true);
content.addEventListener("keydown", this, true);
content.addEventListener("keyup", this, true);
let prefsvc = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch2);
let allowKinetic = prefsvc.getBoolPref("browser.ui.panning.kinetic");
//let allowKinetic = false;
if (allowKinetic)
this._modules.push(new KineticPanningModule(this));
else
this._modules.push(new PanningModule(this));
this._modules.push(new ClickingModule(this));
this._modules.push(new ScrollwheelModule(this));
}
InputHandler.prototype = {
_modules : [],
_grabbed : null,
grab: function(obj) {
//dump("grabbing\n");
this._grabbed = obj;
for each(mod in this._modules) {
if (mod != obj)
mod.cancelPending();
}
// only send events to this object
// call cancel on all modules
},
ungrab: function(obj) {
//dump("unggrabbing\n");
this._grabbed = null;
// only send events to this object
// call cancel on all modules
},
handleEvent: function (aEvent) {
if (this._grabbed) {
this._grabbed.handleEvent(aEvent);
} else {
for each(mod in this._modules)
mod.handleEvent(aEvent);
}
}
};
/**
* Kinetic panning code
*/
function KineticPanningModule(owner) {
this._owner = owner;
}
KineticPanningModule.prototype = {
_owner: null,
_dragData: {
dragging: false,
sX: 0,
sY: 0,
dragStartTimeout: -1,
reset: function() {
this.dragging = false;
this.sX = 0;
this.sY = 0;
if (this.dragStartTimeout != -1)
clearTimeout(this.dragStartTimeout);
this.dragStartTimeout = -1;
}
},
_kineticData: {
// const
kineticStepSize: 15,
kineticDecelloration: 0.004,
momentumBufferSize: 3,
momentumBuffer: [],
momentumBufferIndex: 0,
lastTime: 0,
kineticDuration: 0,
kineticDirX: 0,
kineticDirY: 0,
kineticHandle : -1,
kineticStep : 0,
kineticStartX : 0,
kineticStartY : 0,
kineticInitialVel: 0,
reset: function() {
if (this.kineticHandle != -1) {
window.clearInterval(this.kineticHandle);
this.kineticHandle = -1;
}
this.momentumBuffer = [];
this.momentumBufferIndex = 0;
this.lastTime = 0;
this.kineticDuration = 0;
this.kineticDirX = 0;
this.kineticDirY = 0;
this.kineticStep = 0;
this.kineticStartX = 0;
this.kineticStartY = 0;
this.kineticInitialVel = 0;
}
},
handleEvent: function(aEvent) {
switch (aEvent.type) {
case "mousedown":
return this._onMouseDown(aEvent);
break;
case "mousemove":
return this._onMouseMove(aEvent);
case "mouseout":
case "mouseup":
return this._onMouseUp(aEvent);
}
},
/* If someone else grabs events ahead of us, cancel any pending
* timeouts we may have.
*/
cancelPending: function() {
this._dragData.reset();
// XXX we should cancel kinetic here as well
//dump("canceling drag\n");
},
_dragStart: function(sX, sY) {
this._dragData.dragging = true;
this._dragData.dragStartTimeout = -1;
// grab all events until we stop the drag
this._owner.grab(this);
ws.dragStart(sX, sY);
// set the kinetic start time
this._kineticData.lastTime = Date.now();
},
_dragStop: function(sX, sY) {
// start kinetic scrolling here.
if (!this._startKinetic(sX, sY)) {
this._endKinetic(sX, sY);
}
},
_dragMove: function(sX, sY) {
ws.dragMove(sX, sY);
},
_onMouseDown: function(aEvent) {
// if we're in the process of kineticly scrolling, stop and start over
if (this.kineticHandle != -1)
this._endKinetic(aEvent.screenX, aEvent.screenY);
let dragData = this._dragData;
dragData.sX = aEvent.screenX;
dragData.sY = aEvent.screenY;
dragData.dragStartTimeout = setTimeout(function(self, sX, sY) { self._dragStart(sX, sY) },
200, this, aEvent.screenX, aEvent.screenY);
},
_onMouseUp: function(aEvent) {
let dragData = this._dragData;
if (dragData.dragging)
this._dragStop(aEvent.screenX, aEvent.screenY);
else
this._dragData.reset(); // be sure to reset the timer
},
_onMouseMove: function(aEvent) {
// don't do anything if we're in the process of kineticly scrolling
if (this._kineticData.kineticHandle != -1)
return;
let dragData = this._dragData;
let dx = dragData.sX - aEvent.screenX;
let dy = dragData.sY - aEvent.screenY;
if (!dragData.dragging && dragData.dragStartTimeout != -1) {
if ((Math.abs(dx*dx) + Math.abs(dy*dy)) > 100) {
clearTimeout(dragData.dragStartTimeout);
this._dragStart(aEvent.screenX, aEvent.screenY);
}
}
if (!dragData.dragging)
return;
this._dragMove(aEvent.screenX, aEvent.screenY);
dragData.sX = aEvent.screenX;
dragData.sY = aEvent.screenY;
// update our kinetic data
let kineticData = this._kineticData;
let t = Date.now();
let dt = t - kineticData.lastTime;
kineticData.lastTime = t;
let momentumBuffer = { dx: -dx, dy: -dy, dt: dt }
kineticData.momentumBuffer[kineticData.momentumBufferIndex] = momentumBuffer;
kineticData.momentumBufferIndex++;
kineticData.momentumBufferIndex %= kineticData.momentumBufferSize;
},
_startKinetic: function(sX, sY) {
let kineticData = this._kineticData;
let dx = 0;
let dy = 0;
let dt = 0;
if (kineticData.kineticInitialVel)
return true;
if (!kineticData.momentumBuffer)
return false;
for (let i = 0; i < kineticData.momentumBufferSize; i++) {
let me = kineticData.momentumBuffer[(kineticData.momentumBufferIndex + i) % kineticData.momentumBufferSize];
if (!me)
return false;
dx += me.dx;
dy += me.dy;
dt += me.dt;
}
if (dt <= 0)
return false;
let dist = Math.sqrt(dx*dx+dy*dy);
let vel = dist/dt;
if (vel < 1)
return false;
kineticData.kineticDirX = dx/dist;
kineticData.kineticDirY = dy/dist;
if (kineticData.kineticDirX > 0.9) {
kineticData.kineticDirX = 1;
kineticData.kineticDirY = 0;
} else if (kineticData.kineticDirY < -0.9) {
kineticData.kineticDirX = 0;
kineticData.kineticDirY = -1;
} else if (kineticData.kineticDirX < -0.9) {
kineticData.kineticDirX = -1;
kineticData.kineticDirY = 0;
} else if (kineticData.kineticDirY > 0.9) {
kineticData.kineticDirX = 0;
kineticData.kineticDirY = 1;
}
kineticData.kineticDuration = vel/(2 * kineticData.kineticDecelloration);
kineticData.kineticStep = 0;
kineticData.kineticStartX = sX;
kineticData.kineticStartY = sY;
kineticData.kineticInitialVel = vel;
kineticData.kineticHandle = window.setInterval(this._doKinetic, kineticData.kineticStepSize, this);
return true;
},
_doKinetic: function(self) {
let kineticData = self._kineticData;
let t = kineticData.kineticStep * kineticData.kineticStepSize;
kineticData.kineticStep++;
if (t > kineticData.kineticDuration)
t = kineticData.kineticDuration;
let dist = kineticData.kineticInitialVel * t -
kineticData.kineticDecelloration * t * t;
let newX = Math.floor(kineticData.kineticDirX * dist + kineticData.kineticStartX);
let newY = Math.floor(kineticData.kineticDirY * dist + kineticData.kineticStartY);
self._dragMove(newX, newY);
if(t >= kineticData.kineticDuration)
self._endKinetic(newX, newY);
},
_endKinetic: function(sX, sY) {
ws.dragStop(sX, sY);
this._owner.ungrab(this);
this._dragData.reset();
this._kineticData.reset();
},
};
/**
* Regular non-kinetic panning code
*/
function PanningModule(owner) {
this._owner = owner;
}
PanningModule.prototype = {
_owner: null,
_dragData: {
dragging: false,
sX: 0,
sY: 0,
dragStartTimeout: -1,
reset: function() {
this.dragging = false;
this.sX = 0;
this.sY = 0;
if (this.dragStartTimeout != -1)
clearTimeout(this.dragStartTimeout);
this.dragStartTimeout = -1;
}
},
handleEvent: function(aEvent) {
switch (aEvent.type) {
case "mousedown":
return this._onMouseDown(aEvent);
break;
case "mousemove":
return this._onMouseMove(aEvent);
case "mouseout":
case "mouseup":
return this._onMouseUp(aEvent);
}
},
/* If someone else grabs events ahead of us, cancel any pending
* timeouts we may have.
*/
cancelPending: function() {
this._dragData.reset();
//dump("canceling drag\n");
},
_dragStart: function(sX, sY) {
//dump("starting drag\n");
this._dragData.dragging = true;
this._dragData.dragStartTimeout = -1;
// grab all events until we stop the drag
this._owner.grab(this);
ws.dragStart(sX, sY);
},
_dragStop: function(sX, sY) {
//dump("ending drag\n");
this._dragData.reset();
ws.dragStop(sX, sY);
this._owner.ungrab(this);
},
_dragMove: function(sX, sY) {
//dump("moving drag" + sX + " " + sY + "\n");
ws.dragMove(sX, sY);
},
_onMouseDown: function(aEvent) {
let dragData = this._dragData;
dragData.sX = aEvent.screenX;
dragData.sY = aEvent.screenY;
dragData.dragStartTimeout = setTimeout(function(self, sX, sY) { self._dragStart(sX, sY) },
200, this, aEvent.screenX, aEvent.screenY);
},
_onMouseUp: function(aEvent) {
let dragData = this._dragData;
if (dragData.dragging)
this._dragStop(aEvent.screenX, aEvent.screenY);
else
this._dragData.reset(); // be sure to reset the timer
},
_onMouseMove: function(aEvent) {
let dragData = this._dragData;
let dx = dragData.sX - aEvent.screenX;
let dy = dragData.sY - aEvent.screenY;
if (!dragData.dragging && dragData.dragStartTimeout != -1) {
if ((Math.abs(dx*dx) + Math.abs(dy*dy)) > 100) {
clearTimeout(dragData.dragStartTimeout);
this._dragStart(aEvent.screenX, aEvent.screenY);
}
}
if (!dragData.dragging)
return;
this._dragMove(aEvent.screenX, aEvent.screenY);
dragData.sX = aEvent.screenX;
dragData.sY = aEvent.screenY;
}
};
/**
* Mouse click handlers
*/
function ClickingModule(owner) {
this._owner = owner;
}
ClickingModule.prototype = {
_clickTimeout : -1,
_events : [],
_zoomed : false,
handleEvent: function (aEvent) {
switch (aEvent.type) {
// UI panning events
case "mousedown":
//dump("mousedown\n");
this._events.push({event: aEvent, time: Date.now()});
// we're waiting for a click
if (this._clickTimeout != -1) {
// if we just got another mousedown, don't send anything until we get another mousedown
clearTimeout(this._clickTimeout);
this.clickTimeout = -1
}
break;
case "mouseup":
// keep an eye out for mouseups that didn't start with a mousedown
if (!(this._events.length % 2)) {
this._reset();
break;
}
//dump("mouseup\n");
this._events.push({event: aEvent, time: Date.now()});
if (this._clickTimeout == -1) {
this._clickTimeout = setTimeout(function(self) { self._sendSingleClick() }, 400, this);
} else {
clearTimeout(this._clickTimeout);
this._sendDoubleClick();
}
break;
case "mouseout":
this._reset();
break;
}
},
/* If someone else grabs events ahead of us, cancel any pending
* timeouts we may have.
*/
cancelPending: function() {
//dump("canceling click\n");
this._reset();
},
_reset: function() {
if (this._clickTimeout != -1)
clearTimeout(this._clickTimeout);
this._clickTimeout = -1;
this._events = [];
},
_sendSingleClick: function() {
this._owner.grab(this);
this._redispatchMouseEvent(this._events[0].event);
this._redispatchMouseEvent(this._events[1].event);
this._owner.ungrab(this);
this._reset();
},
_sendDoubleClick: function() {
this._owner.grab(this);
// XXX disable zooming until it works properly.
function optimalElementForPoint(cX, cY) {
var element = Browser.content.elementFromPoint(cX, cY);
if (!element)
return null;
// Find the nearest non-inline ancestor
while (element.parentNode) {
let display = window.getComputedStyle(element, "").getPropertyValue("display");
let zoomable = /table/.test(display) || /block/.test(display);
if (zoomable)
break;
element = element.parentNode;
}
return element;
}
let firstEvent = this._events[0].event;
let zoomElement = optimalElementForPoint(firstEvent.clientX, firstEvent.clientY);
if (zoomElement) {
if (this._zoomed) {
// zoom out
this._zoomed = false;
Browser.content.zoomFromElement(zoomElement);
} else {
// zoom in
this._zoomed = true;
Browser.content.zoomToElement(zoomElement);
}
}
this._owner.ungrab(this);
this._reset();
},
_redispatchMouseEvent: function(aEvent, aType) {
if (!(aEvent instanceof MouseEvent)) {
Components.utils.reportError("_redispatchMouseEvent called with a non-mouse event");
return;
}
var [x, y] = Browser.content._clientToContentCoords(aEvent.clientX, aEvent.clientY);
//dump("sending mouse event to: " + x + " " + y + "\n");
var cwin = Browser.currentBrowser.contentWindow;
var cwu = cwin.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
// Redispatch the mouse event, ignoring the root scroll frame
cwu.sendMouseEvent(aType || aEvent.type,
x, y,
aEvent.button || 0,
aEvent.detail || 1,
0, true);
}
};
/**
* Scrollwheel zooming handler
*/
function ScrollwheelModule(owner) {
this._owner = owner;
}
ScrollwheelModule.prototype = {
handleEvent: function (aEvent) {
switch (aEvent.type) {
// UI panning events
case "DOMMouseScroll":
this._owner.grab(this);
this.deckbrowser.zoom(aEvent.detail);
this._owner.ungrab(this);
break;
}
},
/* If someone else grabs events ahead of us, cancel any pending
* timeouts we may have.
*/
cancelPending: function() {
}
};

File diff suppressed because it is too large Load Diff

View File

@ -55,7 +55,7 @@ const kMaxEngines = 4;
const kDefaultFavIconURL = "chrome://browser/skin/images/default-favicon.png";
[
["gContentBox", "contentBox"],
["gContentBox", "content"],
].forEach(function (elementGlobal) {
var [name, id] = elementGlobal;
window.__defineGetter__(name, function () {
@ -81,20 +81,6 @@ var BrowserUI = {
_favicon : null,
_faviconLink : null,
_setContentPosition : function (aProp, aValue) {
let value = Math.round(aValue);
if (aProp == "left") {
gContentBox.style.marginLeft = value + "px";
gContentBox.style.marginRight = -value + "px";
} else if (aProp == "top") {
gContentBox.style.marginTop = value + "px";
gContentBox.style.marginBottom = -value + "px";
}
},
get _contentTop() {
return parseInt(gContentBox.style.marginTop);
},
_titleChanged : function(aDocument) {
var browser = Browser.currentBrowser;
if (browser && aDocument != browser.contentDocument)
@ -130,21 +116,7 @@ var BrowserUI = {
this.setURI();
this._titleChanged(browser.contentDocument);
this._favicon.src = browser.mIconURL || kDefaultFavIconURL;
this.updateIcon(browser);
let toolbar = document.getElementById("toolbar-main");
if (Browser.content.currentTab.chromeTop) {
// content box was panned, so let's reset it
this._setContentPosition("top", Browser.content.currentTab.chromeTop);
this._setContentPosition("left", 0);
toolbar.top = this._contentTop - toolbar.boxObject.height;
}
else {
// Must be initial conditions
toolbar.top = 0;
this._setContentPosition("top", toolbar.boxObject.height);
this._setContentPosition("left", 0);
}
this.updateIcon();
this.show(UIMODE_NONE);
},
@ -154,8 +126,7 @@ var BrowserUI = {
var faviconURI = ios.newURI(aURI, null, null);
var fis = Cc["@mozilla.org/browser/favicon-service;1"].getService(Ci.nsIFaviconService);
if (faviconURI.schemeIs("javascript") ||
fis.isFailedFavicon(faviconURI))
if (faviconURI.schemeIs("javascript") || fis.isFailedFavicon(faviconURI))
faviconURI = ios.newURI(kDefaultFavIconURL, null, null);
var browser = getBrowser();
@ -209,166 +180,13 @@ var BrowserUI = {
return items;
},
_dragData : {
dragging : false,
startX : 0,
startY : 0,
dragX : 0,
dragY : 0,
lastX : 0,
lastY : 0,
sTop : 0,
sLeft : 0,
uiMode : UIMODE_NONE
},
_scrollToolbar : function bui_scrollToolbar(aEvent) {
var [scrollWidth, ] = Browser.content._contentAreaDimensions;
var [viewportW, ] = Browser.content._effectiveViewportDimensions;
var pannedUI = false;
if (this._dragData.dragging && Browser.content.scrollY == 0) {
let toolbar = document.getElementById("toolbar-main");
let dy = this._dragData.lastY - aEvent.screenY;
this._dragData.dragY += dy;
// NOTE: We should only be scrolling the toolbar if the sidebars are not
// visible (gContentBox.style.marginLeft == "0px")
let sidebarVisible = gContentBox.style.marginLeft != "0px";
let newTop = null;
if (dy > 0 && (toolbar.top > -toolbar.boxObject.height && !sidebarVisible)) {
// Scroll the toolbar up unless it is already scrolled up
newTop = this._dragData.sTop - dy;
// Clip the adjustment to just enough to hide the toolbar
if (newTop < -toolbar.boxObject.height)
newTop = -toolbar.boxObject.height;
// Reset the browser start point
Browser.content.dragData.sX = aEvent.screenX;
Browser.content.dragData.sY = aEvent.screenY;
}
else if (dy < 0 && (toolbar.top < 0 && !sidebarVisible)) {
// Scroll the toolbar down unless it is already down
newTop = this._dragData.sTop - dy;
// Clip the adjustment to just enough to fully show the toolbar
if (newTop > 0)
newTop = 0;
}
// Update the toolbar and browser tops. Stop the mousemove from
// getting to the deckbrowser.
if (newTop != null) {
toolbar.top = newTop;
this._setContentPosition("top", newTop + toolbar.boxObject.height);
// Cache the current top so we can use it when switching tabs
Browser.content.currentTab.chromeTop = this._contentTop;
pannedUI = true;
}
}
if (this._dragData.dragging && (Browser.content.scrollX == 0 || (Browser.content.scrollX + viewportW) == scrollWidth)) {
let tabbar = document.getElementById("tab-list-container");
let sidebar = document.getElementById("browser-controls");
let panelUI = document.getElementById("panel-container");
let toolbar = document.getElementById("toolbar-main");
let dx = this._dragData.lastX - aEvent.screenX;
this._dragData.dragX += dx;
if (Math.abs(this._dragData.screenX - aEvent.screenX) > 30) {
let newLeft = this._dragData.sLeft - dx;
let oldLeft = tabbar.left;
let tabbarW = tabbar.boxObject.width;
let sidebarW = sidebar.boxObject.width;
let contentW = gContentBox.boxObject.width;
// Limit the panning
if (newLeft > 0)
newLeft = 0;
else if (newLeft < -(tabbarW + sidebarW))
newLeft = -(tabbarW + sidebarW);
// Set the UI mode based on where we ended up
var noneMode = (gContentBox.style.marginTop == "0px" ? UIMODE_NONE : UIMODE_URLVIEW);
if (newLeft > -(tabbarW - tabbarW / 3) && newLeft <= 0) {
if (this._dragData.uiMode == UIMODE_CONTROLS) {
this.mode = noneMode;
return;
}
this.mode = UIMODE_TABS;
}
else if (newLeft >= -(tabbarW + sidebarW) && newLeft < -(tabbarW + sidebarW / 3)) {
if (this._dragData.uiMode == UIMODE_TABS) {
this.mode = noneMode;
return;
}
this.mode = UIMODE_CONTROLS;
}
else
this.mode = noneMode;
tabbar.left = newLeft;
// Never let the toolbar pan off the screen
let newToolbarLeft = newLeft;
if (newToolbarLeft < 0)
newToolbarLeft = 0;
toolbar.left = newToolbarLeft;
// Make the toolbar appear/disappear depending on the state of the sidebars
if (newLeft + tabbarW != 0)
toolbar.top = 0;
else
toolbar.top = this._contentTop - toolbar.boxObject.height;
this._setContentPosition("left", newLeft + tabbarW);
sidebar.left = newLeft + tabbarW + contentW;
panelUI.left = newLeft + tabbarW + contentW + sidebarW;
pannedUI = true;
}
}
if (pannedUI) {
aEvent.stopPropagation();
// Force a sync redraw
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.processUpdates();
}
else {
// Reset our start point while the browser is doing its panning
this._dragData.lastX = aEvent.screenX;
this._dragData.lastY = aEvent.screenY;
}
},
_showToolbar : function(aShow) {
var toolbar = document.getElementById("toolbar-main");
if (aShow) {
// Always show the toolbar, either by floating or panning
if (toolbar.top == -toolbar.boxObject.height) {
// Float the toolbar over content
toolbar.top = 0;
}
else if (toolbar.top < 0) {
// Partially showing, so show it completely
toolbar.top = 0;
this._setContentPosition("top", toolbar.boxObject.height);
}
ws.freeze("toolbar-main");
ws.moveFrozenTo("toolbar-main", 0, 0);
}
else {
// If we are floating the toolbar, then hide it again
if (gContentBox.style.marginTop == "0px") {
toolbar.top = -toolbar.boxObject.height;
}
ws.unfreeze("toolbar-main");
}
},
@ -390,31 +208,37 @@ var BrowserUI = {
},
_showPanel : function(aMode) {
let tabbar = document.getElementById("tab-list-container");
let sidebar = document.getElementById("browser-controls");
let panelUI = document.getElementById("panel-container");
let toolbar = document.getElementById("toolbar-main");
let tabbar = document.getElementById("tabs-container");
let sidebar = document.getElementById("browser-controls");
let panelUI = document.getElementById("panel-container");
let toolbar = document.getElementById("toolbar-main");
let canvas = document.getElementById("canvas");
let tabbarW = tabbar.boxObject.width;
let sidebarW = sidebar.boxObject.width;
let contentW = gContentBox.boxObject.width;
let tabbarW = tabbar.boxObject.width;
let sidebarW = sidebar.boxObject.width;
let contentW = canvas.width;
let newLeft = -tabbarW;
switch (aMode) {
case UIMODE_NONE:
Shortcuts.deinit();
break;
case UIMODE_PANEL:
newLeft = -contentW;
this._initPanel();
break;
case UIMODE_CONTROLS:
newLeft = -(tabbarW + sidebarW);
break;
case UIMODE_TABS:
newLeft = 0;
break;
}
let newLeft = -tabbarW;
switch (aMode) {
case UIMODE_NONE:
Shortcuts.deinit();
break;
case UIMODE_PANEL:
newLeft = -contentW;
this._initPanel();
break;
case UIMODE_CONTROLS:
newLeft = -(tabbarW + sidebarW);
break;
case UIMODE_TABS:
newLeft = 0;
break;
}
// XXX some form of this code should be in Browser.panHandler so the UIMODE is
// set correctly when panning.
// OR maybe we should try to removing as much of UIMODE as possible
/*
tabbar.left = newLeft;
let newToolbarLeft = newLeft;
@ -428,6 +252,7 @@ var BrowserUI = {
sidebar.left = newLeft + tabbarW + contentW;
panelUI.left = newLeft + tabbarW + contentW + sidebarW;
panelUI.width = contentW;
*/
},
_initPanel : function() {
@ -451,21 +276,25 @@ var BrowserUI = {
var rect = document.getElementById("browser-container").getBoundingClientRect();
var containerW = rect.right - rect.left;
var containerH = rect.bottom - rect.top;
var toolbar = document.getElementById("toolbar-main");
var toolbarH = toolbar.boxObject.height;
var popup = document.getElementById("popup_autocomplete");
popup.height = containerH - toolbarH;
// XXX need to handle make some of these work again
/*
var sidebar = document.getElementById("browser-controls");
var panelUI = document.getElementById("panel-container");
var tabbar = document.getElementById("tab-list-container");
var tabbar = document.getElementById("tabs-container");
tabbar.left = -tabbar.boxObject.width;
panelUI.left = containerW + sidebar.boxObject.width;
sidebar.left = containerW;
sidebar.height = tabbar.height = (panelUI.height = containerH) - toolbarH;
panelUI.width = containerW - sidebar.boxObject.width - tabbar.boxObject.width;
var popup = document.getElementById("popup_autocomplete");
toolbar.width = containerW;
popup.height = containerH - toolbarH;
*/
},
init : function() {
@ -480,58 +309,58 @@ var BrowserUI = {
this._favicon.addEventListener("error", this, false);
this._autocompleteNavbuttons = document.getElementById("autocomplete_navbuttons");
Browser.content.addEventListener("DOMTitleChanged", this, true);
Browser.content.addEventListener("DOMLinkAdded", this, true);
// XXX these really want to listen whatever is the current browser, not any browser
let browsers = document.getElementById("browsers");
browsers.addEventListener("DOMTitleChanged", this, true);
browsers.addEventListener("DOMLinkAdded", this, true);
document.getElementById("tab-list").addEventListener("TabSelect", this, true);
Browser.content.addEventListener("mousedown", this, true);
Browser.content.addEventListener("mouseup", this, true);
Browser.content.addEventListener("mousemove", this, true);
document.getElementById("tabs").addEventListener("TabSelect", this, true);
window.addEventListener("resize", this, false);
Shortcuts.restore();
},
update : function(aState, aBrowser) {
if (aState == TOOLBARSTATE_INDETERMINATE) {
this._faviconLink = null;
aState = TOOLBARSTATE_LOADED;
this.setURI();
}
update : function(aState) {
var toolbar = document.getElementById("toolbar-main");
if (aState == TOOLBARSTATE_LOADING) {
this.show(UIMODE_URLVIEW);
Browser.content.setLoading(aBrowser, true);
toolbar.top = 0;
toolbar.setAttribute("mode", "loading");
this._favicon.src = "";
this._faviconLink = null;
this.updateIcon(aBrowser);
}
else if (aState == TOOLBARSTATE_LOADED) {
this._setContentPosition("top", toolbar.boxObject.height);
Browser.content.setLoading(aBrowser, false);
switch (aState) {
case TOOLBARSTATE_INDETERMINATE:
this._faviconAdded = false;
aState = TOOLBARSTATE_LOADED;
this.setURI();
toolbar.setAttribute("mode", "view");
case TOOLBARSTATE_LOADED:
toolbar.setAttribute("mode", "view");
if (!this._faviconLink) {
this._faviconLink = aBrowser.currentURI.prePath + "/favicon.ico";
}
this._setIcon(this._faviconLink);
this.updateIcon(aBrowser);
if (!this._faviconLink) {
this._faviconLink = Browser.currentBrowser.currentURI.prePath + "/favicon.ico";
}
this._setIcon(this._faviconLink);
this.updateIcon();
break;
case TOOLBARSTATE_LOADING:
toolbar.setAttribute("mode", "loading");
this.show(UIMODE_URLVIEW);
ws.panTo(0,0, true);
this._favicon.src = "";
this._faviconLink = null;
this.updateIcon();
break;
}
},
updateIcon : function(browser) {
if (Browser.content.isLoading(browser)) {
document.getElementById("urlbar-image-deck").selectedIndex = 0;
updateIcon : function() {
if (Browser.currentTab.isLoading()) {
this._throbber.hidden = false;
this._throbber.setAttribute("loading", "true");
this._favicon.hidden = true;
}
else {
document.getElementById("urlbar-image-deck").selectedIndex = 1;
this._favicon.hidden = false;
this._throbber.hidden = true;
this._throbber.removeAttribute("loading");
}
},
@ -553,7 +382,7 @@ var BrowserUI = {
setURI : function() {
var browser = Browser.currentBrowser;
// FIXME: deckbrowser should not fire TebSelect on the initial tab (bug 454028)
// FIXME: deckbrowser should not fire TabSelect on the initial tab (bug 454028)
if (!browser.currentURI)
return;
@ -600,15 +429,13 @@ var BrowserUI = {
this.show(UIMODE_URLVIEW);
},
updateAutoComplete : function(showDefault)
{
updateAutoComplete : function(showDefault) {
this.updateSearchEngines();
if (showDefault || this._edit.getAttribute("nomatch"))
this._edit.showHistoryPopup();
},
doButtonSearch : function(button)
{
doButtonSearch : function(button) {
if (!("engine" in button) || !button.engine)
return;
@ -625,17 +452,10 @@ var BrowserUI = {
if (this.engines)
return;
// XXXndeakin remove the try-catch once the search service is properly built
try {
var searchService = Cc["@mozilla.org/browser/search-service;1"].
getService(Ci.nsIBrowserSearchService);
} catch (ex) {
this.engines = [ ];
return;
}
var searchService = Cc["@mozilla.org/browser/search-service;1"].getService(Ci.nsIBrowserSearchService);
var engines = searchService.getVisibleEngines({ });
this.engines = engines;
const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var container = this._autocompleteNavbuttons;
for (var e = 0; e < kMaxEngines && e < engines.length; e++) {
@ -666,8 +486,7 @@ var BrowserUI = {
var urllist = document.getElementById("urllist-container");
var container = document.getElementById("browser-container");
if (aMode == UIMODE_URLVIEW)
{
if (aMode == UIMODE_URLVIEW) {
this._showToolbar(true);
this._editToolbar(false);
@ -783,17 +602,18 @@ var BrowserUI = {
},
newTab : function() {
Browser.content.newTab(true);
ws.panTo(0,0, true);
Browser.newTab(true);
this.show(UIMODE_URLEDIT);
},
closeTab : function(aTab) {
Browser.content.removeTab(aTab);
Browser.closeTab(aTab);
this.show(UIMODE_NONE);
},
selectTab : function(aTab) {
Browser.content.selectTab(aTab);
Browser.selectTab(aTab);
this.show(UIMODE_NONE);
},
@ -826,26 +646,6 @@ var BrowserUI = {
case "error":
this._favicon.src = "chrome://browser/skin/images/default-favicon.png";
break;
// UI panning events
case "mousedown":
this._dragData.dragging = true;
this._dragData.dragX = 0;
this._dragData.dragY = 0;
this._dragData.screenX = this._dragData.lastX = aEvent.screenX;
this._dragData.screenY = this._dragData.lastY = aEvent.screenY;
this._dragData.sTop = document.getElementById("toolbar-main").top;
this._dragData.sLeft = document.getElementById("tab-list-container").left;
this._dragData.uiMode = this.mode;
break;
case "mouseup":
this._dragData.dragging = false;
this._dragData.uiMode = UIMODE_NONE;
// Cause the UI to snap, if needed
this._showPanel(this.mode);
break;
case "mousemove":
this._scrollToolbar(aEvent);
break;
// Window size events
case "resize":
this._sizeControls(aEvent);
@ -943,7 +743,7 @@ var BrowserUI = {
this.newTab();
break;
case "cmd_closeTab":
Browser.content.removeTab(Browser.content.browser);
this.closeTab();
break;
case "cmd_sanitize":
Sanitizer.sanitize();

View File

@ -1,17 +1,13 @@
deckbrowser {
-moz-binding: url("chrome://browser/content/deckbrowser.xml#deckbrowser");
}
#urlbar-edit {
-moz-binding: url("chrome://browser/content/urlbar.xml#autocomplete-aligned");
}
#tab-list {
-moz-binding: url("chrome://browser/content/deckbrowser.xml#tablist");
#tabs {
-moz-binding: url("chrome://browser/content/tabs.xml#tablist");
}
richlistitem[type="documenttab"] {
-moz-binding: url("chrome://browser/content/deckbrowser.xml#documenttab");
-moz-binding: url("chrome://browser/content/tabs.xml#documenttab");
}
#prefs-container > scrollbox

View File

@ -25,6 +25,7 @@
* Mark Finkle <mfinkle@mozilla.com>
* Aleks Totic <a@totic.org>
* Johnathan Nightingale <johnath@mozilla.com>
* Stuart Parmenter <stuart@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
@ -60,13 +61,71 @@ __defineGetter__("gPrefService", function () {
});
function getBrowser() {
return Browser.content.browser;
return Browser.currentBrowser;
}
var ws = null;
var ih = null;
var Browser = {
_content : null,
_tabs : [],
_currentTab : null,
startup: function() {
var self = this;
// initalize the CanvasBrowser
this._content = new CanvasBrowser(document.getElementById("canvas"));
// initialize the WidgetStack
ws = new WidgetStack(document.getElementById("browser-container"));
ws.setViewportBounds({ top: 0, left: 0, right: 800, bottom: 480 });
// XXX this should live elsewhere
window.gSidebarVisible = false;
function panHandler(vr) {
var visibleNow = ws.isWidgetVisible("browser-controls") || ws.isWidgetVisible("tabs-container");
// XXX add code here to snap side panels fully out if they start to appear,
// or snap them back if they only showed up for a little bit
if (visibleNow && !gSidebarVisible) {
ws.freeze("toolbar-main");
ws.moveFrozenTo("toolbar-main", 0, 0);
}
else if (!visibleNow && gSidebarVisible) {
ws.unfreeze("toolbar-main");
}
gSidebarVisible = visibleNow;
// deal with checkerboard
/*
let stack = document.getElementById("browser-container");
stack.style.backgroundPosition = -vr.left + "px " + -vr.top + "px";
*/
// this is really only necessary for maemo, where we don't
// always repaint fast enough
window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).processUpdates();
}
ws.setPanHandler(panHandler);
function resizeHandler() { ws.updateSize(); }
window.addEventListener("resize", resizeHandler, false);
setTimeout(resizeHandler, 0);
function viewportHandler(b, ob) { self._content.viewportHandler(b, ob); }
ws.setViewportHandler(viewportHandler);
// initialize input handling
ih = new InputHandler();
// Create the first tab
this.newTab(true);
startup : function() {
window.controllers.appendController(this);
window.controllers.appendController(BrowserUI);
@ -86,25 +145,24 @@ var Browser = {
var styleURI = ios.newURI("chrome://browser/content/scrollbars.css", null, null);
styleSheets.loadAndRegisterSheet(styleURI, styleSheets.AGENT_SHEET);
this._content = document.getElementById("content");
this._content.progressListenerCreator = function (content, browser) {
return new ProgressController(content, browser);
};
var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
os.addObserver(gXPInstallObserver, "xpinstall-install-blocked", false);
os.addObserver(gXPInstallObserver, "xpinstall-download-started", false);
// XXX hook up memory-pressure notification to clear out tab browsers
//os.addObserver(function(subject, topic, data) self.destroyEarliestBrowser(), "memory-pressure", false);
BrowserUI.init();
window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();
this._content.addEventListener("command", this._handleContentCommand, false);
this._content.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver.onUpdatePageReport, false);
this._content.tabList = document.getElementById("tab-list");
this._content.newTab(true);
let browsers = document.getElementById("browsers");
browsers.addEventListener("command", this._handleContentCommand, false);
browsers.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver.onUpdatePageReport, false);
var deckbrowser = this.content;
/* Initialize Spatial Navigation */
/*
var deckbrowser = content;
function panCallback(aElement) {
// SpatialNav calls commandDispatcher.advanceFocus/rewindFocus, which
// can mess the scroll state up. Reset it.
@ -115,12 +173,18 @@ var Browser = {
deckbrowser.ensureElementIsVisible(aElement);
}
SpatialNavigation.init(this.content, panCallback);
SpatialNavigation.init(content, panCallback);
*/
/* Initialize Geolocation */
this.setupGeolocationPrompt();
/* Login Manager */
Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
/* Command line arguments/initial homepage */
// If this is an intial window launch (was a nsICommandLine passed via window params)
// we execute some logic to load the initial launch page
if (window.arguments && window.arguments[0]) {
@ -171,13 +235,26 @@ var Browser = {
gPrefService.setBoolPref("temporary.disablePlugins", false);
},
updateViewportSize: function() {
// XXX make sure this is right, and then add a better function for it.
var [w,h] = this._content._contentAreaDimensions;
w = Math.ceil(this._content._pageToScreen(w));
h = Math.ceil(this._content._pageToScreen(h));
if (!this._currentViewportBounds || w != this._currentViewportBounds.width || h != this._currentViewportBounds.height) {
this._currentViewportBounds = {width: w, height: h};
let bounds = { top: 0, left: 0, right: Math.max(800, w), bottom: Math.max(480, h) }
//dump("setViewportBounds: " + bounds.toSource() + "\n");
ws.setViewportBounds(bounds);
}
},
setPluginState: function(enabled)
{
var phs = Components.classes["@mozilla.org/plugin/host;1"]
.getService(Components.interfaces.nsIPluginHost);
var phs = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
var plugins = phs.getPluginTags({ });
for (var i = 0; i < plugins.length; ++i)
plugins[i].disabled = !enabled;
for (var i = 0; i < plugins.length; ++i)
plugins[i].disabled = !enabled;
},
setupGeolocationPrompt: function() {
@ -234,14 +311,94 @@ var Browser = {
* have more than one
*/
get currentBrowser() {
return this._content.browser;
return this._currentTab.browser;
},
supportsCommand : function(cmd) {
get currentTab() {
return this._currentTab;
},
getTabAtIndex: function(index) {
if (index > this._tabs.length || index < 0)
return null;
return this._tabs[index];
},
getTabFromContent: function(content) {
for (var t = 0; t < this._tabs.length; t++) {
if (this._tabs[t].content == content)
return this._tabs[t];
}
return null;
},
newTab: function(bringFront) {
let newTab = new Tab();
newTab.create();
this._tabs.push(newTab);
let event = document.createEvent("Events");
event.initEvent("TabOpen", true, false);
newTab.content.dispatchEvent(event);
if (bringFront)
this.selectTab(newTab);
return newTab;
},
closeTab: function(tab) {
if (tab instanceof XULElement)
tab = this.getTabFromContent(tab);
if (!tab)
return;
let tabIndex = this._tabs.indexOf(tab);
let nextTab = this._currentTab;
if (this._currentTab == tab) {
nextTab = this.getTabAtIndex(tabIndex + 1) || this.getTabAtIndex(tabIndex - 1);
if (!nextTab)
return;
}
let event = document.createEvent("Events");
event.initEvent("TabClose", true, false);
tab.content.dispatchEvent(event);
tab.destroy();
this._tabs.splice(tabIndex, 1);
// redraw the tabs
for (let t = tabIndex; t < this._tabs.length; t++)
this._tabs[t].updateThumbnail();
this.selectTab(nextTab);
},
selectTab: function(tab) {
if (tab instanceof XULElement)
tab = this.getTabFromContent(tab);
if (!tab || this._currentTab == tab)
return;
this._currentTab = tab;
this._content.setCurrentBrowser(this.currentBrowser);
document.getElementById("tabs").selectedItem = tab.content;
ws.panTo(0,0, true);
let event = document.createEvent("Events");
event.initEvent("TabSelect", true, false);
tab.content.dispatchEvent(event);
},
supportsCommand: function(cmd) {
var isSupported = false;
switch (cmd) {
case "cmd_fullscreen":
case "cmd_downloads":
isSupported = true;
break;
default:
@ -251,12 +408,12 @@ var Browser = {
return isSupported;
},
isCommandEnabled : function(cmd) {
isCommandEnabled: function(cmd) {
return true;
},
doCommand : function(cmd) {
var browser = this.content.browser;
doCommand: function(cmd) {
var browser = this.currentBrowser;
switch (cmd) {
case "cmd_fullscreen":
@ -265,7 +422,7 @@ var Browser = {
}
},
getNotificationBox : function() {
getNotificationBox: function() {
return document.getElementById("notifications");
},
@ -276,7 +433,7 @@ var Browser = {
var findbar = document.getElementById("findbar");
var browser = findbar.browser;
if (!browser) {
browser = this.content.browser;
browser = this.currentBrowser;
findbar.browser = browser;
}
@ -378,174 +535,26 @@ var Browser = {
}
};
function ProgressController(aTabBrowser, aBrowser) {
this._tabbrowser = aTabBrowser;
this.init(aBrowser);
}
ProgressController.prototype = {
_browser : null,
init : function(aBrowser) {
this._browser = aBrowser;
},
onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus) {
// we currently only care about state changes for the main document,
// not sub-frames
if (aWebProgress.DOMWindow != this._browser.contentWindow) {
return;
}
if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
if (aStateFlags & Ci.nsIWebProgressListener.STATE_START)
BrowserUI.update(TOOLBARSTATE_LOADING, this._browser);
else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP)
BrowserUI.update(TOOLBARSTATE_LOADED, this._browser);
}
if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT) {
if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
aWebProgress.DOMWindow.focus();
// update the viewport
this._tabbrowser.updateCanvasState();
// update the tab canvas image
this._tabbrowser.updateBrowser(this._browser, true);
// linkify phone numbers
Browser.translatePhoneNumbers();
//aWebProgress.DOMWindow.scrollbars.visible = false;
}
}
},
// This method is called to indicate progress changes for the currently
// loading page.
onProgressChange : function(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) {
},
// This method is called to indicate a change to the current location.
onLocationChange : function(aWebProgress, aRequest, aLocationURI) {
var location = aLocationURI ? aLocationURI.spec : "";
this._hostChanged = true;
// This code here does not compare uris exactly when determining
// whether or not the message(s) should be hidden since the message
// may be prematurely hidden when an install is invoked by a click
// on a link that looks like this:
//
// <a href="#" onclick="return install();">Install Foo</a>
//
// - which fires a onLocationChange message to uri + '#'...
cBrowser = Browser.currentBrowser;
if (cBrowser.lastURI) {
var oldSpec = cBrowser.lastURI.spec;
var oldIndexOfHash = oldSpec.indexOf("#");
if (oldIndexOfHash != -1)
oldSpec = oldSpec.substr(0, oldIndexOfHash);
var newSpec = location;
var newIndexOfHash = newSpec.indexOf("#");
if (newIndexOfHash != -1)
newSpec = newSpec.substr(0, newSpec.indexOf("#"));
if (newSpec != oldSpec) {
// Remove all the notifications, except for those which want to
// persist across the first location change.
var nBox = Browser.getNotificationBox();
nBox.removeTransientNotifications();
}
}
cBrowser.lastURI = aLocationURI;
if (aWebProgress.DOMWindow == this._browser.contentWindow) {
BrowserUI.setURI();
this._tabbrowser.updateBrowser(this._browser, false);
}
},
// This method is called to indicate a status changes for the currently
// loading page. The message is already formatted for display.
onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage) {
},
// Properties used to cache security state used to update the UI
_state: null,
_host: undefined,
_hostChanged: false, // onLocationChange will flip this bit
// This method is called when the security state of the browser changes.
onSecurityChange : function(aWebProgress, aRequest, aState) {
// Don't need to do anything if the data we use to update the UI hasn't
// changed
if (this._state == aState &&
!this._hostChanged) {
return;
}
this._state = aState;
try {
this._host = getBrowser().contentWindow.location.host;
} catch(ex) {
this._host = null;
}
this._hostChanged = false;
// Don't pass in the actual location object, since it can cause us to
// hold on to the window object too long. Just pass in the fields we
// care about. (bug 424829)
var location = getBrowser().contentWindow.location;
var locationObj = {};
try {
locationObj.host = location.host;
locationObj.hostname = location.hostname;
locationObj.port = location.port;
} catch (ex) {
// Can sometimes throw if the URL being visited has no host/hostname,
// e.g. about:blank. The _state for these pages means we won't need these
// properties anyways, though.
}
getIdentityHandler().checkIdentity(this._state, locationObj);
},
QueryInterface : function(aIID) {
if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
};
function nsBrowserAccess()
{
}
nsBrowserAccess.prototype =
{
QueryInterface : function(aIID)
{
if (aIID.equals(Ci.nsIBrowserDOMWindow) ||
aIID.equals(Ci.nsISupports))
QueryInterface: function(aIID) {
if (aIID.equals(Ci.nsIBrowserDOMWindow) || aIID.equals(Ci.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
},
openURI : function(aURI, aOpener, aWhere, aContext)
{
openURI: function(aURI, aOpener, aWhere, aContext) {
var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
if (isExternal && aURI && aURI.schemeIs("chrome")) {
dump("use -chrome command-line option to load external chrome urls\n");
return null;
}
var loadflags = isExternal ?
Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
@ -567,17 +576,10 @@ nsBrowserAccess.prototype =
"all,dialog=no", url, null, null, null);
}
else {
if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB) {
var tab = Browser._content.newTab(true);
if (tab) {
var content = Browser._content;
var browser = content.getBrowserForDisplay(content.getDisplayForTab(tab));
newWindow = browser.contentWindow;
}
}
else {
if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB)
newWindow = Browser.newTab(true).browser.contentWindow;
else
newWindow = aOpener ? aOpener.top : browser.contentWindow;
}
}
try {
@ -585,9 +587,8 @@ nsBrowserAccess.prototype =
if (aURI) {
if (aOpener) {
location = aOpener.location;
referrer = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService)
.newURI(location, null, null);
referrer = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService)
.newURI(location, null, null);
}
newWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
@ -599,8 +600,7 @@ nsBrowserAccess.prototype =
return newWindow;
},
isTabContentWindow : function(aWindow)
{
isTabContentWindow: function(aWindow) {
return Browser._content.browsers.some(function (browser) browser.contentWindow == aWindow);
}
}
@ -638,7 +638,7 @@ IdentityHandler.prototype = {
/**
* Build out a cache of the elements that we need frequently.
*/
_cacheElements : function() {
_cacheElements: function() {
this._identityPopup = document.getElementById("identity-popup");
this._identityBox = document.getElementById("identity-box");
this._identityPopupContentBox = document.getElementById("identity-popup-content-box");
@ -653,7 +653,7 @@ IdentityHandler.prototype = {
* Handler for mouseclicks on the "More Information" button in the
* "identity-popup" panel.
*/
handleMoreInfoClick : function(event) {
handleMoreInfoClick: function(event) {
displaySecurityInfo();
event.stopPropagation();
},
@ -662,7 +662,7 @@ IdentityHandler.prototype = {
* Helper to parse out the important parts of _lastStatus (of the SSL cert in
* particular) for use in constructing identity UI strings
*/
getIdentityData : function() {
getIdentityData: function() {
var result = {};
var status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus);
var cert = status.serverCert;
@ -700,7 +700,7 @@ IdentityHandler.prototype = {
* @param JS Object location that mirrors an nsLocation (i.e. has .host and
* .hostname and .port)
*/
checkIdentity : function(state, location) {
checkIdentity: function(state, location) {
var currentStatus = getBrowser().securityUI
.QueryInterface(Components.interfaces.nsISSLStatusProvider)
.SSLStatus;
@ -718,7 +718,7 @@ IdentityHandler.prototype = {
/**
* Return the eTLD+1 version of the current hostname
*/
getEffectiveHost : function() {
getEffectiveHost: function() {
// Cache the eTLDService if this is our first time through
if (!this._eTLDService)
this._eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"]
@ -736,7 +736,7 @@ IdentityHandler.prototype = {
* Update the UI to reflect the specified mode, which should be one of the
* IDENTITY_MODE_* constants.
*/
setMode : function(newMode) {
setMode: function(newMode) {
if (!this._identityBox) {
// No identity box means the identity box is not visible, in which
// case there's nothing to do.
@ -757,7 +757,7 @@ IdentityHandler.prototype = {
*
* @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
*/
setIdentityMessages : function(newMode) {
setIdentityMessages: function(newMode) {
if (newMode == this.IDENTITY_MODE_DOMAIN_VERIFIED) {
var iData = this.getIdentityData();
@ -807,7 +807,7 @@ IdentityHandler.prototype = {
*
* @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
*/
setPopupMessages : function(newMode) {
setPopupMessages: function(newMode) {
this._identityPopup.className = newMode;
this._identityPopupContentBox.className = newMode;
@ -857,14 +857,14 @@ IdentityHandler.prototype = {
this._identityPopupContentVerif.textContent = verifier;
},
hideIdentityPopup : function() {
hideIdentityPopup: function() {
this._identityPopup.hidePopup();
},
/**
* Click handler for the identity-box element in primary chrome.
*/
handleIdentityButtonEvent : function(event) {
handleIdentityButtonEvent: function(event) {
event.stopPropagation();
@ -1047,3 +1047,292 @@ const gXPInstallObserver = {
function getNotificationBox(aWindow) {
return Browser.getNotificationBox();
}
function ProgressController(tab) {
this._tab = tab;
}
ProgressController.prototype = {
get browser() {
return this._tab.browser;
},
onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
// ignore notification that aren't about the main document (iframes, etc)
if (aWebProgress.DOMWindow != this._tab.browser.contentWindow)
return;
if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
if (aStateFlags & Ci.nsIWebProgressListener.STATE_START)
this._networkStart();
else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP)
this._networkStop();
} else if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT) {
if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
this._documentStop();
}
}
},
// This method is called to indicate progress changes for the currently
// loading page.
onProgressChange: function(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) {
},
// This method is called to indicate a change to the current location.
onLocationChange: function(aWebProgress, aRequest, aLocationURI) {
// XXX this code is not multiple-tab friendly.
var location = aLocationURI ? aLocationURI.spec : "";
this._hostChanged = true;
// This code here does not compare uris exactly when determining
// whether or not the message(s) should be hidden since the message
// may be prematurely hidden when an install is invoked by a click
// on a link that looks like this:
//
// <a href="#" onclick="return install();">Install Foo</a>
//
// - which fires a onLocationChange message to uri + '#'...
currentBrowser = Browser.currentBrowser;
if (currentBrowser.lastURI) {
var oldSpec = currentBrowser.lastURI.spec;
var oldIndexOfHash = oldSpec.indexOf("#");
if (oldIndexOfHash != -1)
oldSpec = oldSpec.substr(0, oldIndexOfHash);
var newSpec = location;
var newIndexOfHash = newSpec.indexOf("#");
if (newIndexOfHash != -1)
newSpec = newSpec.substr(0, newSpec.indexOf("#"));
if (newSpec != oldSpec) {
// Remove all the notifications, except for those which want to
// persist across the first location change.
// XXX
// var nBox = Browser.getNotificationBox();
// nBox.removeTransientNotifications();
}
}
currentBrowser.lastURI = aLocationURI;
if (aWebProgress.DOMWindow == Browser.currentBrowser.contentWindow) {
BrowserUI.setURI();
}
},
// This method is called to indicate a status changes for the currently
// loading page. The message is already formatted for display.
onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) {
},
_networkStart: function() {
this._tab.setLoading(true);
//dump("started loading network\n");
if (Browser.currentBrowser == this.browser) {
Browser.content.startLoading();
BrowserUI.update(TOOLBARSTATE_LOADING);
}
},
_networkStop: function() {
this._tab.setLoading(false);
if (Browser.currentBrowser == this.browser) {
BrowserUI.update(TOOLBARSTATE_LOADED);
}
},
_documentStop: function() {
//dump("stop, hammer time\n");
// translate any phone numbers
Browser.translatePhoneNumbers();
if (Browser.currentBrowser == this.browser) {
// focus the dom window
this.browser.contentWindow.focus();
Browser.content.endLoading();
}
this._tab.updateThumbnail();
},
// Properties used to cache security state used to update the UI
_state: null,
_host: undefined,
_hostChanged: false, // onLocationChange will flip this bit
// This method is called when the security state of the browser changes.
onSecurityChange: function(aWebProgress, aRequest, aState) {
// Don't need to do anything if the data we use to update the UI hasn't
// changed
if (this._state == aState &&
!this._hostChanged) {
return;
}
this._state = aState;
try {
this._host = getBrowser().contentWindow.location.host;
}
catch(ex) {
this._host = null;
}
this._hostChanged = false;
// Don't pass in the actual location object, since it can cause us to
// hold on to the window object too long. Just pass in the fields we
// care about. (bug 424829)
var location = getBrowser().contentWindow.location;
var locationObj = {};
try {
locationObj.host = location.host;
locationObj.hostname = location.hostname;
locationObj.port = location.port;
}
catch (ex) {
// Can sometimes throw if the URL being visited has no host/hostname,
// e.g. about:blank. The _state for these pages means we won't need these
// properties anyways, though.
}
getIdentityHandler().checkIdentity(this._state, locationObj);
},
QueryInterface: function(aIID) {
if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
};
function Tab() {
}
Tab.prototype = {
_id: null,
_browser: null,
_state: null,
_listener: null,
_loading: false,
_content: null,
get browser() {
return this._browser;
},
get content() {
return this._content;
},
isLoading: function() {
return this._loading;
},
setLoading: function(b) {
this._loading = b;
},
create: function() {
this._content = document.createElement("richlistitem");
this._content.setAttribute("type", "documenttab");
document.getElementById("tabs").addTab(this._content);
this._createBrowser();
},
destroy: function() {
this._destroyBrowser();
document.getElementById("tabs").removeTab(this._content);
},
_createBrowser: function() {
if (this._browser)
throw "Browser already exists";
let browser = this._browser = document.createElement("browser");
browser.className = "deckbrowser-browser";
browser.setAttribute("style", "overflow: hidden; visibility: hidden; width: 1024px; height: 800px;");
browser.setAttribute("contextmenu", document.getElementById("canvas").getAttribute("contextmenu"));
browser.setAttribute("autocompletepopup", document.getElementById("canvas").getAttribute("autocompletepopup"));
browser.setAttribute("type", "content");
document.getElementById("browsers").appendChild(browser);
this._listener = new ProgressController(this);
browser.addProgressListener(this._listener);
},
_destroyBrowser: function() {
document.getElementById("browsers").removeChild(this._browser);
},
saveState: function() {
let state = { };
this._url = browser.contentWindow.location.toString();
var browser = this.getBrowserForDisplay(display);
var doc = browser.contentDocument;
if (doc instanceof HTMLDocument) {
var tags = ["input", "textarea", "select"];
for (var t = 0; t < tags.length; t++) {
var elements = doc.getElementsByTagName(tags[t]);
for (var e = 0; e < elements.length; e++) {
var element = elements[e];
var id;
if (element.id)
id = "#" + element.id;
else if (element.name)
id = "$" + element.name;
if (id)
state[id] = element.value;
}
}
}
state._scrollX = browser.contentWindow.scrollX;
state._scrollY = browser.contentWindow.scrollY;
this._state = state;
},
restoreState: function() {
let state = this._state;
if (!state)
return;
let doc = this._browser.contentDocument;
for (item in state) {
var elem = null;
if (item.charAt(0) == "#") {
elem = doc.getElementById(item.substring(1));
}
else if (item.charAt(0) == "$") {
var list = doc.getElementsByName(item.substring(1));
if (list.length)
elem = list[0];
}
if (elem)
elem.value = state[item];
}
this._browser.contentWindow.scrollTo(state._scrollX, state._scrollY);
},
updateThumbnail: function() {
if (!this._browser)
return;
let srcCanvas = (Browser.currentBrowser == this._browser) ? document.getElementById("canvas") : null;
this._content.updateThumbnail(this._browser, srcCanvas);
}
}

View File

@ -57,13 +57,17 @@
title="&brandShortName;"
titlemodifier="&brandShortName;"
titleseparator="&mainWindow.titleseparator;"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<script type="application/x-javascript" src="chrome://global/content/inlineSpellCheckUI.js"/>
<script type="application/x-javascript" src="chrome://browser/content/commandUtil.js"/>
<script type="application/x-javascript" src="chrome://browser/content/browser.js"/>
<script type="application/x-javascript" src="chrome://browser/content/browser-ui.js"/>
<script type="application/x-javascript" src="chrome://browser/content/sanitize.js"/>
<script type="application/x-javascript" src="chrome://browser/content/CanvasBrowser.js"/>
<script type="application/x-javascript" src="chrome://browser/content/WidgetStack.js"/>
<script type="application/x-javascript" src="chrome://browser/content/InputHandler.js"/>
<stringbundleset id="stringbundleset">
<stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
@ -194,25 +198,72 @@
</panel>
</popupset>
<stack id="browser-container" flex="1" style="overflow: hidden;">
<vbox id="contentBox" style="-moz-stack-sizing: ignore; margin-top: 60px; margin: 0;">
<notificationbox id="notifications" flex="1">
<deckbrowser id="content"
autocompletepopup="popup_autocomplete_content"
flex="1"
onnewtab="CommandUpdater.doCommand('cmd_newTab');"/>
</notificationbox>
</vbox>
<!-- stupid stack needs to be in a box. not sure why -->
<box>
<stack id="browser-container" flex="1" style="width: 800px; height: 480px; max-width: 800px; max-height: 480px; background: lightgrey; /*background-image: url('chrome://browser/content/checkerboard.png')*/">
<toolbar id="toolbar-main" style="-moz-stack-sizing: ignore; height: 60px" top="0" left="0">
<!-- begin: Browser View -->
<hbox id="canvasbox"
style="-moz-stack-sizing: ignore; height: 1440px; width: 800px;"
left="0" top="-480"
vptargetx="0"
vptargety="0"
vptargetw="800"
vptargeth="480"
viewport="true">
<html:canvas id="canvas"
moz-opaque="true"
viewport="true"
style="height: 1440px; width: 800px;"
height="1440" width="800"
autocompletepopup="popup_autocomplete_content"
onnewtab="CommandUpdater.doCommand('cmd_newTab');"/>
</hbox>
<!-- end: Browser View -->
<!-- start: some barriers -->
<spacer style="-moz-stack-sizing: ignore; width: 1px; height: 1px;" barriertype="vertical" size="30" left="0" constraint="vp-relative"/>
<spacer style="-moz-stack-sizing: ignore; width: 1px; height: 1px;" barriertype="vertical" size="30" left="800" constraint="vp-relative"/>
<!-- end: barriers -->
<!-- begin: left bar -->
<vbox id="tabs-container" style="-moz-stack-sizing: ignore; width: 132px; height: 420px;"
left="-132" top="60"
constraint="ignore-y,vp-relative">
<richlistbox id="tabs" onselect="BrowserUI.selectTab(this.selectedItem);" onclosetab="BrowserUI.closeTab(this);"/>
<hbox>
<toolbarbutton id="newtab-button" command="cmd_newTab"/>
<toolbarbutton id="retrievetab-button" command="" hidden="true"/>
</hbox>
</vbox>
<!-- end: left bar -->
<!-- begin: right bar -->
<vbox id="browser-controls" style="-moz-stack-sizing: ignore; width: 80px; height: 420px;"
left="800" top="60"
constraint="ignore-y,vp-relative">
<toolbarbutton id="tool-star" class="browser-control-button" command="cmd_star"/>
<toolbarbutton id="tool-back" class="browser-control-button" command="cmd_back"/>
<toolbarbutton id="tool-forward" class="browser-control-button" command="cmd_forward"/>
<toolbarspring/>
<toolbarbutton id="tool-actions" class="browser-control-button" command="cmd_actions" hidden="true"/>
<toolbarbutton id="tool-panel" class="browser-control-button" command="cmd_panel" type="checkbox"/>
</vbox>
<!-- end: right bar -->
<!-- begin: Main Toolbar -->
<toolbar id="toolbar-main" style="-moz-stack-sizing: ignore; width: 800px; height: 60px" top="-60" left="0" constraint="ignore-x,vp-relative">
<hbox id="urlbar-container" flex="1">
<box id="identity-box"
onclick="getIdentityHandler().handleIdentityButtonEvent(event);"
onkeypress="getIdentityHandler().handleIdentityButtonEvent(event);">
<deck id="urlbar-image-deck">
<box id="urlbar-image-box">
<image id="urlbar-throbber"/>
<image id="urlbar-favicon"/>
</deck>
<image id="urlbar-favicon" hidden="true"/>
</box>
</box>
<hbox id="urlbar-editarea" flex="1">
<description id="urlbar-caption" crop="end" flex="1"/>
@ -238,25 +289,17 @@
</hbox>
<toolbarbutton id="tool-bookmarks" class="urlbar-button" command="cmd_bookmarks"/>
</toolbar>
<!-- end: Main Toolbar -->
<vbox id="browser-controls" style="-moz-stack-sizing: ignore;" top="60" left="0">
<toolbarbutton id="tool-star" class="browser-control-button" command="cmd_star"/>
<toolbarbutton id="tool-back" class="browser-control-button" command="cmd_back"/>
<toolbarbutton id="tool-forward" class="browser-control-button" command="cmd_forward"/>
<toolbarspring/>
<toolbarbutton id="tool-actions" class="browser-control-button" command="cmd_actions" hidden="true"/>
<toolbarbutton id="tool-panel" class="browser-control-button" command="cmd_panel" type="checkbox"/>
</vbox>
<hbox id="panel-container" style="-moz-stack-sizing: ignore;" top="0" left="0">
<hbox id="panel-container" hidden="true" style="-moz-stack-sizing: ignore;" top="60" left="880" constraint="ignore-y,vp-relative">
<vbox id="panel-controls" oncommand="BrowserUI.switchPane(event.target.getAttribute('linkedpanel'));">
<toolbarspring/>
<toolbarbutton id="tool-addons" type="radio" group="1" class="panel-button" linkedpanel="addons-container"/>
<toolbarbutton id="tool-downloads" type="radio" group="1" class="panel-button" linkedpanel="downloads-container"/>
<toolbarbutton id="tool-preferences" type="radio" group="1" class="panel-button" linkedpanel="prefs-container" checked="true"/>
<toolbarbutton id="tool-preferences" type="radio" group="1" class="panel-button" linkedpanel="prefs-container"/>
<toolbarbutton id="tool-shortcuts" type="radio" group="1" class="panel-button" linkedpanel="shortcuts-container" hidden="true"/>
</vbox>
<deck id="panel-items" flex="1" selectedIndex="2">
<deck id="panel-items" flex="1">
<iframe id="addons-container" flex="1"/>
<iframe id="downloads-container" flex="1"/>
@ -272,8 +315,7 @@
<richpref pref="javascript.enabled" type="bool" title="&javascript.enabled.title;">
&javascript.enabled.description;
</richpref>
<richpref pref="plugins.enabled" type="bool" title="&plugins.enabled.title;" id="plugins.enabled"
onsynctopreference="Browser.setPluginState(this.value);">
<richpref pref="plugins.enabled" type="bool" title="&plugins.enabled.title;" onsyncfrompreference="Browser.setPluginState(this.value);">
&plugins.enabled.description;
</richpref>
@ -305,14 +347,6 @@
</deck>
</hbox>
<vbox id="tab-list-container" style="-moz-stack-sizing: ignore;" top="60" left="0">
<richlistbox id="tab-list" onselect="BrowserUI.selectTab(this.selectedItem);" onclosetab="BrowserUI.closeTab(this);"/>
<hbox>
<toolbarbutton id="newtab-button" command="cmd_newTab"/>
<toolbarbutton id="retrievetab-button" command="" hidden="true"/>
</hbox>
</vbox>
<vbox id="urllist-container" hidden="true" style="-moz-stack-sizing: ignore;" top="0" left="0">
<hbox id="urllist-items-container" flex="1">
<richlistbox id="urllist-items" flex="1"
@ -355,12 +389,17 @@
</hbox>
</vbox>
</vbox>
</stack>
</box>
<vbox id="findpanel-placeholder" sizetopopup="always">
<panel id="findpanel" onpopupshown="Browser.doFind()">
<findbar id="findbar"/>
</panel>
</vbox>
<!-- where all the (hidden) <browser> elements go; I wish this could be display: none -->
<box id="browsers"/>
</window>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,143 @@
<?xml version="1.0"?>
<!DOCTYPE bindings PUBLIC "-//MOZILLA//DTD XBL V1.0//EN" "http://www.mozilla.org/xbl">
<bindings
xmlns="http://www.mozilla.org/xbl"
xmlns:xbl="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<binding id="documenttab"
extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
<content>
<xul:stack anonid="page" class="documenttab-container" flex="1">
<html:canvas anonid="canvas" class="documenttab-canvas" width="80" height="60"/>
<xul:vbox align="start">
<xul:image anonid="close" class="documenttab-close"/>
</xul:vbox>
</xul:stack>
</content>
<implementation>
<constructor><![CDATA[
let close = document.getAnonymousElementByAttribute(this, "anonid", "close");
let closefn = new Function("event", this.control.getAttribute("onclosetab"));
var self = this;
close.addEventListener("mousedown", function(event) { closefn.call(self, event); event.stopPropagation(); }, true);
]]></constructor>
<method name="updateThumbnail">
<parameter name="browser"/>
<parameter name="srcCanvas"/>
<body>
<![CDATA[
const tabWidth = 80;
const tabHeight = 60;
let canvas = document.getAnonymousElementByAttribute(this, "anonid", "canvas");
let domWin = browser.contentWindow;
let ctx = canvas.getContext("2d");
if (srcCanvas) {
ctx.drawImage(srcCanvas, 0, 0, tabWidth, tabHeight)
}
else {
let width = domWin.innerWidth;
let height = domWin.innerHeight;
ctx.clearRect(0, 0, tabWidth, tabHeight);
ctx.save();
ctx.scale(tabWidth / width, tabHeight / height);
ctx.drawWindow(domWin, 0, 0, width, height, "white");
ctx.restore();
}
]]>
</body>
</method>
<method name="markInvalid">
<parameter name="browser"/>
<body>
<![CDATA[
let canvas = document.getAnonymousElementByAttribute(this, "anonid", "canvas");
let ctx = canvas.getContext("2d");
ctx.save();
ctx.strokeStyle = "red";
ctx.moveTo(63, 43);
ctx.lineTo(78, 58);
ctx.moveTo(78, 43);
ctx.lineTo(63, 58);
ctx.stroke();
ctx.restore();
]]>
</body>
</method>
</implementation>
</binding>
<!-- very hacky, used to display richlistitems in multiple columns -->
<binding id="tablist"
extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
<content>
<children includes="listheader"/>
<xul:scrollbox allowevents="true" orient="horizontal" anonid="main-box"
flex="1" style="overflow: auto;">
<children/>
</xul:scrollbox>
</content>
<implementation>
<field name="tabsPerColumn">4</field>
<property name="children" readonly="true">
<getter>
<![CDATA[
var childNodes = [];
for (var box = this.firstChild; box; box = box.nextSibling) {
for (var child = box.firstChild; child; child = child.nextSibling) {
if (child instanceof Components.interfaces.nsIDOMXULSelectControlItemElement)
childNodes.push(child);
}
}
return childNodes;
]]>
</getter>
</property>
<method name="addTab">
<parameter name="tab"/>
<body>
<![CDATA[
if (this.children.length % this.tabsPerColumn == 0)
this.appendChild(document.createElement("vbox"));
this.lastChild.appendChild(tab);
return tab;
]]>
</body>
</method>
<method name="removeTab">
<parameter name="tab"/>
<body>
<![CDATA[
var idx = this.getIndexOfItem(tab);
if (idx == -1)
return;
// remove all later tabs and readd them so that there aren't empty columns
var count = this.itemCount - 1;
var tomove = [ ];
for (var c = count; c >= idx; c--) {
var tab = this.getItemAtIndex(c);
tomove.push(tab.parentNode.removeChild(tab));
if (!this.lastChild.hasChildNodes())
this.removeChild(this.lastChild);
}
// subtract 2 because the tab to remove should not be added back again
for (var m = tomove.length - 2; m >= 0; m--)
this.addTab(tomove[m]);
]]>
</body>
</method>
</implementation>
</binding>
</bindings>

View File

@ -16,7 +16,7 @@ browser.jar:
content/browser-ui.js (content/browser-ui.js)
content/commandUtil.js (content/commandUtil.js)
content/urlbar.xml (content/urlbar.xml)
content/deckbrowser.xml (content/deckbrowser.xml)
content/tabs.xml (content/tabs.xml)
content/notification.xml (content/notification.xml)
content/browser.css (content/browser.css)
content/scrollbars.css (content/scrollbars.css)
@ -28,6 +28,9 @@ browser.jar:
content/preferences/richpref.xml (content/preferences/richpref.xml)
* content/sanitize.xul (content/sanitize.xul)
* content/sanitize.js (content/sanitize.js)
content/WidgetStack.js (content/WidgetStack.js)
content/CanvasBrowser.js (content/CanvasBrowser.js)
content/InputHandler.js (content/InputHandler.js)
classic.jar:
% skin browser classic/1.0 %

View File

@ -177,7 +177,7 @@ toolbarbutton.urlbar-cap-button {
}
/* favicon images are 16x16 */
#urlbar-image-deck {
#urlbar-image-box {
max-width: 24px;
max-height: 24px;
min-width: 24px;
@ -189,6 +189,11 @@ toolbarbutton.urlbar-cap-button {
list-style-image: url("chrome://browser/skin/images/throbber.png");
}
#urlbar-favicon {
width: 24px;
height: 24px;
}
#urlbar-editarea {
min-height: 49px;
-moz-box-align: center;
@ -466,12 +471,12 @@ toolbarbutton.panel-button {
}
/* Left sidebar (tabs) ---------------------------------------------------- */
#tab-list-container {
#tabs-container {
background: url("images/left_sidebar_middle.png") rgb(87,87,87) top right repeat-y;
min-width: 132px;
}
#tab-list {
#tabs {
-moz-appearance: none;
margin: 0;
padding: 4px;

View File

@ -4,7 +4,7 @@ cmd_reload.name=Reload page
cmd_stop.name=Stop loading
cmd_search.name=Search
cmd_go.name=Load URL
cmd_openLocation=Open location
cmd_openLocation.name=Open location
cmd_star.name=Star page
cmd_bookmarks.name=View bookmarks
cmd_find.name=Find in page

605
mobile/tests/wsTests.js Normal file
View File

@ -0,0 +1,605 @@
const RED = "data:image/gif;base64,R0lGODdhAgACALMAAAAAAP///wAAAAAAAP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAgACAAAEA5BIEgA7";
const BLUE = "data:image/gif;base64,R0lGODdhAgACALMAAAAAAP///wAAAAAAAP8AAAAAAAAAAAAAAAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAgACAAAEAxBJFAA7";
const ORANGE = "data:image/gif;base64,R0lGODdhAgACALMAAAAAAP///wAAAP//AP8AAP+AAAD/AAAAAAAA//8A/wAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAgACAAAEA7DIEgA7";
var gCheckCount = 0;
// test helpers
function XUL(s, id, attrs) {
let e = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", s);
if (id)
e.setAttribute("id", id);
if (attrs) {
for (var a in attrs)
e.setAttribute(a, attrs[a]);
}
return e;
}
function e(id) {
return document.getElementById(id);
}
function attr(id, a, v) {
e(id).setAttribute(a, v);
}
function CleanDocument(x, y) {
gCheckCount = 0;
var root = document.getElementById("testroot");
while (root.firstChild)
root.removeChild(root.firstChild);
if (x && y) {
var tr = document.getElementById("testroot");
tr.style.width = x + "px";
tr.style.height = y + "px";
}
}
function IMAGE(src, id, x, y, w, h) {
return XUL("image", id, { style: "-moz-stack-sizing: ignore; opacity: 0.5", src: src, width: w, height: h, left: x, top: y });
}
function SetupFourBoxes() {
var s = XUL("stack", "s");
var a = IMAGE(RED, "a", 0, 0, 100, 100);
var b = IMAGE(BLUE, "b", 100, 0, 100, 100);
var c = IMAGE(BLUE, "c", 0, 100, 100, 100);
var d = IMAGE(RED, "d", 100, 100, 100, 100);
s.appendChild(a);
s.appendChild(b);
s.appendChild(c);
s.appendChild(d);
document.getElementById("testroot").appendChild(s);
return s;
}
function SetupOneBoxAndViewport(x,y) {
var s = XUL("stack", "s");
var b = IMAGE(RED, "b", x-20, y-20, 20, 20);
s.appendChild(b);
var vp = IMAGE(ORANGE, "vp", x, y, 50, 50);
vp.setAttribute("viewport", "true");
s.appendChild(vp);
document.getElementById("testroot").appendChild(s);
return s;
}
function SetupEightBoxes(x, y, sz, _wantStack) {
var s = XUL("stack", "s");
x = x || 0;
y = y || 0;
sz = sz || 20;
// boxes are b1-b9 from the top left, going clockwise
var b1 = IMAGE(RED, "b1", x-sz, y-sz, sz, sz);
var b2 = IMAGE(BLUE, "b2", x, y-sz, sz, sz);
var b3 = IMAGE(RED, "b3", x+sz, y-sz, sz, sz);
var b4 = IMAGE(BLUE, "b4", x+sz, y, sz, sz);
var b5 = IMAGE(RED, "b5", x+sz, y+sz, sz, sz);
var b6 = IMAGE(BLUE, "b6", x, y+sz, sz, sz);
var b7 = IMAGE(RED, "b7", x-sz, y+sz, sz, sz);
var b8 = IMAGE(BLUE, "b8", x-sz, y, sz, sz);
s.appendChild(b1);
s.appendChild(b2);
s.appendChild(b3);
s.appendChild(b4);
s.appendChild(b5);
s.appendChild(b6);
s.appendChild(b7);
s.appendChild(b8);
if (_wantStack)
return s;
document.getElementById("testroot").appendChild(s);
return s;
}
function SetupNineBoxes(x, y, sz) {
var s = SetupEightBoxes(x, y, sz, true);
var b0 = IMAGE(ORANGE, "b0", x, y, sz, sz);
s.appendChild(b0);
document.getElementById("testroot").appendChild(s);
return s;
}
function SetupEightBoxesAndViewport(x, y, sz) {
var s = SetupEightBoxes(x, y, sz, true);
var vp = IMAGE(ORANGE, "vp", x, y, 20, 20);
vp.setAttribute("viewport", "true");
s.appendChild(vp);
document.getElementById("testroot").appendChild(s);
return s;
}
function Barrier(x, y, type, vpr) {
if (x != undefined && y != undefined)
throw "Bumper with both x and y given, that won't work";
var spacer = XUL("spacer", null, { style: "-moz-stack-sizing: ignore;", barriertype: type, size: '10' });
if (x != undefined)
spacer.setAttribute("left", x);
if (y != undefined)
spacer.setAttribute("top", y);
if (vpr)
spacer.setAttribute("constraint", "vp-relative");
document.getElementById("s").appendChild(spacer);
return spacer;
}
function checkInnerBoundsInner(ws, x, y, w, h) {
let vwib = ws._viewport.viewportInnerBounds;
if (!((vwib.x != x ||
vwib.y != y ||
(w != undefined && vwib.width != w) ||
(h != undefined && vwib.height != h))))
return null;
return [ vwib.x, vwib.y, vwib.width, vwib.height ];
}
function checkInnerBounds(ws, x, y, w, h) {
gCheckCount++;
var res = checkInnerBoundsInner(ws, x, y, w, h);
if (!res)
return null;
var err;
if (w == undefined || h == undefined) {
err = "(" + gCheckCount + ") expected [" + x + "," + y + "] got [" + res[0] + "," + res[1] + "]";
} else {
err = "(" + gCheckCount + ") expected [" + x + "," + y + "," + w + "," + h + "] got [" + res[0] + "," + res[1] + "," + res[2] + "," + res[3] + "]";
}
throw "checkInnerBounds failed: " + err;
}
function checkRectInner(ws, id, x, y, w, h) {
var e = document.getElementById(id);
var bb = e.getBoundingClientRect();
var wsb = ws._el.getBoundingClientRect();
if (!((bb.left - wsb.left) != x ||
(bb.top - wsb.left) != y ||
(w != undefined && (bb.right - bb.left) != w) ||
(h != undefined && (bb.bottom - bb.top) != h)))
return null;
return [(bb.left - wsb.left), (bb.top - wsb.left), (bb.right - bb.left), (bb.bottom - bb.top)];
}
function checkRect(ws, id, x, y, w, h) {
gCheckCount++;
var res = checkRectInner(ws, id, x, y, w, h);
if (!res)
return; // ok
var err;
if (w == undefined || h == undefined) {
err = "(" + gCheckCount + ") expected [" + x + "," + y + "] got [" + res[0] + "," + res[1] + "]";
} else {
err = "(" + gCheckCount + ") expected [" + x + "," + y + "," + w + "," + h + "] got [" + res[0] + "," + res[1] + "," + res[2] + "," + res[3] + "]";
}
throw "checkRect failed: " + err;
}
//
// check that simple stuff works
//
function simple1() {
CleanDocument();
var s = SetupFourBoxes();
var ws = new WidgetStack(s);
checkRect(ws, "a", 0, 0);
checkRect(ws, "b", 100, 0);
checkRect(ws, "c", 0, 100);
checkRect(ws, "d", 100, 100);
ws.panBy(-10, -10);
checkRect(ws, "a", -10, -10);
checkRect(ws, "b", 90, -10);
checkRect(ws, "c", -10, 90);
checkRect(ws, "d", 90, 90);
// should be the same as panBy(10,10)
ws.dragStart(50, 50);
ws.dragMove(0, 0);
ws.dragMove(60, 60);
ws.dragStop();
checkRect(ws, "a", 0, 0);
checkRect(ws, "b", 100, 0);
checkRect(ws, "c", 0, 100);
checkRect(ws, "d", 100, 100);
return true;
}
// check that ignore-x, ignore-y, and frozen work
function simple2() {
CleanDocument();
var s = SetupFourBoxes();
attr("b", "constraint", "ignore-x");
attr("c", "constraint", "ignore-y");
attr("d", "constraint", "frozen");
var ws = new WidgetStack(s);
ws.panBy(-20, -20);
checkRect(ws, "a", -20, -20);
checkRect(ws, "b", 100, -20);
checkRect(ws, "c", -20, 100);
checkRect(ws, "d", 100, 100);
ws.panBy(20, 20);
checkRect(ws, "a", 0, 0);
checkRect(ws, "b", 100, 0);
checkRect(ws, "c", 0, 100);
checkRect(ws, "d", 100, 100);
return true;
}
function simple3() {
CleanDocument(50,50);
var s = SetupNineBoxes(0, 0, 50);
for (var i = 1; i <= 8; i++) {
attr("b"+i, "constraint", "vp-relative");
}
Barrier(0, undefined, "vertical");
Barrier(25, undefined, "vertical");
Barrier(50, undefined, "vertical");
Barrier(undefined, 50, "horizontal");
var ws = new WidgetStack(s, 50, 50);
ws.panBy(-15, 0);
checkRect(ws, "b0", -5, 0);
// test that dragging does the same thing
ws.dragStart(0, 0);
ws.dragMove(5, 0);
ws.dragMove(10, 0);
ws.dragMove(15, 0);
ws.dragStop();
checkRect(ws, "b0", 0, 0);
// because there's a 10-px bumper, this pan should have no effect
ws.panBy(-5, 0);
checkRect(ws, "b0", 0, 0);
// now we pan beyond the right barrier by 5px
ws.panBy(-15, 0);
checkRect(ws, "b0", -5, 0);
// and then we go back. We should just need 5 to get back to 0.
ws.panBy(5, 0);
checkRect(ws, "b0", 0, 0);
// check that we hit the middle barrier correctly
ws.panBy(-30, 0);
checkRect(ws, "b0", -20, 0);
// this should hit the middle barrier
ws.panBy(-10, 0);
checkRect(ws, "b0", -20, 0);
// and then go past it
ws.panBy(-20, 0);
checkRect(ws, "b0", -30, 0);
// reset
ws.panBy(40, 0);
// now let's do a simpler test of the horizontal barriers; there's only one at 50
ws.panBy(0, -5);
checkRect(ws, "b0", 0, 0);
ws.panBy(0, 5);
checkRect(ws, "b0", 0, 0);
ws.panBy(0, -20);
checkRect(ws, "b0", 0, -10);
ws.panBy(0, 10);
checkRect(ws, "b0", 0, 0);
return true;
}
// now check some viewport stuff
function vp1() {
CleanDocument(50, 50);
var s = SetupOneBoxAndViewport(0, 0);
attr("b", "constraint", "vp-relative");
var ws = new WidgetStack(s, 50, 50);
// explicitly use this form of svb
ws.setViewportBounds({top: 0, left: 0, right: 200, bottom: 200});
checkRect(ws, "b", -20, -20);
ws.panBy(20, 20);
checkRect(ws, "b", 0, 0);
checkRect(ws, "vp", 20, 20);
ws.panBy(50, 50);
checkRect(ws, "b", 0, 0);
checkRect(ws, "vp", 20, 20);
ws.panBy(-20, -20);
ws.panBy(50, 50);
checkRect(ws, "b", 0, 0);
checkRect(ws, "vp", 20, 20);
ws.panBy(-200, -200);
checkRect(ws, "vp", 0, 0);
checkInnerBounds(ws, 150, 150, 50, 50);
ws.panBy(500, 500);
checkRect(ws, "vp", 20, 20);
checkInnerBounds(ws, 0, 0, 50, 50);
return true;
}
function vp2() {
CleanDocument(20, 20);
var s = SetupEightBoxesAndViewport(0, 0);
for (var i = 1; i <= 8; i++) {
attr("b"+i, "constraint", "vp-relative");
}
var ws = new WidgetStack(s, 20, 20);
// b5 is the bottom-right; the initial setup has a 20x20 viewport in the middle
checkRect(ws, "b5", 20, 20);
// explicitly use this form of svb
ws.setViewportBounds(0, 0, 200, 200);
// after resizing the viewport bounds, the rect should get pushed out
checkRect(ws, "b5", 200, 200);
ws.panBy(-500, -500);
checkRect(ws, "b5", 0, 0);
checkInnerBounds(ws, 180, 180, 20, 20);
return true;
}
function vp3() {
CleanDocument(20, 20);
var s = SetupEightBoxesAndViewport(0, 0);
for (var i = 1; i <= 8; i++) {
attr("b"+i, "constraint", "vp-relative");
}
attr("b2", "constraint", "vp-relative,ignore-x");
attr("b4", "constraint", "vp-relative,ignore-y");
var ws = new WidgetStack(s, 20, 20);
// b2 is the top-middle
checkRect(ws, "b2", 0, -20);
// b4 is the right-middle
checkRect(ws, "b4", 20, 0);
ws.setViewportBounds(200, 200);
checkRect(ws, "b2", 0, -20);
checkRect(ws, "b4", 200, 0);
// x pans shouldn't affect ignore-x widgets
ws.panBy(-20, 0);
checkRect(ws, "b2", 0, -20);
ws.panBy( 20, 0);
// y pans shouldn't affect ignore-y widgets
ws.panBy(0, -20);
checkRect(ws, "b4", 200, 0);
ws.panBy(0, 20);
return true;
}
// test whether the right things happen when the viewport size changes
function vp4() {
CleanDocument(20, 20);
var s = SetupEightBoxesAndViewport(0, 0);
for (var i = 1; i <= 8; i++) {
attr("b"+i, "constraint", "vp-relative");
}
attr("b2", "constraint", "vp-relative,ignore-x");
attr("b4", "constraint", "vp-relative,ignore-y");
var ws = new WidgetStack(s, 20, 20);
ws.setViewportBounds(200, 200);
// after resizing the viewport bounds, the rect should get pushed out
checkRect(ws, "b4", 200, 0);
checkRect(ws, "b5", 200, 200);
checkInnerBounds(ws, 0, 0, 20, 20);
ws.panBy(-50, -50);
checkInnerBounds(ws, 50, 50, 20, 20);
// the viewport is now going to grow
ws.setViewportBounds(400, 400);
// ... and the inner bounds should remain the same
checkInnerBounds(ws, 50, 50, 20, 20);
// ... but b4 and b5 should be pushed out
checkRect(ws, "b4", 400-50, 0);
checkRect(ws, "b5", 400-50, 400-50);
// now move to the far corner
ws.panBy(-500, -500);
// now shrink again
ws.setViewportBounds(100, 100);
return true;
}
function vp5() {
CleanDocument(20, 20);
var s = SetupEightBoxesAndViewport(0, 0);
for (var i = 1; i <= 8; i++) {
attr("b"+i, "constraint", "vp-relative");
}
Barrier(0, undefined, "vertical", true);
Barrier(20, undefined, "vertical", true);
Barrier(undefined, 0, "horizontal", true);
Barrier(undefined, 100, "horizontal", true);
var ws = new WidgetStack(s, 20, 20);
ws.setViewportBounds(200, 200);
ws.panBy(-20, 0);
checkRect(ws, "b1", -30, -20);
ws.panBy(10, 0);
checkRect(ws, "b1", -20, -20);
ws.panBy(-5, 0);
checkRect(ws, "b1", -20, -20);
// check the horizontal -- we should hit the first bumper,
// but the other one should always be forever out of range of the vp
ws.panBy(0, -110);
checkRect(ws, "b1", -20, -120);
ws.panBy(0, -300);
checkRect(ws, "b1", -20, -220);
return true;
}
function vp6() {
CleanDocument(20, 20);
var s = SetupEightBoxesAndViewport(0, 0);
for (var i = 1; i <= 8; i++) {
attr("b"+i, "constraint", "vp-relative");
}
var ws = new WidgetStack(s, 20, 20);
ws.setViewportBounds(200, 200);
checkInnerBounds(ws, 0, 0, 20, 20);
ws.panTo(-75, -75);
checkInnerBounds(ws, 75, 75, 20, 20);
// scale up
ws.setViewportBounds(500, 500);
checkInnerBounds(ws, 75, 75, 20, 20);
ws.panTo(-300, -300);
checkInnerBounds(ws, 300, 300, 20, 20);
// scale down
ws.setViewportBounds(200, 200);
checkInnerBounds(ws, 75, 75, 20, 20);// XXX?
ws.panTo(-75, -75);
checkInnerBounds(ws, 75, 75, 20, 20);
return true;
}
function run(s) {
var r = false;
try {
r = window[s].apply(window);
if (r) {
logbase("PASSED: " + s);
} else {
logbase("FAILED: " + s + " (no exception?)");
}
} catch (e) {
logbase("FAILED: " + s + ": " + e);
}
}
function runTests() {
run("simple1");
run("simple2");
run("simple3");
run("vp1");
run("vp2");
run("vp3");
run("vp4");
run("vp5");
run("vp6");
}
function handleLoad() {
gWsDoLog = true;
gWsLogDiv = document.getElementById("logdiv");
runTests();
}

25
mobile/tests/wsTests.xul Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0"?>
<window id="w"
width="800" height="800"
onload="handleLoad();"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml">
<script type="text/javascript" src="../chrome/content/WidgetStack.js"/>
<script type="text/javascript" src="wsTests.js"/>
<vbox>
<spacer style="height: 50px;"/>
<hbox>
<spacer style="width: 50px;"/>
<box id="testroot" style="width: 50px; height: 50px; background: gray; -moz-stack-sizing: ignore;"/>
<spacer flex="1"/>
</hbox>
<spacer style="height: 400px;" flex="1"/>
<hbox>
<spacer style="width: 400px;"/>
<html:div style="font: 9px sans-serif;" id="logdiv"></html:div>
</hbox>
</vbox>
</window>