From 19e225ec8fcd5191b3de886467a6a99f31adceb1 Mon Sep 17 00:00:00 2001 From: Roy Frostig Date: Mon, 27 Jul 2009 19:04:22 -0700 Subject: [PATCH] Changes to KineticController to stop it from painting as it scrolls. Added sidebar snapping. More debug dumps for the content click redispatch bug. --- mobile/chrome/content/InputHandler.js | 88 ++++++++++++++++++------ mobile/chrome/content/browser.js | 98 +++++++++++++++++++++++---- 2 files changed, 150 insertions(+), 36 deletions(-) diff --git a/mobile/chrome/content/InputHandler.js b/mobile/chrome/content/InputHandler.js index 32d2aa8531ae..97914f77940b 100644 --- a/mobile/chrome/content/InputHandler.js +++ b/mobile/chrome/content/InputHandler.js @@ -95,6 +95,43 @@ function InputHandler() { } +/** + * The input handler is an arbiter between the Fennec chrome window inputs and any + * registered input modules. It keeps an array of input module objects. Incoming + * input events are wrapped in an EventInfo object and passed down to the input modules + * in the order of the modules array. + * + * Input modules must provide the following interface: + * + * handleEvent(evInfo) + * Entry point by which InputHandler passes wrapped Fennec chrome window events + * to the module. + * + * cancelPending() + * Called by the InputHandler as a hint to the module that it may wish to reset + * whatever state it might have entered by processing events thus far. For + * instance, a module may have grabbed (cf grab()) focus, in which case the + * InputHandler will call cancelPending() on all remaining modules. + * + * + * + * An input module may wish to grab event focus of the InputHandler, which means that it + * wants to process all incoming events for a while. When the InputHandler is grabbed + * by one of its modules, only that module will receive incoming events until it ungrabs + * the InputHandler. No other modules' handleEvent() function will be called while the + * InputHandler is grabbed. Grabs and ungrabs of the InputHandler require an object reference + * corresponding to the grabbing object. That is, a module must call inputHandler.grab(this) + * and .ungrab(this) in order for the calls to succeed. The object given as the argument + * will be that which is given event focus. grab/ungrab may be nested (that is, a module can + * grab as many times as it wants to) provided that they are one-to-one. That is, if a + * module grabs four times, it should be sure to ungrab that many times as well. Lastly, + * an input module may, in ungrabbing, provide an array of queued EventInfo objects, each of + * which will be passed by the InputHandler to each of the subsequent modules ("subsequent" + * as in "next in the ordering within the modules array") via handleEvent(). This can be + * thought of as the module's way of saying "sorry for grabbing focus, here's everything I + * kept you from processing this whole time" to the modules of lower priority. To prevent + * infinite loops, this event queue is only passed to lower-priority modules. + */ InputHandler.prototype = { /** @@ -110,7 +147,7 @@ InputHandler.prototype = { */ // XXX froystig: grab(null) is supported because the old grab() supported // it, but I'm not sure why. The comment on that was "grab(null) is - // allowed because of mouseout handling. Feel free to remove if that + // allowed because of mouseout handling". Feel free to remove if that // is no longer relevant, or remove this comment if it still is. grab: function grab(grabber) { if (grabber == null) { @@ -148,7 +185,7 @@ InputHandler.prototype = { * events all along. */ // XXX froystig: ungrab(null) is supported here too because the old ungrab() - // happened to support it (not sure if intentionally --- there was no + // happened to support it (not sure if intentionally; there was no // comment it), so cf the corresponding comment on grab(). ungrab: function ungrab(grabber, restoreEventInfos) { if (this._grabber == null && grabber == null) { @@ -242,7 +279,7 @@ function MouseModule(owner) { this._owner = owner; this._dragData = new DragData(this, 50, 200); - this._dragger = this._defaultDragger; + this._dragger = null; this._clicker = null; this._downUpEvents = []; @@ -253,7 +290,10 @@ function MouseModule(owner) { this._fastPath = false; var self = this; - this._kinetic = new KineticController( function (dx, dy) { return self._dragBy(dx, dy); } ); + this._kinetic = new KineticController( + function _dragByBound(dx, dy) { return self._dragBy(dx, dy); }, + function _dragStopBound() { return self._doDragStop(0, 0, true); } + ); } @@ -284,6 +324,8 @@ MouseModule.prototype = { _onMouseDown: function _onMouseDown(evInfo) { this._owner.allowClicks(); + if (this._kinetic.isActive()) + this._kinetic.end(); // walk up the DOM tree in search of nearest scrollable ancestor. nulls are // returned if none found. @@ -294,13 +336,8 @@ MouseModule.prototype = { this._targetScrollInterface = targetScrollInterface; - // fast path: we have no scrollable element nor any element with custom clicker, so - // just let the event bubble on through - //this._fastPath = !targetScrollInterface && !targetClicker; - //if (this._fastPath) - // return; - - this._dragger = (targetScrollInterface && targetScrollbox.customDragger) || this._defaultDragger; + this._dragger = (targetScrollInterface) ? (targetScrollbox.customDragger || this._defaultDragger) + : null; this._clicker = (targetClicker) ? targetClicker.customClicker : null; evInfo.event.stopPropagation(); @@ -309,7 +346,6 @@ MouseModule.prototype = { this._owner.grab(this); if (targetScrollInterface) { - this._kinetic.end(); this._doDragStart(evInfo.event.screenX, evInfo.event.screenY); } @@ -396,18 +432,22 @@ MouseModule.prototype = { this._dragger.dragStart(this._targetScrollInterface); }, - _doDragStop: function _doDragStop(sX, sY) { - let dragData = this._dragData; + _doDragStop: function _doDragStop(sX, sY, kineticStop) { + if (!kineticStop) { // we're not really done, since now it is + // kinetic's turn to scroll about + let dragData = this._dragData; - let dx = dragData.sX - sX; - let dy = dragData.sY - sY; + let dx = dragData.sX - sX; + let dy = dragData.sY - sY; - dragData.setDragPosition(sX, sY); - this._kinetic.addData(sX, sY); + dragData.reset(); + this._kinetic.addData(sX, sY); - this._dragger.dragStop(dx, dy, this._targetScrollInterface); + this._kinetic.start(); - this._kinetic.start(); + } else { // now we're done, says our secret 3rd argument + this._dragger.dragStop(0, 0, this._targetScrollInterface); + } }, _doDragMove: function _doDragMove(sX, sY) { @@ -447,12 +487,14 @@ MouseModule.prototype = { * */ _commitAnotherClick: function _commitAnotherClick() { + this._doSingleClick(); + /* if (this._clickTimeout) { // we're waiting for a second click for double window.clearTimeout(this._clickTimeout); this._doDoubleClick(); } else { this._clickTimeout = window.setTimeout(function _clickTimeout(self) { self._doSingleClick(); }, 400, this); - } + }*/ }, /** @@ -667,9 +709,10 @@ DragData.prototype = { * generated by the kinetic algorithm. It should return true if the * object was panned, false if there was no movement. */ -function KineticController(aPanBy) { +function KineticController(aPanBy, aEndCallback) { this._panBy = aPanBy; this._timer = null; + this._beforeEnd = aEndCallback; try { this._updateInterval = gPrefService.getIntPref("browser.ui.kinetic.updateInterval"); @@ -782,6 +825,7 @@ KineticController.prototype = { }, end: function end() { + this._beforeEnd(); this._reset(); }, diff --git a/mobile/chrome/content/browser.js b/mobile/chrome/content/browser.js index 9cf2cd43d583..b9352e3e531e 100644 --- a/mobile/chrome/content/browser.js +++ b/mobile/chrome/content/browser.js @@ -27,6 +27,7 @@ * Johnathan Nightingale * Stuart Parmenter * Taras Glek + * Roy Frostig * * 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 @@ -89,6 +90,7 @@ function debug() { dump('visibleRect from foo: ' + getVisibleRect().toString() + endl); dump('batch depth: ' + bv._batchOps.length + endl); + dump('renderpause depth: ' + bv._renderMode + endl); dump(endl); @@ -233,12 +235,12 @@ function screenToBrowserView(x, y) { } // Return the visible rect in terms of the tile container -function getVisibleRect() { +function getVisibleRect(noTranslate) { let container = document.getElementById("tile-container"); let containerBCR = container.getBoundingClientRect(); - let x = Math.round(-containerBCR.left); - let y = Math.round(-containerBCR.top); + let x = Math.round(noTranslate ? 0 : -containerBCR.left); + let y = Math.round(noTranslate ? 0 : -containerBCR.top); let w = window.innerWidth; let h = window.innerHeight; @@ -279,12 +281,17 @@ var Browser = { }, dragStop: function dragStop(dx, dy, scroller) { - let ret = this.dragMove(dx, dy, scroller); + let dx = this.dragMove(dx, dy, scroller, true); + + let snapdx = Browser.snapSidebars(scroller); + bv.onAfterVisibleMove(snapdx, 0); + bv.resumeRendering(); - return ret; + + return (dy != 0) || ((dx + snapdx) != 0); }, - dragMove: function dragMove(dx, dy, scroller) { + dragMove: function dragMove(dx, dy, scroller, doReturnDX) { bv.onBeforeVisibleMove(dx, dy); let [x0, y0] = getScrollboxPosition(scroller); @@ -302,7 +309,7 @@ var Browser = { dump('--> scroll asked for ' + dx + ',' + dy + ' and got ' + realdx + ',' + realdy + '\n'); } - return !(realdx == 0 && realdy == 0); + return (doReturnDX) ? realdx : !(realdx == 0 && realdy == 0); } }; @@ -378,12 +385,12 @@ var Browser = { bv.commitBatchOperation(); } window.addEventListener("resize", resizeHandler, false); - + function fullscreenHandler() { if (!window.fullScreen) document.getElementById("toolbar-main").setAttribute("fullscreen", "true"); else - document.getElementById("toolbar-main").removeAttribute("fullscreen"); + document.getElementById("toolbar-main").removeAttribute("fullscreen"); } window.addEventListener("fullscreen", fullscreenHandler, false); @@ -837,9 +844,9 @@ var Browser = { singleClick: function singleClick(cX, cY) { let browser = browserView.getBrowser(); if (browser) { - dump('singleClick was invoked with ' + cX + ', ' + cY + '\n'); + dump('singleClick was invoked with ' + cX + ', ' + cY + '\n'); let [x, y] = transformScreenToBrowser(cX, cY); - dump('dispatching in browser ' + x + ', ' + y + '\n'); + dump('dispatching in browser ' + x + ', ' + y + '\n'); dispatchContentClick(browser, x, y); } }, @@ -850,10 +857,10 @@ var Browser = { let zoomElement = elementFromPoint(browser, cX2, cY2); if (zoomElement) { - // TODO actually zoom to and from element + // TODO actually zoom to and from element //browserView.zoom(this.zoomDir); - //this.zoomDir *= -1; - dump('zooming to/from element: ' + zoomElement + '\n'); + //this.zoomDir *= -1; + dump('zooming to/from element: ' + zoomElement + '\n'); } //let [x, y] = transformScreenToBrowser(cX1, cY1); @@ -863,6 +870,57 @@ var Browser = { } } }; + }, + + /** + * Returns dx of snap + */ + snapSidebars: function snapSidebars(scroller) { + function visibility(bar, visrect) { + try { + let w = bar.width; + let h = bar.height; + bar.restrictTo(visrect); // throws exception if intersection of rects is empty + return [bar.width / w, bar.height / h]; + } catch (e) { + return [0, 0]; + } + } + + let visrect = getVisibleRect(true); + let leftbarCBR = document.getElementById('tabs-container').getBoundingClientRect(); + let ritebarCBR = document.getElementById('browser-controls').getBoundingClientRect(); + + let leftbar = new wsRect(leftbarCBR.left, leftbarCBR.top, leftbarCBR.width, leftbarCBR.height); + let ritebar = new wsRect(ritebarCBR.left, ritebarCBR.top, ritebarCBR.width, ritebarCBR.height); + let leftw = leftbar.width; + let ritew = ritebar.width; + + let [leftvis, ] = visibility(leftbar, visrect); + let [ritevis, ] = visibility(ritebar, visrect); + + let snappedX = 0; + + if (leftvis != 0 && leftvis != 1) { + if (leftvis >= 0.6666) + snappedX = -((1 - leftvis) * leftw); + else + snappedX = leftvis * leftw; + + snappedX = Math.round(snappedX); + scroller.scrollBy(snappedX, 0); + } + else if (ritevis != 0 && ritevis != 1) { + if (ritevis >= 0.6666) + snappedX = (1 - ritevis) * ritew; + else + snappedX = -ritevis * ritew; + + snappedX = Math.round(snappedX); + scroller.scrollBy(snappedX, 0); + } + + return snappedX; } }; @@ -1677,9 +1735,16 @@ Tab.prototype = { }, load: function(uri) { + dump('browser 3: ' + this._browser.contentWindow + '\n'); dump("cb set src\n"); this._browser.setAttribute("src", uri); dump("cb end src\n"); + dump('browser 4: ' + this._browser.contentWindow + '\n'); + try { + dump('QIs to: ' + this._browser.contentWindow.QueryInterface(Ci.nsIDOMChromeWindow) + '\n'); + } catch (e) { + dump('failed to QI\n'); + } }, create: function() { @@ -1697,6 +1762,7 @@ Tab.prototype = { }, _createBrowser: function() { + dump('for the break\n'); if (this._browser) throw "Browser already exists"; @@ -1704,6 +1770,8 @@ Tab.prototype = { let scaledHeight = kDefaultBrowserWidth * (window.innerHeight / window.innerWidth); let browser = this._browser = document.createElement("browser"); + dump('browser 1: ' + browser.contentWindow + '\n'); + browser.setAttribute("style", "overflow: -moz-hidden-unscrollable; visibility: hidden; width: " + kDefaultBrowserWidth + "px; height: " + scaledHeight + "px;"); browser.setAttribute("type", "content"); @@ -1720,6 +1788,8 @@ Tab.prototype = { // stop about:blank from loading browser.stop(); + dump('browser 2: ' + browser.contentWindow + '\n'); + // Attach a separate progress listener to the browser this._listener = new ProgressController(this); browser.addProgressListener(this._listener);