FrameIOS.mm   [plain text]


/*
 * Copyright (C) 2006, 2007, 2008, Apple Inc. All rights reserved.
 */

#import "config.h"
#import "Frame.h"

#if PLATFORM(IOS)

#import "AnimationController.h"
#import "BlockExceptions.h"
#import "DOMCore.h"
#import "DOMCSSStyleDeclarationInternal.h"
#import "DOMInternal.h"
#import "DOMNodeInternal.h"
#import "DOMWindow.h"
#import "Document.h"
#import "DocumentMarker.h"
#import "DocumentMarkerController.h"
#import "Editor.h"
#import "EditorClient.h"
#import "EventHandler.h"
#import "EventNames.h"
#import "FormController.h"
#import "FrameSelection.h"
#import "FrameSnapshottingMac.h"
#import "FrameView.h"
#import "HTMLAreaElement.h"
#import "HTMLDocument.h"
#import "HTMLElement.h"
#import "HTMLNames.h"
#import "HTMLObjectElement.h"
#import "HitTestRequest.h"
#import "HitTestResult.h"
#import "JSDOMWindowBase.h"
#import "NodeRenderStyle.h"
#import "NodeTraversal.h"
#import "Page.h"
#import "PageTransitionEvent.h"
#import "PropertySetCSSStyleDeclaration.h"
#import "RenderLayer.h"
#import "RenderLayerCompositor.h"
#import "RenderTextControl.h"
#import "RenderView.h"
#import "TextBoundaries.h"
#import "TextIterator.h"
#import "VisiblePosition.h"
#import "WAKWindow.h"
#import "WebCoreSystemInterface.h"
#import "VisibleUnits.h"
#import <runtime/JSLock.h>

using namespace WebCore::HTMLNames;
using namespace WTF::Unicode;

using JSC::JSLockHolder;

namespace WebCore {

// Create <html><body (style="...")></body></html> doing minimal amount of work.
void Frame::initWithSimpleHTMLDocument(const String& style, const KURL& url)
{
    m_loader.initForSynthesizedDocument(url);

    RefPtr<HTMLDocument> document = HTMLDocument::createSynthesizedDocument(this, url);
    document->setCompatibilityMode(Document::LimitedQuirksMode);
    document->createDOMWindow();
    setDocument(document);

    ExceptionCode ec;
    RefPtr<Element> rootElement = document->createElementNS(xhtmlNamespaceURI, "html", ec);

    RefPtr<Element> body = document->createElementNS(xhtmlNamespaceURI, "body", ec);
    if (!style.isEmpty())
        body->setAttribute(HTMLNames::styleAttr, style);

    rootElement->appendChild(body, ec);
    document->appendChild(rootElement, ec);
}

int Frame::indexCountOfWordPrecedingSelection(NSString *word) const
{
    int result = -1;

    if (!page() || page()->selection().isNone())
        return result;

    RefPtr<Range> searchRange(rangeOfContents(document()));
    VisiblePosition start(page()->selection().start(), page()->selection().affinity());
    VisiblePosition oneBeforeStart = start.previous();

    setEnd(searchRange.get(), oneBeforeStart.isNotNull() ? oneBeforeStart : start);

    int exception = 0;
    if (searchRange->collapsed(exception)) {
        return result;
    }

    WordAwareIterator it(searchRange.get());
    while (!it.atEnd()) {
        const UChar *chars = it.characters();
        int len = it.length();
        if (len > 1 || !isSpaceOrNewline(chars[0])) {
            int word_start = 0;
            for (int i = 1; i < len; i++) {
                if (isSpaceOrNewline(chars[i]) || chars[i] == 0xA0) {
                    int word_length = i - word_start;
                    NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:const_cast<unichar *>(chars) + word_start length:word_length freeWhenDone:NO];
                    if ([chunk isEqualToString:word])
                        result++;
                    [chunk release];
                    word_start += word_length + 1;
                }
            }
            if (word_start < len) {
                NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:const_cast<unichar *>(chars) + word_start length:len - word_start freeWhenDone:NO];
                if ([chunk isEqualToString:word])
                    result++;
                [chunk release];
            }
        }
        it.advance();
    }

    return result + 1;
}

NSArray *Frame::wordsInCurrentParagraph() const
{
    document()->updateLayout();

    if (!page() || !page()->selection().isCaret())
        return nil;

    VisiblePosition pos(page()->selection().start(), page()->selection().affinity());
    VisiblePosition end(pos);
    if (!isStartOfParagraph(end)) {
        VisiblePosition previous = end.previous();
        UChar c(previous.characterAfter());
        if (!iswpunct(c) && !isSpaceOrNewline(c) && c != 0xA0)
            end = startOfWord(end);
    }
    VisiblePosition start(startOfParagraph(end));

    RefPtr<Range> searchRange(rangeOfContents(document()));
    setStart(searchRange.get(), start);
    setEnd(searchRange.get(), end);

    int exception = 0;
    if (searchRange->collapsed(exception))
        return nil;

    NSMutableArray *words = [NSMutableArray array];

    WordAwareIterator it(searchRange.get());
    while (!it.atEnd()) {
        const UChar *chars = it.characters();
        int len = it.length();
        if (len > 1 || !isSpaceOrNewline(chars[0])) {
            int word_start = 0;
            for (int i = 1; i < len; i++) {
                if (isSpaceOrNewline(chars[i]) || chars[i] == 0xA0) {
                    int word_length = i - word_start;
                    if (word_length > 0) {
                        NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:const_cast<unichar *>(chars) + word_start length:word_length freeWhenDone:NO];
                        [words addObject:chunk];
                        [chunk release];
                    }
                    word_start += word_length + 1;
                }
            }
            if (word_start < len) {
                NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:const_cast<unichar *>(chars) + word_start length:len - word_start freeWhenDone:NO];
                [words addObject:chunk];
                [chunk release];
            }
        }
        it.advance();
    }

    if ([words count] > 0 && isEndOfParagraph(pos) && !isStartOfParagraph(pos)) {
        VisiblePosition previous = pos.previous();
        UChar c(previous.characterAfter());
        if (!isSpaceOrNewline(c) && c != 0xA0)
            [words removeLastObject];
    }

    return words;
}

#define CHECK_FONT_SIZE 0
#define RECT_LOGGING 0

CGRect Frame::renderRectForPoint(CGPoint point, bool* isReplaced, float* fontSize) const
{
    *isReplaced = false;
    *fontSize = 0;

    if (!m_doc || !m_doc->renderBox())
        return CGRectZero;

    // FIXME: why this layer check?
    RenderLayer* layer = m_doc->renderBox()->layer();
    if (!layer)
        return CGRectZero;

    HitTestResult result = eventHandler()->hitTestResultAtPoint(IntPoint(roundf(point.x), roundf(point.y)));

    Node* node = result.innerNode();
    if (!node)
        return CGRectZero;

    RenderObject* hitRenderer = node->renderer();
    RenderObject* renderer = hitRenderer;
#if RECT_LOGGING
    printf("\n%f %f\n", point.x, point.y);
#endif
    while (renderer && !renderer->isBody() && !renderer->isRoot()) {
#if RECT_LOGGING
        CGRect rect = renderer->absoluteBoundingBoxRect(true);
        if (renderer->node()) {
            const char *nodeName = renderer->node()->nodeName().ascii().data();
            printf("%s %f %f %f %f\n", nodeName, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
        }
#endif
        if (renderer->isRenderBlock() || renderer->isInlineBlockOrInlineTable() || renderer->isReplaced()) {
            *isReplaced = renderer->isReplaced();
#if CHECK_FONT_SIZE
            for (RenderObject *textRenderer = hitRenderer; textRenderer; textRenderer = textRenderer->traverseNext(hitRenderer)) {
                if (textRenderer->isText()) {
                    *fontSize = textRenderer->font(true).pixelSize();
                    break;
                }
            }
#endif
            IntRect targetRect = renderer->absoluteBoundingBoxRect(true);
            for (Widget* currView = renderer->view()->frameView(); currView && currView != view(); currView = currView->parent())
                targetRect = currView->convertToContainingView(targetRect);

            return targetRect;
        }
        renderer = renderer->parent();
    }

    return CGRectZero;
}

#define ALLOW_SCROLL_LISTENERS 0

static Node* ancestorRespondingToScrollWheelEvents(const HitTestResult& hitTestResult, Node* terminationNode, IntRect* nodeBounds)
{
    if (nodeBounds)
        *nodeBounds = IntRect();

    Node* scrollingAncestor = 0;
    for (Node* node = hitTestResult.innerNode(); node && node != terminationNode && !node->hasTagName(HTMLNames::bodyTag); node = node->parentNode()) {
#if ALLOW_SCROLL_LISTENERS
        if (node->willRespondToMouseWheelEvents()) {
            scrollingAncestor = node;
            continue;
        }
#endif

        RenderObject* renderer = node->renderer();
        if (!renderer)
            continue;

        if ((renderer->isTextField() || renderer->isTextArea()) && static_cast<RenderTextControl *>(renderer)->canScroll()) {
            scrollingAncestor = node;
            continue;
        }

        RenderStyle* style = renderer->style();
        if (!style)
            continue;

        if (style && renderer->hasOverflowClip() &&
            (style->overflowY() == OAUTO || style->overflowY() == OSCROLL || style->overflowY() == OOVERLAY ||
             style->overflowX() == OAUTO || style->overflowX() == OSCROLL || style->overflowX() == OOVERLAY))
            scrollingAncestor = node;
    }

    return scrollingAncestor;
}

static Node* ancestorRespondingToClickEvents(const HitTestResult& hitTestResult, Node* terminationNode, IntRect* nodeBounds)
{
    bool bodyHasBeenReached = false;
    bool pointerCursorStillValid = true;

    if (nodeBounds)
        *nodeBounds = IntRect();

    Node* pointerCursorNode = 0;
    for (Node* node = hitTestResult.innerNode(); node && node != terminationNode; node = node->parentNode()) {
        ASSERT(!node->isInShadowTree());

        // We only accept pointer nodes before reaching the body tag.
        if (node->hasTagName(HTMLNames::bodyTag)) {
#if USE(UIKIT_EDITING)
            // Make sure we cover the case of an empty editable body.
            if (!pointerCursorNode && node->isContentEditable())
                pointerCursorNode = node;
#endif
            bodyHasBeenReached = true;
            pointerCursorStillValid = false;
        }

        // If we already have a pointer, and we reach a table, don't accept it.
        if (pointerCursorNode && (node->hasTagName(HTMLNames::tableTag) || node->hasTagName(HTMLNames::tbodyTag)))
            pointerCursorStillValid = false;

        // If we haven't reached the body, and we are still paying attention to pointer cursors, and the node has a pointer cursor...
        if (pointerCursorStillValid && node->renderStyle() && node->renderStyle()->cursor() == CURSOR_POINTER)
            pointerCursorNode = node;
        // We want the lowest unbroken chain of pointer cursors.
        else if (pointerCursorNode)
            pointerCursorStillValid = false;

        if (node->willRespondToMouseClickEvents() || node->willRespondToMouseMoveEvents()) {
            // If we're at the body or higher, use the pointer cursor node (which may be null).
            if (bodyHasBeenReached)
                node = pointerCursorNode;

            // If we are interested about the frame, use it.
            if (nodeBounds) {
                // This is a check to see whether this node is an area element.  The only way this can happen is if this is the first check.
                if (node == hitTestResult.innerNode() && node != hitTestResult.innerNonSharedNode() && node->hasTagName(HTMLNames::areaTag))
                    *nodeBounds = pixelSnappedIntRect(static_cast<HTMLAreaElement *>(node)->computeRect(hitTestResult.innerNonSharedNode()->renderer()));
                else if (node && node->renderer())
                    *nodeBounds = node->renderer()->absoluteBoundingBoxRect(true);
            }

            return node;
        }
    }

    return 0;
}

void Frame::betterApproximateNode(const IntPoint& testPoint, NodeQualifier nodeQualifierFunction, Node*& best, Node* failedNode, IntPoint& bestPoint, IntRect& bestRect, const IntRect& testRect)
{
    IntRect candidateRect;
    Node* candidate = nodeQualifierFunction(eventHandler()->hitTestResultAtPoint(testPoint), failedNode, &candidateRect);

    // Bail if we have no candidate, or the candidate is already equal to our current best node,
    // or our candidate is the avoidedNode and there is a current best node.
    if (!candidate || candidate == best)
        return;

    // The document should never be considered the best alternative.
    if (candidate->isDocumentNode())
        return;

    if (best) {
        IntRect bestIntersect = intersection(testRect, bestRect);
        IntRect candidateIntersect = intersection(testRect, candidateRect);
        // if the candidate intersection is smaller than the current best intersection, bail.
        if (candidateIntersect.width() * candidateIntersect.height() <= bestIntersect.width() * bestIntersect.height())
            return;
    }

    // At this point we either don't have a previous best, or our current candidate has a better intersection.
    best = candidate;
    bestPoint = testPoint;
    bestRect = candidateRect;
}

bool Frame::hitTestResultAtViewportLocation(const FloatPoint& viewportLocation, HitTestResult& hitTestResult, IntPoint& center)
{
    if (!m_doc || !m_doc->renderer())
        return false;

    FrameView *view = m_view.get();
    if (!view)
        return false;

    center = view->windowToContents(roundedIntPoint(viewportLocation));
    hitTestResult = eventHandler()->hitTestResultAtPoint(center);
    return true;
}

Node* Frame::qualifyingNodeAtViewportLocation(const FloatPoint& viewportLocation, FloatPoint& adjustedViewportLocation, NodeQualifier nodeQualifierFunction, bool shouldApproximate)
{
    IntPoint testCenter;
    HitTestResult candidateInfo;
    if (!hitTestResultAtViewportLocation(viewportLocation, candidateInfo, testCenter))
        return 0;

    IntPoint bestPoint = testCenter;

    // We have the candidate node at the location, check whether it or one of its ancestors passes
    // the qualifier function, which typically checks if the node responds to a particular event type.
    Node* approximateNode = nodeQualifierFunction(candidateInfo, 0, 0);

#if USE(UIKIT_EDITING)
    if (approximateNode && approximateNode->isContentEditable()) {
        // If we are in editable content, we look for the root editable element.
        approximateNode = approximateNode->rootEditableElement();
        // If we have a focusable node, there is no need to approximate.
        if (approximateNode)
            shouldApproximate = false;
    }
#endif
    if (approximateNode && shouldApproximate) {
        float scale = m_documentScale;

        const int defaultMaxRadius = 15;
        int maxRadius = (scale < 1.0) ? (int)(defaultMaxRadius / scale) : defaultMaxRadius;

        const float testOffsets[] = {
            -.3f, -.3f,
            -.6f, -.6f,
            +.3f, +.3f,
            -.9f, -.9f,
        };

        Node* originalApproximateNode = approximateNode;
        for (unsigned n = 0; n < sizeof(testOffsets) / sizeof(testOffsets[0]); n += 2) {
            IntSize testOffset(testOffsets[n] * maxRadius, testOffsets[n + 1] * maxRadius);
            IntPoint testPoint = testCenter + testOffset;

            HitTestResult candidateInfo = eventHandler()->hitTestResultAtPoint(testPoint);
            Node* candidateNode = nodeQualifierFunction(candidateInfo, 0, 0);
            if (candidateNode && candidateNode->isDescendantOf(originalApproximateNode)) {
                approximateNode = candidateNode;
                bestPoint = testPoint;
                break;
            }
        }
    } else if (!approximateNode && shouldApproximate) {
        // Grab the closest parent element of our failed candidate node.
        Node* candidate = candidateInfo.innerNode();
        Node* failedNode = candidate;

        while (candidate && !candidate->isElementNode())
            candidate = candidate->parentNode();

        if (candidate)
            failedNode = candidate;

        // We don't approximate the node if we are dragging, we instead force the user to be precise.
        float scale = m_documentScale;

        const int defaultMaxRadius = 15;
        int maxRadius = (scale < 1.0) ? (int)(defaultMaxRadius / scale) : defaultMaxRadius;

        // The center point was tested earlier.
        const float testOffsets[] = {
            -.3f, -.3f,
            +.3f, -.3f,
            -.3f, +.3f,
            +.3f, +.3f,
            -.6f, -.6f,
            +.6f, -.6f,
            -.6f, +.6f,
            +.6f, +.6f,
            -1.f, 0,
            +1.f, 0,
            0, +1.f,
            0, -1.f,
        };
        IntRect bestFrame;
        IntRect testRect(testCenter, IntSize());
        testRect.inflate(maxRadius);
        int currentTestRadius = 0;
        for (unsigned n = 0; n < sizeof(testOffsets) / sizeof(testOffsets[0]); n += 2) {
            IntSize testOffset(testOffsets[n] * maxRadius, testOffsets[n + 1] * maxRadius);
            IntPoint testPoint = testCenter + testOffset;
            int testRadius = max(abs(testOffset.width()), abs(testOffset.height()));
            if (testRadius > currentTestRadius) {
                // Bail out with the best match within a radius
                currentTestRadius = testRadius;
                if (approximateNode)
                    break;
            }
            betterApproximateNode(testPoint, nodeQualifierFunction, approximateNode, failedNode, bestPoint, bestFrame, testRect);
        }
    }

    if (approximateNode) {
        IntPoint p = m_view->contentsToWindow(bestPoint);
        adjustedViewportLocation = p;
#if USE(UIKIT_EDITING)
        if (approximateNode->isContentEditable()) {
            // When in editable content, look for the root editable node again,
            // since this could be the node found with the approximation.
            approximateNode = approximateNode->rootEditableElement();
        }
#endif
    }

    return approximateNode;
}

Node* Frame::deepestNodeAtLocation(const FloatPoint& viewportLocation)
{
    IntPoint center;
    HitTestResult hitTestResult;
    if (!hitTestResultAtViewportLocation(viewportLocation, hitTestResult, center))
        return 0;

    return hitTestResult.innerNode();
}

Node* Frame::nodeRespondingToClickEvents(const FloatPoint& viewportLocation, FloatPoint& adjustedViewportLocation)
{
    return qualifyingNodeAtViewportLocation(viewportLocation, adjustedViewportLocation, &ancestorRespondingToClickEvents, true);
}

Node* Frame::nodeRespondingToScrollWheelEvents(const FloatPoint& viewportLocation)
{
    FloatPoint adjustedViewportLocation;
    return qualifyingNodeAtViewportLocation(viewportLocation, adjustedViewportLocation, &ancestorRespondingToScrollWheelEvents, false);
}

int Frame::preferredHeight() const
{
    Document *doc = document();
    if (!doc)
        return 0;

    doc->updateLayout();

    Node *body = doc->body();
    if (!body)
        return 0;

    RenderObject *renderer = body->renderer();
    if (!renderer || !renderer->isRenderBlock())
        return 0;

    RenderBlock *block = static_cast<RenderBlock *>(renderer);
    return block->height() + block->marginTop() + block->marginBottom();
}

int Frame::innerLineHeight(DOMNode *node) const
{
    if (!node)
        return 0;

    Document *doc = document();
    if (!doc)
        return 0;

    doc->updateLayout();

    Node *n = core(node);
    if (!n)
        return 0;

    RenderObject *renderer = n->renderer();
    if (!renderer)
        return 0;

    return renderer->innerLineHeight();
}

void Frame::updateLayout() const
{
    Document *doc = document();
    if (!doc)
        return;
    doc->updateLayout();

    if (view())
        view()->adjustViewSize();
}

NSRect Frame::caretRect() const
{
    if (selection()->isNone())
        return CGRectZero;
    return selection()->isCaret() ? selection()->absoluteCaretBounds() : VisiblePosition(selection()->end()).absoluteCaretBounds();
}

NSRect Frame::rectForScrollToVisible() const
{
    VisibleSelection selection(this->selection()->selection());
    return rectForSelection(selection);
}

NSRect Frame::rectForSelection(VisibleSelection& selection) const
{
    if (selection.isNone())
        return CGRectZero;

    if (selection.isCaret())
        return caretRect();

    if (editor().client())
        editor().client()->suppressSelectionNotifications();

    VisibleSelection originalSelection(selection);
    Position pos;

    // The selection controllers below need to be associated with
    // a frame in order to calculate geometry. This causes them
    // to do more work here than we would like.
    // Ideally, we would have a sort offline geometry-only mode
    // for selection controllers so we could do this kind of
    // work as cheaply as possible.

    pos = originalSelection.start();
    selection.setBase(pos);
    selection.setExtent(pos);
    FrameSelection startFrameSelection(const_cast<Frame*>(this));
    startFrameSelection.suppressCloseTyping();
    startFrameSelection.setSelection(selection);
    FloatRect startRect(startFrameSelection.absoluteCaretBounds());
    startFrameSelection.restoreCloseTyping();

    pos = originalSelection.end();
    selection.setBase(pos);
    selection.setExtent(pos);
    FrameSelection endFrameSelection(const_cast<Frame*>(this));
    endFrameSelection.suppressCloseTyping();
    endFrameSelection.setSelection(selection);
    FloatRect endRect(endFrameSelection.absoluteCaretBounds());
    endFrameSelection.restoreCloseTyping();

    if (editor().client())
        editor().client()->restoreSelectionNotifications();

    return unionRect(startRect, endRect);
}

DOMCSSStyleDeclaration *Frame::styleAtSelectionStart() const
{
    Position start = selection()->start();
    RefPtr<EditingStyle> editingStyle = EditingStyle::styleAtSelectionStart(selection()->selection());
    if (!editingStyle)
        return 0;
    PropertySetCSSStyleDeclaration *propertySetCSSStyleDeclaration = new PropertySetCSSStyleDeclaration(editingStyle->style());
    // The auto-generated code for DOMCSSStyleDeclaration derefs its pointer when it is deallocated.
    return kit((CSSStyleDeclaration *)propertySetCSSStyleDeclaration);
}
    
void Frame::createDefaultFieldEditorDocumentStructure() const
{
    Document* doc = document();

    ASSERT(doc);
    ASSERT(doc->body());

// The system font is different on iPhone 4 (i.e. devices where the screen scale is 2.0).
// <rdar://problem/7933949> New system fonts for iPhone 4 (helvetica neue 55 and 75)
#define STYLE_ATTR "margin:0px;word-wrap:break-word;-webkit-nbsp-mode:space;-webkit-line-break:after-white-space;white-space:nowrap;font-family:"
    RefPtr<Element> body = doc->body();
    body->setAttribute(styleAttr, wkGetScreenScaleFactor() == 2.0 ? STYLE_ATTR"'.Helvetica NeueUI'" : STYLE_ATTR"'Helvetica'");
#undef STYLE_ATTR

    ExceptionCode ec = 0;
    RefPtr<Element> sizeElement = doc->createElementNS(xhtmlNamespaceURI, "div", ec);
    ASSERT(!ec);
    sizeElement->setAttribute(idAttr, "size");
    sizeElement->setAttribute(contenteditableAttr, "false");

    RefPtr<Element> textElement = doc->createElementNS(xhtmlNamespaceURI, "div", ec);
    ASSERT(!ec);
    textElement->setAttribute(idAttr, "text");
    textElement->setAttribute(contenteditableAttr, "true");

    sizeElement->appendChild(textElement, ec);
    ASSERT(!ec);
    body->appendChild(sizeElement, ec);
    ASSERT(!ec);
}

static inline bool inSameEditableContent(const VisiblePosition &a, const VisiblePosition &b)
{
    Position ap = a.deepEquivalent();
    Node *an = ap.deprecatedNode();
    if (!an)
        return false;

    Position bp = b.deepEquivalent();
    Node *bn = bp.deprecatedNode();
    if (!bn)
        return false;

    if (!an->isContentEditable() || !bn->isContentEditable())
        return false;

    return an->rootEditableElement() == bn->rootEditableElement();
}

static inline bool isStartOfEditableContent(const VisiblePosition &p)
{
    return !inSameEditableContent(p, p.previous());
}

static inline bool isEndOfEditableContent(const VisiblePosition &p)
{
    return !inSameEditableContent(p, p.next());
}

unsigned Frame::formElementsCharacterCount() const
{
    Document* doc = document();
    if (!doc)
        return 0;
    return doc->formController().formElementsCharacterCount();
}

void Frame::setTimersPaused(bool paused)
{
    JSLockHolder lock(JSDOMWindowBase::commonVM());
    setTimersPausedInternal(paused);
}

void Frame::setTimersPausedInternal(bool paused)
{
    if (paused) {
        m_timersPausedCount++;
        if (m_timersPausedCount == 1) {
            clearTimers();
            if (document())
                document()->suspendScheduledTasks(ActiveDOMObject::DocumentWillBePaused);
        }
    } else {
        m_timersPausedCount--;
        ASSERT(m_timersPausedCount >= 0);
        if (m_timersPausedCount == 0) {
            if (document())
                document()->resumeScheduledTasks(ActiveDOMObject::DocumentWillBePaused);

            // clearTimers() suspended animations and pending relayouts, reschedule if needed.
            if (animation())
                animation()->resumeAnimationsForDocument(document());

            if (view())
                view()->scheduleRelayout();
        }
    }

    // We need to make sure all subframes' states are up to date.
    for (Frame *frame = tree()->firstChild(); frame; frame = frame->tree()->nextSibling())
        frame->setTimersPausedInternal(paused);
}

void Frame::dispatchPageHideEventBeforePause()
{
    ASSERT(this == page()->mainFrame());
    if (this != page()->mainFrame())
        return;

    for (Frame *frame = this; frame; frame = frame->tree()->traverseNext(this))
        frame->document()->domWindow()->dispatchEvent(PageTransitionEvent::create(eventNames().pagehideEvent, true), document());
}

void Frame::dispatchPageShowEventBeforeResume()
{
    ASSERT(this == page()->mainFrame());
    if (this != page()->mainFrame())
        return;

    for (Frame *frame = this; frame; frame = frame->tree()->traverseNext(this))
        frame->document()->domWindow()->dispatchEvent(PageTransitionEvent::create(eventNames().pageshowEvent, true), document());
}

void Frame::setRangedSelectionBaseToCurrentSelection()
{
    m_rangedSelectionBase = selection()->selection();
}

void Frame::setRangedSelectionBaseToCurrentSelectionStart()
{
    FrameSelection* sel = selection();
    m_rangedSelectionBase = VisibleSelection(sel->selection().start(), sel->affinity());
}

void Frame::setRangedSelectionBaseToCurrentSelectionEnd()
{
    FrameSelection* sel = selection();
    m_rangedSelectionBase = VisibleSelection(sel->selection().end(), sel->affinity());
}

VisibleSelection Frame::rangedSelectionBase() const
{
    return m_rangedSelectionBase;
}

void Frame::clearRangedSelectionInitialExtent()
{
    m_rangedSelectionInitialExtent = VisibleSelection();
}

void Frame::setRangedSelectionInitialExtentToCurrentSelectionStart()
{
    FrameSelection* sel = selection();
    m_rangedSelectionInitialExtent = VisibleSelection(sel->selection().start(), sel->affinity());
}

void Frame::setRangedSelectionInitialExtentToCurrentSelectionEnd()
{
    FrameSelection* sel = selection();
    m_rangedSelectionInitialExtent = VisibleSelection(sel->selection().end(), sel->affinity());
}

VisibleSelection Frame::rangedSelectionInitialExtent() const
{
    return m_rangedSelectionInitialExtent;
}

void Frame::recursiveSetUpdateAppearanceEnabled(bool enabled)
{
    selection()->setUpdateAppearanceEnabled(enabled);
    for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling())
        child->recursiveSetUpdateAppearanceEnabled(enabled);
}

// FIXME: Break this function up into pieces with descriptive function names so that it's easier to follow.
NSArray* Frame::interpretationsForCurrentRoot() const
{
    if (!document())
        return nil;
    
    Element *root = selection()->selectionType() == VisibleSelection::NoSelection ? document()->body() : selection()->rootEditableElement();
    unsigned rootChildCount = root->childNodeCount();
    RefPtr<Range> rangeOfRootContents = Range::create(document(), createLegacyEditingPosition(root, 0), createLegacyEditingPosition(root, rootChildCount));
    
    Vector<DocumentMarker*> markersInRoot = document()->markers()->markersInRange(rangeOfRootContents.get(), DocumentMarker::DictationPhraseWithAlternatives);
    
    // There are no phrases with alternatives, so there is just one interpretation.
    if (markersInRoot.size() == 0)
        return [NSArray arrayWithObject:plainText(rangeOfRootContents.get())];
    
    // The number of interpretations will be i1 * i2 * ... * iN, where iX is the number of interpretations for the Xth phrase with alternatives.
    size_t interpretationsCount = 1;
    
    Vector<DocumentMarker*>::const_iterator end = markersInRoot.end();
    for (Vector<DocumentMarker*>::const_iterator it = markersInRoot.begin(); it != end; ++it)
        interpretationsCount *= (*it)->alternatives().size() + 1;
    
    Vector<Vector<UChar> > interpretations;
    interpretations.grow(interpretationsCount);
    
    Position precedingTextStartPosition = createLegacyEditingPosition(root, 0);
    
    unsigned combinationsSoFar = 1;
    
    Node* pastLastNode = rangeOfRootContents->pastLastNode();
    for (Node* node = rangeOfRootContents->firstNode(); node != pastLastNode; node = NodeTraversal::next(node)) {
        Vector<DocumentMarker> markers = document()->markers()->markersForNode(node);
        Vector<DocumentMarker>::const_iterator end = markers.end();
        for (Vector<DocumentMarker>::const_iterator it = markers.begin(); it != end; ++it) {
            
            const DocumentMarker& marker = *it;
            
            if (marker.type() != DocumentMarker::DictationPhraseWithAlternatives)
                continue;
            
            // First, add text that precede the marker.
            if (precedingTextStartPosition != createLegacyEditingPosition(node, marker.startOffset())) {
                RefPtr<Range> precedingTextRange = Range::create(document(), precedingTextStartPosition, createLegacyEditingPosition(node, marker.startOffset()));
                String precedingText = plainText(precedingTextRange.get());
                if (precedingText.length()) {
                    for (size_t i = 0; i < interpretationsCount; i++)
                        interpretations.at(i).append(precedingText.characters(), precedingText.length());
                }
            }
            
            RefPtr<Range> rangeForMarker = Range::create(document(), createLegacyEditingPosition(node, marker.startOffset()), createLegacyEditingPosition(node, marker.endOffset()));
            String visibleTextForMarker = plainText(rangeForMarker.get());
            size_t interpretationsCountForCurrentMarker = marker.alternatives().size() + 1;
            
            for (size_t i = 0; i < interpretationsCount; i++) {
                
                // Determine text for the ith interpretation. It will either be the visible text, or one of its 
                // alternatives stored in the marker.
                
                size_t indexOfInterpretationForCurrentMarker = (i / combinationsSoFar) % interpretationsCountForCurrentMarker;
                if (indexOfInterpretationForCurrentMarker == 0)
                    interpretations.at(i).append(visibleTextForMarker.characters(), visibleTextForMarker.length());
                else {
                    const String& alternative = marker.alternatives().at(i % marker.alternatives().size());
                    interpretations.at(i).append(alternative.characters(), alternative.length());
                }
            }
            
            combinationsSoFar *= interpretationsCountForCurrentMarker;
            
            precedingTextStartPosition = createLegacyEditingPosition(node, marker.endOffset());
        }
    }
    
    // Finally, add any text after the last marker.
    RefPtr<Range> afterLastMarkerRange = Range::create(document(), precedingTextStartPosition, createLegacyEditingPosition(root, rootChildCount));
    String textAfterLastMarker = plainText(afterLastMarkerRange.get());
    if (textAfterLastMarker.length()) {
        for (size_t i = 0; i < interpretationsCount; i++)
            interpretations.at(i).append(textAfterLastMarker.characters(), textAfterLastMarker.length());
    }
    
    NSMutableArray *result = [NSMutableArray array];
    for (size_t i = 0; i < interpretationsCount; i++)
        [result addObject:(NSString *)String(interpretations.at(i))];
    
    return result;
}

void Frame::setDocumentScale(float newScale)
{
    if (newScale == m_documentScale)
        return;

    m_documentScale = newScale;
    for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling())
        child->setDocumentScale(newScale);

#if USE(ACCELERATED_COMPOSITING)
    RenderView* root = contentRenderer();
    if (root && root->compositor())
        root->compositor()->deviceOrPageScaleFactorChanged();
#endif
}

float Frame::documentScale() const
{
    return m_documentScale;
}

float Frame::minimumDocumentScale() const
{
    WAKWindow *window = [m_view.get()->documentView() window];
    return window ? [window zoomedOutTileScale] : 1;
}

float Frame::deviceScaleFactor() const
{
    WAKWindow *window = [m_view.get()->documentView() window];
    return window ? [window screenScale] : 1;
}

static bool anyFrameHasTiledLayers(Frame* rootFrame)
{
    for (Frame* frame = rootFrame; frame; frame = frame->tree()->traverseNext(rootFrame)) {
        if (frame->containsTiledBackingLayers())
            return true;
    }
    return false;
}

void Frame::viewportOffsetChanged(ViewportOffsetChangeType changeType)
{
#if USE(ACCELERATED_COMPOSITING)
    if (changeType == IncrementalScrollOffset) {
        if (anyFrameHasTiledLayers(this)) {
            if (RenderView* root = contentRenderer()) {
                if (root->compositor())
                    root->compositor()->didChangeVisibleRect();
            }
        }
    }

    if (changeType == CompletedScrollOffset) {
        if (RenderView* root = contentRenderer()) {
            if (root->compositor())
                root->compositor()->updateCompositingLayers(CompositingUpdateOnScroll);
        }
    }
#endif
}

bool Frame::containsTiledBackingLayers() const
{
#if USE(ACCELERATED_COMPOSITING)
    if (RenderView* root = contentRenderer()) {
        if (root->compositor())
            return root->compositor()->hasNonMainLayersWithTiledBacking();
    }
#endif
    return false;
}

void Frame::overflowScrollPositionChangedForNode(const IntPoint& position, Node* node, bool isUserScroll)
{
    RenderObject* renderer = node->renderer();
    if (!renderer || !renderer->hasLayer())
        return;
    
    RenderLayer* layer = toRenderBoxModelObject(renderer)->layer();
    
    layer->setIsUserScroll(isUserScroll);
    layer->scrollToOffsetWithoutAnimation(position);
    layer->setIsUserScroll(false);
    layer->didEndScroll(); // FIXME: Should we always call this?
}

void Frame::resetAllGeolocationPermission()
{
    if (document()->domWindow())
        document()->domWindow()->resetAllGeolocationPermission();

    for (Frame* child = tree()->firstChild(); child; child = child->tree()->nextSibling())
        child->resetAllGeolocationPermission();
}

} // namespace WebCore
#endif // PLATFORM(IOS)

#if ENABLE(IOS_TEXT_AUTOSIZING)
namespace WebCore {

float Frame::textAutosizingWidth() const
{
    return m_textAutosizingWidth;
}

void Frame::setTextAutosizingWidth(float width)
{
    m_textAutosizingWidth = width;
}

} // namespace WebCore
#endif // ENABLE(IOS_TEXT_AUTOSIZING)