From 4c8d7b0f1ccbbf98f70938fb5192fc94dde3b255 Mon Sep 17 00:00:00 2001 From: Nick Takayama Date: Tue, 18 Nov 2008 02:09:35 -0800 Subject: [PATCH] Timers. [#116 state:resolved] Reviewed by ross and me. --- AppKit/CPAnimation.j | 13 +- AppKit/CPApplication.j | 2 +- AppKit/CPEvent.j | 2 +- AppKit/CPImage.j | 42 +++- AppKit/CPTextField.j | 14 +- AppKit/Platform/DOM/CPDOMWindowBridge.j | 17 +- Foundation/CPJSONPConnection.j | 4 +- Foundation/CPRunLoop.j | 166 +++++++++++--- Foundation/CPTimer.j | 281 ++++++++++++++++++++++++ Foundation/CPURLConnection.j | 2 +- Foundation/Foundation.j | 1 + Objective-J/constants.js | 5 + Objective-J/evaluate.js | 2 +- 13 files changed, 483 insertions(+), 68 deletions(-) create mode 100644 Foundation/CPTimer.j diff --git a/AppKit/CPAnimation.j b/AppKit/CPAnimation.j index d60c13526..bbdb79c69 100644 --- a/AppKit/CPAnimation.j +++ b/AppKit/CPAnimation.j @@ -220,11 +220,8 @@ ACTUAL_FRAME_RATE = 0; _progress = 0.0; ACTUAL_FRAME_RATE = 0; _startTime = new Date(); - // FIXME: THIS SHOULD BE A CPTIMER!!! - _timer = window.setInterval(function() { - [self animationTimerDidFire:_timer]; - [[CPRunLoop currentRunLoop] performSelectors]; - }, 1); // must be 1ms not 0 for IE. //_duration * 1000 / _frameRate); + + _timer = [CPTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(animationTimerDidFire:) userInfo:nil repeats:YES]; } /* @@ -239,9 +236,9 @@ ACTUAL_FRAME_RATE = 0; [self setCurrentProgress:progress]; - if (progress == 1.0) + if (progress === 1.0) { - window.clearTimeout(_timer); + [_timer invalidate]; _timer = nil; if ([_delegate respondsToSelector:@selector(animationDidEnd:)]) @@ -258,7 +255,7 @@ ACTUAL_FRAME_RATE = 0; if (!_timer) return; - window.clearTimeout(_timer); + [_timer invalidate]; _timer = nil; if ([_delegate respondsToSelector:@selector(animationDidStop:)]) diff --git a/AppKit/CPApplication.j b/AppKit/CPApplication.j index 6a223543d..73fd8c0fe 100644 --- a/AppKit/CPApplication.j +++ b/AppKit/CPApplication.j @@ -267,7 +267,7 @@ CPRunContinuesResponse = -1002; postNotificationName:CPApplicationDidFinishLaunchingNotification object:self]; - [[CPRunLoop currentRunLoop] performSelectors]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } /*! diff --git a/AppKit/CPEvent.j b/AppKit/CPEvent.j index 413e5079f..f58217b7d 100644 --- a/AppKit/CPEvent.j +++ b/AppKit/CPEvent.j @@ -518,6 +518,6 @@ function _CPEventFirePeriodEvent() { [CPApp sendEvent:[CPEvent otherEventWithType:CPPeriodic location:_CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil subtype:0 data1:0 data2:0]]; - [[CPRunLoop currentRunLoop] performSelectors]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } diff --git a/AppKit/CPImage.j b/AppKit/CPImage.j index 81d1fd923..b0c24bdb9 100644 --- a/AppKit/CPImage.j +++ b/AppKit/CPImage.j @@ -186,6 +186,7 @@ CPImageDidLoadNotification = @"CPImageDidLoadNotification"; should not call this method directly. Instead use one of the initializers. */ + - (void)load { if (_loadStatus == CPImageLoadStatusLoading || _loadStatus == CPImageLoadStatusCompleted) @@ -198,9 +199,38 @@ CPImageDidLoadNotification = @"CPImageDidLoadNotification"; var isSynchronous = YES; // FIXME: We need a better/performance way of doing this. - _image.onload = function () { if (isSynchronous) window.setTimeout(function() { [self _imageDidLoad] }, 0); else [self _imageDidLoad]; } - _image.onerror = function () { if (isSynchronous) window.setTimeout(function() { [self _imageDidError] }, 0); else [self _imageDidError]; } - _image.onabort = function () { if (isSynchronous) window.setTimeout(function() { [self _imageDidAbort] }, 0); else [self _imageDidAbort]; } + _image.onload = function () + { + if (isSynchronous) + window.setTimeout(function() { [self _imageDidLoad]; }, 0); + else + { + [self _imageDidLoad]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + } + } + + _image.onerror = function () + { + if (isSynchronous) + window.setTimeout(function() { [self _imageDidError]; }, 0); + else + { + [self _imageDidError]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + } + } + + _image.onabort = function () + { + if (isSynchronous) + window.setTimeout(function() { [self _imageDidAbort]; }, 0); + else + { + [self _imageDidAbort]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + } + } _image.src = _filename; @@ -232,8 +262,6 @@ CPImageDidLoadNotification = @"CPImageDidLoadNotification"; if ([_delegate respondsToSelector:@selector(imageDidLoad:)]) [_delegate imageDidLoad:self]; - - [[CPRunLoop currentRunLoop] performSelectors]; } /* @ignore */ @@ -243,8 +271,6 @@ CPImageDidLoadNotification = @"CPImageDidLoadNotification"; if ([_delegate respondsToSelector:@selector(imageDidError:)]) [_delegate imageDidError:self]; - - [[CPRunLoop currentRunLoop] performSelectors]; } /* @ignore */ @@ -254,8 +280,6 @@ CPImageDidLoadNotification = @"CPImageDidLoadNotification"; if ([_delegate respondsToSelector:@selector(imageDidAbort:)]) [_delegate imageDidAbort:self]; - - [[CPRunLoop currentRunLoop] performSelectors]; } @end diff --git a/AppKit/CPTextField.j b/AppKit/CPTextField.j index 3de866ca7..2f95b8168 100644 --- a/AppKit/CPTextField.j +++ b/AppKit/CPTextField.j @@ -403,7 +403,7 @@ var _CPTextFieldSquareBezelColor = nil, [self setObjectValue:element.value]; [self sendAction:[self action] to:[self target]]; [[self window] makeFirstResponder:nil]; - [[CPRunLoop currentRunLoop] performSelectors]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; }; //element.onblur = function() { objj_debug_print_backtrace(); } @@ -413,8 +413,8 @@ var _CPTextFieldSquareBezelColor = nil, { //all key presses might trigger the delegate method controlTextDidChange: //record the current string value before we allow this keydown to propagate - _textDidChangeValue = [self stringValue]; - [[CPRunLoop currentRunLoop] performSelectors]; + _textDidChangeValue = [self stringValue]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; return true; } @@ -431,8 +431,8 @@ var _CPTextFieldSquareBezelColor = nil, aDOMEvent.cancelBubble = true; element.blur(); - } - [[CPRunLoop currentRunLoop] performSelectors]; + } + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; }; //inspect keyup to detect changes in order to trigger controlTextDidChange: delegate method @@ -445,8 +445,8 @@ var _CPTextFieldSquareBezelColor = nil, //call to CPControls methods for posting the notification [self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]]; - } - [[CPRunLoop currentRunLoop] performSelectors]; + } + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; }; // If current value is the placeholder value, remove it to allow user to update. diff --git a/AppKit/Platform/DOM/CPDOMWindowBridge.j b/AppKit/Platform/DOM/CPDOMWindowBridge.j index ece1a219c..fdc8bb24f 100644 --- a/AppKit/Platform/DOM/CPDOMWindowBridge.j +++ b/AppKit/Platform/DOM/CPDOMWindowBridge.j @@ -557,8 +557,8 @@ var CTRL_KEY_CODE = 17; if (StopDOMEventPropagation) CPDOMEventStop(aDOMEvent); - - [[CPRunLoop currentRunLoop] performSelectors]; + + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } catch (anException) { @@ -703,8 +703,8 @@ var CTRL_KEY_CODE = 17; if (StopDOMEventPropagation) CPDOMEventStop(aDOMEvent); - - [[CPRunLoop currentRunLoop] performSelectors]; + + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } catch (anException) { @@ -774,8 +774,8 @@ var CTRL_KEY_CODE = 17; if (StopDOMEventPropagation) CPDOMEventStop(aDOMEvent); - - [[CPRunLoop currentRunLoop] performSelectors]; + + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } catch (anException) { @@ -814,8 +814,7 @@ var CTRL_KEY_CODE = 17; //window.liveResize = NO; - [[CPRunLoop currentRunLoop] performSelectors]; - + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } catch (anException) { @@ -848,7 +847,7 @@ var CTRL_KEY_CODE = 17; _pasteboardKeyDownEvent = nil; - [[CPRunLoop currentRunLoop] performSelectors]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } catch (anException) { diff --git a/Foundation/CPJSONPConnection.j b/Foundation/CPJSONPConnection.j index 56f4d9408..6e54213d7 100644 --- a/Foundation/CPJSONPConnection.j +++ b/Foundation/CPJSONPConnection.j @@ -73,7 +73,7 @@ CPJSONPConnectionCallbacks = {}; [_delegate connection:self didReceiveData:data]; [self removeScriptTag]; - [[CPRunLoop currentRunLoop] performSelectors]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; }; if(shouldStartImmediately) @@ -122,4 +122,4 @@ CPJSONPConnectionCallbacks = {}; [self removeScriptTag]; } -@end \ No newline at end of file +@end diff --git a/Foundation/CPRunLoop.j b/Foundation/CPRunLoop.j index ed3da7707..b329c807c 100644 --- a/Foundation/CPRunLoop.j +++ b/Foundation/CPRunLoop.j @@ -46,6 +46,7 @@ var _CPRunLoopPerformPool = [], id _argument; unsigned _order; CPArray _runLoopModes; + BOOL _isValid; } + (void)_poolPerform:(_CPRunLoopPerform)aPerform @@ -67,6 +68,7 @@ var _CPRunLoopPerformPool = [], perform._arguments = anArgument; perform._order = anOrder; perform._runLoopModes = modes; + perform._isValid = YES; return perform; } @@ -85,6 +87,7 @@ var _CPRunLoopPerformPool = [], _argument = anArgument; _order = anOrder; _runLoopModes = modes; + _isValid = YES; } return self; @@ -112,6 +115,9 @@ var _CPRunLoopPerformPool = [], - (BOOL)fireInMode:(CPString)aRunLoopMode { + if (!_isValid) + return YES; + if ([_runLoopModes containsObject:aRunLoopMode]) { [_target performSelector:_selector withObject:_argument]; @@ -122,6 +128,11 @@ var _CPRunLoopPerformPool = [], return NO; } +- (void)invalidate +{ + _isValid = NO; +} + @end /*! @class CPRunLoop @@ -132,9 +143,14 @@ var _CPRunLoopPerformPool = [], */ @implementation CPRunLoop : CPObject { - CPArray _queuedPerforms; + BOOL _runLoopLock; + + Object _timersForModes; //should be a dictionary to allow lookups by mode + Object _nativeTimersForModes; + CPDate _nextTimerFireDatesForModes; + BOOL _didAddTimer; + CPArray _orderedPerforms; - BOOL _isPerformingSelectors; } /* @@ -154,8 +170,11 @@ var _CPRunLoopPerformPool = [], if (self) { - _queuedPerforms = []; _orderedPerforms = []; + + _timersForModes = {}; + _nativeTimersForModes = {}; + _nextTimerFireDatesForModes = {}; } return self; @@ -187,21 +206,15 @@ var _CPRunLoopPerformPool = [], */ - (void)performSelector:(SEL)aSelector target:(id)aTarget argument:(id)anArgument order:(int)anOrder modes:(CPArray)modes { - var perform = [_CPRunLoopPerform performWithSelector:aSelector target:aTarget argument:anArgument order:anOrder modes:modes]; + var perform = [_CPRunLoopPerform performWithSelector:aSelector target:aTarget argument:anArgument order:anOrder modes:modes], + count = _orderedPerforms.length; - if (_isPerformingSelectors) - _queuedPerforms.push(perform); - else - { - var count = _orderedPerforms.length; + // We sort ourselves in reverse because we iterate this list backwards. + while (count--) + if (anOrder < [_orderedPerforms[count] order]) + break; - // We sort ourselves in reverse because we iterate this list backwards. - while (count--) - if (anOrder < [_orderedPerforms[count] order]) - break; - - _orderedPerforms.splice(count + 1, 0, perform); - } + _orderedPerforms.splice(count + 1, 0, perform); } /*! @@ -218,8 +231,8 @@ var _CPRunLoopPerformPool = [], { var perform = _orderedPerforms[count]; - if ([perform selector] == aSelector && [perform target] == aTarget && [perform argument] == anArgument) - [_orderedPerforms removeObjectAtIndex:count]; + if ([perform selector] === aSelector && [perform target] == aTarget && [perform argument] == anArgument) + [_orderedPerforms[count] invalidate]; } } @@ -228,34 +241,129 @@ var _CPRunLoopPerformPool = [], */ - (void)performSelectors { - if (_isPerformingSelectors) + [self limitDateForMode:CPDefaultRunLoopMode]; +} + +/*! + Registers a given timer with a given input mode. +*/ +- (void)addTimer:(CPTimer)aTimer forMode:(CPString)aMode +{ + if (_timersForModes[aMode]) + _timersForModes[aMode].push(aTimer); + else + _timersForModes[aMode] = [aTimer]; + + _didAddTimer = YES; +} + +/*! + Performs one pass through the run loop in the specified mode and returns the date at which the next timer is scheduled to fire. +*/ +- (CPDate)limitDateForMode:(CPString)aMode +{ + //simple locking to try to prevent concurrent iterating over timers + if (_runLoopLock) return; - _isPerformingSelectors = YES; + _runLoopLock = YES; - var index = _orderedPerforms.length; + var now = [CPDate date], + nextFireDate = nil, + nextTimerFireDate = _nextTimerFireDatesForModes[aMode]; + + // Perform Timers if necessary + if (_didAddTimer || nextTimerFireDate && nextTimerFireDate <= now) + { + _didAddTimer = NO; + + // Cancel existing window.setTimeout + if (_nativeTimersForModes[aMode] !== nil) + { + window.clearNativeTimeout(_nativeTimersForModes[aMode]); + + _nativeTimersForModes[aMode] = nil; + } + + // Empty timers to avoid catastrophe if a timer is added during a timer fire. + var timers = _timersForModes[aMode], + index = timers.length; + + _timersForModes[aMode] = nil; + + // Loop through timers looking for ones that had fired + while (index--) + { + var timer = timers[index]; + + if (timer._isValid && timer._fireDate <= now) + [timer fire]; + + // Timer may or may not still be valid + if (timer._isValid) + nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate]; + + else + timers.splice(index, 1); + } + + var newTimers = _timersForModes[aMode]; + + if (newTimers && newTimers.length) + { + index = newTimers.length; + + while (index--) + { + var timer = newTimers[index]; + + if ([timer isValid]) + nextFireDate = (nextFireDate === nil) ? timer._fireDate : [nextFireDate earlierDate:timer._fireDate]; + else + newTimers.splice(index, 1); + } + + _timersForModes[aMode] = newTimers.concat(timers); + } + else + _timersForModes[aMode] = timers; + + _nextTimerFireDatesForModes[aMode] = nextFireDate; + + //initiate a new window.setTimeout if there are any timers + if (_nextTimerFireDatesForModes[aMode] !== nil) + _nativeTimersForModes[aMode] = window.setNativeTimeout(function() { _nativeTimersForModes[aMode] = nil; [self limitDateForMode:aMode]; }, MAX(0, [nextFireDate timeIntervalSinceNow] * 1000)); + } + + // Run loop performers + var performs = _orderedPerforms, + index = performs.length; + + _orderedPerforms = []; while (index--) { - var perform = _orderedPerforms[index]; + var perform = performs[index]; if ([perform fireInMode:CPDefaultRunLoopMode]) { [_CPRunLoopPerform _poolPerform:perform]; - _orderedPerforms.splice(index, 1); + performs.splice(index, 1); } } - _isPerformingSelectors = NO; - - if (_queuedPerforms.length) + if (_orderedPerforms.length) { - _orderedPerforms = _orderedPerforms.concat(_queuedPerforms); + _orderedPerforms = _orderedPerforms.concat(performs); _orderedPerforms.sort(_CPRunLoopPerformCompare); } + else + _orderedPerforms = performs; - _queuedPerforms = []; + _runLoopLock = NO; + + return nextFireDate; } -@end \ No newline at end of file +@end diff --git a/Foundation/CPTimer.j b/Foundation/CPTimer.j new file mode 100644 index 000000000..3aa13dc45 --- /dev/null +++ b/Foundation/CPTimer.j @@ -0,0 +1,281 @@ +/* + * CPTimer.j + * Foundation + * + * Created by Nick Takayama. + * Copyright 2008. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import "CPObject.j" +@import "CPInvocation.j" +@import "CPDate.j" +@import "CPRunLoop.j" + + +@implementation CPTimer : CPObject +{ + CPTimeInterval _timeInterval; + CPInvocation _invocation; + Function _callback; + + BOOL _repeats; + BOOL _isValid; + CPDate _fireDate; + id _userInfo; +} + +/*! + Returns a new NSTimer object and adds it to the current NSRunLoop object in the default mode. +*/ ++ (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat +{ + var timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat]; + + //add to the runloop + [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; + + return timer; +} + +/*! + Returns a new NSTimer object and adds it to the current NSRunLoop object in the default mode. +*/ ++ (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat +{ + var timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat] + + //add to the runloop + [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; + + return timer; +} + +/*! + Returns a new NSTimer object and adds it to the current NSRunLoop object in the default mode. +*/ ++ (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat +{ + var timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat]; + + //add to the runloop + [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; + + return timer; +} + +/*! + Returns a new NSTimer that, when added to a run loop, will fire after seconds. +*/ ++ (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat +{ + return [[self alloc] initWithFireDate:nil interval:seconds invocation:anInvocation repeats:shouldRepeat]; +} + +/*! + Returns a new NSTimer that, when added to a run loop, will fire after seconds. +*/ ++ (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat +{ + return [[self alloc] initWithFireDate:nil interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat]; +} + +/*! + Returns a new NSTimer that, when added to a run loop, will fire after seconds. +*/ ++ (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat +{ + return [[self alloc] initWithFireDate:nil interval:seconds callback:aFunction repeats:shouldRepeat]; +} + +/*! + Initializes a new NSTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that. +*/ +- (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat +{ + self = [super init]; + + if (self) + { + _timeInterval = seconds; + _invocation = anInvocation; + _repeats = shouldRepeat; + _isValid = YES; + _fireDate = aDate; + } + + return self; +} + +/*! + Initializes a new NSTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that. +*/ +- (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat +{ + var invocation = [CPInvocation invocationWithMethodSignature:1]; + + [invocation setTarget:aTarget]; + [invocation setSelector:aSelector]; + [invocation setArgument:self atIndex:2]; + + self = [self initWithFireDate:aDate interval:seconds invocation:invocation repeats:shouldRepeat]; + + if (self) + _userInfo = userInfo; + + return self; +} + +/*! + Initializes a new NSTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that. +*/ +- (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat +{ + self = [super init]; + + if (self) + { + _timeInterval = seconds; + _callback = aFunction; + _repeats = shouldRepeat; + _isValid = YES; + _fireDate = aDate; + } + + return self; +} + +/*! + Returns the receiver’s time interval. +*/ +- (CPTimeInterval)timeInterval +{ + return _timeInterval; +} + +/*! + Returns the date at which the receiver will fire. +*/ +- (CPDate)fireDate +{ + return _fireDate; +} + +/*! + Resets the receiver to fire next at a given date. +*/ +- (void)setFireDate:(CPDate)aDate +{ + _fireDate = aDate; +} + +/*! + Causes the receiver’s message to be sent to its target. +*/ +- (void)fire +{ + if (!_isValid) + return; + + if (_callback) + _callback(); + else + [_invocation invoke]; + + if (!_isValid) + return; + + if (_repeats) + _fireDate = [CPDate dateWithTimeIntervalSinceNow:_timeInterval]; + + else + [self invalidate]; +} + +/*! + Returns a Boolean value that indicates whether the receiver is currently valid. +*/ +- (BOOL)isValid +{ + return _isValid; +} + +/*! + Stops the receiver from ever firing again and requests its removal from its NSRunLoop object. +*/ +- (void)invalidate +{ + _isValid = NO; + _userInfo = nil; + _invocation = nil; + _callback = nil; +} + +/*! + Returns the receiver's userInfo object. +*/ +- (id)userInfo +{ + return _userInfo; +} + +@end + +var CPTimersTimeoutID = 1000, + CPTimersForTimeoutIDs = {} + +var _CPTimerBridgeTimer = function(codeOrFunction, aDelay, shouldRepeat, functionArgs) +{ + var timeoutID = CPTimersTimeoutID++; + theFunction = nil; + + if (typeof codeOrFunction === "string") + theFunction = function() { new Function(codeOrFunction)(); CPTimersForTimeoutIDs[timeoutID] = nil; } + else + { + if (!functionArgs) + functionArgs = []; + + theFunction = function() { codeOrFunction.apply(window, functionArgs); CPTimersForTimeoutIDs[timeoutID] = nil;} + } + + CPTimersForTimeoutIDs[timeoutID] = [CPTimer scheduledTimerWithTimeInterval:aDelay / 1000 callback:theFunction repeats:shouldRepeat]; + + return timeoutID; +} + +window.setTimeout = function(codeOrFunction, aDelay) +{ + return _CPTimerBridgeTimer(codeOrFunction, aDelay, NO, Array.prototype.slice.apply(arguments, [2])); +} + +window.clearTimeout = function(aTimeoutID) +{ + var timer = CPTimersForTimeoutIDs[aTimeoutID]; + + if (timer) + [timer invalidate]; +} + +window.setInterval = function(codeOrFunction, aDelay, functionArgs) +{ + return _CPTimerBridgeTimer(codeOrFunction, aDelay, YES, Array.prototype.slice.apply(arguments, [2])); +} + +window.clearInterval = function(aTimeoutID) +{ + window.clearTimeout(aTimeoutID); +} diff --git a/Foundation/CPURLConnection.j b/Foundation/CPURLConnection.j index 91073fcae..58169c2c3 100644 --- a/Foundation/CPURLConnection.j +++ b/Foundation/CPURLConnection.j @@ -242,7 +242,7 @@ var CPURLConnectionDelegate = nil; } } - [[CPRunLoop currentRunLoop] performSelectors]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } /* @ignore */ diff --git a/Foundation/Foundation.j b/Foundation/Foundation.j index 35e48c688..172d879a3 100755 --- a/Foundation/Foundation.j +++ b/Foundation/Foundation.j @@ -47,6 +47,7 @@ @import "CPRunLoop.j" @import "CPSortDescriptor.j" @import "CPString.j" +@import "CPTimer.j" @import "CPUndoManager.j" @import "CPURLConnection.j" @import "CPURLRequest.j" diff --git a/Objective-J/constants.js b/Objective-J/constants.js index 747d5e04c..0b4df679a 100644 --- a/Objective-J/constants.js +++ b/Objective-J/constants.js @@ -68,6 +68,11 @@ var NO = false, SQRT1_2 = Math.SQRT1_2, SQRT2 = Math.SQRT2; +window.setNativeTimeout = window.setTimeout; +window.clearNativeTimeout = window.clearTimeout; +window.setNativeInterval = window.setInterval; +window.clearNativeInterval = window.clearInterval; + // Detecting Browser Features #define ACTIVE_X window.ActiveXObject #define OPERA window.opera diff --git a/Objective-J/evaluate.js b/Objective-J/evaluate.js index 46703dff2..ba5649211 100644 --- a/Objective-J/evaluate.js +++ b/Objective-J/evaluate.js @@ -133,7 +133,7 @@ objj_context.prototype.schedule = function() var context = this; - window.setTimeout(function () { context.evaluate(); }, 0); + window.setNativeTimeout(function () { context.evaluate(); }, 0); } objj_context.prototype.pushFragment = function(aFragment)