blob: 23c7d339659ce536690abc0fccc9a5fdaa42f1a3 [file] [log] [blame]
/*
* Copyright (C) 2009 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "WebVisiblePositionInternal.h"
#if PLATFORM(IOS_FAMILY)
#import "DOMNodeInternal.h"
#import "DOMRangeInternal.h"
#import <WebCore/DocumentMarkerController.h>
#import <WebCore/Editing.h>
#import <WebCore/FrameSelection.h>
#import <WebCore/HTMLTextFormControlElement.h>
#import <WebCore/Node.h>
#import <WebCore/Position.h>
#import <WebCore/Range.h>
#import <WebCore/RenderTextControl.h>
#import <WebCore/RenderedDocumentMarker.h>
#import <WebCore/SimpleRange.h>
#import <WebCore/TextBoundaries.h>
#import <WebCore/TextFlags.h>
#import <WebCore/TextGranularity.h>
#import <WebCore/TextIterator.h>
#import <WebCore/VisibleUnits.h>
#import <wtf/cocoa/VectorCocoa.h>
using namespace WebCore;
//-------------------
@implementation WebVisiblePosition (Internal)
// Since VisiblePosition isn't refcounted, we have to use new and delete on a copy.
+ (WebVisiblePosition *)_wrapVisiblePosition:(VisiblePosition)visiblePosition
{
auto vp = adoptNS([[WebVisiblePosition alloc] init]);
VisiblePosition *copy = new VisiblePosition(visiblePosition);
vp->_internal = reinterpret_cast<WebObjectInternal *>(copy);
return vp.autorelease();
}
// Returns nil if visible position is null.
+ (WebVisiblePosition *)_wrapVisiblePositionIfValid:(VisiblePosition)visiblePosition
{
return (visiblePosition.isNotNull() ? [WebVisiblePosition _wrapVisiblePosition:visiblePosition] : nil);
}
- (VisiblePosition)_visiblePosition
{
VisiblePosition *vp = reinterpret_cast<VisiblePosition *>(_internal);
return *vp;
}
@end
@implementation WebVisiblePosition
- (void)dealloc
{
VisiblePosition *vp = reinterpret_cast<VisiblePosition *>(_internal);
delete vp;
_internal = nil;
[super dealloc];
}
// FIXME: Overriding isEqual: without overriding hash will cause trouble if this ever goes into an NSSet or is the key in an NSDictionary,
// since two equal objects could have different hashes.
- (BOOL)isEqual:(id)other
{
if (![other isKindOfClass:[WebVisiblePosition class]])
return NO;
return [self _visiblePosition] == [(WebVisiblePosition *)other _visiblePosition];
}
- (NSComparisonResult)compare:(WebVisiblePosition *)other
{
VisiblePosition myVP = [self _visiblePosition];
VisiblePosition otherVP = [other _visiblePosition];
if (myVP == otherVP)
return NSOrderedSame;
else if (myVP < otherVP)
return NSOrderedAscending;
else
return NSOrderedDescending;
}
- (int)distanceFromPosition:(WebVisiblePosition *)other
{
return distanceBetweenPositions([self _visiblePosition], [other _visiblePosition]);
}
- (NSString *)description
{
NSMutableString *description = [NSMutableString stringWithString:[super description]];
VisiblePosition vp = [self _visiblePosition];
int offset = vp.deepEquivalent().offsetInContainerNode();
[description appendFormat:@"(offset=%d, context=([%c|%c], [u+%04x|u+%04x])", offset, vp.characterBefore(), vp.characterAfter(),
vp.characterBefore(), vp.characterAfter()];
return description;
}
- (TextDirection)textDirection
{
// TODO: implement
return TextDirection::LTR;
}
- (BOOL)directionIsDownstream:(WebTextAdjustmentDirection)direction
{
if (direction == WebTextAdjustmentBackward)
return NO;
if (direction == WebTextAdjustmentForward)
return YES;
if ([self textDirection] == TextDirection::LTR)
return (direction == WebTextAdjustmentRight);
return (direction == WebTextAdjustmentLeft);
}
- (WebVisiblePosition *)positionByMovingInDirection:(WebTextAdjustmentDirection)direction amount:(UInt32)amount withAffinityDownstream:(BOOL)affinityDownstream
{
auto vp = [self _visiblePosition];
vp.setAffinity(affinityDownstream ? Affinity::Downstream : Affinity::Upstream);
switch (direction) {
case WebTextAdjustmentForward: {
for (UInt32 i = 0; i < amount; i++)
vp = vp.next();
break;
}
case WebTextAdjustmentBackward: {
for (UInt32 i = 0; i < amount; i++)
vp = vp.previous();
break;
}
case WebTextAdjustmentRight: {
for (UInt32 i = 0; i < amount; i++)
vp = vp.right();
break;
}
case WebTextAdjustmentLeft: {
for (UInt32 i = 0; i < amount; i++)
vp = vp.left();
break;
}
case WebTextAdjustmentUp: {
int xOffset = vp.lineDirectionPointForBlockDirectionNavigation();
for (UInt32 i = 0; i < amount; i++)
vp = previousLinePosition(vp, xOffset);
break;
}
case WebTextAdjustmentDown: {
int xOffset = vp.lineDirectionPointForBlockDirectionNavigation();
for (UInt32 i = 0; i < amount; i++)
vp = nextLinePosition(vp, xOffset);
break;
}
default: {
ASSERT_NOT_REACHED();
break;
}
}
return [WebVisiblePosition _wrapVisiblePositionIfValid:vp];
}
- (WebVisiblePosition *)positionByMovingInDirection:(WebTextAdjustmentDirection)direction amount:(UInt32)amount
{
return [self positionByMovingInDirection:direction amount:amount withAffinityDownstream:YES];
}
static inline TextGranularity toTextGranularity(WebTextGranularity webGranularity)
{
TextGranularity granularity;
switch (webGranularity) {
case WebTextGranularityCharacter:
granularity = TextGranularity::CharacterGranularity;
break;
case WebTextGranularityWord:
granularity = TextGranularity::WordGranularity;
break;
case WebTextGranularitySentence:
granularity = TextGranularity::SentenceGranularity;
break;
case WebTextGranularityLine:
granularity = TextGranularity::LineGranularity;
break;
case WebTextGranularityParagraph:
granularity = TextGranularity::ParagraphGranularity;
break;
case WebTextGranularityAll:
granularity = TextGranularity::DocumentGranularity;
break;
default:
ASSERT_NOT_REACHED();
break;
}
return granularity;
}
static inline SelectionDirection toSelectionDirection(WebTextAdjustmentDirection direction)
{
SelectionDirection result;
switch (direction) {
case WebTextAdjustmentForward:
result = SelectionDirection::Forward;
break;
case WebTextAdjustmentBackward:
result = SelectionDirection::Backward;
break;
case WebTextAdjustmentRight:
result = SelectionDirection::Right;
break;
case WebTextAdjustmentLeft:
result = SelectionDirection::Left;
break;
case WebTextAdjustmentUp:
result = SelectionDirection::Left;
break;
case WebTextAdjustmentDown:
result = SelectionDirection::Right;
break;
}
return result;
}
// Returnes YES only if a position is at a boundary of a text unit of the specified granularity in the particular direction.
- (BOOL)atBoundaryOfGranularity:(WebTextGranularity)granularity inDirection:(WebTextAdjustmentDirection)direction
{
return atBoundaryOfGranularity([self _visiblePosition], toTextGranularity(granularity), toSelectionDirection(direction));
}
// Returns the next boundary position of a text unit of the given granularity in the given direction, or nil if there is no such position.
- (WebVisiblePosition *)positionOfNextBoundaryOfGranularity:(WebTextGranularity)granularity inDirection:(WebTextAdjustmentDirection)direction
{
return [WebVisiblePosition _wrapVisiblePositionIfValid:positionOfNextBoundaryOfGranularity([self _visiblePosition], toTextGranularity(granularity), toSelectionDirection(direction))];
}
// Returns YES if position is within a text unit of the given granularity. If the position is at a boundary, returns YES only if
// if the boundary is part of the text unit in the given direction.
- (BOOL)withinTextUnitOfGranularity:(WebTextGranularity)granularity inDirectionIfAtBoundary:(WebTextAdjustmentDirection)direction
{
return withinTextUnitOfGranularity([self _visiblePosition], toTextGranularity(granularity), toSelectionDirection(direction));
}
// Returns range of the enclosing text unit of the given granularity, or nil if there is no such enclosing unit. Whether a boundary position
// is enclosed depends on the given direction, using the same rule as -[WebVisiblePosition withinTextUnitOfGranularity:inDirectionAtBoundary:].
- (DOMRange *)enclosingTextUnitOfGranularity:(WebTextGranularity)granularity inDirectionIfAtBoundary:(WebTextAdjustmentDirection)direction
{
return kit(enclosingTextUnitOfGranularity([self _visiblePosition], toTextGranularity(granularity), toSelectionDirection(direction)));
}
- (WebVisiblePosition *)positionAtStartOrEndOfWord
{
// Ripped from WebCore::Frame::moveSelectionToStartOrEndOfCurrentWord
// Note: this is the iOS notion, not the unicode notion.
// Here, a word starts with non-whitespace or at the start of a line and
// ends at the next whitespace, or at the end of a line.
// Selection rule: If the selection is before the first character or
// just after the first character of a word that is longer than one
// character, move to the start of the word. Otherwise, move to the
// end of the word.
VisiblePosition pos = [self _visiblePosition];
VisiblePosition originalPos(pos);
UChar32 ch = pos.characterAfter();
bool isComplex = requiresContextForWordBoundary(ch);
if (isComplex) {
// for complex layout, find word around insertion point
VisiblePosition visibleWordStart = startOfWord(pos);
Position wordStart = visibleWordStart.deepEquivalent();
// place caret in front of word if pos is within first 2 characters of word (see Selection Rule above)
if (wordStart.deprecatedEditingOffset() + 1 >= pos.deepEquivalent().deprecatedEditingOffset()) {
pos = wordStart;
} else {
// calculate end of word to insert caret after word
VisiblePosition visibleWordEnd = endOfWord(pos);
Position wordEnd = visibleWordEnd.deepEquivalent();
pos = wordEnd;
}
} else {
UChar32 c = pos.characterAfter();
CFCharacterSetRef set = CFCharacterSetGetPredefined(kCFCharacterSetWhitespaceAndNewline);
if (c == 0 || CFCharacterSetIsLongCharacterMember(set, c)) {
// search backward for a non-space
while (1) {
if (pos.isNull() || isStartOfLine(pos))
break;
c = pos.characterBefore();
if (!CFCharacterSetIsLongCharacterMember(set, c))
break;
pos = pos.previous(CannotCrossEditingBoundary); // stay in editable content
}
}
else {
// See how far the selection extends into the current word.
// Follow the rule stated above.
int index = 0;
while (1) {
if (pos.isNull() || isStartOfLine(pos))
break;
c = pos.characterBefore();
if (CFCharacterSetIsLongCharacterMember(set, c))
break;
pos = pos.previous(CannotCrossEditingBoundary); // stay in editable content
index++;
if (index > 1)
break;
}
if (index > 1) {
// search forward for a space
pos = originalPos;
while (1) {
if (pos.isNull() || isEndOfLine(pos))
break;
c = pos.characterAfter();
if (CFCharacterSetIsLongCharacterMember(set, c))
break;
pos = pos.next(CannotCrossEditingBoundary); // stay in editable content
}
}
}
}
return [WebVisiblePosition _wrapVisiblePositionIfValid:pos];
}
- (BOOL)isEditable
{
return isEditablePosition([self _visiblePosition].deepEquivalent());
}
- (BOOL)requiresContextForWordBoundary
{
VisiblePosition vp = [self _visiblePosition];
return requiresContextForWordBoundary(vp.characterAfter()) || requiresContextForWordBoundary(vp.characterBefore());
}
- (BOOL)atAlphaNumericBoundaryInDirection:(WebTextAdjustmentDirection)direction
{
static CFCharacterSetRef set = CFCharacterSetGetPredefined(kCFCharacterSetAlphaNumeric);
VisiblePosition pos = [self _visiblePosition];
UChar32 charBefore = pos.characterBefore();
UChar32 charAfter = pos.characterAfter();
bool before = CFCharacterSetIsCharacterMember(set, charBefore);
bool after = CFCharacterSetIsCharacterMember(set, charAfter);
return [self directionIsDownstream:direction] ? (before && !after) : (!before && after);
}
- (DOMRange *)enclosingRangeWithDictationPhraseAlternatives:(NSArray **)alternatives
{
ASSERT(alternatives);
if (!alternatives)
return nil;
// *alternatives should not already point to an array.
ASSERT(!*alternatives);
*alternatives = nil;
auto position = [self _visiblePosition];
auto* node = position.deepEquivalent().anchorNode();
if (!node)
return nil;
unsigned offset = position.deepEquivalent().deprecatedEditingOffset();
auto& document = node->document();
for (auto marker : document.markers().markersFor(*node, DocumentMarker::DictationPhraseWithAlternatives)) {
if (marker->startOffset() <= offset && marker->endOffset() >= offset) {
*alternatives = createNSArray(std::get<Vector<String>>(marker->data())).autorelease();
return kit(makeSimpleRange(*node, *marker));
}
}
return nil;
}
- (DOMRange *)enclosingRangeWithCorrectionIndicator
{
auto position = [self _visiblePosition];
auto* node = position.deepEquivalent().anchorNode();
if (!node)
return nil;
unsigned offset = position.deepEquivalent().deprecatedEditingOffset();
auto& document = node->document();
for (auto marker : document.markers().markersFor(*node, DocumentMarker::Spelling)) {
if (marker->startOffset() <= offset && marker->endOffset() >= offset)
return kit(makeSimpleRange(*node, *marker));
}
return nil;
}
- (NSSelectionAffinity)affinity
{
return (NSSelectionAffinity)[self _visiblePosition].affinity();
}
- (void)setAffinity:(NSSelectionAffinity)affinity
{
reinterpret_cast<VisiblePosition *>(_internal)->setAffinity((WebCore::Affinity)affinity);
}
@end
@implementation DOMRange (VisiblePositionExtensions)
- (WebVisiblePosition *)startPosition
{
auto& range = *core(self);
return [WebVisiblePosition _wrapVisiblePosition:makeDeprecatedLegacyPosition(&range.startContainer(), range.startOffset())];
}
- (WebVisiblePosition *)endPosition
{
auto& range = *core(self);
return [WebVisiblePosition _wrapVisiblePosition:makeDeprecatedLegacyPosition(&range.endContainer(), range.endOffset())];
}
- (DOMRange *)enclosingWordRange
{
VisibleSelection selection([self.startPosition _visiblePosition], [self.endPosition _visiblePosition]);
selection = FrameSelection::wordSelectionContainingCaretSelection(selection);
WebVisiblePosition *start = [WebVisiblePosition _wrapVisiblePosition:selection.visibleStart()];
WebVisiblePosition *end = [WebVisiblePosition _wrapVisiblePosition:selection.visibleEnd()];
return [DOMRange rangeForFirstPosition:start second:end];
}
+ (DOMRange *)rangeForFirstPosition:(WebVisiblePosition *)first second:(WebVisiblePosition *)second
{
auto firstPosition = [first _visiblePosition];
auto secondPosition = [second _visiblePosition];
if (secondPosition < firstPosition)
std::swap(firstPosition, secondPosition);
return kit(makeSimpleRange(firstPosition, secondPosition));
}
@end
@implementation DOMNode (VisiblePositionExtensions)
- (DOMRange *)rangeOfContents
{
DOMRange *range = [[self ownerDocument] createRange];
[range setStart:self offset:0];
[range setEnd:self offset:[[self childNodes] length]];
return range;
}
- (WebVisiblePosition *)startPosition
{
// When in editable content, we need to calculate the startPosition from the beginning of the editable area.
auto& node = *core(self);
if (node.isContentEditable())
return [WebVisiblePosition _wrapVisiblePosition:startOfEditableContent(VisiblePosition(makeDeprecatedLegacyPosition(&node, 0)))];
return [[self rangeOfContents] startPosition];
}
- (WebVisiblePosition *)endPosition
{
// When in editable content, we need to calculate the endPosition from the end of the editable area.
auto& node = *core(self);
if (node.isContentEditable())
return [WebVisiblePosition _wrapVisiblePosition:endOfEditableContent(VisiblePosition(makeDeprecatedLegacyPosition(&node, 0)))];
return [[self rangeOfContents] endPosition];
}
@end
@implementation DOMHTMLInputElement (VisiblePositionExtensions)
- (WebVisiblePosition *)startPosition
{
Node* node = core(self);
RenderObject* object = node->renderer();
if (!is<RenderTextControl>(object))
return [super startPosition];
VisiblePosition visiblePosition = downcast<RenderTextControl>(*object).textFormControlElement().visiblePositionForIndex(0);
return [WebVisiblePosition _wrapVisiblePosition:visiblePosition];
}
- (WebVisiblePosition *)endPosition
{
Node* node = core(self);
RenderObject* object = node->renderer();
if (!is<RenderTextControl>(object))
return [super endPosition];
RenderTextControl& textControl = downcast<RenderTextControl>(*object);
VisiblePosition visiblePosition = textControl.textFormControlElement().visiblePositionForIndex(textControl.textFormControlElement().value().length());
return [WebVisiblePosition _wrapVisiblePosition:visiblePosition];
}
@end
@implementation DOMHTMLTextAreaElement (VisiblePositionExtensions)
- (WebVisiblePosition *)startPosition
{
Node* node = core(self);
RenderObject* object = node->renderer();
if (!object)
return [super startPosition];
VisiblePosition visiblePosition = downcast<RenderTextControl>(*object).textFormControlElement().visiblePositionForIndex(0);
return [WebVisiblePosition _wrapVisiblePosition:visiblePosition];
}
- (WebVisiblePosition *)endPosition
{
Node* node = core(self);
RenderObject* object = node->renderer();
if (!object)
return [super endPosition];
RenderTextControl& textControl = downcast<RenderTextControl>(*object);
VisiblePosition visiblePosition = textControl.textFormControlElement().visiblePositionForIndex(textControl.textFormControlElement().value().length());
return [WebVisiblePosition _wrapVisiblePosition:visiblePosition];
}
@end
#endif // PLATFORM(IOS_FAMILY)