1 /* 2 * Copyright (C) 2004, 2005, 2006, 2009 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "Position.h" 28 29 #include "CSSComputedStyleDeclaration.h" 30 #include "Logging.h" 31 #include "PositionIterator.h" 32 #include "RenderBlock.h" 33 #include "Text.h" 34 #include "TextIterator.h" 35 #include "VisiblePosition.h" 36 #include "htmlediting.h" 37 #include "visible_units.h" 38 #include <stdio.h> 39 #include <wtf/text/CString.h> 40 #include <wtf/unicode/CharacterNames.h> 41 42 namespace WebCore { 43 44 using namespace HTMLNames; 45 46 static Node* nextRenderedEditable(Node* node) 47 { 48 while ((node = node->nextLeafNode())) { 49 if (!node->rendererIsEditable()) 50 continue; 51 RenderObject* renderer = node->renderer(); 52 if (!renderer) 53 continue; 54 if ((renderer->isBox() && toRenderBox(renderer)->inlineBoxWrapper()) || (renderer->isText() && toRenderText(renderer)->firstTextBox())) 55 return node; 56 } 57 return 0; 58 } 59 60 static Node* previousRenderedEditable(Node* node) 61 { 62 while ((node = node->previousLeafNode())) { 63 if (!node->rendererIsEditable()) 64 continue; 65 RenderObject* renderer = node->renderer(); 66 if (!renderer) 67 continue; 68 if ((renderer->isBox() && toRenderBox(renderer)->inlineBoxWrapper()) || (renderer->isText() && toRenderText(renderer)->firstTextBox())) 69 return node; 70 } 71 return 0; 72 } 73 74 Position::Position(PassRefPtr<Node> anchorNode, int offset) 75 : m_anchorNode(anchorNode) 76 , m_offset(offset) 77 , m_anchorType(anchorTypeForLegacyEditingPosition(m_anchorNode.get(), m_offset)) 78 , m_isLegacyEditingPosition(true) 79 { 80 } 81 82 Position::Position(PassRefPtr<Node> anchorNode, AnchorType anchorType) 83 : m_anchorNode(anchorNode) 84 , m_offset(0) 85 , m_anchorType(anchorType) 86 , m_isLegacyEditingPosition(false) 87 { 88 ASSERT(anchorType != PositionIsOffsetInAnchor); 89 } 90 91 Position::Position(PassRefPtr<Node> anchorNode, int offset, AnchorType anchorType) 92 : m_anchorNode(anchorNode) 93 , m_offset(offset) 94 , m_anchorType(anchorType) 95 , m_isLegacyEditingPosition(false) 96 { 97 ASSERT(!m_anchorNode || !editingIgnoresContent(m_anchorNode.get())); 98 ASSERT(anchorType == PositionIsOffsetInAnchor); 99 } 100 101 void Position::moveToPosition(PassRefPtr<Node> node, int offset) 102 { 103 ASSERT(!editingIgnoresContent(node.get())); 104 ASSERT(anchorType() == PositionIsOffsetInAnchor || m_isLegacyEditingPosition); 105 m_anchorNode = node; 106 m_offset = offset; 107 if (m_isLegacyEditingPosition) 108 m_anchorType = anchorTypeForLegacyEditingPosition(m_anchorNode.get(), m_offset); 109 } 110 void Position::moveToOffset(int offset) 111 { 112 ASSERT(anchorType() == PositionIsOffsetInAnchor || m_isLegacyEditingPosition); 113 m_offset = offset; 114 if (m_isLegacyEditingPosition) 115 m_anchorType = anchorTypeForLegacyEditingPosition(m_anchorNode.get(), m_offset); 116 } 117 118 Node* Position::containerNode() const 119 { 120 if (!m_anchorNode) 121 return 0; 122 123 switch (anchorType()) { 124 case PositionIsOffsetInAnchor: 125 return m_anchorNode.get(); 126 case PositionIsBeforeAnchor: 127 case PositionIsAfterAnchor: 128 return m_anchorNode->parentNode(); 129 } 130 ASSERT_NOT_REACHED(); 131 return 0; 132 } 133 134 int Position::computeOffsetInContainerNode() const 135 { 136 if (!m_anchorNode) 137 return 0; 138 139 switch (anchorType()) { 140 case PositionIsOffsetInAnchor: 141 return std::min(lastOffsetInNode(m_anchorNode.get()), m_offset); 142 case PositionIsBeforeAnchor: 143 return m_anchorNode->nodeIndex(); 144 case PositionIsAfterAnchor: 145 return m_anchorNode->nodeIndex() + 1; 146 } 147 ASSERT_NOT_REACHED(); 148 return 0; 149 } 150 151 int Position::offsetForPositionAfterAnchor() const 152 { 153 ASSERT(m_anchorType == PositionIsAfterAnchor); 154 ASSERT(!m_isLegacyEditingPosition); 155 return lastOffsetForEditing(m_anchorNode.get()); 156 } 157 158 // Neighbor-anchored positions are invalid DOM positions, so they need to be 159 // fixed up before handing them off to the Range object. 160 Position Position::parentAnchoredEquivalent() const 161 { 162 if (!m_anchorNode) 163 return Position(); 164 165 // FIXME: This should only be necessary for legacy positions, but is also needed for positions before and after Tables 166 if (m_offset <= 0 && m_anchorType != PositionIsAfterAnchor) { 167 if (m_anchorNode->parentNode() && (editingIgnoresContent(m_anchorNode.get()) || isTableElement(m_anchorNode.get()))) 168 return positionInParentBeforeNode(m_anchorNode.get()); 169 return firstPositionInOrBeforeNode(m_anchorNode.get()); 170 } 171 if (!m_anchorNode->offsetInCharacters() && (m_anchorType == PositionIsAfterAnchor || static_cast<unsigned>(m_offset) == m_anchorNode->childNodeCount()) 172 && (editingIgnoresContent(m_anchorNode.get()) || isTableElement(m_anchorNode.get()))) { 173 return positionInParentAfterNode(m_anchorNode.get()); 174 } 175 176 return Position(containerNode(), computeOffsetInContainerNode(), PositionIsOffsetInAnchor); 177 } 178 179 Node* Position::computeNodeBeforePosition() const 180 { 181 if (!m_anchorNode) 182 return 0; 183 184 switch (anchorType()) { 185 case PositionIsOffsetInAnchor: 186 return m_anchorNode->childNode(m_offset - 1); // -1 converts to childNode((unsigned)-1) and returns null. 187 case PositionIsBeforeAnchor: 188 return m_anchorNode->previousSibling(); 189 case PositionIsAfterAnchor: 190 return m_anchorNode.get(); 191 } 192 ASSERT_NOT_REACHED(); 193 return 0; 194 } 195 196 Node* Position::computeNodeAfterPosition() const 197 { 198 if (!m_anchorNode) 199 return 0; 200 201 switch (anchorType()) { 202 case PositionIsOffsetInAnchor: 203 return m_anchorNode->childNode(m_offset); 204 case PositionIsBeforeAnchor: 205 return m_anchorNode.get(); 206 case PositionIsAfterAnchor: 207 return m_anchorNode->nextSibling(); 208 } 209 ASSERT_NOT_REACHED(); 210 return 0; 211 } 212 213 Position::AnchorType Position::anchorTypeForLegacyEditingPosition(Node* anchorNode, int offset) 214 { 215 if (anchorNode && editingIgnoresContent(anchorNode)) { 216 if (offset == 0) 217 return Position::PositionIsBeforeAnchor; 218 return Position::PositionIsAfterAnchor; 219 } 220 return Position::PositionIsOffsetInAnchor; 221 } 222 223 // FIXME: This method is confusing (does it return anchorNode() or containerNode()?) and should be renamed or removed 224 Element* Position::element() const 225 { 226 Node* n = anchorNode(); 227 while (n && !n->isElementNode()) 228 n = n->parentNode(); 229 return static_cast<Element*>(n); 230 } 231 232 PassRefPtr<CSSComputedStyleDeclaration> Position::computedStyle() const 233 { 234 Element* elem = element(); 235 if (!elem) 236 return 0; 237 return WebCore::computedStyle(elem); 238 } 239 240 Position Position::previous(PositionMoveType moveType) const 241 { 242 Node* n = deprecatedNode(); 243 if (!n) 244 return *this; 245 246 int o = deprecatedEditingOffset(); 247 // FIXME: Negative offsets shouldn't be allowed. We should catch this earlier. 248 ASSERT(o >= 0); 249 250 if (o > 0) { 251 Node* child = n->childNode(o - 1); 252 if (child) 253 return lastPositionInOrAfterNode(child); 254 255 // There are two reasons child might be 0: 256 // 1) The node is node like a text node that is not an element, and therefore has no children. 257 // Going backward one character at a time is correct. 258 // 2) The old offset was a bogus offset like (<br>, 1), and there is no child. 259 // Going from 1 to 0 is correct. 260 switch (moveType) { 261 case CodePoint: 262 return Position(n, o - 1); 263 case Character: 264 return Position(n, uncheckedPreviousOffset(n, o)); 265 case BackwardDeletion: 266 return Position(n, uncheckedPreviousOffsetForBackwardDeletion(n, o)); 267 } 268 } 269 270 ContainerNode* parent = n->parentNode(); 271 if (!parent) 272 return *this; 273 274 return Position(parent, n->nodeIndex()); 275 } 276 277 Position Position::next(PositionMoveType moveType) const 278 { 279 ASSERT(moveType != BackwardDeletion); 280 281 Node* n = deprecatedNode(); 282 if (!n) 283 return *this; 284 285 int o = deprecatedEditingOffset(); 286 // FIXME: Negative offsets shouldn't be allowed. We should catch this earlier. 287 ASSERT(o >= 0); 288 289 Node* child = n->childNode(o); 290 if (child || (!n->hasChildNodes() && o < lastOffsetForEditing(n))) { 291 if (child) 292 return firstPositionInOrBeforeNode(child); 293 294 // There are two reasons child might be 0: 295 // 1) The node is node like a text node that is not an element, and therefore has no children. 296 // Going forward one character at a time is correct. 297 // 2) The new offset is a bogus offset like (<br>, 1), and there is no child. 298 // Going from 0 to 1 is correct. 299 return Position(n, (moveType == Character) ? uncheckedNextOffset(n, o) : o + 1); 300 } 301 302 ContainerNode* parent = n->parentNode(); 303 if (!parent) 304 return *this; 305 306 return Position(parent, n->nodeIndex() + 1); 307 } 308 309 int Position::uncheckedPreviousOffset(const Node* n, int current) 310 { 311 return n->renderer() ? n->renderer()->previousOffset(current) : current - 1; 312 } 313 314 int Position::uncheckedPreviousOffsetForBackwardDeletion(const Node* n, int current) 315 { 316 return n->renderer() ? n->renderer()->previousOffsetForBackwardDeletion(current) : current - 1; 317 } 318 319 int Position::uncheckedNextOffset(const Node* n, int current) 320 { 321 return n->renderer() ? n->renderer()->nextOffset(current) : current + 1; 322 } 323 324 bool Position::atFirstEditingPositionForNode() const 325 { 326 if (isNull()) 327 return true; 328 return m_anchorType == PositionIsBeforeAnchor || m_offset <= 0; 329 } 330 331 bool Position::atLastEditingPositionForNode() const 332 { 333 if (isNull()) 334 return true; 335 return m_anchorType == PositionIsAfterAnchor || m_offset >= lastOffsetForEditing(deprecatedNode()); 336 } 337 338 // A position is considered at editing boundary if one of the following is true: 339 // 1. It is the first position in the node and the next visually equivalent position 340 // is non editable. 341 // 2. It is the last position in the node and the previous visually equivalent position 342 // is non editable. 343 // 3. It is an editable position and both the next and previous visually equivalent 344 // positions are both non editable. 345 bool Position::atEditingBoundary() const 346 { 347 Position nextPosition = downstream(CanCrossEditingBoundary); 348 if (atFirstEditingPositionForNode() && nextPosition.isNotNull() && !nextPosition.deprecatedNode()->rendererIsEditable()) 349 return true; 350 351 Position prevPosition = upstream(CanCrossEditingBoundary); 352 if (atLastEditingPositionForNode() && prevPosition.isNotNull() && !prevPosition.deprecatedNode()->rendererIsEditable()) 353 return true; 354 355 return nextPosition.isNotNull() && !nextPosition.deprecatedNode()->rendererIsEditable() 356 && prevPosition.isNotNull() && !prevPosition.deprecatedNode()->rendererIsEditable(); 357 } 358 359 Node* Position::parentEditingBoundary() const 360 { 361 if (!m_anchorNode || !m_anchorNode->document()) 362 return 0; 363 364 Node* documentElement = m_anchorNode->document()->documentElement(); 365 if (!documentElement) 366 return 0; 367 368 Node* boundary = m_anchorNode.get(); 369 while (boundary != documentElement && boundary->parentNode() && m_anchorNode->rendererIsEditable() == boundary->parentNode()->rendererIsEditable()) 370 boundary = boundary->parentNode(); 371 372 return boundary; 373 } 374 375 376 bool Position::atStartOfTree() const 377 { 378 if (isNull()) 379 return true; 380 return !deprecatedNode()->parentNode() && m_offset <= 0; 381 } 382 383 bool Position::atEndOfTree() const 384 { 385 if (isNull()) 386 return true; 387 return !deprecatedNode()->parentNode() && m_offset >= lastOffsetForEditing(deprecatedNode()); 388 } 389 390 int Position::renderedOffset() const 391 { 392 if (!deprecatedNode()->isTextNode()) 393 return m_offset; 394 395 if (!deprecatedNode()->renderer()) 396 return m_offset; 397 398 int result = 0; 399 RenderText* textRenderer = toRenderText(deprecatedNode()->renderer()); 400 for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { 401 int start = box->start(); 402 int end = box->start() + box->len(); 403 if (m_offset < start) 404 return result; 405 if (m_offset <= end) { 406 result += m_offset - start; 407 return result; 408 } 409 result += box->len(); 410 } 411 return result; 412 } 413 414 // return first preceding DOM position rendered at a different location, or "this" 415 Position Position::previousCharacterPosition(EAffinity affinity) const 416 { 417 if (isNull()) 418 return Position(); 419 420 Node* fromRootEditableElement = deprecatedNode()->rootEditableElement(); 421 422 bool atStartOfLine = isStartOfLine(VisiblePosition(*this, affinity)); 423 bool rendered = isCandidate(); 424 425 Position currentPos = *this; 426 while (!currentPos.atStartOfTree()) { 427 currentPos = currentPos.previous(); 428 429 if (currentPos.deprecatedNode()->rootEditableElement() != fromRootEditableElement) 430 return *this; 431 432 if (atStartOfLine || !rendered) { 433 if (currentPos.isCandidate()) 434 return currentPos; 435 } else if (rendersInDifferentPosition(currentPos)) 436 return currentPos; 437 } 438 439 return *this; 440 } 441 442 // return first following position rendered at a different location, or "this" 443 Position Position::nextCharacterPosition(EAffinity affinity) const 444 { 445 if (isNull()) 446 return Position(); 447 448 Node* fromRootEditableElement = deprecatedNode()->rootEditableElement(); 449 450 bool atEndOfLine = isEndOfLine(VisiblePosition(*this, affinity)); 451 bool rendered = isCandidate(); 452 453 Position currentPos = *this; 454 while (!currentPos.atEndOfTree()) { 455 currentPos = currentPos.next(); 456 457 if (currentPos.deprecatedNode()->rootEditableElement() != fromRootEditableElement) 458 return *this; 459 460 if (atEndOfLine || !rendered) { 461 if (currentPos.isCandidate()) 462 return currentPos; 463 } else if (rendersInDifferentPosition(currentPos)) 464 return currentPos; 465 } 466 467 return *this; 468 } 469 470 // Whether or not [node, 0] and [node, lastOffsetForEditing(node)] are their own VisiblePositions. 471 // If true, adjacent candidates are visually distinct. 472 // FIXME: Disregard nodes with renderers that have no height, as we do in isCandidate. 473 // FIXME: Share code with isCandidate, if possible. 474 static bool endsOfNodeAreVisuallyDistinctPositions(Node* node) 475 { 476 if (!node || !node->renderer()) 477 return false; 478 479 if (!node->renderer()->isInline()) 480 return true; 481 482 // Don't include inline tables. 483 if (node->hasTagName(tableTag)) 484 return false; 485 486 // There is a VisiblePosition inside an empty inline-block container. 487 return node->renderer()->isReplaced() && canHaveChildrenForEditing(node) && toRenderBox(node->renderer())->height() != 0 && !node->firstChild(); 488 } 489 490 static Node* enclosingVisualBoundary(Node* node) 491 { 492 while (node && !endsOfNodeAreVisuallyDistinctPositions(node)) 493 node = node->parentNode(); 494 495 return node; 496 } 497 498 // upstream() and downstream() want to return positions that are either in a 499 // text node or at just before a non-text node. This method checks for that. 500 static bool isStreamer(const PositionIterator& pos) 501 { 502 if (!pos.node()) 503 return true; 504 505 if (isAtomicNode(pos.node())) 506 return true; 507 508 return pos.atStartOfNode(); 509 } 510 511 // This function and downstream() are used for moving back and forth between visually equivalent candidates. 512 // For example, for the text node "foo bar" where whitespace is collapsible, there are two candidates 513 // that map to the VisiblePosition between 'b' and the space. This function will return the left candidate 514 // and downstream() will return the right one. 515 // Also, upstream() will return [boundary, 0] for any of the positions from [boundary, 0] to the first candidate 516 // in boundary, where endsOfNodeAreVisuallyDistinctPositions(boundary) is true. 517 Position Position::upstream(EditingBoundaryCrossingRule rule) const 518 { 519 Node* startNode = deprecatedNode(); 520 if (!startNode) 521 return Position(); 522 523 // iterate backward from there, looking for a qualified position 524 Node* boundary = enclosingVisualBoundary(startNode); 525 // FIXME: PositionIterator should respect Before and After positions. 526 PositionIterator lastVisible = m_anchorType == PositionIsAfterAnchor ? Position(m_anchorNode, caretMaxOffset(m_anchorNode.get())) : *this; 527 PositionIterator currentPos = lastVisible; 528 bool startEditable = startNode->rendererIsEditable(); 529 Node* lastNode = startNode; 530 bool boundaryCrossed = false; 531 for (; !currentPos.atStart(); currentPos.decrement()) { 532 Node* currentNode = currentPos.node(); 533 534 // Don't check for an editability change if we haven't moved to a different node, 535 // to avoid the expense of computing rendererIsEditable(). 536 if (currentNode != lastNode) { 537 // Don't change editability. 538 bool currentEditable = currentNode->rendererIsEditable(); 539 if (startEditable != currentEditable) { 540 if (rule == CannotCrossEditingBoundary) 541 break; 542 boundaryCrossed = true; 543 } 544 lastNode = currentNode; 545 } 546 547 // If we've moved to a position that is visually distinct, return the last saved position. There 548 // is code below that terminates early if we're *about* to move to a visually distinct position. 549 if (endsOfNodeAreVisuallyDistinctPositions(currentNode) && currentNode != boundary) 550 return lastVisible; 551 552 // skip position in unrendered or invisible node 553 RenderObject* renderer = currentNode->renderer(); 554 if (!renderer || renderer->style()->visibility() != VISIBLE) 555 continue; 556 557 if (rule == CanCrossEditingBoundary && boundaryCrossed) { 558 lastVisible = currentPos; 559 break; 560 } 561 562 // track last visible streamer position 563 if (isStreamer(currentPos)) 564 lastVisible = currentPos; 565 566 // Don't move past a position that is visually distinct. We could rely on code above to terminate and 567 // return lastVisible on the next iteration, but we terminate early to avoid doing a nodeIndex() call. 568 if (endsOfNodeAreVisuallyDistinctPositions(currentNode) && currentPos.atStartOfNode()) 569 return lastVisible; 570 571 // Return position after tables and nodes which have content that can be ignored. 572 if (editingIgnoresContent(currentNode) || isTableElement(currentNode)) { 573 if (currentPos.atEndOfNode()) 574 return positionAfterNode(currentNode); 575 continue; 576 } 577 578 // return current position if it is in rendered text 579 if (renderer->isText() && toRenderText(renderer)->firstTextBox()) { 580 if (currentNode != startNode) { 581 // This assertion fires in layout tests in the case-transform.html test because 582 // of a mix-up between offsets in the text in the DOM tree with text in the 583 // render tree which can have a different length due to case transformation. 584 // Until we resolve that, disable this so we can run the layout tests! 585 //ASSERT(currentOffset >= renderer->caretMaxOffset()); 586 return Position(currentNode, renderer->caretMaxOffset()); 587 } 588 589 unsigned textOffset = currentPos.offsetInLeafNode(); 590 RenderText* textRenderer = toRenderText(renderer); 591 InlineTextBox* lastTextBox = textRenderer->lastTextBox(); 592 for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { 593 if (textOffset <= box->start() + box->len()) { 594 if (textOffset > box->start()) 595 return currentPos; 596 continue; 597 } 598 599 if (box == lastTextBox || textOffset != box->start() + box->len() + 1) 600 continue; 601 602 // The text continues on the next line only if the last text box is not on this line and 603 // none of the boxes on this line have a larger start offset. 604 605 bool continuesOnNextLine = true; 606 InlineBox* otherBox = box; 607 while (continuesOnNextLine) { 608 otherBox = otherBox->nextLeafChild(); 609 if (!otherBox) 610 break; 611 if (otherBox == lastTextBox || (otherBox->renderer() == textRenderer && static_cast<InlineTextBox*>(otherBox)->start() > textOffset)) 612 continuesOnNextLine = false; 613 } 614 615 otherBox = box; 616 while (continuesOnNextLine) { 617 otherBox = otherBox->prevLeafChild(); 618 if (!otherBox) 619 break; 620 if (otherBox == lastTextBox || (otherBox->renderer() == textRenderer && static_cast<InlineTextBox*>(otherBox)->start() > textOffset)) 621 continuesOnNextLine = false; 622 } 623 624 if (continuesOnNextLine) 625 return currentPos; 626 } 627 } 628 } 629 630 return lastVisible; 631 } 632 633 // This function and upstream() are used for moving back and forth between visually equivalent candidates. 634 // For example, for the text node "foo bar" where whitespace is collapsible, there are two candidates 635 // that map to the VisiblePosition between 'b' and the space. This function will return the right candidate 636 // and upstream() will return the left one. 637 // Also, downstream() will return the last position in the last atomic node in boundary for all of the positions 638 // in boundary after the last candidate, where endsOfNodeAreVisuallyDistinctPositions(boundary). 639 Position Position::downstream(EditingBoundaryCrossingRule rule) const 640 { 641 Node* startNode = deprecatedNode(); 642 if (!startNode) 643 return Position(); 644 645 // iterate forward from there, looking for a qualified position 646 Node* boundary = enclosingVisualBoundary(startNode); 647 // FIXME: PositionIterator should respect Before and After positions. 648 PositionIterator lastVisible = m_anchorType == PositionIsAfterAnchor ? Position(m_anchorNode, caretMaxOffset(m_anchorNode.get())) : *this; 649 PositionIterator currentPos = lastVisible; 650 bool startEditable = startNode->rendererIsEditable(); 651 Node* lastNode = startNode; 652 bool boundaryCrossed = false; 653 for (; !currentPos.atEnd(); currentPos.increment()) { 654 Node* currentNode = currentPos.node(); 655 656 // Don't check for an editability change if we haven't moved to a different node, 657 // to avoid the expense of computing rendererIsEditable(). 658 if (currentNode != lastNode) { 659 // Don't change editability. 660 bool currentEditable = currentNode->rendererIsEditable(); 661 if (startEditable != currentEditable) { 662 if (rule == CannotCrossEditingBoundary) 663 break; 664 boundaryCrossed = true; 665 } 666 667 lastNode = currentNode; 668 } 669 670 // stop before going above the body, up into the head 671 // return the last visible streamer position 672 if (currentNode->hasTagName(bodyTag) && currentPos.atEndOfNode()) 673 break; 674 675 // Do not move to a visually distinct position. 676 if (endsOfNodeAreVisuallyDistinctPositions(currentNode) && currentNode != boundary) 677 return lastVisible; 678 // Do not move past a visually disinct position. 679 // Note: The first position after the last in a node whose ends are visually distinct 680 // positions will be [boundary->parentNode(), originalBlock->nodeIndex() + 1]. 681 if (boundary && boundary->parentNode() == currentNode) 682 return lastVisible; 683 684 // skip position in unrendered or invisible node 685 RenderObject* renderer = currentNode->renderer(); 686 if (!renderer || renderer->style()->visibility() != VISIBLE) 687 continue; 688 689 if (rule == CanCrossEditingBoundary && boundaryCrossed) { 690 lastVisible = currentPos; 691 break; 692 } 693 694 // track last visible streamer position 695 if (isStreamer(currentPos)) 696 lastVisible = currentPos; 697 698 // Return position before tables and nodes which have content that can be ignored. 699 if (editingIgnoresContent(currentNode) || isTableElement(currentNode)) { 700 if (currentPos.offsetInLeafNode() <= renderer->caretMinOffset()) 701 return Position(currentNode, renderer->caretMinOffset()); 702 continue; 703 } 704 705 // return current position if it is in rendered text 706 if (renderer->isText() && toRenderText(renderer)->firstTextBox()) { 707 if (currentNode != startNode) { 708 ASSERT(currentPos.atStartOfNode()); 709 return Position(currentNode, renderer->caretMinOffset()); 710 } 711 712 unsigned textOffset = currentPos.offsetInLeafNode(); 713 RenderText* textRenderer = toRenderText(renderer); 714 InlineTextBox* lastTextBox = textRenderer->lastTextBox(); 715 for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { 716 if (textOffset <= box->end()) { 717 if (textOffset >= box->start()) 718 return currentPos; 719 continue; 720 } 721 722 if (box == lastTextBox || textOffset != box->start() + box->len()) 723 continue; 724 725 // The text continues on the next line only if the last text box is not on this line and 726 // none of the boxes on this line have a larger start offset. 727 728 bool continuesOnNextLine = true; 729 InlineBox* otherBox = box; 730 while (continuesOnNextLine) { 731 otherBox = otherBox->nextLeafChild(); 732 if (!otherBox) 733 break; 734 if (otherBox == lastTextBox || (otherBox->renderer() == textRenderer && static_cast<InlineTextBox*>(otherBox)->start() >= textOffset)) 735 continuesOnNextLine = false; 736 } 737 738 otherBox = box; 739 while (continuesOnNextLine) { 740 otherBox = otherBox->prevLeafChild(); 741 if (!otherBox) 742 break; 743 if (otherBox == lastTextBox || (otherBox->renderer() == textRenderer && static_cast<InlineTextBox*>(otherBox)->start() >= textOffset)) 744 continuesOnNextLine = false; 745 } 746 747 if (continuesOnNextLine) 748 return currentPos; 749 } 750 } 751 } 752 753 return lastVisible; 754 } 755 756 bool Position::hasRenderedNonAnonymousDescendantsWithHeight(RenderObject* renderer) 757 { 758 RenderObject* stop = renderer->nextInPreOrderAfterChildren(); 759 for (RenderObject *o = renderer->firstChild(); o && o != stop; o = o->nextInPreOrder()) 760 if (o->node()) { 761 if ((o->isText() && toRenderText(o)->linesBoundingBox().height()) || 762 (o->isBox() && toRenderBox(o)->borderBoundingBox().height())) 763 return true; 764 } 765 return false; 766 } 767 768 bool Position::nodeIsUserSelectNone(Node* node) 769 { 770 return node && node->renderer() && node->renderer()->style()->userSelect() == SELECT_NONE; 771 } 772 773 bool Position::isCandidate() const 774 { 775 if (isNull()) 776 return false; 777 778 RenderObject* renderer = deprecatedNode()->renderer(); 779 if (!renderer) 780 return false; 781 782 if (renderer->style()->visibility() != VISIBLE) 783 return false; 784 785 if (renderer->isBR()) 786 // FIXME: The condition should be m_anchorType == PositionIsBeforeAnchor, but for now we still need to support legacy positions. 787 return !m_offset && m_anchorType != PositionIsAfterAnchor && !nodeIsUserSelectNone(deprecatedNode()->parentNode()); 788 789 if (renderer->isText()) 790 return !nodeIsUserSelectNone(deprecatedNode()) && inRenderedText(); 791 792 if (isTableElement(deprecatedNode()) || editingIgnoresContent(deprecatedNode())) 793 return (atFirstEditingPositionForNode() || atLastEditingPositionForNode()) && !nodeIsUserSelectNone(deprecatedNode()->parentNode()); 794 795 if (m_anchorNode->hasTagName(htmlTag)) 796 return false; 797 798 if (renderer->isBlockFlow()) { 799 if (toRenderBlock(renderer)->height() || m_anchorNode->hasTagName(bodyTag)) { 800 if (!Position::hasRenderedNonAnonymousDescendantsWithHeight(renderer)) 801 return atFirstEditingPositionForNode() && !Position::nodeIsUserSelectNone(deprecatedNode()); 802 return m_anchorNode->rendererIsEditable() && !Position::nodeIsUserSelectNone(deprecatedNode()) && atEditingBoundary(); 803 } 804 } else 805 return m_anchorNode->rendererIsEditable() && !Position::nodeIsUserSelectNone(deprecatedNode()) && atEditingBoundary(); 806 807 return false; 808 } 809 810 bool Position::inRenderedText() const 811 { 812 if (isNull() || !deprecatedNode()->isTextNode()) 813 return false; 814 815 RenderObject* renderer = deprecatedNode()->renderer(); 816 if (!renderer) 817 return false; 818 819 RenderText *textRenderer = toRenderText(renderer); 820 for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { 821 if (m_offset < static_cast<int>(box->start()) && !textRenderer->containsReversedText()) { 822 // The offset we're looking for is before this node 823 // this means the offset must be in content that is 824 // not rendered. Return false. 825 return false; 826 } 827 if (box->containsCaretOffset(m_offset)) 828 // Return false for offsets inside composed characters. 829 return m_offset == 0 || m_offset == textRenderer->nextOffset(textRenderer->previousOffset(m_offset)); 830 } 831 832 return false; 833 } 834 835 static unsigned caretMaxRenderedOffset(const Node* n) 836 { 837 RenderObject* r = n->renderer(); 838 if (r) 839 return r->caretMaxRenderedOffset(); 840 841 if (n->isCharacterDataNode()) 842 return static_cast<const CharacterData*>(n)->length(); 843 return 1; 844 } 845 846 bool Position::isRenderedCharacter() const 847 { 848 if (isNull() || !deprecatedNode()->isTextNode()) 849 return false; 850 851 RenderObject* renderer = deprecatedNode()->renderer(); 852 if (!renderer) 853 return false; 854 855 RenderText* textRenderer = toRenderText(renderer); 856 for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { 857 if (m_offset < static_cast<int>(box->start()) && !textRenderer->containsReversedText()) { 858 // The offset we're looking for is before this node 859 // this means the offset must be in content that is 860 // not rendered. Return false. 861 return false; 862 } 863 if (m_offset >= static_cast<int>(box->start()) && m_offset < static_cast<int>(box->start() + box->len())) 864 return true; 865 } 866 867 return false; 868 } 869 870 bool Position::rendersInDifferentPosition(const Position &pos) const 871 { 872 if (isNull() || pos.isNull()) 873 return false; 874 875 RenderObject* renderer = deprecatedNode()->renderer(); 876 if (!renderer) 877 return false; 878 879 RenderObject* posRenderer = pos.deprecatedNode()->renderer(); 880 if (!posRenderer) 881 return false; 882 883 if (renderer->style()->visibility() != VISIBLE || 884 posRenderer->style()->visibility() != VISIBLE) 885 return false; 886 887 if (deprecatedNode() == pos.deprecatedNode()) { 888 if (deprecatedNode()->hasTagName(brTag)) 889 return false; 890 891 if (m_offset == pos.deprecatedEditingOffset()) 892 return false; 893 894 if (!deprecatedNode()->isTextNode() && !pos.deprecatedNode()->isTextNode()) { 895 if (m_offset != pos.deprecatedEditingOffset()) 896 return true; 897 } 898 } 899 900 if (deprecatedNode()->hasTagName(brTag) && pos.isCandidate()) 901 return true; 902 903 if (pos.deprecatedNode()->hasTagName(brTag) && isCandidate()) 904 return true; 905 906 if (deprecatedNode()->enclosingBlockFlowElement() != pos.deprecatedNode()->enclosingBlockFlowElement()) 907 return true; 908 909 if (deprecatedNode()->isTextNode() && !inRenderedText()) 910 return false; 911 912 if (pos.deprecatedNode()->isTextNode() && !pos.inRenderedText()) 913 return false; 914 915 int thisRenderedOffset = renderedOffset(); 916 int posRenderedOffset = pos.renderedOffset(); 917 918 if (renderer == posRenderer && thisRenderedOffset == posRenderedOffset) 919 return false; 920 921 int ignoredCaretOffset; 922 InlineBox* b1; 923 getInlineBoxAndOffset(DOWNSTREAM, b1, ignoredCaretOffset); 924 InlineBox* b2; 925 pos.getInlineBoxAndOffset(DOWNSTREAM, b2, ignoredCaretOffset); 926 927 LOG(Editing, "renderer: %p [%p]\n", renderer, b1); 928 LOG(Editing, "thisRenderedOffset: %d\n", thisRenderedOffset); 929 LOG(Editing, "posRenderer: %p [%p]\n", posRenderer, b2); 930 LOG(Editing, "posRenderedOffset: %d\n", posRenderedOffset); 931 LOG(Editing, "node min/max: %d:%d\n", caretMinOffset(deprecatedNode()), caretMaxRenderedOffset(deprecatedNode())); 932 LOG(Editing, "pos node min/max: %d:%d\n", caretMinOffset(pos.deprecatedNode()), caretMaxRenderedOffset(pos.deprecatedNode())); 933 LOG(Editing, "----------------------------------------------------------------------\n"); 934 935 if (!b1 || !b2) { 936 return false; 937 } 938 939 if (b1->root() != b2->root()) { 940 return true; 941 } 942 943 if (nextRenderedEditable(deprecatedNode()) == pos.deprecatedNode() 944 && thisRenderedOffset == (int)caretMaxRenderedOffset(deprecatedNode()) && !posRenderedOffset) { 945 return false; 946 } 947 948 if (previousRenderedEditable(deprecatedNode()) == pos.deprecatedNode() 949 && !thisRenderedOffset && posRenderedOffset == (int)caretMaxRenderedOffset(pos.deprecatedNode())) { 950 return false; 951 } 952 953 return true; 954 } 955 956 // This assumes that it starts in editable content. 957 Position Position::leadingWhitespacePosition(EAffinity affinity, bool considerNonCollapsibleWhitespace) const 958 { 959 ASSERT(isEditablePosition(*this)); 960 if (isNull()) 961 return Position(); 962 963 if (upstream().deprecatedNode()->hasTagName(brTag)) 964 return Position(); 965 966 Position prev = previousCharacterPosition(affinity); 967 if (prev != *this && prev.deprecatedNode()->inSameContainingBlockFlowElement(deprecatedNode()) && prev.deprecatedNode()->isTextNode()) { 968 String string = static_cast<Text *>(prev.deprecatedNode())->data(); 969 UChar c = string[prev.deprecatedEditingOffset()]; 970 if (considerNonCollapsibleWhitespace ? (isSpaceOrNewline(c) || c == noBreakSpace) : isCollapsibleWhitespace(c)) 971 if (isEditablePosition(prev)) 972 return prev; 973 } 974 975 return Position(); 976 } 977 978 // This assumes that it starts in editable content. 979 Position Position::trailingWhitespacePosition(EAffinity, bool considerNonCollapsibleWhitespace) const 980 { 981 ASSERT(isEditablePosition(*this)); 982 if (isNull()) 983 return Position(); 984 985 VisiblePosition v(*this); 986 UChar c = v.characterAfter(); 987 // The space must not be in another paragraph and it must be editable. 988 if (!isEndOfParagraph(v) && v.next(CannotCrossEditingBoundary).isNotNull()) 989 if (considerNonCollapsibleWhitespace ? (isSpaceOrNewline(c) || c == noBreakSpace) : isCollapsibleWhitespace(c)) 990 return *this; 991 992 return Position(); 993 } 994 995 void Position::getInlineBoxAndOffset(EAffinity affinity, InlineBox*& inlineBox, int& caretOffset) const 996 { 997 getInlineBoxAndOffset(affinity, primaryDirection(), inlineBox, caretOffset); 998 } 999 1000 static bool isNonTextLeafChild(RenderObject* object) 1001 { 1002 if (object->firstChild()) 1003 return false; 1004 if (object->isText()) 1005 return false; 1006 return true; 1007 } 1008 1009 static InlineTextBox* searchAheadForBetterMatch(RenderObject* renderer) 1010 { 1011 RenderBlock* container = renderer->containingBlock(); 1012 RenderObject* next = renderer; 1013 while ((next = next->nextInPreOrder(container))) { 1014 if (next->isRenderBlock()) 1015 return 0; 1016 if (next->isBR()) 1017 return 0; 1018 if (isNonTextLeafChild(next)) 1019 return 0; 1020 if (next->isText()) { 1021 InlineTextBox* match = 0; 1022 int minOffset = INT_MAX; 1023 for (InlineTextBox* box = toRenderText(next)->firstTextBox(); box; box = box->nextTextBox()) { 1024 int caretMinOffset = box->caretMinOffset(); 1025 if (caretMinOffset < minOffset) { 1026 match = box; 1027 minOffset = caretMinOffset; 1028 } 1029 } 1030 if (match) 1031 return match; 1032 } 1033 } 1034 return 0; 1035 } 1036 1037 static Position downstreamIgnoringEditingBoundaries(Position position) 1038 { 1039 Position lastPosition; 1040 while (position != lastPosition) { 1041 lastPosition = position; 1042 position = position.downstream(CanCrossEditingBoundary); 1043 } 1044 return position; 1045 } 1046 1047 static Position upstreamIgnoringEditingBoundaries(Position position) 1048 { 1049 Position lastPosition; 1050 while (position != lastPosition) { 1051 lastPosition = position; 1052 position = position.upstream(CanCrossEditingBoundary); 1053 } 1054 return position; 1055 } 1056 1057 void Position::getInlineBoxAndOffset(EAffinity affinity, TextDirection primaryDirection, InlineBox*& inlineBox, int& caretOffset) const 1058 { 1059 caretOffset = deprecatedEditingOffset(); 1060 RenderObject* renderer = deprecatedNode()->renderer(); 1061 1062 if (!renderer->isText()) { 1063 inlineBox = 0; 1064 if (canHaveChildrenForEditing(deprecatedNode()) && renderer->isBlockFlow() && hasRenderedNonAnonymousDescendantsWithHeight(renderer)) { 1065 // Try a visually equivalent position with possibly opposite editability. This helps in case |this| is in 1066 // an editable block but surrounded by non-editable positions. It acts to negate the logic at the beginning 1067 // of RenderObject::createVisiblePosition(). 1068 Position equivalent = downstreamIgnoringEditingBoundaries(*this); 1069 if (equivalent == *this) { 1070 equivalent = upstreamIgnoringEditingBoundaries(*this); 1071 if (equivalent == *this || downstreamIgnoringEditingBoundaries(equivalent) == *this) 1072 return; 1073 } 1074 1075 equivalent.getInlineBoxAndOffset(UPSTREAM, primaryDirection, inlineBox, caretOffset); 1076 return; 1077 } 1078 if (renderer->isBox()) { 1079 inlineBox = toRenderBox(renderer)->inlineBoxWrapper(); 1080 if (!inlineBox || (caretOffset > inlineBox->caretMinOffset() && caretOffset < inlineBox->caretMaxOffset())) 1081 return; 1082 } 1083 } else { 1084 RenderText* textRenderer = toRenderText(renderer); 1085 1086 InlineTextBox* box; 1087 InlineTextBox* candidate = 0; 1088 1089 for (box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { 1090 int caretMinOffset = box->caretMinOffset(); 1091 int caretMaxOffset = box->caretMaxOffset(); 1092 1093 if (caretOffset < caretMinOffset || caretOffset > caretMaxOffset || (caretOffset == caretMaxOffset && box->isLineBreak())) 1094 continue; 1095 1096 if (caretOffset > caretMinOffset && caretOffset < caretMaxOffset) { 1097 inlineBox = box; 1098 return; 1099 } 1100 1101 if (((caretOffset == caretMaxOffset) ^ (affinity == DOWNSTREAM)) 1102 || ((caretOffset == caretMinOffset) ^ (affinity == UPSTREAM))) 1103 break; 1104 1105 candidate = box; 1106 } 1107 if (candidate && candidate == textRenderer->lastTextBox() && affinity == DOWNSTREAM) { 1108 box = searchAheadForBetterMatch(textRenderer); 1109 if (box) 1110 caretOffset = box->caretMinOffset(); 1111 } 1112 inlineBox = box ? box : candidate; 1113 } 1114 1115 if (!inlineBox) 1116 return; 1117 1118 unsigned char level = inlineBox->bidiLevel(); 1119 1120 if (inlineBox->direction() == primaryDirection) { 1121 if (caretOffset == inlineBox->caretRightmostOffset()) { 1122 InlineBox* nextBox = inlineBox->nextLeafChild(); 1123 if (!nextBox || nextBox->bidiLevel() >= level) 1124 return; 1125 1126 level = nextBox->bidiLevel(); 1127 InlineBox* prevBox = inlineBox; 1128 do { 1129 prevBox = prevBox->prevLeafChild(); 1130 } while (prevBox && prevBox->bidiLevel() > level); 1131 1132 if (prevBox && prevBox->bidiLevel() == level) // For example, abc FED 123 ^ CBA 1133 return; 1134 1135 // For example, abc 123 ^ CBA 1136 while (InlineBox* nextBox = inlineBox->nextLeafChild()) { 1137 if (nextBox->bidiLevel() < level) 1138 break; 1139 inlineBox = nextBox; 1140 } 1141 caretOffset = inlineBox->caretRightmostOffset(); 1142 } else { 1143 InlineBox* prevBox = inlineBox->prevLeafChild(); 1144 if (!prevBox || prevBox->bidiLevel() >= level) 1145 return; 1146 1147 level = prevBox->bidiLevel(); 1148 InlineBox* nextBox = inlineBox; 1149 do { 1150 nextBox = nextBox->nextLeafChild(); 1151 } while (nextBox && nextBox->bidiLevel() > level); 1152 1153 if (nextBox && nextBox->bidiLevel() == level) 1154 return; 1155 1156 while (InlineBox* prevBox = inlineBox->prevLeafChild()) { 1157 if (prevBox->bidiLevel() < level) 1158 break; 1159 inlineBox = prevBox; 1160 } 1161 caretOffset = inlineBox->caretLeftmostOffset(); 1162 } 1163 return; 1164 } 1165 1166 if (caretOffset == inlineBox->caretLeftmostOffset()) { 1167 InlineBox* prevBox = inlineBox->prevLeafChild(); 1168 if (!prevBox || prevBox->bidiLevel() < level) { 1169 // Left edge of a secondary run. Set to the right edge of the entire run. 1170 while (InlineBox* nextBox = inlineBox->nextLeafChild()) { 1171 if (nextBox->bidiLevel() < level) 1172 break; 1173 inlineBox = nextBox; 1174 } 1175 caretOffset = inlineBox->caretRightmostOffset(); 1176 } else if (prevBox->bidiLevel() > level) { 1177 // Right edge of a "tertiary" run. Set to the left edge of that run. 1178 while (InlineBox* tertiaryBox = inlineBox->prevLeafChild()) { 1179 if (tertiaryBox->bidiLevel() <= level) 1180 break; 1181 inlineBox = tertiaryBox; 1182 } 1183 caretOffset = inlineBox->caretLeftmostOffset(); 1184 } 1185 } else { 1186 InlineBox* nextBox = inlineBox->nextLeafChild(); 1187 if (!nextBox || nextBox->bidiLevel() < level) { 1188 // Right edge of a secondary run. Set to the left edge of the entire run. 1189 while (InlineBox* prevBox = inlineBox->prevLeafChild()) { 1190 if (prevBox->bidiLevel() < level) 1191 break; 1192 inlineBox = prevBox; 1193 } 1194 caretOffset = inlineBox->caretLeftmostOffset(); 1195 } else if (nextBox->bidiLevel() > level) { 1196 // Left edge of a "tertiary" run. Set to the right edge of that run. 1197 while (InlineBox* tertiaryBox = inlineBox->nextLeafChild()) { 1198 if (tertiaryBox->bidiLevel() <= level) 1199 break; 1200 inlineBox = tertiaryBox; 1201 } 1202 caretOffset = inlineBox->caretRightmostOffset(); 1203 } 1204 } 1205 } 1206 1207 TextDirection Position::primaryDirection() const 1208 { 1209 TextDirection primaryDirection = LTR; 1210 for (const RenderObject* r = m_anchorNode->renderer(); r; r = r->parent()) { 1211 if (r->isBlockFlow()) { 1212 primaryDirection = r->style()->direction(); 1213 break; 1214 } 1215 } 1216 1217 return primaryDirection; 1218 } 1219 1220 1221 void Position::debugPosition(const char* msg) const 1222 { 1223 if (isNull()) 1224 fprintf(stderr, "Position [%s]: null\n", msg); 1225 else 1226 fprintf(stderr, "Position [%s]: %s [%p] at %d\n", msg, deprecatedNode()->nodeName().utf8().data(), deprecatedNode(), m_offset); 1227 } 1228 1229 #ifndef NDEBUG 1230 1231 void Position::formatForDebugger(char* buffer, unsigned length) const 1232 { 1233 String result; 1234 1235 if (isNull()) 1236 result = "<null>"; 1237 else { 1238 char s[1024]; 1239 result += "offset "; 1240 result += String::number(m_offset); 1241 result += " of "; 1242 deprecatedNode()->formatForDebugger(s, sizeof(s)); 1243 result += s; 1244 } 1245 1246 strncpy(buffer, result.utf8().data(), length - 1); 1247 } 1248 1249 void Position::showAnchorTypeAndOffset() const 1250 { 1251 if (m_isLegacyEditingPosition) 1252 fputs("legacy, ", stderr); 1253 switch (anchorType()) { 1254 case PositionIsOffsetInAnchor: 1255 fputs("offset", stderr); 1256 break; 1257 case PositionIsAfterAnchor: 1258 fputs("after", stderr); 1259 break; 1260 case PositionIsBeforeAnchor: 1261 fputs("before", stderr); 1262 break; 1263 } 1264 fprintf(stderr, ", offset:%d\n", m_offset); 1265 } 1266 1267 void Position::showTreeForThis() const 1268 { 1269 if (anchorNode()) { 1270 anchorNode()->showTreeForThis(); 1271 showAnchorTypeAndOffset(); 1272 } 1273 } 1274 1275 #endif 1276 1277 1278 1279 } // namespace WebCore 1280 1281 #ifndef NDEBUG 1282 1283 void showTree(const WebCore::Position& pos) 1284 { 1285 pos.showTreeForThis(); 1286 } 1287 1288 void showTree(const WebCore::Position* pos) 1289 { 1290 if (pos) 1291 pos->showTreeForThis(); 1292 } 1293 1294 #endif 1295