DOMUIKitExtensions.mm   [plain text]


/*
 * Copyright (C) 2007, 2008, 2009, 2013 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. 
 *
 */

#import "config.h"

#if PLATFORM(IOS)

#import "DOMUIKitExtensions.h"

#import "CachedImage.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 "FloatPoint.h"
#import "FontCascade.h"
#import "FrameSelection.h"
#import "HTMLAreaElement.h"
#import "htmlediting.h"
#import "HTMLImageElement.h"
#import "HTMLInputElement.h"
#import "HTMLSelectElement.h"
#import "HTMLTextAreaElement.h"
#import "Image.h"
#import "InlineBox.h"
#import "Node.h"
#import "Range.h"
#import "RenderBlock.h"
#import "RenderBlockFlow.h"
#import "RenderBox.h"
#import "RoundedRect.h"
#import "RenderObject.h"
#import "RenderStyleConstants.h"
#import "RenderText.h"
#import "SharedBuffer.h"
#import "VisiblePosition.h"
#import "VisibleUnits.h"

#import "WAKAppKitStubs.h"

using namespace WebCore;

using WebCore::FloatPoint;
using WebCore::FontCascade;
using WebCore::HTMLAreaElement;
using WebCore::HTMLImageElement;
using WebCore::HTMLSelectElement;
using WebCore::InlineBox;
using WebCore::IntRect;
using WebCore::Node;
using WebCore::Position;
using WebCore::Range;
using WebCore::RenderBlock;
using WebCore::RenderBox;
using WebCore::RenderObject;
using WebCore::RenderStyle;
using WebCore::RenderText;
using WebCore::RootInlineBox;
using WebCore::VisiblePosition;

@implementation DOMRange (UIKitExtensions)

- (void)move:(UInt32)amount inDirection:(WebTextAdjustmentDirection)direction
{
    Range *range = core(self);
    FrameSelection frameSelection;
    frameSelection.moveTo(range);
    
    TextGranularity granularity = 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 = LineGranularity;
    } else if (direction == WebTextAdjustmentUp) {
        direction = WebTextAdjustmentBackward;
        granularity = LineGranularity;
    }
    
    for (UInt32 i = 0; i < amount; i++)
        frameSelection.modify(FrameSelection::AlterationMove, (SelectionDirection)direction, granularity);
    
    ExceptionCode ignored;
    Position start = frameSelection.selection().start().parentAnchoredEquivalent();
    Position end = frameSelection.selection().end().parentAnchoredEquivalent();
    if (start.containerNode())
        range->setStart(*start.containerNode(), start.offsetInContainerNode(), ignored);
    if (end.containerNode())
        range->setEnd(*end.containerNode(), end.offsetInContainerNode(), ignored);
}

- (void)extend:(UInt32)amount inDirection:(WebTextAdjustmentDirection)direction
{
    Range *range = core(self);
    FrameSelection frameSelection;
    frameSelection.moveTo(range);
    
    for (UInt32 i = 0; i < amount; i++)
        frameSelection.modify(FrameSelection::AlterationExtend, (SelectionDirection)direction, CharacterGranularity);    
    
    ExceptionCode ignored;
    Position start = frameSelection.selection().start().parentAnchoredEquivalent();
    Position end = frameSelection.selection().end().parentAnchoredEquivalent();
    if (start.containerNode())
        range->setStart(*start.containerNode(), start.offsetInContainerNode(), ignored);
    if (end.containerNode())
        range->setEnd(*end.containerNode(), end.offsetInContainerNode(), ignored);
}

- (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 = [NSArray arrayWithObject:[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 = [NSArray arrayWithObject:quadObject];
        [quadObject release];
    }

    return quads;
}

- (NSArray *)borderRadii
{
    RenderObject* renderer = core(self)->renderer();
    
    if (is<RenderBox>(renderer)) {
        RoundedRect::Radii radii = downcast<RenderBox>(*renderer).borderRadii();
        return @[[NSValue valueWithSize:(FloatSize)radii.topLeft()],
                 [NSValue valueWithSize:(FloatSize)radii.topRight()],
                 [NSValue valueWithSize:(FloatSize)radii.bottomLeft()],
                 [NSValue valueWithSize:(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).inlineElementContinuation())
        && !renderer->isTable();
}

- (BOOL)isSelectableBlock
{
    RenderObject* renderer = core(self)->renderer();
    return renderer && (is<RenderBlockFlow>(*renderer) || (is<RenderBlock>(*renderer) && downcast<RenderBlock>(*renderer).inlineElementContinuation() != 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
{
    RenderObject* renderer = core(self)->renderer();
    if (!is<RenderBlockFlow>(renderer))
        return nil;

    RenderBlockFlow* block = downcast<RenderBlockFlow>(renderer);
    RenderText* renderText = block->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->isEmpty()) {
            result = 0;
        } else if (is<RenderBlockFlow>(*renderer) || (is<RenderBlock>(*renderer) && downcast<RenderBlock>(*renderer).inlineElementContinuation())) {
            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 *)anOwner
{
    // ignores transforms
    return anOwner ? snappedIntRect(core(self)->computeRect(core(anOwner)->renderer())) : CGRectZero;
}

- (WKQuad)absoluteQuadWithOwner:(DOMNode *)anOwner
{
    if (anOwner) {
        // FIXME: ECLAIR
        //WebCore::FloatQuad theQuad = core(self)->getAbsoluteQuad(core(anOwner)->renderer());
        WebCore::IntRect rect = snappedIntRect(core(self)->computeRect(core(anOwner)->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 *)anOwner
{
    return [NSArray arrayWithObject:[NSValue valueWithRect:[self boundingBoxWithOwner:anOwner]]];
}

- (NSArray *)absoluteQuadsWithOwner:(DOMNode *)anOwner
{
    WKQuadObject* quadObject = [[WKQuadObject alloc] initWithQuad:[self absoluteQuadWithOwner:anOwner]];
    NSArray*    quadArray = [NSArray arrayWithObject: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
{
    WebCore::CachedImage* cachedImage = core(self)->cachedImage();
    if (!cachedImage)
        return nil;
    WebCore::Image* image = cachedImage->image();
    if (!image)
        return nil;
    WebCore::SharedBuffer* data = rawImageData ? cachedImage->resourceBuffer() : image->data();
    if (!data)
        return nil;
    return data->createNSData().autorelease();
}

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

@end

#endif