author | Ehsan Akhgari <ehsan@mozilla.com> |
Sat, 09 Apr 2011 23:26:53 -0400 | |
changeset 67791 | 56d25b561e19d465520d6d369d80b66b194df30c |
parent 67790 | f15bfa1cb14b56e067e1cfcf2f2eb75a6b27691d (current diff) |
parent 67754 | fe3f7889918b2c2a7a37e75d78d6fd436d3949a0 (diff) |
child 67792 | 57af1004637a729e035380ea1afb54236cbc7eff |
push id | 1 |
push user | root |
push date | Tue, 26 Apr 2011 22:38:44 +0000 |
treeherder | mozilla-beta@bfdb6e623a36 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 2.2a1pre |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
configure.in | file | annotate | diff | comparison | revisions |
--- a/configure.in +++ b/configure.in @@ -6209,16 +6209,24 @@ dnl ==================================== dnl = Universalchardet dnl ======================================================== MOZ_ARG_DISABLE_BOOL(universalchardet, [ --disable-universalchardet Disable universal encoding detection], MOZ_UNIVERSALCHARDET=, MOZ_UNIVERSALCHARDET=1 ) +if test -n "${JAVA_BIN_PATH}"; then + dnl Look for javac and jar in the specified path. + JAVA_PATH="$JAVA_BIN_PATH" +else + dnl No path specified, so look for javac and jar in $JAVA_HOME & $PATH. + JAVA_PATH="$JAVA_HOME/bin:$PATH" +fi + MOZ_PATH_PROG(JAVA, java, :, [$JAVA_PATH]) MOZ_PATH_PROG(JAVAC, javac, :, [$JAVA_PATH]) MOZ_PATH_PROG(JAR, jar, :, [$JAVA_PATH]) if test -n "${JAVA_BIN_PATH}" -o "$OS_TARGET" = Android; then if test -z "$JAVA" -o "$JAVA" = ":" -o -z "$JAVAC" -o "$JAVAC" = ":" -o -z "$JAR" -o "$JAR" = ":"; then AC_MSG_ERROR([The programs java, javac and jar were not found. Set \$JAVA_HOME to your java sdk directory or use --with-java-bin-path={java-bin-dir}]) fi
--- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -758,22 +758,16 @@ public: /** * Dynamic downcast to a Thebes layer. Returns null if this is not * a ThebesLayer. */ virtual ThebesLayer* AsThebesLayer() { return nsnull; } /** - * Dynamic cast to a ContainerLayer. Returns null if this is not - * a ContainerLayer. - */ - virtual ContainerLayer* AsContainerLayer() { return nsnull; } - - /** * Dynamic cast to a ShadowLayer. Return null if this is not a * ShadowLayer. Can be used anytime. */ virtual ShadowLayer* AsShadowLayer() { return nsnull; } // These getters can be used anytime. They return the effective // values that should be used when drawing this layer to screen, // accounting for this layer possibly being a shadow. @@ -1030,18 +1024,16 @@ public: */ void SetFrameMetrics(const FrameMetrics& aFrameMetrics) { mFrameMetrics = aFrameMetrics; } // These getters can be used anytime. - virtual ContainerLayer* AsContainerLayer() { return this; } - virtual Layer* GetFirstChild() { return mFirstChild; } virtual Layer* GetLastChild() { return mLastChild; } const FrameMetrics& GetFrameMetrics() { return mFrameMetrics; } MOZ_LAYER_DECL_NAME("ContainerLayer", TYPE_CONTAINER) /** * ContainerLayer backends need to override ComputeEffectiveTransforms
--- a/layout/base/FrameLayerBuilder.h +++ b/layout/base/FrameLayerBuilder.h @@ -54,19 +54,17 @@ class nsRootPresContext; namespace mozilla { enum LayerState { LAYER_NONE, LAYER_INACTIVE, LAYER_ACTIVE, // Force an active layer even if it causes incorrect rendering, e.g. // when the layer has rounded rect clips. - LAYER_ACTIVE_FORCE, - // Special layer that is metadata only. - LAYER_ACTIVE_EMPTY + LAYER_ACTIVE_FORCE }; /** * The FrameLayerBuilder belongs to an nsDisplayListBuilder and is * responsible for converting display lists into layer trees. * * The most important API in this class is BuildContainerLayerFor. This * method takes a display list as input and constructs a ContainerLayer
--- a/layout/base/nsDisplayItemTypes.h +++ b/layout/base/nsDisplayItemTypes.h @@ -79,17 +79,16 @@ enum Type { TYPE_PAGE_SEQUENCE, TYPE_PLUGIN, TYPE_PLUGIN_READBACK, TYPE_PRINT_PREVIEW_BACKGROUND, TYPE_PRINT_PLUGIN, TYPE_REMOTE, TYPE_REMOTE_SHADOW, TYPE_SCROLL_LAYER, - TYPE_SCROLL_INFO_LAYER, TYPE_SELECTION_OVERLAY, TYPE_SOLID_COLOR, TYPE_TABLE_CELL_BACKGROUND, TYPE_TABLE_CELL_SELECTION, TYPE_TABLE_ROW_BACKGROUND, TYPE_TABLE_ROW_GROUP_BACKGROUND, TYPE_TABLE_BORDER_BACKGROUND, TYPE_TEXT,
--- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -1831,35 +1831,16 @@ nsDisplayScrollLayer::ComputeVisibility( #ifdef NS_BUILD_REFCNT_LOGGING nsDisplayScrollLayer::~nsDisplayScrollLayer() { MOZ_COUNT_DTOR(nsDisplayScrollLayer); } #endif -nsDisplayScrollInfoLayer::nsDisplayScrollInfoLayer( - nsDisplayListBuilder* aBuilder, - nsDisplayList* aList, - nsIFrame* aForFrame, - nsIFrame* aViewportFrame) - : nsDisplayScrollLayer(aBuilder, aList, aForFrame, aViewportFrame) -{ -#ifdef NS_BUILD_REFCNT_LOGGING - MOZ_COUNT_CTOR(nsDisplayScrollInfoLayer); -#endif -} - -#ifdef NS_BUILD_REFCNT_LOGGING -nsDisplayScrollInfoLayer::~nsDisplayScrollInfoLayer() -{ - MOZ_COUNT_DTOR(nsDisplayScrollInfoLayer); -} -#endif - nsDisplayClip::nsDisplayClip(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayItem* aItem, const nsRect& aRect) : nsDisplayWrapList(aBuilder, aFrame, aItem) { MOZ_COUNT_CTOR(nsDisplayClip); mClip = SnapBounds(aBuilder->IsSnappingEnabled() && !aBuilder->IsInTransform(), aBuilder->CurrentPresContext(), aRect); }
--- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -1832,40 +1832,16 @@ public: // This causes incorrect rendering for rounded clips! return mozilla::LAYER_ACTIVE_FORCE; } private: nsIFrame* mViewportFrame; }; /** - * Like nsDisplayScrollLayer, but only has metadata on the scroll frame. This - * creates a layer that has no Thebes child layer, but still allows the - * compositor process to know of the scroll frame's existence. - */ -class nsDisplayScrollInfoLayer : public nsDisplayScrollLayer -{ -public: - nsDisplayScrollInfoLayer(nsDisplayListBuilder* aBuilder, nsDisplayList* aList, - nsIFrame* aForFrame, nsIFrame* aViewportFrame); - NS_DISPLAY_DECL_NAME("ScrollInfoLayer", TYPE_SCROLL_INFO_LAYER) - -#ifdef NS_BUILD_REFCNT_LOGGING - virtual ~nsDisplayScrollInfoLayer(); -#endif - - virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder, - LayerManager* aManager) - { - return mozilla::LAYER_ACTIVE_EMPTY; - } - -}; - -/** * nsDisplayClip can clip a list of items, but we take a single item * initially and then later merge other items into it when we merge * adjacent matching nsDisplayClips */ class nsDisplayClip : public nsDisplayWrapList { public: /** * @param aFrame the frame that should be considered the underlying
--- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -1956,68 +1956,55 @@ nsGfxScrollFrameInner::BuildDisplayList( // Not all our descendants will be clipped by overflow clipping, but all // the ones that aren't clipped will be out of flow frames that have already // had dirty rects saved for them by their parent frames calling // MarkOutOfFlowChildrenForDisplayList, so it's safe to restrict our // dirty rect here. dirtyRect.IntersectRect(aDirtyRect, mScrollPort); // Override the dirty rectangle if the displayport has been set. - PRBool usingDisplayport = - nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &dirtyRect); + nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &dirtyRect); nsDisplayListCollection set; nsPresContext* presContext = mOuter->PresContext(); // Since making new layers is expensive, only use nsDisplayScrollLayer // if the area is scrollable. // // Scroll frames can be generated with a scroll range that is 0, 0. // Furthermore, it is not worth the memory tradeoff to allow asynchronous // scrolling of small scroll frames. We use an arbitrary minimum scroll // range of 20 pixels to eliminate many gfx scroll frames from becoming a // layer. // + PRInt32 appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); nsRect scrollRange = GetScrollRange(); ScrollbarStyles styles = GetScrollbarStylesFromFrame(); mShouldBuildLayer = (XRE_GetProcessType() == GeckoProcessType_Content && (styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN || styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) && - (!mIsRoot || !mOuter->PresContext()->IsRootContentDocument())); + (scrollRange.width >= NSIntPixelsToAppUnits(20, appUnitsPerDevPixel) || + scrollRange.height >= NSIntPixelsToAppUnits(20, appUnitsPerDevPixel))) && + (!mIsRoot || !mOuter->PresContext()->IsRootContentDocument()); if (ShouldBuildLayer()) { + // Note that using StackingContext breaks z order, so the resulting + // rendering can be incorrect for weird edge cases! + nsDisplayList list; - if (usingDisplayport) { - // Once a displayport is set, assume that scrolling needs to be fast - // so create a layer with all the content inside. The compositor - // process will be able to scroll the content asynchronously. - // - // Note that using StackingContext breaks z order, so the resulting - // rendering can be incorrect for weird edge cases! - - rv = mScrolledFrame->BuildDisplayListForStackingContext( - aBuilder, dirtyRect + mOuter->GetOffsetTo(mScrolledFrame), &list); - - nsDisplayScrollLayer* layerItem = new (aBuilder) nsDisplayScrollLayer( - aBuilder, &list, mScrolledFrame, mOuter); - set.Content()->AppendNewToTop(layerItem); - } else { - // If there is no displayport set, there is no reason here to force a - // layer that needs a memory-expensive allocation, but the compositor - // process would still like to know that it exists. - - nsDisplayScrollLayer* layerItem = new (aBuilder) nsDisplayScrollInfoLayer( - aBuilder, &list, mScrolledFrame, mOuter); - set.Content()->AppendNewToTop(layerItem); - - rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set); - } - } else { + rv = mScrolledFrame->BuildDisplayListForStackingContext( + aBuilder, dirtyRect + mOuter->GetOffsetTo(mScrolledFrame), &list); + + nsDisplayScrollLayer* layerItem = new (aBuilder) nsDisplayScrollLayer( + aBuilder, &list, mScrolledFrame, mOuter); + set.Content()->AppendNewToTop(layerItem); + } else + { rv = mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, dirtyRect, set); } NS_ENSURE_SUCCESS(rv, rv); nsRect clip; clip = mScrollPort + aBuilder->ToReferenceFrame(mOuter); nscoord radii[8];
--- a/layout/ipc/RenderFrameParent.cpp +++ b/layout/ipc/RenderFrameParent.cpp @@ -134,18 +134,23 @@ FindViewForId(const ViewMap& aMap, ViewI { ViewMap::const_iterator iter = aMap.find(aId); return iter != aMap.end() ? iter->second : NULL; } static const FrameMetrics* GetFrameMetrics(Layer* aLayer) { - ContainerLayer* container = aLayer->AsContainerLayer(); - return container ? &container->GetFrameMetrics() : NULL; + // Children are not container layers, so they don't have frame metrics. Give + // them a blank metric. + if (!aLayer->GetFirstChild()) + return NULL; + + ContainerLayer* container = static_cast<ContainerLayer*>(aLayer); + return &container->GetFrameMetrics(); } static nsIntPoint GetRootFrameOffset(nsIFrame* aContainerFrame, nsDisplayListBuilder* aBuilder) { nscoord auPerDevPixel = aContainerFrame->PresContext()->AppUnitsPerDevPixel(); // Offset to the content rect in case we have borders or padding @@ -336,19 +341,20 @@ IsTempLayerManager(LayerManager* aManage // aXScale and aYScale are used to calculate any values that need to be in // chrome-document CSS pixels and aren't part of the rendering loop, such as // the initial scroll offset for a new view. static void BuildViewMap(ViewMap& oldContentViews, ViewMap& newContentViews, nsFrameLoader* aFrameLoader, Layer* aLayer, float aXScale = 1, float aYScale = 1) { - ContainerLayer* container = aLayer->AsContainerLayer(); - if (!container) + if (!aLayer->GetFirstChild()) return; + + ContainerLayer* container = static_cast<ContainerLayer*>(aLayer); const FrameMetrics metrics = container->GetFrameMetrics(); const ViewID scrollId = metrics.mScrollId; if (metrics.IsScrollable()) { nscoord auPerDevPixel = aFrameLoader->GetPrimaryFrameOfOwningContent() ->PresContext()->AppUnitsPerDevPixel(); nsContentView* view = FindViewForId(oldContentViews, scrollId); if (view) {
--- a/mobile/app/mobile.js +++ b/mobile/app/mobile.js @@ -390,19 +390,19 @@ pref("javascript.options.methodjit.chrom pref("dom.max_chrome_script_run_time", 0); // disable slow script dialog for chrome pref("dom.max_script_run_time", 20); // JS error console pref("devtools.errorconsole.enabled", false); // kinetic tweakables -pref("browser.ui.kinetic.updateInterval", 16); -pref("browser.ui.kinetic.exponentialC", 1400); -pref("browser.ui.kinetic.polynomialC", 100); +pref("browser.ui.kinetic.updateInterval", 30); +pref("browser.ui.kinetic.decelerationRate", 20); +pref("browser.ui.kinetic.speedSensitivity", 80); pref("browser.ui.kinetic.swipeLength", 160); // zooming pref("browser.ui.zoom.pageFitGranularity", 9); // don't zoom to fit by less than 1/9 (11%) pref("browser.ui.zoom.animationDuration", 200); // ms duration of double-tap zoom animation pref("browser.ui.zoom.reflow", false); // Change text wrapping on double-tap pref("browser.ui.zoom.reflow.fontSize", 720);
--- a/mobile/chrome/content/input.js +++ b/mobile/chrome/content/input.js @@ -55,23 +55,16 @@ const kOverTapWait = 150; const kLongTapWait = 500; // maximum drag distance in inches while axis locking can still be reverted const kAxisLockRevertThreshold = 0.8; // Same as NS_EVENT_STATE_ACTIVE from nsIEventStateManager.h const kStateActive = 0x00000001; -// After a drag begins, kinetic panning is stopped if the drag doesn't become -// a pan in 300 milliseconds. -const kStopKineticPanOnDragTimeout = 300; - -// Max velocity of a pan. This is in pixels/millisecond. -const kMaxVelocity = 6; - /** * MouseModule * * Handles all touch-related input such as dragging and tapping. * * The Fennec chrome DOM tree has elements that are augmented dynamically with * custom JS properties that tell the MouseModule they have custom support for * either dragging or clicking. These JS properties are JS objects that expose @@ -207,31 +200,29 @@ MouseModule.prototype = { // stop kinetic panning if targetScrollbox has changed if (this._kinetic.isActive() && this._dragger != dragger) this._kinetic.end(); this._targetScrollbox = targetScrollInterface ? targetScrollInterface.element : targetScrollbox; this._targetScrollInterface = targetScrollInterface; // Do tap - if (!this._kinetic.isActive()) { - let event = document.createEvent("Events"); - event.initEvent("TapDown", true, true); - event.clientX = aEvent.clientX; - event.clientY = aEvent.clientY; - let success = aEvent.target.dispatchEvent(event); - if (success) { - this._recordEvent(aEvent); - this._target = aEvent.target; - this._mouseOverTimeout.once(kOverTapWait); - this._longClickTimeout.once(kLongTapWait); - } else { - // cancel all pending content clicks - this._cleanClickBuffer(); - } + let event = document.createEvent("Events"); + event.initEvent("TapDown", true, true); + event.clientX = aEvent.clientX; + event.clientY = aEvent.clientY; + let success = aEvent.target.dispatchEvent(event); + if (success) { + this._recordEvent(aEvent); + this._target = aEvent.target; + this._mouseOverTimeout.once(kOverTapWait); + this._longClickTimeout.once(kLongTapWait); + } else { + // cancel all pending content clicks + this._cleanClickBuffer(); } // Do pan if (dragger) { let draggable = dragger.isDraggable(targetScrollbox, targetScrollInterface); dragData.locked = !draggable.x || !draggable.y; if (draggable.x || draggable.y) { this._dragger = dragger; @@ -332,18 +323,21 @@ MouseModule.prototype = { // let [sX, sY] = dragData.panPosition(); this.dX += dragData.prevPanX - sX; this.dY += dragData.prevPanY - sY; if (dragData.isPan()) { // Only pan when mouse event isn't part of a click. Prevent jittering on tap. this._kinetic.addData(sX - dragData.prevPanX, sY - dragData.prevPanY); - this._dragBy(this.dX, this.dY); - // dragBy will reset dX and dY values to 0. + if (!this._waitingForPaint) { + this._dragBy(this.dX, this.dY); + this.dX = 0; + this.dY = 0; + } // Let everyone know when mousemove begins a pan if (!oldIsPan && dragData.isPan()) { this._mouseOverTimeout.clear(); this._longClickTimeout.clear(); let event = document.createEvent("Events"); event.initEvent("PanBegin", true, false); @@ -362,67 +356,55 @@ MouseModule.prototype = { /** * Inform our dragger of a dragStart. */ _doDragStart: function _doDragStart(aEvent, aDraggable) { let dragData = this._dragData; dragData.setDragStart(aEvent.screenX, aEvent.screenY, aDraggable); this._kinetic.addData(0, 0); - this._dragStartTime = Date.now(); if (!this._kinetic.isActive()) this._dragger.dragStart(aEvent.clientX, aEvent.clientY, aEvent.target, this._targetScrollInterface); }, /** Finish a drag. */ _doDragStop: function _doDragStop() { let dragData = this._dragData; if (!dragData.dragging) return; dragData.endDrag(); // Note: it is possible for kinetic scrolling to be active from a // mousedown/mouseup event previous to this one. In this case, we // want the kinetic panner to tell our drag interface to stop. - if (dragData.isPan()) { - if (Date.now() - this._dragStartTime > kStopKineticPanOnDragTimeout) - this._kinetic._velocity.set(0, 0); + if (!dragData.isPan() && !this._kinetic.isActive()) { + // There was no pan and no kinetic scrolling, so just stop dragger. + this._dragger.dragStop(0, 0, this._targetScrollInterface); + this._dragger = null; + } else if (dragData.isPan()) { // Start kinetic pan. this._kinetic.start(); - } else { - this._kinetic.end(); - this._dragger.dragStop(0, 0, this._targetScrollInterface); - this._dragger = null; } }, /** * Used by _onMouseMove() above and by KineticController's timer to do the * actual dragMove signalling to the dragger. We'd put this in _onMouseMove() * but then KineticController would be adding to its own data as it signals * the dragger of dragMove()s. */ _dragBy: function _dragBy(dX, dY, aIsKinetic) { - let dragged = true; let dragData = this._dragData; - if (!this._waitingForPaint || aIsKinetic) { - let dragData = this._dragData; - dragged = this._dragger.dragMove(dX, dY, this._targetScrollInterface, aIsKinetic); - if (dragged && !this._waitingForPaint) { - this._waitingForPaint = true; - mozRequestAnimationFrame(this); - } - this.dX = 0; - this.dY = 0; + let dragged = this._dragger.dragMove(dX, dY, this._targetScrollInterface, aIsKinetic); + if (dragged && !this._waitingForPaint) { + this._waitingForPaint = true; + mozRequestAnimationFrame(this); } - if (!dragData.isPan()) - this._kinetic.pause(); - return dragged; }, /** Callback for kinetic scroller. */ _kineticStop: function _kineticStop() { // Kinetic panning could finish while user is panning, so don't finish // the pan just yet. let dragData = this._dragData; @@ -829,130 +811,122 @@ function KineticController(aPanBy, aEndC this._velocity = new Point(0, 0); this._acceleration = new Point(0, 0); this._time = 0; this._timeStart = 0; // How often do we change the position of the scroll pane? Too often and panning may jerk near // the end. Too little and panning will be choppy. In milliseconds. this._updateInterval = Services.prefs.getIntPref("browser.ui.kinetic.updateInterval"); - // Constants that affect the "friction" of the scroll pane. - this._exponentialC = Services.prefs.getIntPref("browser.ui.kinetic.exponentialC"); - this._polynomialC = Services.prefs.getIntPref("browser.ui.kinetic.polynomialC") / 1000000; + // "Friction" of the scroll pane. The lower, the less friction and the further distance traveled. + this._decelerationRate = Services.prefs.getIntPref("browser.ui.kinetic.decelerationRate") / 10000; + // A multiplier for the initial velocity of the movement. + this._speedSensitivity = Services.prefs.getIntPref("browser.ui.kinetic.speedSensitivity") / 100; // Number of milliseconds that can contain a swipe. Movements earlier than this are disregarded. this._swipeLength = Services.prefs.getIntPref("browser.ui.kinetic.swipeLength"); this._reset(); } KineticController.prototype = { _reset: function _reset() { this._active = false; - this._paused = false; this.momentumBuffer = []; this._velocity.set(0, 0); }, isActive: function isActive() { return this._active; }, _startTimer: function _startTimer() { - let self = this; + // Use closed form of a parabola to calculate each position for panning. + // x(t) = v0*t + .5*t^2*a + // where: v0 is initial velocity + // a is acceleration + // t is time elapsed + // + // x(t) + // ^ + // | | + // | + // | | + // | ....^^^^.... + // | ...^^ | ^^... + // | ...^ ^... + // |.. | .. + // -----------------------------------> t + // t0 tf=-v0/a + // + // Using this formula, distance moved is independent of the time between each frame, unlike time + // step approaches. Once the time is up, set the position to x(tf) and stop the timer. - let lastp = this._position; // track last position vector because pan takes deltas + let lastx = this._position; // track last position vector because pan takes differences let v0 = this._velocity; // initial velocity let a = this._acceleration; // acceleration - let c = this._exponentialC; - let p = new Point(0, 0); - let dx, dy, t, realt; - function calcP(v0, a, t) { - // Important traits for this function: - // p(t=0) is 0 - // p'(t=0) is v0 - // - // We use exponential to get a smoother stop, but by itself exponential - // is too smooth at the end. Adding a polynomial with the appropriate - // weight helps to balance - return v0 * Math.exp(-t / c) * -c + a * t * t + v0 * c; - } - - this._calcV = function(v0, a, t) { - return v0 * Math.exp(-t / c) + 2 * a * t; - } + // Temporary "bins" so that we don't create new objects during pan. + let aBin = new Point(0, 0); + let v0Bin = new Point(0, 0); + let self = this; let callback = { onBeforePaint: function kineticHandleEvent(timeStamp) { - // Someone called end() on us between timer intervals - // or we are paused. - if (!self.isActive() || self._paused) + if (!self.isActive()) // someone called end() on us between timer intervals return; // To make animation end fast enough but to keep smoothness, average the ideal // time frame (smooth animation) with the actual time lapse (end fast enough). // Animation will never take longer than 2 times the ideal length of time. - realt = timeStamp - self._initialTime; + let realt = timeStamp - self._initialTime; self._time += self._updateInterval; - t = (self._time + realt) / 2; + let t = (self._time + realt) / 2; - // Calculate new position. - p.x = calcP(v0.x, a.x, t); - p.y = calcP(v0.y, a.y, t); - dx = Math.round(p.x - lastp.x); - dy = Math.round(p.y - lastp.y); + // Calculate new position using x(t) formula. + let x = v0Bin.set(v0).scale(t).add(aBin.set(a).scale(0.5 * t * t)); + let dx = x.x - lastx.x; + let dy = x.y - lastx.y; + lastx.set(x); - // Test to see if movement is finished for each component. - if (dx * a.x > 0) { - dx = 0; - lastp.x = 0; + // Test to see if movement is finished for each component. As seen in graph, we want the + // final position to be at tf. + if (t >= -v0.x / a.x) { + // Plug in t=-v0/a into x(t) to get final position. + dx = -v0.x * v0.x / 2 / a.x - lastx.x; + // Reset components. Next frame: a's component will be 0 and t >= NaN will be false. + lastx.x = 0; v0.x = 0; a.x = 0; } // Symmetric to above case. - if (dy * a.y > 0) { - dy = 0; - lastp.y = 0; + if (t >= -v0.y / a.y) { + dy = -v0.y * v0.y / 2 / a.y - lastx.y; + lastx.y = 0; v0.y = 0; a.y = 0; } - if (v0.x == 0 && v0.y == 0) { + let panned = false; + try { panned = self._panBy(Math.round(-dx), Math.round(-dy), true); } catch (e) {} + if (!panned) self.end(); - } else { - let panStop = false; - if (dx != 0 || dy != 0) { - try { panStop = !self._panBy(-dx, -dy, true); } catch (e) {} - lastp.add(dx, dy); - } - - if (panStop) - self.end(); - else - mozRequestAnimationFrame(this); - } + else + mozRequestAnimationFrame(this); } }; this._active = true; - this._paused = false; mozRequestAnimationFrame(callback); }, start: function start() { function sign(x) { return x ? ((x > 0) ? 1 : -1) : 0; } - function clampFromZero(x, closerToZero, furtherFromZero) { - if (x >= 0) - return Math.max(closerToZero, Math.min(furtherFromZero, x)); - return Math.min(-closerToZero, Math.max(-furtherFromZero, x)); - } - let mb = this.momentumBuffer; let mblen = this.momentumBuffer.length; let lastTime = mb[mblen - 1].t; let distanceX = 0; let distanceY = 0; let swipeLength = this._swipeLength; @@ -961,53 +935,34 @@ KineticController.prototype = { for (let i = 0; i < mblen; i++) { me = mb[i]; if (lastTime - me.t < swipeLength) { distanceX += me.dx; distanceY += me.dy; } } - let currentVelocityX = 0; - let currentVelocityY = 0; - - if (this.isActive()) { - // If active, then we expect this._calcV to be defined. - let currentTime = Date.now() - this._initialTime; - currentVelocityX = Util.clamp(this._calcV(this._velocity.x, this._acceleration.x, currentTime), -kMaxVelocity, kMaxVelocity); - currentVelocityY = Util.clamp(this._calcV(this._velocity.y, this._acceleration.y, currentTime), -kMaxVelocity, kMaxVelocity); - } - - if (currentVelocityX * this._velocity.x <= 0) - currentVelocityX = 0; - if (currentVelocityY * this._velocity.y <= 0) - currentVelocityY = 0; - - let swipeTime = Math.min(swipeLength, lastTime - mb[0].t); - this._velocity.x = clampFromZero((distanceX / swipeTime) + currentVelocityX, Math.abs(currentVelocityX), 6); - this._velocity.y = clampFromZero((distanceY / swipeTime) + currentVelocityY, Math.abs(currentVelocityY), 6); + // Only allow kinetic scrolling to speed up if kinetic scrolling is active. + this._velocity.x = (distanceX < 0 ? Math.min : Math.max)((distanceX / swipeLength) * this._speedSensitivity, this._velocity.x); + this._velocity.y = (distanceY < 0 ? Math.min : Math.max)((distanceY / swipeLength) * this._speedSensitivity, this._velocity.y); // Set acceleration vector to opposite signs of velocity - this._acceleration.set(this._velocity.clone().map(sign).scale(-this._polynomialC)); + this._acceleration.set(this._velocity.clone().map(sign).scale(-this._decelerationRate)); this._position.set(0, 0); this._initialTime = mozAnimationStartTime; this._time = 0; this.momentumBuffer = []; - if (!this.isActive() || this._paused) + if (!this.isActive()) this._startTimer(); return true; }, - pause: function pause() { - this._paused = true; - }, - end: function end() { if (this.isActive()) { if (this._beforeEnd) this._beforeEnd(); this._reset(); } },