diff --git a/AppKit/CPComboBox.j b/AppKit/CPComboBox.j index 317de9d1e..b5aa1b7c2 100644 --- a/AppKit/CPComboBox.j +++ b/AppKit/CPComboBox.j @@ -843,8 +843,9 @@ var CPComboBoxTextSubview = @"text", // In FireFox this needs to be done in setTimeout, otherwise there is no caret // We have to save the input element now, when we lose focus it will change. var element = [self _inputElement]; - window.setTimeout(function() { + [[CPRunLoop mainRunLoop] performBlock:function() + { // This will prevent to jump to the focused element var previousScrollingOrigin = [self _scrollToVisibleRectAndReturnPreviousOrigin]; @@ -852,7 +853,7 @@ var CPComboBoxTextSubview = @"text", [self _restorePreviousScrollingOrigin:previousScrollingOrigin]; - }, 0); + } argument:nil order:0 modes:[CPDefaultRunLoopMode]]; #endif return NO; diff --git a/AppKit/CPTextField.j b/AppKit/CPTextField.j index 00894190e..0dbb30bde 100644 --- a/AppKit/CPTextField.j +++ b/AppKit/CPTextField.j @@ -70,7 +70,6 @@ var CPTextFieldDOMCurrentElement = nil, var CPSecureTextFieldCharacter = "\u2022"; - function CPTextFieldBlurFunction(anEvent, owner, domElement, inputElement, resigning, didBlurRef) { if (owner && domElement != inputElement.parentNode) @@ -87,7 +86,7 @@ function CPTextFieldBlurFunction(anEvent, owner, domElement, inputElement, resig */ if ([owner _isWithinUsablePlatformRect]) { - window.setTimeout(function() + [[CPRunLoop mainRunLoop] performBlock:function() { // This will prevent to jump to the focused element var previousScrollingOrigin = [owner _scrollToVisibleRectAndReturnPreviousOrigin]; @@ -95,7 +94,7 @@ function CPTextFieldBlurFunction(anEvent, owner, domElement, inputElement, resig inputElement.focus(); [owner _restorePreviousScrollingOrigin:previousScrollingOrigin]; - }, 0.0); + } argument:nil order:0 modes:[CPDefaultRunLoopMode]]; } } @@ -671,11 +670,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); CPTextFieldInputOwner = self; - window.setTimeout(function() + [[CPRunLoop mainRunLoop] performBlock:function() { - /* - setTimeout handlers are not guaranteed to fire in the order they were initiated. This can cause a race condition when several windows with text fields are opened quickly, resulting in several instances of this timeout function being fired, perhaps out of order. So we have to check that by the time this function is fired, CPTextFieldInputOwner has not been changed to another text field in the meantime. - */ if (CPTextFieldInputOwner !== self) return; @@ -693,7 +689,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); _willBecomeFirstResponderByClick = NO; [self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]]; - }, 0.0); + } argument:nil order:0 modes:[CPDefaultRunLoopMode]]; #endif @@ -1510,7 +1506,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); if (immediately) element.select(); else - window.setTimeout(function() { element.select(); }, 0); + [[CPRunLoop mainRunLoop] performBlock:function(){ element.select(); } argument:nil order:0 modes:[CPDefaultRunLoopMode]]; } else if (wind !== nil && [wind makeFirstResponder:self]) [self _selectText:sender immediately:immediately]; diff --git a/AppKit/CPTokenField.j b/AppKit/CPTokenField.j index 3bc4b3ea3..3724b20a2 100644 --- a/AppKit/CPTokenField.j +++ b/AppKit/CPTokenField.j @@ -448,14 +448,14 @@ CPTokenFieldDeleteButtonType = 1; element.style.width = CGRectGetWidth(contentRect) + "px"; element.style.height = [font defaultLineHeightForFont] + "px"; - window.setTimeout(function() + [[CPRunLoop mainRunLoop] performBlock:function() { [_tokenScrollView documentView]._DOMElement.appendChild(element); //post CPControlTextDidBeginEditingNotification [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]]; - window.setTimeout(function() + [[CPRunLoop mainRunLoop] performBlock:function() { // This will prevent to jump to the focused element var previousScrollingOrigin = [self _scrollToVisibleRectAndReturnPreviousOrigin]; @@ -465,10 +465,10 @@ CPTokenFieldDeleteButtonType = 1; [self _restorePreviousScrollingOrigin:previousScrollingOrigin]; CPTokenFieldInputOwner = self; - }, 0.0); + } argument:nil order:0 modes:[CPDefaultRunLoopMode]]; [self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]]; - }, 0.0); + } argument:nil order:0 modes:[CPDefaultRunLoopMode]]; [[[self window] platformWindow] _propagateCurrentDOMEvent:YES]; diff --git a/Foundation/CPRunLoop.j b/Foundation/CPRunLoop.j index f12e4642c..da7906ee2 100644 --- a/Foundation/CPRunLoop.j +++ b/Foundation/CPRunLoop.j @@ -42,6 +42,7 @@ var _CPRunLoopPerformPool = [], /* @ignore */ @implementation _CPRunLoopPerform : CPObject { + Function _block; id _target; SEL _selector; id _argument; @@ -64,6 +65,7 @@ var _CPRunLoopPerformPool = [], { var perform = _CPRunLoopPerformPool.pop(); + perform._block = nil; perform._target = aTarget; perform._selector = aSelector; perform._argument = anArgument; @@ -77,6 +79,26 @@ var _CPRunLoopPerformPool = [], return [[self alloc] initWithSelector:aSelector target:aTarget argument:anArgument order:anOrder modes:modes]; } ++ (_CPRunLoopPerform)performWithBlock:(Function)aBlock argument:(id)anArgument order:(unsigned)anOrder modes:(CPArray)modes +{ + if (_CPRunLoopPerformPool.length) + { + var perform = _CPRunLoopPerformPool.pop(); + + perform._target = nil; + perform._selector = nil; + perform._block = aBlock; + perform._argument = anArgument; + perform._order = anOrder; + perform._runLoopModes = modes; + perform._isValid = YES; + + return perform; + } + + return [[self alloc] initWithBlock:aBlock argument:anArgument order:anOrder modes:modes]; +} + - (id)initWithSelector:(SEL)aSelector target:(SEL)aTarget argument:(id)anArgument order:(unsigned)anOrder modes:(CPArray)modes { self = [super init]; @@ -94,6 +116,22 @@ var _CPRunLoopPerformPool = [], return self; } +- (id)initWithBlock:(Function)aBlock argument:(id)anArgument order:(unsigned)anOrder modes:(CPArray)modes +{ + self = [super init]; + + if (self) + { + _block = aBlock; + _argument = anArgument; + _order = anOrder; + _runLoopModes = modes; + _isValid = YES; + } + + return self; +} + - (SEL)selector { return _selector; @@ -121,7 +159,10 @@ var _CPRunLoopPerformPool = [], if ([_runLoopModes containsObject:aRunLoopMode]) { - [_target performSelector:_selector withObject:_argument]; + if (_block) + _block(_argument); + else + [_target performSelector:_selector withObject:_argument]; return YES; } @@ -225,6 +266,23 @@ var CPRunLoopLastNativeRunLoop = 0; _orderedPerforms.splice(count + 1, 0, perform); } + +/*! + @ignore +*/ +- (void)performBlock:(Function)aBlock argument:(id)anArgument order:(int)anOrder modes:(CPArray)modes +{ + var perform = [_CPRunLoopPerform performWithBlock:aBlock argument:anArgument order:anOrder modes:modes], + count = _orderedPerforms.length; + + // 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); +} + /*! Cancels the specified selector and target. @param aSelector the selector of the method to invoke diff --git a/Tests/Foundation/CPRunLoopTest.j b/Tests/Foundation/CPRunLoopTest.j index 321795e52..50cfcd3f5 100644 --- a/Tests/Foundation/CPRunLoopTest.j +++ b/Tests/Foundation/CPRunLoopTest.j @@ -34,6 +34,18 @@ [self assert:[10] equals:performer2.callArgs]; } +- (void)testPerformBlock +{ + var i = 0; + + [[CPRunLoop mainRunLoop] performBlock:function() + {i++} argument:nil order:0 modes:[CPDefaultRunLoopMode]]; + + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + + [self assert:i equals:1]; +} + @end @implementation Performer : CPObject