gecko/mobile/chrome/content/deckbrowser.xml

1159 lines
40 KiB
XML

<?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="deckbrowser">
<content>
<xul:deck flex="1" selectedIndex="0">
<xul:stack anonid="cstack" flex="1" style="overflow: hidden;">
<html:canvas anonid="ccanvas"
moz-opaque="true"
style="-moz-stack-sizing: ignore;"/>
</xul:stack>
<xul:deck anonid="display-list" flex="1"/>
</xul:deck>
</content>
<resources>
<stylesheet src="chrome://global/content/deckbrowser.css"/>
</resources>
<implementation implements="nsIObserver">
<constructor><![CDATA[
this._zoomLevel = 1;
// panning
this._stack.addEventListener("mousedown", this.stackEventHandler, true);
// need mouseup handled on the window to catch mouseups on e.g. the toolbar
window.addEventListener("mouseup", this.stackEventHandler, true);
this._stack.addEventListener("mousemove", this.stackEventHandler, true);
// zoom
// FIXME: dblclicks don't work on the device
// this._stack.addEventListener("dblclick", this.stackEventHandler, true);
this._stack.addEventListener("DOMMouseScroll", this.stackEventHandler, true);
var self = this;
var obs = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
obs.addObserver(function(subject, topic, data) self.destroyEarliestBrowser(),
"memory-pressure", false);
this._dragStartTimeout = -1;
this.PAN_EVENTS_TO_TRACK = 2;
this._panEventTracker = new Array(this.PAN_EVENTS_TO_TRACK);
this._panEventTrackerIndex = 0;
]]></constructor>
<property name="dragData" readonly="true">
<getter>
<![CDATA[
if (!this.currentTab.dragData) {
this.currentTab.dragData = {
dragging: false,
dragX: 0,
dragY: 0,
sX: 0,
sY: 0,
pageX: 0,
pageY: 0,
oldPageX: 0,
oldPageY: 0
}
}
return this.currentTab.dragData;
]]>
</getter>
</property>
<field name="_stack">
document.getAnonymousElementByAttribute(this, "anonid", "cstack");
</field>
<field name="_canvas">
document.getAnonymousElementByAttribute(this, "anonid", "ccanvas");
</field>
<property name="browser" readonly="true">
<getter>
<![CDATA[
return this.getBrowserForDisplay(this.displayList.selectedPanel);
]]>
</getter>
</property>
<property name="displayList" readonly="true">
<getter>
return document.getAnonymousElementByAttribute(this, "anonid", "display-list");
</getter>
</property>
<field name="tabList">
null
</field>
<field name="progressListenerCreator"/>
<method name="updateCanvasState">
<parameter name="aNewDoc"/>
<body><![CDATA[
if (aNewDoc)
this._updateViewState();
if (this._updateTimeout)
clearTimeout(this._updateTimeout);
var self = this;
this._updateTimeout = setTimeout(function () {
if (!self.dragData.dragging)
self._browserToCanvas();
}, 100);
]]></body>
</method>
<method name="_updateViewState">
<body><![CDATA[
// Reset the pan data.
this.dragData.pageX = 0;
this.dragData.pageY = 0;
this._zoomed = false;
this.zoomToPage();
]]></body>
</method>
<field name="currentTab"/>
<method name="getDisplayForTab">
<parameter name="tab"/>
<body><![CDATA[
var tabList = this.tabList.childNodes;
for (var t = 0; t < tabList.length; t++) {
if (tab == tabList[t])
return this.displayList.childNodes[t];
}
return null;
]]>
</body>
</method>
<method name="getTabForDisplay">
<parameter name="display"/>
<body><![CDATA[
var displayList = this.displayList.childNodes;
for (var t = 0; t < displayList.length; t++) {
if (display == displayList[t])
return this.tabList.childNodes[t];
}
return null;
]]>
</body>
</method>
<method name="getBrowserForDisplay">
<parameter name="display"/>
<body><![CDATA[
if (!display)
return null;
var browser = display.firstChild;
if (browser && browser.localName == "browser")
return browser;
browser = display.lastChild;
return (browser && browser.localName == "browser") ? browser : null;
]]>
</body>
</method>
<method name="setLoading">
<parameter name="browser"/>
<body><![CDATA[
var tab = this.getTabForDisplay(browser.parentNode);
if (tab)
tab.dragData = null;
]]></body>
</method>
<method name="updateBrowser">
<parameter name="browser"/>
<parameter name="done"/>
<body><![CDATA[
var display = browser.parentNode;
var domWin = browser.contentWindow;
display.url = domWin.location.toString();
if (!done || domWin.location == "about:blank")
return;
this.restoreBrowserState(display);
var tab = this.getTabForDisplay(display);
if (tab) {
tab.updateTab(browser);
var canvas = display.firstChild;
if (canvas.localName == "canvas")
display.removeChild(canvas);
}
]]></body>
</method>
<method name="selectTab">
<parameter name="tab"/>
<body><![CDATA[
var currentTab = this.currentTab;
this.currentTab = tab;
if (currentTab) {
var currentDisplay = this.getDisplayForTab(currentTab);
var currentBrowser = this.getBrowserForDisplay(currentDisplay);
if (currentBrowser) {
currentDisplay.url = currentBrowser.contentWindow.location.toString();
currentBrowser.setAttribute("type", "content");
currentTab.updateTab(currentBrowser);
}
}
var display = this.getDisplayForTab(tab);
var browser = this.getBrowserForDisplay(display);
if (!browser) {
browser = this.createBrowser(true, tab, display);
browser.loadURI(display.url, null, null, false);
}
display.lastAccess = Date.now();
browser.setAttribute("type", "content-primary");
this.displayList.selectedPanel = display;
var event = document.createEvent("Events");
event.initEvent("TabSelect", true, false);
tab.dispatchEvent(event);
]]></body>
</method>
<method name="newTab">
<parameter name="makeFront"/>
<body><![CDATA[
var browser = this.createBrowser(makeFront, null, null);
if (!browser)
return null;
var tab = this.getTabForDisplay(browser.parentNode);
var evt = document.createEvent("Events");
evt.initEvent("TabOpen", true, false);
tab.dispatchEvent(evt);
return tab;
]]></body>
</method>
<method name="removeTab">
<parameter name="tab"/>
<body><![CDATA[
if (!tab)
return;
var display = this.getDisplayForTab(tab);
if (display)
display.removeChild(display);
tab.parentNode.removeChild(tab);
var evt = document.createEvent("Events");
evt.initEvent("TabClose", true, false);
tab.dispatchEvent(evt);
]]></body>
</method>
<method name="createBrowser">
<parameter name="makeFront"/>
<parameter name="tab"/>
<parameter name="display"/>
<body><![CDATA[
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var browser = document.createElementNS(XUL_NS, "browser");
browser.className = "deckbrowser-browser";
browser.setAttribute("style", "overflow: hidden");
browser.setAttribute("contextmenu", this.getAttribute("contextmenu"));
browser.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
browser.flex = 1;
if (makeFront)
browser.setAttribute("type", "content-primary");
else
browser.setAttribute("type", "content");
var nextDisplay;
var displayList = this.displayList;
if (tab) {
var nextTab = tab.nextSibling;
if (nextTab)
nextDisplay = this.getDisplayForTab(nextTab);
}
if (!display) {
display = document.createElementNS(XUL_NS, "deck");
displayList.insertBefore(display, nextDisplay);
}
display.appendChild(browser);
display.selectedIndex = 0;
if (this.progressListenerCreator) {
var listener = this.progressListenerCreator(this, browser);
browser.addProgressListener(listener);
display.progressListener = listener;
}
if (!tab) {
tab = document.createElementNS(XUL_NS, "richlistitem");
tab.setAttribute("type", "documenttab");
this.tabList.appendChild(tab);
}
if (tab == this.tabList.selectedItem) {
// already selected, but need to update the selected panel
display.lastAccess = Date.now();
displayList.selectedPanel = display;
}
else if (makeFront) {
this.tabList.selectedItem = tab;
}
return browser;
]]></body>
</method>
<method name="destroyBrowser">
<parameter name="browser"/>
<body><![CDATA[
if (!browser || browser == this.browser)
return;
var display = browser.parentNode;
this.saveBrowserState(display);
var domWin = browser.contentWindow;
var tab = this.getTabForDisplay(display);
if (tab)
tab.markInvalid();
const XHTML_NS = "http://www.w3.org/1999/xhtml";
var canvas = document.createElementNS(XHTML_NS, "canvas");
canvas.setAttribute("width", domWin.innerWidth);
canvas.setAttribute("height", domWin.innerHeight);
var ctx = canvas.getContext("2d");
ctx.drawWindow(domWin, 0, 0, domWin.innerWidth, domWin.innerHeight, "rgba(0,0,0,0)");
display.insertBefore(canvas, display.firstChild);
display.lastAccess = Date.now();
display.progressListener = null;
display.removeChild(browser);
]]></body>
</method>
<method name="destroyEarliestBrowser">
<body><![CDATA[
var earliestBrowser = null;
var earliest = Date.now();
var displayList = this.displayList.childNodes;
for (var t = 0; t < displayList.length; t++) {
var display = displayList[t];
var browser = this.getBrowserForDisplay(display);
if (browser &&
display != this.displayList.selectedItem &&
display.lastAccess < earliest) {
earliestBrowser = browser;
earliest = display.lastAccess;
}
}
if (earliestBrowser)
this.destroyBrowser(earliestBrowser);
]]></body>
</method>
<method name="saveBrowserState">
<parameter name="display"/>
<body><![CDATA[
var state = { };
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 = window.scrollX;
state._scrollY = window.scrollY;
display.state = state;
]]></body>
</method>
<method name="restoreBrowserState">
<parameter name="display"/>
<body><![CDATA[
var state = display.state;
if (!state)
return;
var browser = this.getBrowserForDisplay(display);
var doc = 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);
]]></body>
</method>
<method name="_browserToCanvas">
<body><![CDATA[
// FIXME: canvas needs to know it's actual width/height
var rect = this._canvas.getBoundingClientRect();
var w = rect.right - rect.left;
var h = rect.bottom - rect.top;
this._canvas.width = w;
this._canvas.height = h;
var ctx = this._canvas.getContext("2d");
ctx.clearRect(0,0,w,h);
ctx.save();
ctx.scale(this._zoomLevel, this._zoomLevel);
ctx.drawWindow(this.browser.contentWindow,
this.dragData.pageX, this.dragData.pageY,
w / this._zoomLevel, h / this._zoomLevel,
"white");
ctx.restore();
]]></body>
</method>
<method name="_updateCanvasPosition">
<body><![CDATA[
this._canvas.style.marginLeft = this.dragData.dragX + "px";
this._canvas.style.marginRight = -this.dragData.dragX + "px";
this._canvas.style.marginTop = this.dragData.dragY + "px";
this._canvas.style.marginBottom = -this.dragData.dragY + "px";
// Force a sync redraw
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils)
.redraw();
]]></body>
</method>
<method name="_clampZoomLevel">
<parameter name="aZoomLevel"/>
<body><![CDATA[
const min = 0.2;
const max = 2.0;
return Math.min(Math.max(min, aZoomLevel), max);
]]></body>
</method>
<property name="zoomLevel" onget="return this._zoomLevel;">
<setter><![CDATA[
this._zoomLevel = this._clampZoomLevel(val);
this._browserToCanvas();
return val;
]]></setter>
</property>
<method name="zoom">
<parameter name="aDirection"/>
<body><![CDATA[
if (aDirection == 0)
return;
var zoomDelta = 0.05; // 1/20
if (aDirection >= 0)
zoomDelta *= -1;
this._zoomLevel = this._clampZoomLevel(this._zoomLevel + zoomDelta);
this._browserToCanvas();
]]></body>
</method>
<method name="zoomToPage">
<body><![CDATA[
// Adjust the zoomLevel to fit the page contents in our window
// width
var [contentWidth, ] = this._contentAreaDimensions;
var canvasRect = this._canvas.getBoundingClientRect();
var canvasWidth = canvasRect.right - canvasRect.left;
if (contentWidth)
this._zoomLevel = canvasWidth / contentWidth;
]]></body>
</method>
<method name="zoomToElement">
<parameter name="aElement"/>
<body><![CDATA[
const margin = 15;
// scale to the element's width
var elRect = this._getPagePosition(aElement);
var zoomLevel = this.browser.boxObject.width / (elRect.width + (2 * margin));
this._zoomLevel = Math.min(zoomLevel, 10);
// pan to the element
this._panTo(Math.max(elRect.x - margin, 0),
Math.max(0, elRect.y - margin));
]]></body>
</method>
/**
* Retrieve the content element for a given point (relative to the top
* left corner of the browser window).
*/
<method name="elementFromPoint">
<parameter name="aX"/>
<parameter name="aY"/>
<body><![CDATA[
var cdoc = this.browser.contentDocument;
var [x, y] = this._clientToContentCoords(aX, aY);
var element = cdoc.elementFromPoint(x, y);
// Reset scroll state
this.browser.contentWindow.scrollTo(0, 0);
return element;
]]></body>
</method>
<method name="_getPagePosition">
<parameter name="aElement"/>
<body><![CDATA[
var r = aElement.getBoundingClientRect();
var retVal = {
width: r.right - r.left,
height: r.bottom - r.top,
x: r.left,
y: r.top
};
return retVal;
]]></body>
</method>
<method name="_redispatchMouseEvent">
<parameter name="aEvent"/>
<parameter name="aType"/>
<body><![CDATA[
if (!(aEvent instanceof MouseEvent)) {
Components.utils.reportError("_redispatchMouseEvent called with a non-mouse event");
return;
}
var [x, y] = this._clientToContentCoords(aEvent.clientX, aEvent.clientY);
var cwin = this.browser.contentWindow;
var cwu = cwin.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
cwu.sendMouseEvent(aType || aEvent.type,
x, y,
aEvent.button || 0,
aEvent.detail || 1,
0);
// Reset scroll state
cwin.scrollTo(0, 0);
]]></body>
</method>
<!-- Given a set of client coordinates (relative to the app window),
scrolls the content window as close to the clicked on content item
as possible, and returns the remaining offsets into the content
window if the object isn't at 0, 0. Callers should be sure to reset
scroll state after calling this method.
-->
<method name="_clientToContentCoords">
<parameter name="aClientX"/>
<parameter name="aClientY"/>
<body><![CDATA[
// Need to adjust for the toolbar height, etc.
var browserTop = this.browser.getBoundingClientRect().top;
var clickOffsetX = (aClientX / this._zoomLevel) + this.dragData.pageX;
var clickOffsetY = ((aClientY - browserTop) / this._zoomLevel) + this.dragData.pageY;
// Scroll the browser so that the event is targeted properly
var cwin = this.browser.contentWindow;
cwin.scrollTo(clickOffsetX, clickOffsetY);
// Might not have been able to scroll all the way if we're zoomed in,
// so we need to account for that difference.
var pageOffsetX = clickOffsetX - cwin.scrollX;
var pageOffsetY = clickOffsetY - cwin.scrollY;
return [pageOffsetX, pageOffsetY];
]]></body>
</method>
<property name="_contentAreaDimensions" readonly="true">
<getter>
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];
// 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))
throw "Can't get content width/height";
return [w, h];
</getter>
</property>
<property name="_effectiveCanvasDimensions" readonly="true">
<getter><![CDATA[
return [this._canvas.width / this._zoomLevel,
this._canvas.height / this._zoomLevel];
]]></getter>
</property>
<property name="scrollX" readonly="true">
<getter><![CDATA[
return this.dragData.pageX - this.dragData.dragX / this._zoomLevel;
]]></getter>
</property>
<property name="scrollY" readonly="true">
<getter><![CDATA[
return this.dragData.pageY - this.dragData.dragY / this._zoomLevel;
]]></getter>
</property>
<field name="_fireOverpan">
0
</field>
/**
* Given a set of page coordinates, constrain them such that they
* fit within the rect defined by [0,0] and [x,y], where x and y are
* the maximum values that can be used for the canvas' .top and .left
* such that it is still within the scrollable area of the page, taking
* into account the current zoomLevel.
*/
<method name="_constrainPanCoords">
<parameter name="aX"/>
<parameter name="aY"/>
<body><![CDATA[
const OVERPAN_LIMIT = 30;
const OVERPAN_LEFT = 1;
const OVERPAN_RIGHT = 2;
const OVERPAN_TOP = 3;
const OVERPAN_BOTTOM = 4;
var origX = aX;
var origY = aY;
var [contentAreaWidth, contentAreaHeight] = this._contentAreaDimensions;
var [canvasW, canvasH] = this._effectiveCanvasDimensions;
var offscreenWidth = contentAreaWidth - canvasW;
if (offscreenWidth <= 0) {
// Content is narrower than viewport, no need to pan horizontally
aX = 0;
// Check for an overpan
if (origX < -OVERPAN_LIMIT)
this._fireOverpan = OVERPAN_LEFT;
else if (origX > OVERPAN_LIMIT)
this._fireOverpan = OVERPAN_RIGHT;
} else {
var newPageX = Math.min(this.dragData.pageX + aX, offscreenWidth);
newPageX = Math.max(newPageX, 0);
aX = newPageX - this.dragData.pageX;
// Check for an overpan
if (origX < -OVERPAN_LIMIT && aX <= 0 && newPageX == 0)
this._fireOverpan = OVERPAN_LEFT;
else if (origX > OVERPAN_LIMIT && aX >= 0 && (offscreenWidth - newPageX) == 0)
this._fireOverpan = OVERPAN_RIGHT;
}
var offscreenHeight = contentAreaHeight - canvasH;
if (offscreenHeight <= 0) {
// Content is shorter than viewport, no need to pan vertically
aY = 0;
// Check for an overpan
if (origY < -OVERPAN_LIMIT)
this._fireOverpan = OVERPAN_TOP;
else if (origY > OVERPAN_LIMIT)
this._fireOverpan = OVERPAN_BOTTOM;
} else {
// min of 0, max of contentAreaHeight - canvasHeight
var newPageY = Math.min(this.dragData.pageY + aY, offscreenHeight);
newPageY = Math.max(newPageY, 0);
aY = newPageY - this.dragData.pageY;
// Check for an overpan
if (origY < -OVERPAN_LIMIT && aY <= 0 && newPageY == 0)
this._fireOverpan = OVERPAN_TOP;
else if (origY > OVERPAN_LIMIT && aY >= 0 && (offscreenHeight - newPageY) == 0)
this._fireOverpan = OVERPAN_BOTTOM;
}
return [aX, aY];
]]></body>
</method>
<method name="_moveCanvas">
<parameter name="aDx"/>
<parameter name="aDy"/>
<body><![CDATA[
// Constrain offsets to the actual scrollWidth/scrollHeight
var [x, y] = this._constrainPanCoords(aDx, aDy);
// Canvas needs to move up for content to scroll down
this.dragData.dragX = -x;
this.dragData.dragY = -y;
this._updateCanvasPosition();
]]></body>
</method>
<method name="_startKinetic">
<body><![CDATA[
var p2 = this._panEventTracker[this._panEventTrackerIndex];
var p1 = this._panEventTracker[(this._panEventTrackerIndex + 1) % this.PAN_EVENTS_TO_TRACK];
if (p2 && p1) {
var dx = p2.x - p1.x;
var dy = p2.y - p1.y;
var dt = p2.t - p1.t;
if (dt > 0) {
this.dragData.velocityX = -dx / dt;
this.dragData.velocityY = -dy / dt;
this.dragData.originalX = this.dragData.dragX;
this.dragData.originalY = this.dragData.dragY;
var [canvasWidth, canvasHeight] = this._effectiveCanvasDimensions;
var [contentAreaWidth, contentAreaHeight] = this._contentAreaDimensions;
let destX = Math.min(this.dragData.pageX - this.dragData.dragX + this.dragData.velocityX * 800, contentAreaWidth - canvasWidth);
let destY = Math.min(this.dragData.pageY - this.dragData.dragY + this.dragData.velocityY * 800, contentAreaHeight - canvasHeight);
destX = Math.max(destX, 0);
destY = Math.max(destY, 0);
this.dragData.destinationX = -destX + this.dragData.pageX;
this.dragData.destinationY = -destY + this.dragData.pageY;
this.dragData.kineticId = window.setInterval(this._doKinetic, dt / (this.PAN_EVENTS_TO_TRACK - 1), this, dt / (this.PAN_EVENTS_TO_TRACK - 1));
} else {
this._endPan();
}
} else {
this._endPan()
}
]]></body>
</method>
<method name="_doKinetic">
<parameter name="self"/>
<parameter name="dt"/>
<body><![CDATA[
let startX = self.dragData.dragX;
let startY = self.dragData.dragY;
const stopTheshold = 3; //Distance from the destination point that we'll consider to be "there"
if (Math.abs(self.dragData.destinationX - self.dragData.dragX) < stopTheshold) {
self.dragData.velocityX = 1/dt;
}
if (Math.abs(self.dragData.destinationY - self.dragData.dragY) < stopTheshold) {
self.dragData.velocityY = 1/dt;
}
let dx = self.dragData.originalX == self.dragData.destinationX ? 0 :
self.dragData.velocityX * dt *(Math.sqrt(Math.abs(self.dragData.destinationX - self.dragData.dragX)) /
Math.sqrt(Math.abs(self.dragData.destinationX - self.dragData.originalX)));
let dy = self.dragData.originalY == self.dragData.destinationY ? 0 :
self.dragData.velocityY * dt * (Math.sqrt(Math.abs(self.dragData.destinationY - self.dragData.dragY)) /
Math.sqrt(Math.abs(self.dragData.destinationY - self.dragData.originalY)));
let nextX = self.dragData.dragX - dx;
let nextY = self.dragData.dragY - dy;
if((self.dragData.originalX > nextX &&
nextX > self.dragData.destinationX) ||
(self.dragData.originalX < nextX &&
nextX < self.dragData.destinationX))
self.dragData.dragX = nextX;
else
self.dragData.dragX = self.dragData.destinationX;
if((self.dragData.originalY > nextY &&
nextY > self.dragData.destinationY) ||
(self.dragData.originalY < nextY &&
nextY < self.dragData.destinationY))
self.dragData.dragY = nextY;
else
self.dragData.dragY = self.dragData.destinationY;
self._updateCanvasPosition();
let actualDx = startX - self.dragData.dragX;
let actualDy = startY - self.dragData.dragY
if ((actualDx / (self.dragData.destinationX - self.dragData.originalX) < 0 && actualDy / (self.dragData.destinationY - self.dragData.originalY) < 0) || ( Math.abs(actualDx) < 4 && Math.abs(actualDy) < 4)) {
self._endKinetic();
}
]]></body>
</method>
<method name="_endKinetic">
<body><![CDATA[
window.clearInterval(this.dragData.kineticId);
this.dragData.kineticId = 0;
// dragX/dragY are guaranteed to be within the correct bounds, so just
// update pageX/pageY directly.
this.dragData.pageX -= this.dragData.dragX / this._zoomLevel;
this.dragData.pageY -= this.dragData.dragY / this._zoomLevel;
// relocate the canvas to 0x0 in the window
this.dragData.dragX = 0;
this.dragData.dragY = 0;
// update canvas position and draw the canvas at the new location
this._browserToCanvas();
this._updateCanvasPosition();
this.dragData.dragging = false;
]]></body>
</method>
<!-- Pans directly to a given X/Y (in page coordinates) -->
<method name="_panTo">
<parameter name="aX"/>
<parameter name="aY"/>
<body><![CDATA[
var [deltaX, deltaY] = this._constrainPanCoords(aX - this.dragData.pageX,
aY - this.dragData.pageY);
this.dragData.pageX += deltaX;
this.dragData.pageY += deltaY;
this._browserToCanvas();
]]></body>
</method>
<method name="_dragStartTimer">
<body><![CDATA[
this.dragData.lastMouseEvent = Date.now() - 10;
this.dragData.dragging = true;
this._dragStartTimeout = -1;
]]></body>
</method>
<method name="_endPan">
<body><![CDATA[
// dragX/dragY are garanteed to be within the correct bounds, so just
// update pageX/pageY directly.
this.dragData.pageX -= this.dragData.dragX / this._zoomLevel;
this.dragData.pageY -= this.dragData.dragY / this._zoomLevel;
// relocate the canvas to 0x0 in the window
this.dragData.dragX = 0;
this.dragData.dragY = 0;
// update canvas position and draw the canvas at the new location
this._browserToCanvas();
this._updateCanvasPosition();
this.dragData.dragging = false;
// Do we need to fire a content overpan event
if (this._fireOverpan > 0) {
var event = document.createEvent("UIEvents");
event.initUIEvent("overpan", true, false, window, this._fireOverpan);
this.dispatchEvent(event);
}
this._fireOverpan = 0;
]]></body>
</method>
<field name="stackEventHandler">
<![CDATA[
({
deckbrowser: this,
handleEvent: function seh_handleEvent(aEvent) {
if (!aEvent.type in this) {
Components.reportError("MouseController called with unknown event type " + aEvent.type + "\n");
return;
}
this[aEvent.type](aEvent);
},
mousedown: function seh_mousedown(aEvent) {
if (aEvent.button != 0)
return;
// cancel any pending canvas updates, since we're going to update again
if (this._updateTimeout)
clearTimeout(this._updateTimeout);
var zoomLevel = this.deckbrowser._zoomLevel;
var dragData = this.deckbrowser.dragData;
// The start of the current portion drag
dragData.sX = aEvent.screenX;
dragData.sY = aEvent.screenY;
// The total delta between current mouse position and sX/sY
dragData.dragX = 0;
dragData.dragY = 0;
//this.deckbrowser._updateCanvasPosition();
var self = this.deckbrowser;
this.deckbrowser._dragStartTimeout = setTimeout(function () {
self._dragStartTimer();
}, 200);
this._lastMouseDown = aEvent;
for (var i = 0; i < self.PAN_EVENTS_TO_TRACK; i++) {
self._panEventTracker[i] = null;
}
if (self.dragData.kineticId)
self._endPan();
},
mouseup: function seh_mouseup(aEvent) {
var skipKinetic = false;
if (aEvent.button == 0 && this.deckbrowser.dragData.dragging) {
if (this.deckbrowser._fireOverpan) {
this.deckbrowser._endPan();
skipKinetic = true;
} else {
this.deckbrowser.dragData.dragging = false;
}
} else if (aEvent.originalTarget == this.deckbrowser._canvas) {
// Mouseup on canvas that isn't releasing from a drag
// cancel scrollStart timer
clearTimeout(this.deckbrowser._dragStartTimeout);
this.deckbrowser._dragStartTimeout = -1;
// send mousedown & mouseup
this.deckbrowser._redispatchMouseEvent(this._lastMouseDown);
this._lastMouseDown = null;
this.deckbrowser._redispatchMouseEvent(aEvent);
// FIXME: dblclick events don't fire on the n810, check to see if
// we should treat this as a double-click
if (this._lastMouseUp &&
(aEvent.timeStamp - this._lastMouseUp.timeStamp) < 400 &&
Math.abs(aEvent.clientX - this._lastMouseUp.clientX) < 30 &&
Math.abs(aEvent.clientY - this._lastMouseUp.clientY) < 30) {
this.dblclick(aEvent);
return;
}
this._lastMouseUp = aEvent;
}
if (!skipKinetic)
this.deckbrowser._startKinetic();
for (var i = 0; i < this.deckbrowser.PAN_EVENTS_TO_TRACK; i++) {
this.deckbrowser._panEventTracker[i] = null;
}
},
mousemove: function seh_mousemove(aEvent) {
if (!this.deckbrowser.dragData.dragging) {
// If we've moved more than N pixels lets go ahead and assume we're dragging
// and not wait for the timeout to complete.
if (this.deckbrowser._dragStartTimeout != -1 &&
(Math.abs(this.deckbrowser.dragData.sX - aEvent.screenX) > 10 ||
Math.abs(this.deckbrowser.dragData.sY - aEvent.screenY) > 10)) {
clearTimeout(this.deckbrowser._dragStartTimeout);
this.deckbrowser._dragStartTimer();
} else {
return false;
}
}
var dx = this.deckbrowser.dragData.sX - aEvent.screenX;
var dy = this.deckbrowser.dragData.sY - aEvent.screenY;
// Filter out noise in big panning operations which are
// almost certainly intended to be on-axis horizontal or
// vertical pans.
if (Math.abs(dx) > 40 || Math.abs(dy) > 40) {
if (Math.abs(dx/dy) < 0.3) // dx is a lot less than dy, probably a vertical drag
dx = 0;
else if (Math.abs(dy/dx) < 0.3) // probably a horizontal drag
dy = 0;
}
let now = Date.now();
if (this.deckbrowser.dragData.dragging) {
this.deckbrowser._panEventTrackerIndex = (this.deckbrowser._panEventTrackerIndex + 1) % this.deckbrowser.PAN_EVENTS_TO_TRACK;
var pt = new Object();
pt.x = aEvent.screenX;
pt.y = aEvent.screenY;
pt.t = now;
this.deckbrowser._panEventTracker[this.deckbrowser._panEventTrackerIndex] = pt;
}
this.deckbrowser._moveCanvas(dx, dy);
if (now - this.deckbrowser.dragData.lastMouseEvent < 75) { // FIXME: make this a constant
//dump("dropping event\n");
return false;
}
this.deckbrowser.dragData.lastMouseEvent = now;
aEvent.preventDefault();
return true;
},
DOMMouseScroll: function seh_DOMMouseScroll(aEvent) {
this.deckbrowser.zoom(aEvent.detail);
},
dblclick: function seh_dblclick(aEvent) {
var target = aEvent.originalTarget;
var dragData = this.deckbrowser.dragData;
if (this.deckbrowser._zoomed) {
// reset zoom, pan state
this.deckbrowser._zoomLevel = this._oldZoomLevel;
[dragData.pageX, dragData.pageY] = [dragData.oldPageX, dragData.oldPageY];
this.deckbrowser._browserToCanvas();
this.deckbrowser._zoomed = false;
} else {
var element = this.deckbrowser.elementFromPoint(aEvent.clientX, aEvent.clientY);
if (!element) {
Components.utils.reportError("elementFromPoint returned null\n");
return;
}
// Find the nearest non-inline ancestor
while (element.parentNode) {
var display = window.getComputedStyle(element, "").getPropertyValue("display");
var zoomable = /table/.test(display) || /block/.test(display);
if (zoomable)
break;
element = element.parentNode;
}
// Remember pageX/pageY
[dragData.oldPageX, dragData.oldPageY] = [dragData.pageX, dragData.pageY];
this._oldZoomLevel = this.deckbrowser._zoomLevel;
this.deckbrowser.zoomToElement(element);
this.deckbrowser._zoomed = true;
}
}
});
]]>
</field>
</implementation>
</binding>
<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>
<method name="updateTab">
<parameter name="browser"/>
<body>
<![CDATA[
let canvas = document.getAnonymousElementByAttribute(this, "anonid", "canvas");
let domWin = browser.contentWindow;
let width = domWin.innerWidth;
let height = domWin.innerHeight;
let ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, 80, 60);
ctx.restore(); // XXXndeakin remove this
ctx.save();
ctx.scale(80 / width, 60 / height);
ctx.drawWindow(domWin, 0, 0, width, height, "rgba(0,0,0,0)");
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>
</bindings>