htmlediting.cpp 37.8 KB
Newer Older
kocienda's avatar
kocienda committed
1
/*
darin's avatar
darin committed
2
 * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
kocienda's avatar
kocienda committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *
 * 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 COMPUTER, 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 COMPUTER, 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. 
 */

mjs's avatar
mjs committed
26
#include "config.h"
kocienda's avatar
kocienda committed
27 28
#include "htmlediting.h"

darin's avatar
darin committed
29 30
#include "Document.h"
#include "EditingText.h"
31 32 33
#include "HTMLBRElement.h"
#include "HTMLDivElement.h"
#include "HTMLElementFactory.h"
darin's avatar
darin committed
34
#include "HTMLInterchange.h"
35
#include "HTMLLIElement.h"
darin's avatar
darin committed
36
#include "HTMLNames.h"
37
#include "HTMLObjectElement.h"
38 39
#include "HTMLOListElement.h"
#include "HTMLUListElement.h"
weinig's avatar
weinig committed
40
#include "PositionIterator.h"
darin's avatar
darin committed
41
#include "RenderObject.h"
justing's avatar
justing committed
42
#include "Range.h"
43
#include "VisibleSelection.h"
justing's avatar
justing committed
44
#include "Text.h"
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
45
#include "TextIterator.h"
justing's avatar
justing committed
46 47
#include "VisiblePosition.h"
#include "visible_units.h"
48
#include <wtf/StdLibExtras.h>
49
#include <wtf/unicode/CharacterNames.h>
kocienda's avatar
kocienda committed
50

51 52 53 54
#if ENABLE(WML)
#include "WMLNames.h"
#endif

darin's avatar
darin committed
55 56
using namespace std;

darin's avatar
darin committed
57
namespace WebCore {
kocienda's avatar
kocienda committed
58

darin's avatar
darin committed
59
using namespace HTMLNames;
kocienda's avatar
kocienda committed
60

justing's avatar
justing committed
61 62
// Atomic means that the node has no children, or has children which are ignored for the
// purposes of editing.
darin's avatar
darin committed
63
bool isAtomicNode(const Node *node)
justing's avatar
justing committed
64
{
harrison's avatar
harrison committed
65 66 67
    return node && (!node->hasChildNodes() || editingIgnoresContent(node));
}

68 69 70 71 72 73 74 75 76
// Returns true for nodes that either have no content, or have content that is ignored (skipped 
// over) while editing.  There are no VisiblePositions inside these nodes.
bool editingIgnoresContent(const Node* node)
{
    return !canHaveChildrenForEditing(node) && !node->isTextNode();
}

bool canHaveChildrenForEditing(const Node* node)
{
77 78 79 80 81
    return !node->isTextNode()
        && !node->hasTagName(brTag)
        && !node->hasTagName(imgTag)
        && !node->hasTagName(inputTag)
        && !node->hasTagName(textareaTag)
82
        && (!node->hasTagName(objectTag) || static_cast<const HTMLObjectElement*>(node)->useFallbackContent())
83 84 85 86
        && !node->hasTagName(iframeTag)
        && !node->hasTagName(embedTag)
        && !node->hasTagName(appletTag)
        && !node->hasTagName(selectTag)
87
#if ENABLE(WML)
88
        && !node->hasTagName(WMLNames::doTag)
89
#endif
90
        && ((!node->hasTagName(hrTag) && !node->hasTagName(datagridTag)) || node->hasChildNodes());
91 92
}

justing's avatar
justing committed
93 94 95 96
// Compare two positions, taking into account the possibility that one or both
// could be inside a shadow tree. Only works for non-null values.
int comparePositions(const Position& a, const Position& b)
{
97
    Node* nodeA = a.deprecatedNode();
justing's avatar
justing committed
98
    ASSERT(nodeA);
99
    Node* nodeB = b.deprecatedNode();
justing's avatar
justing committed
100
    ASSERT(nodeB);
eric@webkit.org's avatar
eric@webkit.org committed
101 102
    int offsetA = a.deprecatedEditingOffset();
    int offsetB = b.deprecatedEditingOffset();
justing's avatar
justing committed
103 104 105 106 107 108 109 110

    Node* shadowAncestorA = nodeA->shadowAncestorNode();
    if (shadowAncestorA == nodeA)
        shadowAncestorA = 0;
    Node* shadowAncestorB = nodeB->shadowAncestorNode();
    if (shadowAncestorB == nodeB)
        shadowAncestorB = 0;

adele's avatar
adele committed
111
    int bias = 0;
justing's avatar
justing committed
112 113 114 115
    if (shadowAncestorA != shadowAncestorB) {
        if (shadowAncestorA) {
            nodeA = shadowAncestorA;
            offsetA = 0;
adele's avatar
adele committed
116
            bias = 1;
justing's avatar
justing committed
117 118 119 120
        }
        if (shadowAncestorB) {
            nodeB = shadowAncestorB;
            offsetB = 0;
adele's avatar
adele committed
121
            bias = -1;
justing's avatar
justing committed
122 123 124
        }
    }

adele's avatar
adele committed
125 126
    int result = Range::compareBoundaryPoints(nodeA, offsetA, nodeB, offsetB);
    return result ? result : bias;
justing's avatar
justing committed
127 128
}

129 130 131 132 133
int comparePositions(const VisiblePosition& a, const VisiblePosition& b)
{
    return comparePositions(a.deepEquivalent(), b.deepEquivalent());
}

justing's avatar
justing committed
134
Node* highestEditableRoot(const Position& position)
justing's avatar
justing committed
135
{
136
    Node* node = position.deprecatedNode();
justing's avatar
justing committed
137 138 139
    if (!node)
        return 0;
        
justing's avatar
justing committed
140
    Node* highestRoot = editableRootForPosition(position);
justing's avatar
justing committed
141 142 143 144 145
    if (!highestRoot)
        return 0;
    
    node = highestRoot;
    while (node) {
146
        if (node->rendererIsEditable())
justing's avatar
justing committed
147 148 149 150 151 152 153 154 155
            highestRoot = node;
        if (node->hasTagName(bodyTag))
            break;
        node = node->parentNode();
    }
    
    return highestRoot;
}

justing's avatar
justing committed
156 157 158 159 160 161 162
Node* lowestEditableAncestor(Node* node)
{
    if (!node)
        return 0;
    
    Node *lowestRoot = 0;
    while (node) {
163
        if (node->rendererIsEditable())
justing's avatar
justing committed
164 165 166 167 168 169 170 171 172
            return node->rootEditableElement();
        if (node->hasTagName(bodyTag))
            break;
        node = node->parentNode();
    }
    
    return lowestRoot;
}

justing's avatar
justing committed
173 174
bool isEditablePosition(const Position& p)
{
175
    Node* node = p.deprecatedNode();
justing's avatar
justing committed
176 177 178 179 180 181
    if (!node)
        return false;
        
    if (node->renderer() && node->renderer()->isTable())
        node = node->parentNode();
    
182
    return node->rendererIsEditable();
justing's avatar
justing committed
183 184
}

185 186
bool isAtUnsplittableElement(const Position& pos)
{
187
    Node* node = pos.deprecatedNode();
188 189 190 191
    return (node == editableRootForPosition(pos) || node == enclosingNodeOfType(pos, &isTableCell));
}
    
    
justing's avatar
justing committed
192 193
bool isRichlyEditablePosition(const Position& p)
{
194
    Node* node = p.deprecatedNode();
justing's avatar
justing committed
195 196 197 198 199 200
    if (!node)
        return false;
        
    if (node->renderer() && node->renderer()->isTable())
        node = node->parentNode();
    
201
    return node->rendererIsRichlyEditable();
justing's avatar
justing committed
202 203 204 205
}

Element* editableRootForPosition(const Position& p)
{
206
    Node* node = p.deprecatedNode();
justing's avatar
justing committed
207 208 209 210 211 212 213 214 215
    if (!node)
        return 0;
        
    if (node->renderer() && node->renderer()->isTable())
        node = node->parentNode();
    
    return node->rootEditableElement();
}

216 217 218 219 220 221 222
// Finds the enclosing element until which the tree can be split.
// When a user hits ENTER, he/she won't expect this element to be split into two.
// You may pass it as the second argument of splitTreeToNode.
Element* unsplittableElementForPosition(const Position& p)
{
    // Since enclosingNodeOfType won't search beyond the highest root editable node,
    // this code works even if the closest table cell was outside of the root editable node.
223
    Element* enclosingCell = static_cast<Element*>(enclosingNodeOfType(p, &isTableCell));
224 225 226 227 228 229
    if (enclosingCell)
        return enclosingCell;

    return editableRootForPosition(p);
}

justing's avatar
justing committed
230 231
Position nextCandidate(const Position& position)
{
weinig's avatar
weinig committed
232
    PositionIterator p = position;
justing's avatar
justing committed
233
    while (!p.atEnd()) {
weinig's avatar
weinig committed
234
        p.increment();
justing's avatar
justing committed
235
        if (p.isCandidate())
justing's avatar
justing committed
236 237 238 239 240 241 242 243 244
            return p;
    }
    return Position();
}

Position nextVisuallyDistinctCandidate(const Position& position)
{
    Position p = position;
    Position downstreamStart = p.downstream();
eric@webkit.org's avatar
eric@webkit.org committed
245
    while (!p.atEndOfTree()) {
ap@webkit.org's avatar
ap@webkit.org committed
246
        p = p.next(Character);
justing's avatar
justing committed
247
        if (p.isCandidate() && p.downstream() != downstreamStart)
justing's avatar
justing committed
248 249 250 251 252 253 254
            return p;
    }
    return Position();
}

Position previousCandidate(const Position& position)
{
weinig's avatar
weinig committed
255
    PositionIterator p = position;
justing's avatar
justing committed
256
    while (!p.atStart()) {
weinig's avatar
weinig committed
257
        p.decrement();
justing's avatar
justing committed
258
        if (p.isCandidate())
justing's avatar
justing committed
259 260 261 262 263 264 265 266 267
            return p;
    }
    return Position();
}

Position previousVisuallyDistinctCandidate(const Position& position)
{
    Position p = position;
    Position downstreamStart = p.downstream();
eric@webkit.org's avatar
eric@webkit.org committed
268
    while (!p.atStartOfTree()) {
ap@webkit.org's avatar
ap@webkit.org committed
269
        p = p.previous(Character);
justing's avatar
justing committed
270
        if (p.isCandidate() && p.downstream() != downstreamStart)
justing's avatar
justing committed
271 272 273 274 275
            return p;
    }
    return Position();
}

justing's avatar
justing committed
276 277
VisiblePosition firstEditablePositionAfterPositionInRoot(const Position& position, Node* highestRoot)
{
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
278
    // position falls before highestRoot.
279
    if (comparePositions(position, firstPositionInNode(highestRoot)) == -1 && highestRoot->rendererIsEditable())
280
        return firstPositionInNode(highestRoot);
eric@webkit.org's avatar
eric@webkit.org committed
281

justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
282 283
    Position p = position;
    
284 285
    if (Node* shadowAncestor = p.deprecatedNode()->shadowAncestorNode())
        if (shadowAncestor != p.deprecatedNode())
286
            p = positionAfterNode(shadowAncestor);
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
287
    
288 289
    while (p.deprecatedNode() && !isEditablePosition(p) && p.deprecatedNode()->isDescendantOf(highestRoot))
        p = isAtomicNode(p.deprecatedNode()) ? positionInParentAfterNode(p.deprecatedNode()) : nextVisuallyDistinctCandidate(p);
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
290
    
291
    if (p.deprecatedNode() && p.deprecatedNode() != highestRoot && !p.deprecatedNode()->isDescendantOf(highestRoot))
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
292 293
        return VisiblePosition();
    
justing's avatar
justing committed
294
    return VisiblePosition(p);
justing's avatar
justing committed
295 296 297 298
}

VisiblePosition lastEditablePositionBeforePositionInRoot(const Position& position, Node* highestRoot)
{
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
299
    // When position falls after highestRoot, the result is easy to compute.
300 301
    if (comparePositions(position, lastPositionInNode(highestRoot)) == 1)
        return lastPositionInNode(highestRoot);
eric@webkit.org's avatar
eric@webkit.org committed
302

justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
303
    Position p = position;
304 305

    if (Node* shadowAncestor = p.deprecatedNode()->shadowAncestorNode()) {
306
        if (shadowAncestor != p.deprecatedNode())
307 308
            p = firstPositionInOrBeforeNode(shadowAncestor);
    }
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
309
    
310 311
    while (p.deprecatedNode() && !isEditablePosition(p) && p.deprecatedNode()->isDescendantOf(highestRoot))
        p = isAtomicNode(p.deprecatedNode()) ? positionInParentBeforeNode(p.deprecatedNode()) : previousVisuallyDistinctCandidate(p);
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
312
    
313
    if (p.deprecatedNode() && p.deprecatedNode() != highestRoot && !p.deprecatedNode()->isDescendantOf(highestRoot))
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
314 315
        return VisiblePosition();
    
justing's avatar
justing committed
316
    return VisiblePosition(p);
justing's avatar
justing committed
317 318
}

eric@webkit.org's avatar
eric@webkit.org committed
319
// FIXME: The method name, comment, and code say three different things here!
justing's avatar
justing committed
320
// Whether or not content before and after this node will collapse onto the same line as it.
justing's avatar
justing committed
321
bool isBlock(const Node* node)
justing's avatar
justing committed
322 323 324 325 326
{
    return node && node->renderer() && !node->renderer()->isInline();
}

// FIXME: Deploy this in all of the places where enclosingBlockFlow/enclosingBlockFlowOrTableElement are used.
justing's avatar
justing committed
327 328 329
// FIXME: Pass a position to this function.  The enclosing block of [table, x] for example, should be the 
// block that contains the table and not the table, and this function should be the only one responsible for 
// knowing about these kinds of special cases.
330
Node* enclosingBlock(Node* node, EditingBoundaryCrossingRule rule)
justing's avatar
justing committed
331
{
332
    return static_cast<Element*>(enclosingNodeOfType(firstPositionInOrBeforeNode(node), isBlock, rule));
justing's avatar
justing committed
333 334
}

335 336 337 338 339 340 341 342 343
TextDirection directionOfEnclosingBlock(const Position& position)
{
    Node* enclosingBlockNode = enclosingBlock(position.containerNode());
    if (!enclosingBlockNode)
        return LTR;
    RenderObject* renderer = enclosingBlockNode->renderer();
    return renderer ? renderer->style()->direction() : LTR;
}

justing's avatar
justing committed
344
// This method is used to create positions in the DOM. It returns the maximum valid offset
harrison's avatar
harrison committed
345
// in a node.  It returns 1 for some elements even though they do not have children, which
346
// creates technically invalid DOM Positions.  Be sure to call parentAnchoredEquivalent
justing's avatar
justing committed
347
// on a Position before using it to create a DOM Range, or an exception will be thrown.
eric@webkit.org's avatar
eric@webkit.org committed
348
int lastOffsetForEditing(const Node* node)
justing's avatar
justing committed
349
{
adele's avatar
adele committed
350 351 352
    ASSERT(node);
    if (!node)
        return 0;
darin's avatar
darin committed
353
    if (node->offsetInCharacters())
ap@webkit.org's avatar
ap@webkit.org committed
354
        return node->maxCharacterOffset();
355

justing's avatar
justing committed
356 357
    if (node->hasChildNodes())
        return node->childNodeCount();
358

harrison's avatar
harrison committed
359
    // NOTE: This should preempt the childNodeCount for, e.g., select nodes
ap@webkit.org's avatar
ap@webkit.org committed
360
    if (editingIgnoresContent(node))
justing's avatar
justing committed
361 362 363 364 365
        return 1;

    return 0;
}

justing's avatar
justing committed
366
String stringWithRebalancedWhitespace(const String& string, bool startIsStartOfParagraph, bool endIsEndOfParagraph)
justing's avatar
justing committed
367
{
368 369 370 371 372 373 374 375 376
    Vector<UChar> rebalancedString;
    append(rebalancedString, string);

    bool previousCharacterWasSpace = false;
    for (size_t i = 0; i < rebalancedString.size(); i++) {
        if (!isWhitespace(rebalancedString[i])) {
            previousCharacterWasSpace = false;
            continue;
        }
justing's avatar
justing committed
377

378 379 380 381 382 383 384 385 386
        if (previousCharacterWasSpace || (!i && startIsStartOfParagraph) || (i + 1 == rebalancedString.size() && endIsEndOfParagraph)) {
            rebalancedString[i] = noBreakSpace;
            previousCharacterWasSpace = false;
        } else {
            rebalancedString[i] = ' ';
            previousCharacterWasSpace = true;
        }
            
    }
justing's avatar
justing committed
387

388
    return String::adopt(rebalancedString);
justing's avatar
justing committed
389 390
}

darin's avatar
darin committed
391
bool isTableStructureNode(const Node *node)
kocienda's avatar
kocienda committed
392 393 394 395 396
{
    RenderObject *r = node->renderer();
    return (r && (r->isTableCell() || r->isTableRow() || r->isTableSection() || r->isTableCol()));
}

darin's avatar
darin committed
397
const String& nonBreakingSpaceString()
kocienda's avatar
kocienda committed
398
{
399
    DEFINE_STATIC_LOCAL(String, nonBreakingSpaceString, (&noBreakSpace, 1));
kocienda's avatar
kocienda committed
400 401 402
    return nonBreakingSpaceString;
}

harrison's avatar
harrison committed
403
// FIXME: need to dump this
darin's avatar
darin committed
404
bool isSpecialElement(const Node *n)
mjs's avatar
mjs committed
405
{
406 407 408
    if (!n)
        return false;
        
mjs's avatar
mjs committed
409 410
    if (!n->isHTMLElement())
        return false;
mjs's avatar
mjs committed
411

412
    if (n->isLink())
mjs's avatar
mjs committed
413 414
        return true;

mjs's avatar
mjs committed
415
    RenderObject *renderer = n->renderer();
416 417 418 419
    if (!renderer)
        return false;
        
    if (renderer->style()->display() == TABLE || renderer->style()->display() == INLINE_TABLE)
mjs's avatar
mjs committed
420
        return true;
mjs's avatar
mjs committed
421

422
    if (renderer->style()->isFloating())
mjs's avatar
mjs committed
423
        return true;
mjs's avatar
mjs committed
424

mjs's avatar
mjs committed
425
    if (renderer->style()->position() != StaticPosition)
mjs's avatar
mjs committed
426
        return true;
427
        
mjs's avatar
mjs committed
428 429
    return false;
}
mjs's avatar
mjs committed
430

darin's avatar
darin committed
431
static Node* firstInSpecialElement(const Position& pos)
mjs's avatar
mjs committed
432
{
433 434 435
    // FIXME: This begins at pos.deprecatedNode(), which doesn't necessarily contain pos (suppose pos was [img, 0]).  See <rdar://problem/5027702>.
    Node* rootEditableElement = pos.deprecatedNode()->rootEditableElement();
    for (Node* n = pos.deprecatedNode(); n && n->rootEditableElement() == rootEditableElement; n = n->parentNode())
darin's avatar
darin committed
436 437
        if (isSpecialElement(n)) {
            VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
438
            VisiblePosition firstInElement = VisiblePosition(firstPositionInOrBeforeNode(n), DOWNSTREAM);
darin's avatar
darin committed
439 440 441 442
            if (isTableElement(n) && vPos == firstInElement.next())
                return n;
            if (vPos == firstInElement)
                return n;
harrison's avatar
harrison committed
443
        }
darin's avatar
darin committed
444 445
    return 0;
}
mjs's avatar
mjs committed
446

darin's avatar
darin committed
447 448
static Node* lastInSpecialElement(const Position& pos)
{
449 450 451
    // FIXME: This begins at pos.deprecatedNode(), which doesn't necessarily contain pos (suppose pos was [img, 0]).  See <rdar://problem/5027702>.
    Node* rootEditableElement = pos.deprecatedNode()->rootEditableElement();
    for (Node* n = pos.deprecatedNode(); n && n->rootEditableElement() == rootEditableElement; n = n->parentNode())
darin's avatar
darin committed
452 453
        if (isSpecialElement(n)) {
            VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
454
            VisiblePosition lastInElement = VisiblePosition(Position(n, n->childNodeCount(), Position::PositionIsOffsetInAnchor), DOWNSTREAM);
darin's avatar
darin committed
455 456 457 458 459 460
            if (isTableElement(n) && vPos == lastInElement.previous())
                return n;
            if (vPos == lastInElement)
                return n;
        }
    return 0;
mjs's avatar
mjs committed
461 462
}

darin's avatar
darin committed
463
bool isFirstVisiblePositionInSpecialElement(const Position& pos)
mjs's avatar
mjs committed
464
{
darin's avatar
darin committed
465 466
    return firstInSpecialElement(pos);
}
mjs's avatar
mjs committed
467

darin's avatar
darin committed
468 469 470 471
Position positionBeforeContainingSpecialElement(const Position& pos, Node** containingSpecialElement)
{
    Node* n = firstInSpecialElement(pos);
    if (!n)
mjs's avatar
mjs committed
472
        return pos;
473
    Position result = positionInParentBeforeNode(n);
474
    if (result.isNull() || result.deprecatedNode()->rootEditableElement() != pos.deprecatedNode()->rootEditableElement())
darin's avatar
darin committed
475 476 477
        return pos;
    if (containingSpecialElement)
        *containingSpecialElement = n;
mjs's avatar
mjs committed
478 479
    return result;
}
mjs's avatar
mjs committed
480

mjs's avatar
mjs committed
481
bool isLastVisiblePositionInSpecialElement(const Position& pos)
mjs's avatar
mjs committed
482
{
darin's avatar
darin committed
483
    return lastInSpecialElement(pos);
mjs's avatar
mjs committed
484 485
}

darin's avatar
darin committed
486
Position positionAfterContainingSpecialElement(const Position& pos, Node **containingSpecialElement)
mjs's avatar
mjs committed
487
{
darin's avatar
darin committed
488 489
    Node* n = lastInSpecialElement(pos);
    if (!n)
mjs's avatar
mjs committed
490
        return pos;
491
    Position result = positionInParentAfterNode(n);
492
    if (result.isNull() || result.deprecatedNode()->rootEditableElement() != pos.deprecatedNode()->rootEditableElement())
darin's avatar
darin committed
493 494 495
        return pos;
    if (containingSpecialElement)
        *containingSpecialElement = n;
mjs's avatar
mjs committed
496
    return result;
kocienda's avatar
kocienda committed
497 498
}

darin's avatar
darin committed
499
Position positionOutsideContainingSpecialElement(const Position &pos, Node **containingSpecialElement)
kocienda's avatar
kocienda committed
500
{
harrison's avatar
harrison committed
501 502 503 504
    if (isFirstVisiblePositionInSpecialElement(pos))
        return positionBeforeContainingSpecialElement(pos, containingSpecialElement);
    if (isLastVisiblePositionInSpecialElement(pos))
        return positionAfterContainingSpecialElement(pos, containingSpecialElement);
mjs's avatar
mjs committed
505
    return pos;
kocienda's avatar
kocienda committed
506
}
harrison's avatar
harrison committed
507

justing's avatar
justing committed
508 509 510
Node* isFirstPositionAfterTable(const VisiblePosition& visiblePosition)
{
    Position upstream(visiblePosition.deepEquivalent().upstream());
511 512
    if (upstream.deprecatedNode() && upstream.deprecatedNode()->renderer() && upstream.deprecatedNode()->renderer()->isTable() && upstream.atLastEditingPositionForNode())
        return upstream.deprecatedNode();
justing's avatar
justing committed
513 514 515 516 517 518 519
    
    return 0;
}

Node* isLastPositionBeforeTable(const VisiblePosition& visiblePosition)
{
    Position downstream(visiblePosition.deepEquivalent().downstream());
520 521
    if (downstream.deprecatedNode() && downstream.deprecatedNode()->renderer() && downstream.deprecatedNode()->renderer()->isTable() && downstream.atFirstEditingPositionForNode())
        return downstream.deprecatedNode();
justing's avatar
justing committed
522 523 524 525
    
    return 0;
}

526
// Returns the visible position at the beginning of a node
rniwa@webkit.org's avatar
rniwa@webkit.org committed
527
VisiblePosition visiblePositionBeforeNode(Node* node)
528 529 530
{
    ASSERT(node);
    if (node->childNodeCount())
531
        return VisiblePosition(firstPositionInOrBeforeNode(node), DOWNSTREAM);
532
    ASSERT(node->parentNode());
533
    return positionInParentBeforeNode(node);
534 535 536
}

// Returns the visible position at the ending of a node
rniwa@webkit.org's avatar
rniwa@webkit.org committed
537
VisiblePosition visiblePositionAfterNode(Node* node)
538 539 540
{
    ASSERT(node);
    if (node->childNodeCount())
541
        return VisiblePosition(lastPositionInOrAfterNode(node), DOWNSTREAM);
542
    ASSERT(node->parentNode());
543
    return positionInParentAfterNode(node);
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
}

// Create a range object with two visible positions, start and end.
// create(PassRefPtr<Document>, const Position&, const Position&); will use deprecatedEditingOffset
// Use this function instead of create a regular range object (avoiding editing offset).
PassRefPtr<Range> createRange(PassRefPtr<Document> document, const VisiblePosition& start, const VisiblePosition& end, ExceptionCode& ec)
{
    ec = 0;
    RefPtr<Range> selectedRange = Range::create(document);
    selectedRange->setStart(start.deepEquivalent().containerNode(), start.deepEquivalent().computeOffsetInContainerNode(), ec);
    if (!ec)
        selectedRange->setEnd(end.deepEquivalent().containerNode(), end.deepEquivalent().computeOffsetInContainerNode(), ec);
    return selectedRange.release();
}

// Extend rangeToExtend to include nodes that wraps range and visibly starts and ends inside or at the boudnaries of maximumRange
// e.g. if the original range spaned "hello" in <div>hello</div>, then this function extends the range to contain div's around it.
// Call this function before copying / moving paragraphs to contain all wrapping nodes.
// This function stops extending the range immediately below rootNode; i.e. the extended range can contain a child node of rootNode
// but it can never contain rootNode itself.
PassRefPtr<Range> extendRangeToWrappingNodes(PassRefPtr<Range> range, const Range* maximumRange, const Node* rootNode)
{
    ASSERT(range);
    ASSERT(maximumRange);

    ExceptionCode ec = 0;
    Node* ancestor = range->commonAncestorContainer(ec);// find the cloeset common ancestor
    Node* highestNode = 0;
    // traverse through ancestors as long as they are contained within the range, content-editable, and below rootNode (could be =0).
573
    while (ancestor && ancestor->rendererIsEditable() && isNodeVisiblyContainedWithin(ancestor, maximumRange) && ancestor != rootNode) {
574 575 576 577 578 579 580 581 582 583 584 585 586
        highestNode = ancestor;
        ancestor = ancestor->parentNode();
    }

    if (!highestNode)
        return range;

    // Create new range with the highest editable node contained within the range
    RefPtr<Range> extendedRange = Range::create(range->ownerDocument());
    extendedRange->selectNode(highestNode, ec);
    return extendedRange.release();
}

darin's avatar
darin committed
587
bool isListElement(Node *n)
harrison's avatar
harrison committed
588
{
harrison's avatar
harrison committed
589 590 591
    return (n && (n->hasTagName(ulTag) || n->hasTagName(olTag) || n->hasTagName(dlTag)));
}

592 593 594 595 596
bool isListItem(Node *n)
{
    return n && n->renderer() && n->renderer()->isListItem();
}

justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
597
Node* enclosingNodeWithTag(const Position& p, const QualifiedName& tagName)
lweintraub's avatar
lweintraub committed
598
{
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
599
    if (p.isNull())
lweintraub's avatar
lweintraub committed
600
        return 0;
justing's avatar
justing committed
601
        
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
602
    Node* root = highestEditableRoot(p);
603
    for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) {
604
        if (root && !n->rendererIsEditable())
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
605
            continue;
lweintraub's avatar
lweintraub committed
606 607
        if (n->hasTagName(tagName))
            return n;
justing's avatar
justing committed
608 609 610 611
        if (n == root)
            return 0;
    }
    
justing's avatar
justing committed
612 613 614
    return 0;
}

615
Node* enclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule)
justing's avatar
justing committed
616
{
617 618
    // FIXME: support CanSkipCrossEditingBoundary
    ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary);
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
619
    if (p.isNull())
justing's avatar
justing committed
620 621
        return 0;
        
622
    Node* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(p) : 0;
623
    for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) {
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
624 625
        // Don't return a non-editable node if the input position was editable, since
        // the callers from editing will no doubt want to perform editing inside the returned node.
626
        if (root && !n->rendererIsEditable())
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
627
            continue;
628
        if (nodeIsOfType(n))
justing's avatar
justing committed
629
            return n;
630
        if (n == root)
justing's avatar
justing committed
631 632 633
            return 0;
    }
    
lweintraub's avatar
lweintraub committed
634 635 636
    return 0;
}

637
Node* highestEnclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule)
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
638 639
{
    Node* highest = 0;
640
    Node* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(p) : 0;
641
    for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) {
642
        if (root && !n->rendererIsEditable())
643 644
            continue;
        if (nodeIsOfType(n))
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
645 646 647 648 649 650 651 652
            highest = n;
        if (n == root)
            break;
    }
    
    return highest;
}

justing's avatar
justing committed
653
Node* enclosingTableCell(const Position& p)
justing's avatar
justing committed
654
{
655
    return static_cast<Element*>(enclosingNodeOfType(p, isTableCell));
justing's avatar
justing committed
656 657
}

justing's avatar
justing committed
658 659 660 661 662
Node* enclosingAnchorElement(const Position& p)
{
    if (p.isNull())
        return 0;
    
663
    Node* node = p.deprecatedNode();
justing's avatar
justing committed
664 665 666 667 668
    while (node && !(node->isElementNode() && node->isLink()))
        node = node->parentNode();
    return node;
}

669
HTMLElement* enclosingList(Node* node)
justing's avatar
justing committed
670 671 672
{
    if (!node)
        return 0;
justing's avatar
justing committed
673
        
674
    Node* root = highestEditableRoot(firstPositionInOrBeforeNode(node));
justing's avatar
justing committed
675
    
676
    for (ContainerNode* n = node->parentNode(); n; n = n->parentNode()) {
justing's avatar
justing committed
677
        if (n->hasTagName(ulTag) || n->hasTagName(olTag))
678
            return toHTMLElement(n);
justing's avatar
justing committed
679 680 681 682
        if (n == root)
            return 0;
    }
    
justing's avatar
justing committed
683 684 685
    return 0;
}

686
Node* enclosingListChild(Node *node)
harrison's avatar
harrison committed
687
{
lweintraub's avatar
lweintraub committed
688 689
    if (!node)
        return 0;
justing's avatar
justing committed
690 691
    // Check for a list item element, or for a node whose parent is a list element.  Such a node
    // will appear visually as a list item (but without a list marker)
692
    Node* root = highestEditableRoot(firstPositionInOrBeforeNode(node));
justing's avatar
justing committed
693 694 695
    
    // FIXME: This function is inappropriately named if it starts with node instead of node->parentNode()
    for (Node* n = node; n && n->parentNode(); n = n->parentNode()) {
justing's avatar
justing committed
696
        if (n->hasTagName(liTag) || isListElement(n->parentNode()))
697
            return n;
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
698
        if (n == root || isTableCell(n))
justing's avatar
justing committed
699
            return 0;
harrison's avatar
harrison committed
700 701 702
    }
    
    return 0;
harrison's avatar
harrison committed
703 704
}

705
static HTMLElement* embeddedSublist(Node* listItem)
justing's avatar
justing committed
706 707 708 709
{
    // Check the DOM so that we'll find collapsed sublists without renderers.
    for (Node* n = listItem->firstChild(); n; n = n->nextSibling()) {
        if (isListElement(n))
710
            return toHTMLElement(n);
justing's avatar
justing committed
711 712 713 714 715 716 717 718 719 720
    }
    
    return 0;
}

static Node* appendedSublist(Node* listItem)
{
    // Check the DOM so that we'll find collapsed sublists without renderers.
    for (Node* n = listItem->nextSibling(); n; n = n->nextSibling()) {
        if (isListElement(n))
721
            return toHTMLElement(n);
722
        if (isListItem(listItem))
justing's avatar
justing committed
723 724 725 726 727 728
            return 0;
    }
    
    return 0;
}

eric@webkit.org's avatar
eric@webkit.org committed
729
// FIXME: This method should not need to call isStartOfParagraph/isEndOfParagraph
justing's avatar
justing committed
730 731 732
Node* enclosingEmptyListItem(const VisiblePosition& visiblePos)
{
    // Check that position is on a line by itself inside a list item
733
    Node* listChildNode = enclosingListChild(visiblePos.deepEquivalent().deprecatedNode());
justing's avatar
justing committed
734 735
    if (!listChildNode || !isStartOfParagraph(visiblePos) || !isEndOfParagraph(visiblePos))
        return 0;
eric@webkit.org's avatar
eric@webkit.org committed
736

737 738
    VisiblePosition firstInListChild(firstPositionInOrBeforeNode(listChildNode));
    VisiblePosition lastInListChild(lastPositionInOrAfterNode(listChildNode));
eric@webkit.org's avatar
eric@webkit.org committed
739

justing's avatar
justing committed
740 741
    if (firstInListChild != visiblePos || lastInListChild != visiblePos)
        return 0;
justing's avatar
justing committed
742 743 744 745 746 747 748
    
    if (embeddedSublist(listChildNode) || appendedSublist(listChildNode))
        return 0;
        
    return listChildNode;
}

749
HTMLElement* outermostEnclosingList(Node* node, Node* rootList)
justing's avatar
justing committed
750
{
751 752 753
    HTMLElement* list = enclosingList(node);
    if (!list)
        return 0;
754 755 756 757

    while (HTMLElement* nextList = enclosingList(list)) {
        if (nextList == rootList)
            break;
758
        list = nextList;
759 760
    }

761
    return list;
justing's avatar
justing committed
762 763
}

bfulgham@webkit.org's avatar
bfulgham@webkit.org committed
764 765
bool canMergeLists(Element* firstList, Element* secondList)
{
766
    if (!firstList || !secondList || !firstList->isHTMLElement() || !secondList->isHTMLElement())
bfulgham@webkit.org's avatar
bfulgham@webkit.org committed
767 768 769
        return false;

    return firstList->hasTagName(secondList->tagQName())// make sure the list types match (ol vs. ul)
770
    && firstList->rendererIsEditable() && secondList->rendererIsEditable() // both lists are editable
bfulgham@webkit.org's avatar
bfulgham@webkit.org committed
771
    && firstList->rootEditableElement() == secondList->rootEditableElement()// don't cross editing boundaries
772
    && isVisiblyAdjacent(positionInParentAfterNode(firstList), positionInParentBeforeNode(secondList));
bfulgham@webkit.org's avatar
bfulgham@webkit.org committed
773 774 775
    // Make sure there is no visible content between this li and the previous list
}

lweintraub's avatar
lweintraub committed
776 777 778 779 780 781 782 783 784
Node* highestAncestor(Node* node)
{
    ASSERT(node);
    Node* parent = node;
    while ((node = node->parentNode()))
        parent = node;
    return parent;
}

harrison's avatar
harrison committed
785
// FIXME: do not require renderer, so that this can be used within fragments, or rename to isRenderedTable()
786
bool isTableElement(Node* n)
harrison's avatar
harrison committed
787
{
788 789 790 791
    if (!n || !n->isElementNode())
        return false;

    RenderObject* renderer = n->renderer();
harrison's avatar
harrison committed
792 793 794
    return (renderer && (renderer->style()->display() == TABLE || renderer->style()->display() == INLINE_TABLE));
}

justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
795 796 797 798 799 800 801 802 803
bool isTableCell(const Node* node)
{
    RenderObject* r = node->renderer();
    if (!r)
        return node->hasTagName(tdTag) || node->hasTagName(thTag);
    
    return r->isTableCell();
}

804 805
bool isEmptyTableCell(const Node* node)
{
806 807 808 809 810 811 812
    // Returns true IFF the passed in node is one of:
    //   .) a table cell with no children,
    //   .) a table cell with a single BR child, and which has no other child renderers, including :before and :after renderers
    //   .) the BR child of such a table cell

    // Find rendered node
    while (node && !node->renderer())
813
        node = node->parentNode();
814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834
    if (!node)
        return false;

    // Make sure the rendered node is a table cell or <br>.
    // If it's a <br>, then the parent node has to be a table cell.
    RenderObject* renderer = node->renderer();
    if (renderer->isBR()) {
        renderer = renderer->parent();
        if (!renderer)
            return false;
    }
    if (!renderer->isTableCell())
        return false;

    // Check that the table cell contains no child renderers except for perhaps a single <br>.
    RenderObject* childRenderer = renderer->firstChild();
    if (!childRenderer)
        return true;
    if (!childRenderer->isBR())
        return false;
    return !childRenderer->nextSibling();
835 836
}

837
PassRefPtr<HTMLElement> createDefaultParagraphElement(Document* document)
kocienda's avatar
kocienda committed
838
{
839
    return HTMLDivElement::create(document);
kocienda's avatar
kocienda committed
840 841
}

842
PassRefPtr<HTMLElement> createBreakElement(Document* document)
kocienda's avatar
kocienda committed
843
{
844
    return HTMLBRElement::create(document);
kocienda's avatar
kocienda committed
845 846
}

847
PassRefPtr<HTMLElement> createOrderedListElement(Document* document)
harrison's avatar
harrison committed
848
{
849
    return HTMLOListElement::create(document);
harrison's avatar
harrison committed
850 851
}

852
PassRefPtr<HTMLElement> createUnorderedListElement(Document* document)
harrison's avatar
harrison committed
853
{
854
    return HTMLUListElement::create(document);
harrison's avatar
harrison committed
855 856
}

857
PassRefPtr<HTMLElement> createListItemElement(Document* document)
justing's avatar
justing committed
858
{
859
    return HTMLLIElement::create(document);
justing's avatar
justing committed
860 861
}

eric@webkit.org's avatar
eric@webkit.org committed
862 863 864 865 866
PassRefPtr<HTMLElement> createHTMLElement(Document* document, const QualifiedName& name)
{
    return HTMLElementFactory::createHTMLElement(name, document, 0, false);
}

867
PassRefPtr<HTMLElement> createHTMLElement(Document* document, const AtomicString& tagName)
justing's avatar
justing committed
868
{
eric@webkit.org's avatar
eric@webkit.org committed
869
    return createHTMLElement(document, QualifiedName(nullAtom, tagName, xhtmlNamespaceURI));
justing's avatar
justing committed
870 871
}

darin's avatar
darin committed
872
bool isTabSpanNode(const Node *node)
873
{
justin.garcia@apple.com's avatar
justin.garcia@apple.com committed
874
    return node && node->hasTagName(spanTag) && node->isElementNode() && static_cast<const Element *>(node)->getAttribute(classAttr) == AppleTabSpanClass;
875 876
}

darin's avatar
darin committed
877
bool isTabSpanTextNode(const Node *node)
878
{
justing's avatar
justing committed
879
    return node && node->isTextNode() && node->parentNode() && isTabSpanNode(node->parentNode());
880 881
}

darin's avatar
darin committed
882
Node *tabSpanNode(const Node *node)
harrison's avatar
harrison committed
883 884 885 886
{
    return isTabSpanTextNode(node) ? node->parentNode() : 0;
}