1 /*
2 * Copyright (C) 2004, 2008 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 "SelectionController.h"
28
29 #include "CString.h"
30 #include "DeleteSelectionCommand.h"
31 #include "Document.h"
32 #include "Editor.h"
33 #include "Element.h"
34 #include "EventHandler.h"
35 #include "ExceptionCode.h"
36 #include "FocusController.h"
37 #include "FloatQuad.h"
38 #include "Frame.h"
39 #include "FrameTree.h"
40 #include "FrameView.h"
41 #include "GraphicsContext.h"
42 #include "HTMLInputElement.h"
43 #include "HTMLNames.h"
44 #include "HitTestRequest.h"
45 #include "HitTestResult.h"
46 #include "Page.h"
47 #include "Range.h"
48 #include "RenderTheme.h"
49 #include "RenderView.h"
50 #include "TextIterator.h"
51 #include "TypingCommand.h"
52 #include "htmlediting.h"
53 #include "visible_units.h"
54 #include <stdio.h>
55
56 #define EDIT_DEBUG 0
57
58 namespace WebCore {
59
60 using namespace HTMLNames;
61
62 const int NoXPosForVerticalArrowNavigation = INT_MIN;
63
SelectionController(Frame * frame,bool isDragCaretController)64 SelectionController::SelectionController(Frame* frame, bool isDragCaretController)
65 : m_frame(frame)
66 , m_xPosForVerticalArrowNavigation(NoXPosForVerticalArrowNavigation)
67 , m_needsLayout(true)
68 , m_absCaretBoundsDirty(true)
69 , m_lastChangeWasHorizontalExtension(false)
70 , m_isDragCaretController(isDragCaretController)
71 , m_isCaretBlinkingSuspended(false)
72 , m_focused(frame && frame->page() && frame->page()->focusController()->focusedFrame() == frame)
73 {
74 }
75
moveTo(const VisiblePosition & pos,bool userTriggered)76 void SelectionController::moveTo(const VisiblePosition &pos, bool userTriggered)
77 {
78 setSelection(VisibleSelection(pos.deepEquivalent(), pos.deepEquivalent(), pos.affinity()), true, true, userTriggered);
79 }
80
moveTo(const VisiblePosition & base,const VisiblePosition & extent,bool userTriggered)81 void SelectionController::moveTo(const VisiblePosition &base, const VisiblePosition &extent, bool userTriggered)
82 {
83 setSelection(VisibleSelection(base.deepEquivalent(), extent.deepEquivalent(), base.affinity()), true, true, userTriggered);
84 }
85
moveTo(const Position & pos,EAffinity affinity,bool userTriggered)86 void SelectionController::moveTo(const Position &pos, EAffinity affinity, bool userTriggered)
87 {
88 setSelection(VisibleSelection(pos, affinity), true, true, userTriggered);
89 }
90
moveTo(const Range * r,EAffinity affinity,bool userTriggered)91 void SelectionController::moveTo(const Range *r, EAffinity affinity, bool userTriggered)
92 {
93 setSelection(VisibleSelection(startPosition(r), endPosition(r), affinity), true, true, userTriggered);
94 }
95
moveTo(const Position & base,const Position & extent,EAffinity affinity,bool userTriggered)96 void SelectionController::moveTo(const Position &base, const Position &extent, EAffinity affinity, bool userTriggered)
97 {
98 setSelection(VisibleSelection(base, extent, affinity), true, true, userTriggered);
99 }
100
setSelection(const VisibleSelection & s,bool closeTyping,bool clearTypingStyle,bool userTriggered)101 void SelectionController::setSelection(const VisibleSelection& s, bool closeTyping, bool clearTypingStyle, bool userTriggered)
102 {
103 m_lastChangeWasHorizontalExtension = false;
104
105 if (m_isDragCaretController) {
106 invalidateCaretRect();
107 m_sel = s;
108 m_needsLayout = true;
109 invalidateCaretRect();
110 return;
111 }
112 if (!m_frame) {
113 m_sel = s;
114 return;
115 }
116
117 Node* baseNode = s.base().node();
118 Document* document = 0;
119 if (baseNode)
120 document = baseNode->document();
121
122 // <http://bugs.webkit.org/show_bug.cgi?id=23464>: Infinite recursion at SelectionController::setSelection
123 // if document->frame() == m_frame we can get into an infinite loop
124 if (document && document->frame() != m_frame && document != m_frame->document()) {
125 document->frame()->selection()->setSelection(s, closeTyping, clearTypingStyle, userTriggered);
126 return;
127 }
128
129 if (closeTyping)
130 TypingCommand::closeTyping(m_frame->editor()->lastEditCommand());
131
132 if (clearTypingStyle)
133 m_frame->clearTypingStyle();
134
135 if (m_sel == s)
136 return;
137
138 VisibleSelection oldSelection = m_sel;
139
140 m_sel = s;
141
142 m_needsLayout = true;
143
144 if (!s.isNone())
145 m_frame->setFocusedNodeIfNeeded();
146
147 m_frame->selectionLayoutChanged();
148 // Always clear the x position used for vertical arrow navigation.
149 // It will be restored by the vertical arrow navigation code if necessary.
150 m_xPosForVerticalArrowNavigation = NoXPosForVerticalArrowNavigation;
151 selectFrameElementInParentIfFullySelected();
152 m_frame->notifyRendererOfSelectionChange(userTriggered);
153 m_frame->respondToChangedSelection(oldSelection, closeTyping);
154 if (userTriggered)
155 m_frame->revealSelection(ScrollAlignment::alignToEdgeIfNeeded, true);
156
157 notifyAccessibilityForSelectionChange();
158 }
159
removingNodeRemovesPosition(Node * node,const Position & position)160 static bool removingNodeRemovesPosition(Node* node, const Position& position)
161 {
162 if (!position.node())
163 return false;
164
165 if (position.node() == node)
166 return true;
167
168 if (!node->isElementNode())
169 return false;
170
171 Element* element = static_cast<Element*>(node);
172 return element->contains(position.node()) || element->contains(position.node()->shadowAncestorNode());
173 }
174
nodeWillBeRemoved(Node * node)175 void SelectionController::nodeWillBeRemoved(Node *node)
176 {
177 if (isNone())
178 return;
179
180 // There can't be a selection inside a fragment, so if a fragment's node is being removed,
181 // the selection in the document that created the fragment needs no adjustment.
182 if (node && highestAncestor(node)->nodeType() == Node::DOCUMENT_FRAGMENT_NODE)
183 return;
184
185 bool baseRemoved = removingNodeRemovesPosition(node, m_sel.base());
186 bool extentRemoved = removingNodeRemovesPosition(node, m_sel.extent());
187 bool startRemoved = removingNodeRemovesPosition(node, m_sel.start());
188 bool endRemoved = removingNodeRemovesPosition(node, m_sel.end());
189
190 bool clearRenderTreeSelection = false;
191 bool clearDOMTreeSelection = false;
192
193 if (startRemoved || endRemoved) {
194 // FIXME: When endpoints are removed, we should just alter the selection, instead of blowing it away.
195 clearRenderTreeSelection = true;
196 clearDOMTreeSelection = true;
197 } else if (baseRemoved || extentRemoved) {
198 // The base and/or extent are about to be removed, but the start and end aren't.
199 // Change the base and extent to the start and end, but don't re-validate the
200 // selection, since doing so could move the start and end into the node
201 // that is about to be removed.
202 if (m_sel.isBaseFirst())
203 m_sel.setWithoutValidation(m_sel.start(), m_sel.end());
204 else
205 m_sel.setWithoutValidation(m_sel.end(), m_sel.start());
206 // FIXME: This could be more efficient if we had an isNodeInRange function on Ranges.
207 } else if (comparePositions(m_sel.start(), Position(node, 0)) == -1 && comparePositions(m_sel.end(), Position(node, 0)) == 1) {
208 // If we did nothing here, when this node's renderer was destroyed, the rect that it
209 // occupied would be invalidated, but, selection gaps that change as a result of
210 // the removal wouldn't be invalidated.
211 // FIXME: Don't do so much unnecessary invalidation.
212 clearRenderTreeSelection = true;
213 }
214
215 if (clearRenderTreeSelection) {
216 RefPtr<Document> document = m_sel.start().node()->document();
217 document->updateStyleIfNeeded();
218 if (RenderView* view = toRenderView(document->renderer()))
219 view->clearSelection();
220 }
221
222 if (clearDOMTreeSelection)
223 setSelection(VisibleSelection(), false, false);
224 }
225
willBeModified(EAlteration alter,EDirection direction)226 void SelectionController::willBeModified(EAlteration alter, EDirection direction)
227 {
228 if (alter != EXTEND)
229 return;
230 if (m_lastChangeWasHorizontalExtension)
231 return;
232
233 Position start = m_sel.start();
234 Position end = m_sel.end();
235 // FIXME: This is probably not correct for right and left when the direction is RTL.
236 switch (direction) {
237 case RIGHT:
238 case FORWARD:
239 m_sel.setBase(start);
240 m_sel.setExtent(end);
241 break;
242 case LEFT:
243 case BACKWARD:
244 m_sel.setBase(end);
245 m_sel.setExtent(start);
246 break;
247 }
248 }
249
directionOfEnclosingBlock()250 TextDirection SelectionController::directionOfEnclosingBlock()
251 {
252 Node* n = m_sel.extent().node();
253 Node* enclosingBlockNode = enclosingBlock(n);
254 if (!enclosingBlockNode)
255 return LTR;
256 RenderObject* renderer = enclosingBlockNode->renderer();
257 if (renderer)
258 return renderer->style()->direction();
259 return LTR;
260 }
261
modifyExtendingRight(TextGranularity granularity)262 VisiblePosition SelectionController::modifyExtendingRight(TextGranularity granularity)
263 {
264 VisiblePosition pos(m_sel.extent(), m_sel.affinity());
265
266 // The difference between modifyExtendingRight and modifyExtendingForward is:
267 // modifyExtendingForward always extends forward logically.
268 // modifyExtendingRight behaves the same as modifyExtendingForward except for extending character or word,
269 // it extends forward logically if the enclosing block is LTR direction,
270 // but it extends backward logically if the enclosing block is RTL direction.
271 switch (granularity) {
272 case CharacterGranularity:
273 if (directionOfEnclosingBlock() == LTR)
274 pos = pos.next(true);
275 else
276 pos = pos.previous(true);
277 break;
278 case WordGranularity:
279 if (directionOfEnclosingBlock() == LTR)
280 pos = nextWordPosition(pos);
281 else
282 pos = previousWordPosition(pos);
283 break;
284 case SentenceGranularity:
285 case LineGranularity:
286 case ParagraphGranularity:
287 case SentenceBoundary:
288 case LineBoundary:
289 case ParagraphBoundary:
290 case DocumentBoundary:
291 // FIXME: implement all of the above?
292 pos = modifyExtendingForward(granularity);
293 }
294 return pos;
295 }
296
modifyExtendingForward(TextGranularity granularity)297 VisiblePosition SelectionController::modifyExtendingForward(TextGranularity granularity)
298 {
299 VisiblePosition pos(m_sel.extent(), m_sel.affinity());
300 switch (granularity) {
301 case CharacterGranularity:
302 pos = pos.next(true);
303 break;
304 case WordGranularity:
305 pos = nextWordPosition(pos);
306 break;
307 case SentenceGranularity:
308 pos = nextSentencePosition(pos);
309 break;
310 case LineGranularity:
311 pos = nextLinePosition(pos, xPosForVerticalArrowNavigation(EXTENT));
312 break;
313 case ParagraphGranularity:
314 pos = nextParagraphPosition(pos, xPosForVerticalArrowNavigation(EXTENT));
315 break;
316 case SentenceBoundary:
317 pos = endOfSentence(VisiblePosition(m_sel.end(), m_sel.affinity()));
318 break;
319 case LineBoundary:
320 pos = logicalEndOfLine(VisiblePosition(m_sel.end(), m_sel.affinity()));
321 break;
322 case ParagraphBoundary:
323 pos = endOfParagraph(VisiblePosition(m_sel.end(), m_sel.affinity()));
324 break;
325 case DocumentBoundary:
326 pos = VisiblePosition(m_sel.end(), m_sel.affinity());
327 if (isEditablePosition(pos.deepEquivalent()))
328 pos = endOfEditableContent(pos);
329 else
330 pos = endOfDocument(pos);
331 break;
332 }
333
334 return pos;
335 }
336
modifyMovingRight(TextGranularity granularity)337 VisiblePosition SelectionController::modifyMovingRight(TextGranularity granularity)
338 {
339 VisiblePosition pos;
340 switch (granularity) {
341 case CharacterGranularity:
342 if (isRange())
343 pos = VisiblePosition(m_sel.end(), m_sel.affinity());
344 else
345 pos = VisiblePosition(m_sel.extent(), m_sel.affinity()).right(true);
346 break;
347 case WordGranularity:
348 case SentenceGranularity:
349 case LineGranularity:
350 case ParagraphGranularity:
351 case SentenceBoundary:
352 case LineBoundary:
353 case ParagraphBoundary:
354 case DocumentBoundary:
355 // FIXME: Implement all of the above.
356 pos = modifyMovingForward(granularity);
357 break;
358 }
359 return pos;
360 }
361
modifyMovingForward(TextGranularity granularity)362 VisiblePosition SelectionController::modifyMovingForward(TextGranularity granularity)
363 {
364 VisiblePosition pos;
365 // FIXME: Stay in editable content for the less common granularities.
366 switch (granularity) {
367 case CharacterGranularity:
368 if (isRange())
369 pos = VisiblePosition(m_sel.end(), m_sel.affinity());
370 else
371 pos = VisiblePosition(m_sel.extent(), m_sel.affinity()).next(true);
372 break;
373 case WordGranularity:
374 pos = nextWordPosition(VisiblePosition(m_sel.extent(), m_sel.affinity()));
375 break;
376 case SentenceGranularity:
377 pos = nextSentencePosition(VisiblePosition(m_sel.extent(), m_sel.affinity()));
378 break;
379 case LineGranularity: {
380 // down-arrowing from a range selection that ends at the start of a line needs
381 // to leave the selection at that line start (no need to call nextLinePosition!)
382 pos = VisiblePosition(m_sel.end(), m_sel.affinity());
383 if (!isRange() || !isStartOfLine(pos))
384 pos = nextLinePosition(pos, xPosForVerticalArrowNavigation(START));
385 break;
386 }
387 case ParagraphGranularity:
388 pos = nextParagraphPosition(VisiblePosition(m_sel.end(), m_sel.affinity()), xPosForVerticalArrowNavigation(START));
389 break;
390 case SentenceBoundary:
391 pos = endOfSentence(VisiblePosition(m_sel.end(), m_sel.affinity()));
392 break;
393 case LineBoundary:
394 pos = logicalEndOfLine(VisiblePosition(m_sel.end(), m_sel.affinity()));
395 break;
396 case ParagraphBoundary:
397 pos = endOfParagraph(VisiblePosition(m_sel.end(), m_sel.affinity()));
398 break;
399 case DocumentBoundary:
400 pos = VisiblePosition(m_sel.end(), m_sel.affinity());
401 if (isEditablePosition(pos.deepEquivalent()))
402 pos = endOfEditableContent(pos);
403 else
404 pos = endOfDocument(pos);
405 break;
406
407 }
408 return pos;
409 }
410
modifyExtendingLeft(TextGranularity granularity)411 VisiblePosition SelectionController::modifyExtendingLeft(TextGranularity granularity)
412 {
413 VisiblePosition pos(m_sel.extent(), m_sel.affinity());
414
415 // The difference between modifyExtendingLeft and modifyExtendingBackward is:
416 // modifyExtendingBackward always extends backward logically.
417 // modifyExtendingLeft behaves the same as modifyExtendingBackward except for extending character or word,
418 // it extends backward logically if the enclosing block is LTR direction,
419 // but it extends forward logically if the enclosing block is RTL direction.
420 switch (granularity) {
421 case CharacterGranularity:
422 if (directionOfEnclosingBlock() == LTR)
423 pos = pos.previous(true);
424 else
425 pos = pos.next(true);
426 break;
427 case WordGranularity:
428 if (directionOfEnclosingBlock() == LTR)
429 pos = previousWordPosition(pos);
430 else
431 pos = nextWordPosition(pos);
432 break;
433 case SentenceGranularity:
434 case LineGranularity:
435 case ParagraphGranularity:
436 case SentenceBoundary:
437 case LineBoundary:
438 case ParagraphBoundary:
439 case DocumentBoundary:
440 pos = modifyExtendingBackward(granularity);
441 }
442 return pos;
443 }
444
modifyExtendingBackward(TextGranularity granularity)445 VisiblePosition SelectionController::modifyExtendingBackward(TextGranularity granularity)
446 {
447 VisiblePosition pos(m_sel.extent(), m_sel.affinity());
448
449 // Extending a selection backward by word or character from just after a table selects
450 // the table. This "makes sense" from the user perspective, esp. when deleting.
451 // It was done here instead of in VisiblePosition because we want VPs to iterate
452 // over everything.
453 switch (granularity) {
454 case CharacterGranularity:
455 pos = pos.previous(true);
456 break;
457 case WordGranularity:
458 pos = previousWordPosition(pos);
459 break;
460 case SentenceGranularity:
461 pos = previousSentencePosition(pos);
462 break;
463 case LineGranularity:
464 pos = previousLinePosition(pos, xPosForVerticalArrowNavigation(EXTENT));
465 break;
466 case ParagraphGranularity:
467 pos = previousParagraphPosition(pos, xPosForVerticalArrowNavigation(EXTENT));
468 break;
469 case SentenceBoundary:
470 pos = startOfSentence(VisiblePosition(m_sel.start(), m_sel.affinity()));
471 break;
472 case LineBoundary:
473 pos = logicalStartOfLine(VisiblePosition(m_sel.start(), m_sel.affinity()));
474 break;
475 case ParagraphBoundary:
476 pos = startOfParagraph(VisiblePosition(m_sel.start(), m_sel.affinity()));
477 break;
478 case DocumentBoundary:
479 pos = VisiblePosition(m_sel.start(), m_sel.affinity());
480 if (isEditablePosition(pos.deepEquivalent()))
481 pos = startOfEditableContent(pos);
482 else
483 pos = startOfDocument(pos);
484 break;
485 }
486 return pos;
487 }
488
modifyMovingLeft(TextGranularity granularity)489 VisiblePosition SelectionController::modifyMovingLeft(TextGranularity granularity)
490 {
491 VisiblePosition pos;
492 switch (granularity) {
493 case CharacterGranularity:
494 if (isRange())
495 pos = VisiblePosition(m_sel.start(), m_sel.affinity());
496 else
497 pos = VisiblePosition(m_sel.extent(), m_sel.affinity()).left(true);
498 break;
499 case WordGranularity:
500 case SentenceGranularity:
501 case LineGranularity:
502 case ParagraphGranularity:
503 case SentenceBoundary:
504 case LineBoundary:
505 case ParagraphBoundary:
506 case DocumentBoundary:
507 // FIXME: Implement all of the above.
508 pos = modifyMovingBackward(granularity);
509 break;
510 }
511 return pos;
512 }
513
modifyMovingBackward(TextGranularity granularity)514 VisiblePosition SelectionController::modifyMovingBackward(TextGranularity granularity)
515 {
516 VisiblePosition pos;
517 switch (granularity) {
518 case CharacterGranularity:
519 if (isRange())
520 pos = VisiblePosition(m_sel.start(), m_sel.affinity());
521 else
522 pos = VisiblePosition(m_sel.extent(), m_sel.affinity()).previous(true);
523 break;
524 case WordGranularity:
525 pos = previousWordPosition(VisiblePosition(m_sel.extent(), m_sel.affinity()));
526 break;
527 case SentenceGranularity:
528 pos = previousSentencePosition(VisiblePosition(m_sel.extent(), m_sel.affinity()));
529 break;
530 case LineGranularity:
531 pos = previousLinePosition(VisiblePosition(m_sel.start(), m_sel.affinity()), xPosForVerticalArrowNavigation(START));
532 break;
533 case ParagraphGranularity:
534 pos = previousParagraphPosition(VisiblePosition(m_sel.start(), m_sel.affinity()), xPosForVerticalArrowNavigation(START));
535 break;
536 case SentenceBoundary:
537 pos = startOfSentence(VisiblePosition(m_sel.start(), m_sel.affinity()));
538 break;
539 case LineBoundary:
540 pos = logicalStartOfLine(VisiblePosition(m_sel.start(), m_sel.affinity()));
541 break;
542 case ParagraphBoundary:
543 pos = startOfParagraph(VisiblePosition(m_sel.start(), m_sel.affinity()));
544 break;
545 case DocumentBoundary:
546 pos = VisiblePosition(m_sel.start(), m_sel.affinity());
547 if (isEditablePosition(pos.deepEquivalent()))
548 pos = startOfEditableContent(pos);
549 else
550 pos = startOfDocument(pos);
551 break;
552 }
553 return pos;
554 }
555
modify(EAlteration alter,EDirection dir,TextGranularity granularity,bool userTriggered)556 bool SelectionController::modify(EAlteration alter, EDirection dir, TextGranularity granularity, bool userTriggered)
557 {
558 if (userTriggered) {
559 SelectionController trialSelectionController;
560 trialSelectionController.setSelection(m_sel);
561 trialSelectionController.setLastChangeWasHorizontalExtension(m_lastChangeWasHorizontalExtension);
562 trialSelectionController.modify(alter, dir, granularity, false);
563
564 bool change = m_frame->shouldChangeSelection(trialSelectionController.selection());
565 if (!change)
566 return false;
567 }
568
569 if (m_frame)
570 m_frame->setSelectionGranularity(granularity);
571
572 willBeModified(alter, dir);
573
574 VisiblePosition pos;
575 switch (dir) {
576 case RIGHT:
577 if (alter == MOVE)
578 pos = modifyMovingRight(granularity);
579 else
580 pos = modifyExtendingRight(granularity);
581 break;
582 case FORWARD:
583 if (alter == EXTEND)
584 pos = modifyExtendingForward(granularity);
585 else
586 pos = modifyMovingForward(granularity);
587 break;
588 case LEFT:
589 if (alter == MOVE)
590 pos = modifyMovingLeft(granularity);
591 else
592 pos = modifyExtendingLeft(granularity);
593 break;
594 case BACKWARD:
595 if (alter == EXTEND)
596 pos = modifyExtendingBackward(granularity);
597 else
598 pos = modifyMovingBackward(granularity);
599 break;
600 }
601
602 if (pos.isNull())
603 return false;
604
605 // Some of the above operations set an xPosForVerticalArrowNavigation.
606 // Setting a selection will clear it, so save it to possibly restore later.
607 // Note: the START position type is arbitrary because it is unused, it would be
608 // the requested position type if there were no xPosForVerticalArrowNavigation set.
609 int x = xPosForVerticalArrowNavigation(START);
610
611 switch (alter) {
612 case MOVE:
613 moveTo(pos, userTriggered);
614 break;
615 case EXTEND:
616 setExtent(pos, userTriggered);
617 break;
618 }
619
620 if (granularity == LineGranularity || granularity == ParagraphGranularity)
621 m_xPosForVerticalArrowNavigation = x;
622
623 if (userTriggered) {
624 // User modified selection change also sets the granularity back to character.
625 // NOTE: The one exception is that we need to keep word granularity to
626 // preserve smart delete behavior when extending by word (e.g. double-click),
627 // then shift-option-right arrow, then delete needs to smart delete, per TextEdit.
628 if (!(alter == EXTEND && granularity == WordGranularity && m_frame->selectionGranularity() == WordGranularity))
629 m_frame->setSelectionGranularity(CharacterGranularity);
630 }
631
632 setNeedsLayout();
633
634 m_lastChangeWasHorizontalExtension = alter == EXTEND;
635
636 return true;
637 }
638
639 // FIXME: Maybe baseline would be better?
absoluteCaretY(const VisiblePosition & c,int & y)640 static bool absoluteCaretY(const VisiblePosition &c, int &y)
641 {
642 IntRect rect = c.absoluteCaretBounds();
643 if (rect.isEmpty())
644 return false;
645 y = rect.y() + rect.height() / 2;
646 return true;
647 }
648
modify(EAlteration alter,int verticalDistance,bool userTriggered)649 bool SelectionController::modify(EAlteration alter, int verticalDistance, bool userTriggered)
650 {
651 if (verticalDistance == 0)
652 return false;
653
654 if (userTriggered) {
655 SelectionController trialSelectionController;
656 trialSelectionController.setSelection(m_sel);
657 trialSelectionController.setLastChangeWasHorizontalExtension(m_lastChangeWasHorizontalExtension);
658 trialSelectionController.modify(alter, verticalDistance, false);
659
660 bool change = m_frame->shouldChangeSelection(trialSelectionController.selection());
661 if (!change)
662 return false;
663 }
664
665 bool up = verticalDistance < 0;
666 if (up)
667 verticalDistance = -verticalDistance;
668
669 willBeModified(alter, up ? BACKWARD : FORWARD);
670
671 VisiblePosition pos;
672 int xPos = 0;
673 switch (alter) {
674 case MOVE:
675 pos = VisiblePosition(up ? m_sel.start() : m_sel.end(), m_sel.affinity());
676 xPos = xPosForVerticalArrowNavigation(up ? START : END);
677 m_sel.setAffinity(up ? UPSTREAM : DOWNSTREAM);
678 break;
679 case EXTEND:
680 pos = VisiblePosition(m_sel.extent(), m_sel.affinity());
681 xPos = xPosForVerticalArrowNavigation(EXTENT);
682 m_sel.setAffinity(DOWNSTREAM);
683 break;
684 }
685
686 int startY;
687 if (!absoluteCaretY(pos, startY))
688 return false;
689 if (up)
690 startY = -startY;
691 int lastY = startY;
692
693 VisiblePosition result;
694 VisiblePosition next;
695 for (VisiblePosition p = pos; ; p = next) {
696 next = (up ? previousLinePosition : nextLinePosition)(p, xPos);
697 if (next.isNull() || next == p)
698 break;
699 int nextY;
700 if (!absoluteCaretY(next, nextY))
701 break;
702 if (up)
703 nextY = -nextY;
704 if (nextY - startY > verticalDistance)
705 break;
706 if (nextY >= lastY) {
707 lastY = nextY;
708 result = next;
709 }
710 }
711
712 if (result.isNull())
713 return false;
714
715 switch (alter) {
716 case MOVE:
717 moveTo(result, userTriggered);
718 break;
719 case EXTEND:
720 setExtent(result, userTriggered);
721 break;
722 }
723
724 if (userTriggered)
725 m_frame->setSelectionGranularity(CharacterGranularity);
726
727 return true;
728 }
729
expandUsingGranularity(TextGranularity granularity)730 bool SelectionController::expandUsingGranularity(TextGranularity granularity)
731 {
732 if (isNone())
733 return false;
734
735 m_sel.expandUsingGranularity(granularity);
736 m_needsLayout = true;
737 return true;
738 }
739
xPosForVerticalArrowNavigation(EPositionType type)740 int SelectionController::xPosForVerticalArrowNavigation(EPositionType type)
741 {
742 int x = 0;
743
744 if (isNone())
745 return x;
746
747 Position pos;
748 switch (type) {
749 case START:
750 pos = m_sel.start();
751 break;
752 case END:
753 pos = m_sel.end();
754 break;
755 case BASE:
756 pos = m_sel.base();
757 break;
758 case EXTENT:
759 pos = m_sel.extent();
760 break;
761 }
762
763 Frame *frame = pos.node()->document()->frame();
764 if (!frame)
765 return x;
766
767 if (m_xPosForVerticalArrowNavigation == NoXPosForVerticalArrowNavigation) {
768 VisiblePosition visiblePosition(pos, m_sel.affinity());
769 // VisiblePosition creation can fail here if a node containing the selection becomes visibility:hidden
770 // after the selection is created and before this function is called.
771 x = visiblePosition.isNotNull() ? visiblePosition.xOffsetForVerticalNavigation() : 0;
772 m_xPosForVerticalArrowNavigation = x;
773 }
774 else
775 x = m_xPosForVerticalArrowNavigation;
776
777 return x;
778 }
779
clear()780 void SelectionController::clear()
781 {
782 setSelection(VisibleSelection());
783 }
784
setBase(const VisiblePosition & pos,bool userTriggered)785 void SelectionController::setBase(const VisiblePosition &pos, bool userTriggered)
786 {
787 setSelection(VisibleSelection(pos.deepEquivalent(), m_sel.extent(), pos.affinity()), true, true, userTriggered);
788 }
789
setExtent(const VisiblePosition & pos,bool userTriggered)790 void SelectionController::setExtent(const VisiblePosition &pos, bool userTriggered)
791 {
792 setSelection(VisibleSelection(m_sel.base(), pos.deepEquivalent(), pos.affinity()), true, true, userTriggered);
793 }
794
setBase(const Position & pos,EAffinity affinity,bool userTriggered)795 void SelectionController::setBase(const Position &pos, EAffinity affinity, bool userTriggered)
796 {
797 setSelection(VisibleSelection(pos, m_sel.extent(), affinity), true, true, userTriggered);
798 }
799
setExtent(const Position & pos,EAffinity affinity,bool userTriggered)800 void SelectionController::setExtent(const Position &pos, EAffinity affinity, bool userTriggered)
801 {
802 setSelection(VisibleSelection(m_sel.base(), pos, affinity), true, true, userTriggered);
803 }
804
setNeedsLayout(bool flag)805 void SelectionController::setNeedsLayout(bool flag)
806 {
807 m_needsLayout = flag;
808 }
809
layout()810 void SelectionController::layout()
811 {
812 if (isNone() || !m_sel.start().node()->inDocument() || !m_sel.end().node()->inDocument()) {
813 m_caretRect = IntRect();
814 return;
815 }
816
817 m_sel.start().node()->document()->updateStyleIfNeeded();
818
819 m_caretRect = IntRect();
820
821 if (isCaret()) {
822 VisiblePosition pos(m_sel.start(), m_sel.affinity());
823 if (pos.isNotNull()) {
824 ASSERT(pos.deepEquivalent().node()->renderer());
825
826 // First compute a rect local to the renderer at the selection start
827 RenderObject* renderer;
828 IntRect localRect = pos.localCaretRect(renderer);
829
830 // Get the renderer that will be responsible for painting the caret (which
831 // is either the renderer we just found, or one of its containers)
832 RenderObject* caretPainter = caretRenderer();
833
834 // Compute an offset between the renderer and the caretPainter
835 IntSize offsetFromPainter;
836 bool unrooted = false;
837 while (renderer != caretPainter) {
838 RenderObject* containerObject = renderer->container();
839 if (!containerObject) {
840 unrooted = true;
841 break;
842 }
843 offsetFromPainter += renderer->offsetFromContainer(containerObject);
844 renderer = containerObject;
845 }
846
847 if (!unrooted) {
848 // Move the caret rect to the coords of the painter
849 localRect.move(offsetFromPainter);
850 m_caretRect = localRect;
851 }
852
853 m_absCaretBoundsDirty = true;
854 }
855 }
856
857 m_needsLayout = false;
858 }
859
caretRenderer() const860 RenderObject* SelectionController::caretRenderer() const
861 {
862 Node* node = m_sel.start().node();
863 if (!node)
864 return 0;
865
866 RenderObject* renderer = node->renderer();
867 if (!renderer)
868 return 0;
869
870 // if caretNode is a block and caret is inside it then caret should be painted by that block
871 bool paintedByBlock = renderer->isBlockFlow() && caretRendersInsideNode(node);
872 return paintedByBlock ? renderer : renderer->containingBlock();
873 }
874
localCaretRect() const875 IntRect SelectionController::localCaretRect() const
876 {
877 if (m_needsLayout)
878 const_cast<SelectionController*>(this)->layout();
879
880 return m_caretRect;
881 }
882
absoluteBoundsForLocalRect(const IntRect & rect) const883 IntRect SelectionController::absoluteBoundsForLocalRect(const IntRect& rect) const
884 {
885 RenderObject* caretPainter = caretRenderer();
886 if (!caretPainter)
887 return IntRect();
888
889 return caretPainter->localToAbsoluteQuad(FloatRect(rect)).enclosingBoundingBox();
890 }
891
absoluteCaretBounds()892 IntRect SelectionController::absoluteCaretBounds()
893 {
894 recomputeCaretRect();
895 return m_absCaretBounds;
896 }
897
repaintRectForCaret(IntRect caret)898 static IntRect repaintRectForCaret(IntRect caret)
899 {
900 if (caret.isEmpty())
901 return IntRect();
902 // Ensure that the dirty rect intersects the block that paints the caret even in the case where
903 // the caret itself is just outside the block. See <https://bugs.webkit.org/show_bug.cgi?id=19086>.
904 caret.inflateX(1);
905 return caret;
906 }
907
caretRepaintRect() const908 IntRect SelectionController::caretRepaintRect() const
909 {
910 return absoluteBoundsForLocalRect(repaintRectForCaret(localCaretRect()));
911 }
912
recomputeCaretRect()913 bool SelectionController::recomputeCaretRect()
914 {
915 if (!m_frame)
916 return false;
917
918 FrameView* v = m_frame->document()->view();
919 if (!v)
920 return false;
921
922 if (!m_needsLayout)
923 return false;
924
925 IntRect oldRect = m_caretRect;
926 IntRect newRect = localCaretRect();
927 if (oldRect == newRect && !m_absCaretBoundsDirty)
928 return false;
929
930 IntRect oldAbsCaretBounds = m_absCaretBounds;
931 // FIXME: Rename m_caretRect to m_localCaretRect.
932 m_absCaretBounds = absoluteBoundsForLocalRect(m_caretRect);
933 m_absCaretBoundsDirty = false;
934
935 if (oldAbsCaretBounds == m_absCaretBounds)
936 return false;
937
938 IntRect oldAbsoluteCaretRepaintBounds = m_absoluteCaretRepaintBounds;
939 // We believe that we need to inflate the local rect before transforming it to obtain the repaint bounds.
940 m_absoluteCaretRepaintBounds = caretRepaintRect();
941
942 if (RenderView* view = toRenderView(m_frame->document()->renderer())) {
943 // FIXME: make caret repainting container-aware.
944 view->repaintRectangleInViewAndCompositedLayers(oldAbsoluteCaretRepaintBounds, false);
945 view->repaintRectangleInViewAndCompositedLayers(m_absoluteCaretRepaintBounds, false);
946 }
947
948 return true;
949 }
950
invalidateCaretRect()951 void SelectionController::invalidateCaretRect()
952 {
953 if (!isCaret())
954 return;
955
956 Document* d = m_sel.start().node()->document();
957
958 // recomputeCaretRect will always return false for the drag caret,
959 // because its m_frame is always 0.
960 bool caretRectChanged = recomputeCaretRect();
961
962 // EDIT FIXME: This is an unfortunate hack.
963 // Basically, we can't trust this layout position since we
964 // can't guarantee that the check to see if we are in unrendered
965 // content will work at this point. We may have to wait for
966 // a layout and re-render of the document to happen. So, resetting this
967 // flag will cause another caret layout to happen the first time
968 // that we try to paint the caret after this call. That one will work since
969 // it happens after the document has accounted for any editing
970 // changes which may have been done.
971 // And, we need to leave this layout here so the caret moves right
972 // away after clicking.
973 m_needsLayout = true;
974
975 if (!caretRectChanged) {
976 if (RenderView* view = toRenderView(d->renderer()))
977 view->repaintRectangleInViewAndCompositedLayers(caretRepaintRect(), false);
978 }
979 }
980
paintCaret(GraphicsContext * p,int tx,int ty,const IntRect & clipRect)981 void SelectionController::paintCaret(GraphicsContext* p, int tx, int ty, const IntRect& clipRect)
982 {
983 if (! m_sel.isCaret())
984 return;
985
986 if (m_needsLayout)
987 layout();
988
989 IntRect drawingRect = localCaretRect();
990 drawingRect.move(tx, ty);
991 IntRect caret = intersection(drawingRect, clipRect);
992 if (!caret.isEmpty()) {
993 Color caretColor = Color::black;
994 Element* element = rootEditableElement();
995 if (element && element->renderer())
996 caretColor = element->renderer()->style()->color();
997
998 p->fillRect(caret, caretColor);
999 }
1000 }
1001
debugRenderer(RenderObject * r,bool selected) const1002 void SelectionController::debugRenderer(RenderObject *r, bool selected) const
1003 {
1004 if (r->node()->isElementNode()) {
1005 Element *element = static_cast<Element *>(r->node());
1006 fprintf(stderr, "%s%s\n", selected ? "==> " : " ", element->localName().string().utf8().data());
1007 }
1008 else if (r->isText()) {
1009 RenderText* textRenderer = toRenderText(r);
1010 if (textRenderer->textLength() == 0 || !textRenderer->firstTextBox()) {
1011 fprintf(stderr, "%s#text (empty)\n", selected ? "==> " : " ");
1012 return;
1013 }
1014
1015 static const int max = 36;
1016 String text = textRenderer->text();
1017 int textLength = text.length();
1018 if (selected) {
1019 int offset = 0;
1020 if (r->node() == m_sel.start().node())
1021 offset = m_sel.start().deprecatedEditingOffset();
1022 else if (r->node() == m_sel.end().node())
1023 offset = m_sel.end().deprecatedEditingOffset();
1024
1025 int pos;
1026 InlineTextBox *box = textRenderer->findNextInlineTextBox(offset, pos);
1027 text = text.substring(box->start(), box->len());
1028
1029 String show;
1030 int mid = max / 2;
1031 int caret = 0;
1032
1033 // text is shorter than max
1034 if (textLength < max) {
1035 show = text;
1036 caret = pos;
1037 }
1038
1039 // too few characters to left
1040 else if (pos - mid < 0) {
1041 show = text.left(max - 3) + "...";
1042 caret = pos;
1043 }
1044
1045 // enough characters on each side
1046 else if (pos - mid >= 0 && pos + mid <= textLength) {
1047 show = "..." + text.substring(pos - mid + 3, max - 6) + "...";
1048 caret = mid;
1049 }
1050
1051 // too few characters on right
1052 else {
1053 show = "..." + text.right(max - 3);
1054 caret = pos - (textLength - show.length());
1055 }
1056
1057 show.replace('\n', ' ');
1058 show.replace('\r', ' ');
1059 fprintf(stderr, "==> #text : \"%s\" at offset %d\n", show.utf8().data(), pos);
1060 fprintf(stderr, " ");
1061 for (int i = 0; i < caret; i++)
1062 fprintf(stderr, " ");
1063 fprintf(stderr, "^\n");
1064 }
1065 else {
1066 if ((int)text.length() > max)
1067 text = text.left(max - 3) + "...";
1068 else
1069 text = text.left(max);
1070 fprintf(stderr, " #text : \"%s\"\n", text.utf8().data());
1071 }
1072 }
1073 }
1074
contains(const IntPoint & point)1075 bool SelectionController::contains(const IntPoint& point)
1076 {
1077 Document* document = m_frame->document();
1078
1079 // Treat a collapsed selection like no selection.
1080 if (!isRange())
1081 return false;
1082 if (!document->renderer())
1083 return false;
1084
1085 HitTestRequest request(HitTestRequest::ReadOnly |
1086 HitTestRequest::Active);
1087 HitTestResult result(point);
1088 document->renderView()->layer()->hitTest(request, result);
1089 Node* innerNode = result.innerNode();
1090 if (!innerNode || !innerNode->renderer())
1091 return false;
1092
1093 VisiblePosition visiblePos(innerNode->renderer()->positionForPoint(result.localPoint()));
1094 if (visiblePos.isNull())
1095 return false;
1096
1097 if (m_sel.visibleStart().isNull() || m_sel.visibleEnd().isNull())
1098 return false;
1099
1100 Position start(m_sel.visibleStart().deepEquivalent());
1101 Position end(m_sel.visibleEnd().deepEquivalent());
1102 Position p(visiblePos.deepEquivalent());
1103
1104 return comparePositions(start, p) <= 0 && comparePositions(p, end) <= 0;
1105 }
1106
1107 // Workaround for the fact that it's hard to delete a frame.
1108 // Call this after doing user-triggered selections to make it easy to delete the frame you entirely selected.
1109 // Can't do this implicitly as part of every setSelection call because in some contexts it might not be good
1110 // for the focus to move to another frame. So instead we call it from places where we are selecting with the
1111 // mouse or the keyboard after setting the selection.
selectFrameElementInParentIfFullySelected()1112 void SelectionController::selectFrameElementInParentIfFullySelected()
1113 {
1114 // Find the parent frame; if there is none, then we have nothing to do.
1115 Frame* parent = m_frame->tree()->parent();
1116 if (!parent)
1117 return;
1118 Page* page = m_frame->page();
1119 if (!page)
1120 return;
1121
1122 // Check if the selection contains the entire frame contents; if not, then there is nothing to do.
1123 if (!isRange())
1124 return;
1125 if (!isStartOfDocument(selection().visibleStart()))
1126 return;
1127 if (!isEndOfDocument(selection().visibleEnd()))
1128 return;
1129
1130 // Get to the <iframe> or <frame> (or even <object>) element in the parent frame.
1131 Document* doc = m_frame->document();
1132 Element* ownerElement = doc->ownerElement();
1133 if (!ownerElement)
1134 return;
1135 Node* ownerElementParent = ownerElement->parentNode();
1136 if (!ownerElementParent)
1137 return;
1138
1139 // This method's purpose is it to make it easier to select iframes (in order to delete them). Don't do anything if the iframe isn't deletable.
1140 if (!ownerElementParent->isContentEditable())
1141 return;
1142
1143 // Create compute positions before and after the element.
1144 unsigned ownerElementNodeIndex = ownerElement->nodeIndex();
1145 VisiblePosition beforeOwnerElement(VisiblePosition(ownerElementParent, ownerElementNodeIndex, SEL_DEFAULT_AFFINITY));
1146 VisiblePosition afterOwnerElement(VisiblePosition(ownerElementParent, ownerElementNodeIndex + 1, VP_UPSTREAM_IF_POSSIBLE));
1147
1148 // Focus on the parent frame, and then select from before this element to after.
1149 VisibleSelection newSelection(beforeOwnerElement, afterOwnerElement);
1150 if (parent->shouldChangeSelection(newSelection)) {
1151 page->focusController()->setFocusedFrame(parent);
1152 parent->selection()->setSelection(newSelection);
1153 }
1154 }
1155
selectAll()1156 void SelectionController::selectAll()
1157 {
1158 Document* document = m_frame->document();
1159
1160 if (document->focusedNode() && document->focusedNode()->canSelectAll()) {
1161 document->focusedNode()->selectAll();
1162 return;
1163 }
1164
1165 Node* root = 0;
1166 if (isContentEditable())
1167 root = highestEditableRoot(m_sel.start());
1168 else {
1169 root = shadowTreeRootNode();
1170 if (!root)
1171 root = document->documentElement();
1172 }
1173 if (!root)
1174 return;
1175 VisibleSelection newSelection(VisibleSelection::selectionFromContentsOfNode(root));
1176 if (m_frame->shouldChangeSelection(newSelection))
1177 setSelection(newSelection);
1178 selectFrameElementInParentIfFullySelected();
1179 m_frame->notifyRendererOfSelectionChange(true);
1180 }
1181
setSelectedRange(Range * range,EAffinity affinity,bool closeTyping)1182 bool SelectionController::setSelectedRange(Range* range, EAffinity affinity, bool closeTyping)
1183 {
1184 if (!range)
1185 return false;
1186
1187 ExceptionCode ec = 0;
1188 Node* startContainer = range->startContainer(ec);
1189 if (ec)
1190 return false;
1191
1192 Node* endContainer = range->endContainer(ec);
1193 if (ec)
1194 return false;
1195
1196 ASSERT(startContainer);
1197 ASSERT(endContainer);
1198 ASSERT(startContainer->document() == endContainer->document());
1199
1200 m_frame->document()->updateLayoutIgnorePendingStylesheets();
1201
1202 // Non-collapsed ranges are not allowed to start at the end of a line that is wrapped,
1203 // they start at the beginning of the next line instead
1204 bool collapsed = range->collapsed(ec);
1205 if (ec)
1206 return false;
1207
1208 int startOffset = range->startOffset(ec);
1209 if (ec)
1210 return false;
1211
1212 int endOffset = range->endOffset(ec);
1213 if (ec)
1214 return false;
1215
1216 // FIXME: Can we provide extentAffinity?
1217 VisiblePosition visibleStart(startContainer, startOffset, collapsed ? affinity : DOWNSTREAM);
1218 VisiblePosition visibleEnd(endContainer, endOffset, SEL_DEFAULT_AFFINITY);
1219 setSelection(VisibleSelection(visibleStart, visibleEnd), closeTyping);
1220 return true;
1221 }
1222
isInPasswordField() const1223 bool SelectionController::isInPasswordField() const
1224 {
1225 Node* startNode = start().node();
1226 if (!startNode)
1227 return false;
1228
1229 startNode = startNode->shadowAncestorNode();
1230 if (!startNode)
1231 return false;
1232
1233 if (!startNode->hasTagName(inputTag))
1234 return false;
1235
1236 return static_cast<HTMLInputElement*>(startNode)->inputType() == HTMLInputElement::PASSWORD;
1237 }
1238
caretRendersInsideNode(Node * node) const1239 bool SelectionController::caretRendersInsideNode(Node* node) const
1240 {
1241 if (!node)
1242 return false;
1243 return !isTableElement(node) && !editingIgnoresContent(node);
1244 }
1245
focusedOrActiveStateChanged()1246 void SelectionController::focusedOrActiveStateChanged()
1247 {
1248 bool activeAndFocused = isFocusedAndActive();
1249
1250 // Because RenderObject::selectionBackgroundColor() and
1251 // RenderObject::selectionForegroundColor() check if the frame is active,
1252 // we have to update places those colors were painted.
1253 if (RenderView* view = toRenderView(m_frame->document()->renderer()))
1254 view->repaintRectangleInViewAndCompositedLayers(enclosingIntRect(m_frame->selectionBounds()));
1255
1256 // Caret appears in the active frame.
1257 if (activeAndFocused)
1258 m_frame->setSelectionFromNone();
1259 m_frame->setCaretVisible(activeAndFocused);
1260
1261 // Update for caps lock state
1262 m_frame->eventHandler()->capsLockStateMayHaveChanged();
1263
1264 // Because CSSStyleSelector::checkOneSelector() and
1265 // RenderTheme::isFocused() check if the frame is active, we have to
1266 // update style and theme state that depended on those.
1267 if (Node* node = m_frame->document()->focusedNode()) {
1268 node->setNeedsStyleRecalc();
1269 if (RenderObject* renderer = node->renderer())
1270 if (renderer && renderer->style()->hasAppearance())
1271 renderer->theme()->stateChanged(renderer, FocusState);
1272 }
1273
1274 // Secure keyboard entry is set by the active frame.
1275 if (m_frame->document()->useSecureKeyboardEntryWhenActive())
1276 m_frame->setUseSecureKeyboardEntry(activeAndFocused);
1277 }
1278
pageActivationChanged()1279 void SelectionController::pageActivationChanged()
1280 {
1281 focusedOrActiveStateChanged();
1282 }
1283
setFocused(bool flag)1284 void SelectionController::setFocused(bool flag)
1285 {
1286 if (m_focused == flag)
1287 return;
1288 m_focused = flag;
1289
1290 focusedOrActiveStateChanged();
1291 }
1292
isFocusedAndActive() const1293 bool SelectionController::isFocusedAndActive() const
1294 {
1295 return m_focused && m_frame->page() && m_frame->page()->focusController()->isActive();
1296 }
1297
1298 #ifndef NDEBUG
1299
formatForDebugger(char * buffer,unsigned length) const1300 void SelectionController::formatForDebugger(char* buffer, unsigned length) const
1301 {
1302 m_sel.formatForDebugger(buffer, length);
1303 }
1304
showTreeForThis() const1305 void SelectionController::showTreeForThis() const
1306 {
1307 m_sel.showTreeForThis();
1308 }
1309
1310 #endif
1311
1312 }
1313
1314 #ifndef NDEBUG
1315
showTree(const WebCore::SelectionController & sel)1316 void showTree(const WebCore::SelectionController& sel)
1317 {
1318 sel.showTreeForThis();
1319 }
1320
showTree(const WebCore::SelectionController * sel)1321 void showTree(const WebCore::SelectionController* sel)
1322 {
1323 if (sel)
1324 sel->showTreeForThis();
1325 }
1326
1327 #endif
1328