From 4a210130b4babf759f1aab9ba45411ba306a2a9f Mon Sep 17 00:00:00 2001 From: Andrew Joslin Date: Mon, 19 May 2014 08:13:45 -0600 Subject: [PATCH] fix(scrollView): stop memory-leak when destroying scrollView Fixes #1096 --- js/angular/controller/scrollController.js | 1 + js/utils/dom.js | 11 ++++ js/views/scrollView.js | 72 +++++++++++++++-------- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/js/angular/controller/scrollController.js b/js/angular/controller/scrollController.js index c8619118f32..16975a5b5cb 100644 --- a/js/angular/controller/scrollController.js +++ b/js/angular/controller/scrollController.js @@ -51,6 +51,7 @@ function($scope, scrollViewOptions, $timeout, $window, $$scrollValueCache, $loca $scope.$on('$destroy', function() { deregisterInstance(); + scrollView.__removeEventHandlers(); ionic.off('resize', resize, $window); $window.removeEventListener('resize', resize); backListenDone(); diff --git a/js/utils/dom.js b/js/utils/dom.js index 65b8d6a1cbf..193a62ed153 100644 --- a/js/utils/dom.js +++ b/js/utils/dom.js @@ -250,6 +250,17 @@ return null; }, + elementHasParent: function(element, parent) { + var current = element; + while (current) { + if (current.parentNode === parent) { + return true; + } + current = current.parentNode; + } + return false; + }, + /** * @ngdoc method * @name ionic.DomUtil#rectContains diff --git a/js/views/scrollView.js b/js/views/scrollView.js index a849b0cde53..ce8bfd76d28 100644 --- a/js/views/scrollView.js +++ b/js/views/scrollView.js @@ -668,7 +668,7 @@ ionic.views.Scroll = ionic.views.View.inherit({ scrollBottomOffsetToTop = container.getBoundingClientRect().bottom; //distance from top of focused element to the bottom of the scroll view - var elementTopOffsetToScrollBottom = e.detail.elementTop - scrollBottomOffsetToTop; + var elementTopOffsetToScrollBottom = e.detail.elementTop - scrollBottomOffsetToTop; var scrollTop = elementTopOffsetToScrollBottom + scrollMidpointOffset; ionic.tap.cloneFocusedInput(container, self); @@ -807,7 +807,7 @@ ionic.views.Scroll = ionic.views.View.inherit({ // Mouse Events var mousedown = false; - container.addEventListener("mousedown", function(e) { + self.mouseDown = function(e) { if ( ionic.tap.ignoreScrollStart(e) || e.target.tagName === 'SELECT' ) { return; } @@ -815,9 +815,9 @@ ionic.views.Scroll = ionic.views.View.inherit({ e.preventDefault(); mousedown = true; - }, false); + }; - document.addEventListener("mousemove", function(e) { + self.mouseMove = function(e) { if (!mousedown || e.defaultPrevented) { return; } @@ -825,9 +825,9 @@ ionic.views.Scroll = ionic.views.View.inherit({ self.doTouchMove(getEventTouches(e), e.timeStamp); mousedown = true; - }, false); + }; - document.addEventListener("mouseup", function(e) { + self.mouseUp = function(e) { if (!mousedown) { return; } @@ -835,27 +835,54 @@ ionic.views.Scroll = ionic.views.View.inherit({ self.doTouchEnd(e.timeStamp); mousedown = false; - }, false); - - var wheelShowBarFn = ionic.debounce(function() { - self.__fadeScrollbars('in'); - }, 500, true); + }; - var wheelHideBarFn = ionic.debounce(function() { - self.__fadeScrollbars('out'); - }, 100, false); + self.mouseWheel = ionic.animationFrameThrottle(function(e) { + if (ionic.DomUtil.elementHasParent(e.target, self.__container)) { + self.hintResize(); + self.scrollBy( + e.wheelDeltaX/self.options.wheelDampen, + -e.wheelDeltaY/self.options.wheelDampen + ); + self.__fadeScrollbars('in'); + clearTimeout(self.__wheelHideBarTimeout); + self.__wheelHideBarTimeout = setTimeout(function() { + self.__fadeScrollbars('out'); + }, 100); + } + }); - //For Firefox - document.addEventListener('mousewheel', onMouseWheel); - } - function onMouseWheel(e) { - self.hintResize(); - wheelShowBarFn(); - self.scrollBy(e.wheelDeltaX/self.options.wheelDampen, -e.wheelDeltaY/self.options.wheelDampen); - wheelHideBarFn(); + container.addEventListener("mousedown", self.mouseDown, false); + document.addEventListener("mousemove", self.mouseMove, false); + document.addEventListener("mouseup", self.mouseUp, false); + document.addEventListener('mousewheel', self.mouseWheel, false); } }, + __removeEventHandlers: function() { + var container = this.__container; + + container.removeEventListener('touchstart', self.touchStart); + document.removeEventListener('touchmove', self.touchMove); + document.removeEventListener('touchend', self.touchEnd); + document.removeEventListener('touchcancel', self.touchCancel); + + container.removeEventListener("pointerdown", self.touchStart); + document.removeEventListener("pointermove", self.touchMove); + document.removeEventListener("pointerup", self.touchEnd); + document.removeEventListener("pointercancel", self.touchEnd); + + container.removeEventListener("MSPointerDown", self.touchStart); + document.removeEventListener("MSPointerMove", self.touchMove); + document.removeEventListener("MSPointerUp", self.touchEnd); + document.removeEventListener("MSPointerCancel", self.touchEnd); + + container.removeEventListener("mousedown", self.mouseDown); + document.removeEventListener("mousemove", self.mouseMove); + document.removeEventListener("mouseup", self.mouseUp); + document.removeEventListener('mousewheel', self.mouseWheel); + }, + /** Create a scroll bar div with the given direction **/ __createScrollbar: function(direction) { var bar = document.createElement('div'), @@ -1503,7 +1530,6 @@ ionic.views.Scroll = ionic.views.View.inherit({ }, - /** * Touch start handler for scrolling support */