Skip to content

Commit

Permalink
Fixed: browser would scroll text fields into view when they focused.
Browse files Browse the repository at this point in the history
Previously, there were two instances in which a browser would forcibly scroll a text field into view, out of Cappuccino's control:

- A text field is first responder in the key window, is partially or fully offscreen, and you click somewhere else within the same window. If the target of the click does not accept first responder, the text field is refocused.

- A text field is first responder in a non-key window, is partially or fully offscreen, and you make its window the key window. In that case the text field is made first responder and is focused.

In both cases, focusing the text field causes the browser to scroll the viewport out of Cappuccino's control such that the text field is completely onscreen.

With this commit, before a text field is focused, it is checked to ensure it is completely within the usable content rect of the platform window. If not, it refuses first responder. If the window is becoming key, the first responder is set to nil.
  • Loading branch information
aparajita committed Mar 13, 2013
1 parent 69bda94 commit 481f7c6
Showing 1 changed file with 52 additions and 14 deletions.
66 changes: 52 additions & 14 deletions AppKit/CPTextField.j
Expand Up @@ -59,15 +59,22 @@ function CPTextFieldBlurFunction(anEvent, owner, domElement, inputElement, resig
if (owner && domElement != inputElement.parentNode)
return;

if (!resigning && [[owner window] isKeyWindow])
var ownerWindow = [owner window];

if (!resigning && [ownerWindow isKeyWindow])
{
/*
Browsers blur text fields when a click occurs anywhere outside the text field. That is normal for browsers, but in Cocoa the key view retains focus unless the click target accepts first responder. So if we lost focus but were not told to resign and our window is still key, restore focus.
Browsers blur text fields when a click occurs anywhere outside the text field. That is normal for browsers, but in Cocoa the key view retains focus unless the click target accepts first responder. So if we lost focus but were not told to resign and our window is still key, restore focus,
but only if the text field is completely within the browser window. If we restore focus when it
is off screen, the entire body scrolls out of our control.
*/
window.setTimeout(function()
if ([owner _isWithinUsablePlatformRect])
{
inputElement.focus();
}, 0.0);
window.setTimeout(function()
{
inputElement.focus();
}, 0.0);
}
}

CPTextFieldHandleBlur(anEvent, @ref(owner));
Expand Down Expand Up @@ -537,7 +544,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
_isEditing = NO;

if ([[self window] isKeyWindow])
[self _becomeFirstKeyResponder];
return [self _becomeFirstKeyResponder];

return YES;
}
Expand All @@ -547,11 +554,17 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
Since a first responder but non-key window text field can't receive input it should not even look like an active text field (Cocoa has a "slightly active" text field look it uses when another window is the key window, but Cappuccino doesn't today.)
*/
- (void)_becomeFirstKeyResponder
- (BOOL)_becomeFirstKeyResponder
{
// Make sure the text field is visible so the browser will not scroll without the NSScrollView knowing about it.
// Make sure the text field is visible so the browser will not scroll
// without the NSScrollView knowing about it.
[self scrollRectToVisible:[self bounds]];

// If the text field is still not completely on screen, refuse to become
// first responder, because the browser will scroll it into view out of our control.
if (![self _isWithinUsablePlatformRect])
return NO;

[self setThemeState:CPThemeStateEditing];

[self _updatePlaceholderState];
Expand All @@ -576,11 +589,16 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");

switch ([self alignment])
{
case CPCenterTextAlignment: element.style.textAlign = "center";
break;
case CPRightTextAlignment: element.style.textAlign = "right";
break;
default: element.style.textAlign = "left";
case CPCenterTextAlignment:
element.style.textAlign = "center";
break;

case CPRightTextAlignment:
element.style.textAlign = "right";
break;

default:
element.style.textAlign = "left";
}

var contentRect = [self contentRectForBounds:[self bounds]],
Expand All @@ -606,6 +624,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
}

element.style.top = topPoint;

var left = _CGRectGetMinX(contentRect);

// If the browser has a built in left padding, compensate for it. We need the input text to be exactly on top of the original text.
Expand Down Expand Up @@ -658,6 +677,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
}, 0.0);

#endif

return YES;
}

/* @ignore */
Expand Down Expand Up @@ -767,7 +788,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
- (void)_windowDidBecomeKey:(CPNotification)aNotification
{
if ([[self window] isKeyWindow] && [[self window] firstResponder] === self)
[self _becomeFirstKeyResponder];
if (![self _becomeFirstKeyResponder])
[[self window] makeFirstResponder:nil];
}

- (BOOL)_valueIsValid:(CPString)aValue
Expand Down Expand Up @@ -1563,6 +1585,22 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
}
}

#pragma mark Private

- (BOOL)_isWithinUsablePlatformRect
{
var wind = [self window],
frame = [self convertRectToBase:[self bounds]],
usableRect = [[wind platformWindow] usableContentFrame];

frame.origin = [wind convertBaseToGlobal:frame.origin];

return (CGRectGetMinX(frame) >= CGRectGetMinX(usableRect) &&
CGRectGetMaxX(frame) <= CGRectGetMaxX(usableRect) &&
CGRectGetMinY(frame) >= CGRectGetMinY(usableRect) &&
CGRectGetMaxY(frame) <= CGRectGetMaxY(usableRect));
}

@end

var secureStringForString = function(aString)
Expand Down

0 comments on commit 481f7c6

Please sign in to comment.