DOMUIKitExtensions.mm   [plain text]

/*
* Copyright (C) 2007-2017 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. ``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
* 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.
*
*/

#if PLATFORM(IOS_FAMILY)

#import "DOMUIKitExtensions.h"

#import "DOM.h"
#import "DOMCore.h"
#import "DOMExtensions.h"
#import "DOMHTML.h"
#import "DOMHTMLAreaElementInternal.h"
#import "DOMHTMLElementInternal.h"
#import "DOMHTMLImageElementInternal.h"
#import "DOMHTMLSelectElementInternal.h"
#import "DOMInternal.h"
#import "DOMNodeInternal.h"
#import "DOMRangeInternal.h"
#import <WebCore/CachedImage.h>
#import <WebCore/Editing.h>
#import <WebCore/FloatPoint.h>
#import <WebCore/FontCascade.h>
#import <WebCore/FrameSelection.h>
#import <WebCore/HTMLAreaElement.h>
#import <WebCore/HTMLImageElement.h>
#import <WebCore/HTMLInputElement.h>
#import <WebCore/HTMLSelectElement.h>
#import <WebCore/HTMLTextAreaElement.h>
#import <WebCore/Image.h>
#import <WebCore/InlineBox.h>
#import <WebCore/Node.h>
#import <WebCore/Range.h>
#import <WebCore/RenderBlock.h>
#import <WebCore/RenderBlockFlow.h>
#import <WebCore/RenderBox.h>
#import <WebCore/RenderObject.h>
#import <WebCore/RenderStyleConstants.h>
#import <WebCore/RenderText.h>
#import <WebCore/RoundedRect.h>
#import <WebCore/SharedBuffer.h>
#import <WebCore/VisiblePosition.h>
#import <WebCore/VisibleUnits.h>
#import <WebCore/WAKAppKitStubs.h>

using WebCore::Node;
using WebCore::Position;
using WebCore::Range;
using WebCore::RenderBlock;
using WebCore::RenderBox;
using WebCore::RenderObject;
using WebCore::RenderText;
using WebCore::VisiblePosition;

@implementation DOMRange (UIKitExtensions)

- (void)move:(UInt32)amount inDirection:(WebTextAdjustmentDirection)direction
{
Range *range = core(self);
WebCore::FrameSelection frameSelection;
frameSelection.moveTo(range);

WebCore::TextGranularity granularity = WebCore::CharacterGranularity;
// Until WebKit supports vertical layout, "down" is equivalent to "forward by a line" and
// "up" is equivalent to "backward by a line".
if (direction == WebTextAdjustmentDown) {
direction = WebTextAdjustmentForward;
granularity = WebCore::LineGranularity;
} else if (direction == WebTextAdjustmentUp) {
direction = WebTextAdjustmentBackward;
granularity = WebCore::LineGranularity;
}

for (UInt32 i = 0; i < amount; i++)
frameSelection.modify(WebCore::FrameSelection::AlterationMove, (WebCore::SelectionDirection)direction, granularity);

Position start = frameSelection.selection().start().parentAnchoredEquivalent();
Position end = frameSelection.selection().end().parentAnchoredEquivalent();
if (start.containerNode())
range->setStart(*start.containerNode(), start.offsetInContainerNode());
if (end.containerNode())
range->setEnd(*end.containerNode(), end.offsetInContainerNode());
}

- (void)extend:(UInt32)amount inDirection:(WebTextAdjustmentDirection)direction
{
Range *range = core(self);
WebCore::FrameSelection frameSelection;
frameSelection.moveTo(range);

for (UInt32 i = 0; i < amount; i++)
frameSelection.modify(WebCore::FrameSelection::AlterationExtend, (WebCore::SelectionDirection)direction, WebCore::CharacterGranularity);

Position start = frameSelection.selection().start().parentAnchoredEquivalent();
Position end = frameSelection.selection().end().parentAnchoredEquivalent();
if (start.containerNode())
range->setStart(*start.containerNode(), start.offsetInContainerNode());
if (end.containerNode())
range->setEnd(*end.containerNode(), end.offsetInContainerNode());
}

- (DOMNode *)firstNode
{
return kit(core(self)->firstNode());
}

@end

//-------------------

@implementation DOMNode (UIKitExtensions)

// NOTE: Code blatantly copied from [WebInspector _hightNode:] in WebKit/WebInspector/WebInspector.m@19861
- (NSArray *)boundingBoxes
{
NSArray *rects = nil;
NSRect bounds = [self boundingBox];
if (!NSIsEmptyRect(bounds)) {
if ([self isKindOfClass:[DOMElement class]]) {
DOMDocument *document = [self ownerDocument];
DOMCSSStyleDeclaration *style = [document getComputedStyle:(DOMElement *)self pseudoElement:@""];
if ([[style getPropertyValue:@"display"] isEqualToString:@"inline"])
rects = [self lineBoxRects];
} else if ([self isKindOfClass:[DOMText class]])
rects = [self lineBoxRects];
}

if (![rects count])
rects = @[[NSValue valueWithRect:bounds]];

return rects;
}

- (NSArray *)absoluteQuads
{
NSArray *quads = nil;
NSRect bounds = [self boundingBox];
if (!NSIsEmptyRect(bounds)) {
if ([self isKindOfClass:[DOMElement class]]) {
DOMDocument *document = [self ownerDocument];
DOMCSSStyleDeclaration *style = [document getComputedStyle:(DOMElement *)self pseudoElement:@""];
if ([[style getPropertyValue:@"display"] isEqualToString:@"inline"])
quads = [self lineBoxQuads];
} else if ([self isKindOfClass:[DOMText class]])
quads = [self lineBoxQuads];
}

if (![quads count]) {
WKQuadObject* quadObject = [[WKQuadObject alloc] initWithQuad:[self absoluteQuad]];
quads = @[quadObject];
[quadObject release];
}

return quads;
}

- (NSArray *)borderRadii
{
RenderObject* renderer = core(self)->renderer();

if (is<RenderBox>(renderer)) {
WebCore::RoundedRect::Radii radii = downcast<RenderBox>(*renderer).borderRadii();
return @[[NSValue valueWithSize:(WebCore::FloatSize)radii.topLeft()],
[NSValue valueWithSize:(WebCore::FloatSize)radii.topRight()],
[NSValue valueWithSize:(WebCore::FloatSize)radii.bottomLeft()],
[NSValue valueWithSize:(WebCore::FloatSize)radii.bottomRight()]];
}
NSValue *emptyValue = [NSValue valueWithSize:CGSizeZero];
return @[emptyValue, emptyValue, emptyValue, emptyValue];
}

- (BOOL)containsOnlyInlineObjects
{
RenderObject* renderer = core(self)->renderer();
return renderer
&& renderer->childrenInline()
&& (is<RenderBlock>(*renderer) && !downcast<RenderBlock>(*renderer).inlineContinuation())
&& !renderer->isTable();
}

- (BOOL)isSelectableBlock
{
RenderObject* renderer = core(self)->renderer();
return renderer && (is<WebCore::RenderBlockFlow>(*renderer) || (is<RenderBlock>(*renderer) && downcast<RenderBlock>(*renderer).inlineContinuation() != nil));
}

- (DOMRange *)rangeOfContainingParagraph
{
DOMRange *result = nil;

Node *node = core(self);
VisiblePosition visiblePosition(createLegacyEditingPosition(node, 0), WebCore::DOWNSTREAM);
VisiblePosition visibleParagraphStart = startOfParagraph(visiblePosition);
VisiblePosition visibleParagraphEnd = endOfParagraph(visiblePosition);

Position paragraphStart = visibleParagraphStart.deepEquivalent().parentAnchoredEquivalent();
Position paragraphEnd = visibleParagraphEnd.deepEquivalent().parentAnchoredEquivalent();

if (paragraphStart.isNotNull() && paragraphEnd.isNotNull()) {
Ref<Range> range = Range::create(*node->ownerDocument(), paragraphStart, paragraphEnd);
result = kit(range.ptr());
}

return result;
}

- (CGFloat)textHeight
{
RenderObject* renderer = core(self)->renderer();
if (is<RenderText>(renderer))
return downcast<RenderText>(*renderer).style().computedLineHeight();

return CGFLOAT_MAX;
}

- (DOMNode *)findExplodedTextNodeAtPoint:(CGPoint)point
{
auto* renderer = core(self)->renderer();
if (!is<WebCore::RenderBlockFlow>(renderer))
return nil;

auto* renderText = downcast<WebCore::RenderBlockFlow>(*renderer).findClosestTextAtAbsolutePoint(point);
if (renderText && renderText->textNode())
return kit(renderText->textNode());

return nil;
}

@end

//-----------------

@implementation DOMElement (DOMUIKitComplexityExtensions)
- (int)structuralComplexityContribution { return 0; }
@end

@implementation DOMHTMLElement (DOMUIKitComplexityExtensions)

- (int)structuralComplexityContribution
{
int result = 0;
RenderObject * renderer = core(self)->renderer();
if (renderer) {
if (renderer->isFloatingOrOutOfFlowPositioned() ||
renderer->isWidget()) {
result = INT_MAX;
} else if (!renderer->firstChildSlow()) {
result = 0;
} else if (is<WebCore::RenderBlockFlow>(*renderer) || (is<RenderBlock>(*renderer) && downcast<RenderBlock>(*renderer).inlineContinuation())) {
BOOL noCost = NO;
if (is<RenderBox>(*renderer)) {
RenderBox& asBox = renderer->enclosingBox();
RenderObject* parent = asBox.parent();
RenderBox* parentRenderBox = is<RenderBox>(parent) ? downcast<RenderBox>(parent) : nullptr;
if (parentRenderBox && asBox.width() == parentRenderBox->width()) {
noCost = YES;
}
}
result = (noCost ? 0 : 1);
} else if (renderer->hasTransform()) {
result = INT_MAX;
}
}
return result;
}

@end

@implementation DOMHTMLBodyElement (DOMUIKitComplexityExtensions)
- (int)structuralComplexityContribution { return 0; }
@end

// Maximally complex elements

@implementation DOMHTMLFormElement (DOMUIKitComplexityExtensions)
- (int)structuralComplexityContribution { return INT_MAX; }
@end

@implementation DOMHTMLTableElement (DOMUIKitComplexityExtensions)
- (int)structuralComplexityContribution { return INT_MAX; }
@end

@implementation DOMHTMLFrameElement (DOMUIKitComplexityExtensions)
- (int)structuralComplexityContribution { return INT_MAX; }
@end

@implementation DOMHTMLIFrameElement (DOMUIKitComplexityExtensions)
- (int)structuralComplexityContribution { return INT_MAX; }
@end

@implementation DOMHTMLButtonElement (DOMUIKitComplexityExtensions)
- (int)structuralComplexityContribution { return INT_MAX; }
@end

@implementation DOMHTMLTextAreaElement (DOMUIKitComplexityExtensions)
- (int)structuralComplexityContribution { return INT_MAX; }
@end

@implementation DOMHTMLInputElement (DOMUIKitComplexityExtensions)
- (int)structuralComplexityContribution { return INT_MAX; }
@end

@implementation DOMHTMLSelectElement (DOMUIKitComplexityExtensions)
- (int)structuralComplexityContribution { return INT_MAX; }
@end

//-----------------

@implementation DOMHTMLAreaElement (DOMUIKitExtensions)

- (CGRect)boundingBoxWithOwner:(DOMNode *)owner
{
// ignores transforms
return owner ? snappedIntRect(core(self)->computeRect(core(owner)->renderer())) : CGRectZero;
}

- (WKQuad)absoluteQuadWithOwner:(DOMNode *)owner
{
if (owner) {
WebCore::IntRect rect = snappedIntRect(core(self)->computeRect(core(owner)->renderer()));
WKQuad quad;
quad.p1 = CGPointMake(rect.x(), rect.y());
quad.p2 = CGPointMake(rect.maxX(), rect.y());
quad.p3 = CGPointMake(rect.maxX(), rect.maxY());
quad.p4 = CGPointMake(rect.x(), rect.maxY());
return quad;
}

WKQuad zeroQuad = { CGPointZero, CGPointZero, CGPointZero, CGPointZero };
return zeroQuad;
}

- (NSArray *)boundingBoxesWithOwner:(DOMNode *)owner
{
return @[[NSValue valueWithRect:[self boundingBoxWithOwner:owner]]];
}

- (NSArray *)absoluteQuadsWithOwner:(DOMNode *)owner
{
WKQuadObject *quadObject = [[WKQuadObject alloc] initWithQuad:[self absoluteQuadWithOwner:owner]];
NSArray *quadArray = @[quadObject];
[quadObject release];
return quadArray;
}

@end

@implementation DOMHTMLSelectElement (DOMUIKitExtensions)

- (unsigned)completeLength
{
return core(self)->listItems().size();
}

- (DOMNode *)listItemAtIndex:(int)anIndex
{
return kit(core(self)->listItems()[anIndex]);
}

@end

@implementation DOMHTMLImageElement (WebDOMHTMLImageElementOperationsPrivate)

- (NSData *)dataRepresentation:(BOOL)rawImageData
{
auto* cachedImage = core(self)->cachedImage();
if (!cachedImage)
return nil;
auto* image = cachedImage->image();
if (!image)
return nil;
auto* data = rawImageData ? cachedImage->resourceBuffer() : image->data();
if (!data)
return nil;
return data->createNSData().autorelease();
}

- (NSString *)mimeType
{
auto* cachedImage = core(self)->cachedImage();
if (!cachedImage || !cachedImage->image())
return nil;
return cachedImage->response().mimeType();
}

@end

#endif