eb2bd5b96c51af6ed8256719d9f4ed2d4e3c045b
[WebKit-https.git] / Source / WebCore / page / DOMSelection.cpp
1 /*
2  * Copyright (C) 2007, 2009 Apple Inc. All rights reserved.
3  * Copyright (C) 2012 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer. 
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution. 
14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission. 
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30
31 #include "config.h"
32 #include "DOMSelection.h"
33
34 #include "Document.h"
35 #include "ExceptionCode.h"
36 #include "Frame.h"
37 #include "FrameSelection.h"
38 #include "Node.h"
39 #include "Range.h"
40 #include "TextIterator.h"
41 #include "TreeScope.h"
42 #include "htmlediting.h"
43 #include <wtf/text/WTFString.h>
44
45 namespace WebCore {
46
47 static Node* selectionShadowAncestor(Frame* frame)
48 {
49     Node* node = frame->selection().selection().base().anchorNode();
50     if (!node)
51         return 0;
52
53     if (!node->isInShadowTree())
54         return 0;
55
56     return frame->document()->ancestorInThisScope(node);
57 }
58
59 DOMSelection::DOMSelection(const TreeScope* treeScope)
60     : DOMWindowProperty(treeScope->rootNode()->document().frame())
61     , m_treeScope(treeScope)
62 {
63 }
64
65 void DOMSelection::clearTreeScope()
66 {
67     m_treeScope = 0;
68 }
69
70 const VisibleSelection& DOMSelection::visibleSelection() const
71 {
72     ASSERT(m_frame);
73     return m_frame->selection().selection();
74 }
75
76 static Position anchorPosition(const VisibleSelection& selection)
77 {
78     Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
79     return anchor.parentAnchoredEquivalent();
80 }
81
82 static Position focusPosition(const VisibleSelection& selection)
83 {
84     Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
85     return focus.parentAnchoredEquivalent();
86 }
87
88 static Position basePosition(const VisibleSelection& selection)
89 {
90     return selection.base().parentAnchoredEquivalent();
91 }
92
93 static Position extentPosition(const VisibleSelection& selection)
94 {
95     return selection.extent().parentAnchoredEquivalent();
96 }
97
98 Node* DOMSelection::anchorNode() const
99 {
100     if (!m_frame)
101         return 0;
102
103     return shadowAdjustedNode(anchorPosition(visibleSelection()));
104 }
105
106 int DOMSelection::anchorOffset() const
107 {
108     if (!m_frame)
109         return 0;
110
111     return shadowAdjustedOffset(anchorPosition(visibleSelection()));
112 }
113
114 Node* DOMSelection::focusNode() const
115 {
116     if (!m_frame)
117         return 0;
118
119     return shadowAdjustedNode(focusPosition(visibleSelection()));
120 }
121
122 int DOMSelection::focusOffset() const
123 {
124     if (!m_frame)
125         return 0;
126
127     return shadowAdjustedOffset(focusPosition(visibleSelection()));
128 }
129
130 Node* DOMSelection::baseNode() const
131 {
132     if (!m_frame)
133         return 0;
134
135     return shadowAdjustedNode(basePosition(visibleSelection()));
136 }
137
138 int DOMSelection::baseOffset() const
139 {
140     if (!m_frame)
141         return 0;
142
143     return shadowAdjustedOffset(basePosition(visibleSelection()));
144 }
145
146 Node* DOMSelection::extentNode() const
147 {
148     if (!m_frame)
149         return 0;
150
151     return shadowAdjustedNode(extentPosition(visibleSelection()));
152 }
153
154 int DOMSelection::extentOffset() const
155 {
156     if (!m_frame)
157         return 0;
158
159     return shadowAdjustedOffset(extentPosition(visibleSelection()));
160 }
161
162 bool DOMSelection::isCollapsed() const
163 {
164     if (!m_frame || selectionShadowAncestor(m_frame))
165         return true;
166     return !m_frame->selection().isRange();
167 }
168
169 String DOMSelection::type() const
170 {
171     if (!m_frame)
172         return String();
173
174     FrameSelection& selection = m_frame->selection();
175
176     // This is a WebKit DOM extension, incompatible with an IE extension
177     // IE has this same attribute, but returns "none", "text" and "control"
178     // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
179     if (selection.isNone())
180         return "None";
181     if (selection.isCaret())
182         return "Caret";
183     return "Range";
184 }
185
186 int DOMSelection::rangeCount() const
187 {
188     if (!m_frame)
189         return 0;
190     return m_frame->selection().isNone() ? 0 : 1;
191 }
192
193 void DOMSelection::collapse(Node* node, int offset, ExceptionCode& ec)
194 {
195     if (!m_frame)
196         return;
197
198     if (offset < 0) {
199         ec = INDEX_SIZE_ERR;
200         return;
201     }
202
203     if (!isValidForPosition(node))
204         return;
205
206     // FIXME: Eliminate legacy editing positions
207     m_frame->selection().moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
208 }
209
210 void DOMSelection::collapseToEnd(ExceptionCode& ec)
211 {
212     if (!m_frame)
213         return;
214
215     const VisibleSelection& selection = m_frame->selection().selection();
216
217     if (selection.isNone()) {
218         ec = INVALID_STATE_ERR;
219         return;
220     }
221
222     m_frame->selection().moveTo(VisiblePosition(selection.end(), DOWNSTREAM));
223 }
224
225 void DOMSelection::collapseToStart(ExceptionCode& ec)
226 {
227     if (!m_frame)
228         return;
229
230     const VisibleSelection& selection = m_frame->selection().selection();
231
232     if (selection.isNone()) {
233         ec = INVALID_STATE_ERR;
234         return;
235     }
236
237     m_frame->selection().moveTo(VisiblePosition(selection.start(), DOWNSTREAM));
238 }
239
240 void DOMSelection::empty()
241 {
242     if (!m_frame)
243         return;
244     m_frame->selection().clear();
245 }
246
247 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode& ec)
248 {
249     if (!m_frame)
250         return;
251
252     if (baseOffset < 0 || extentOffset < 0) {
253         ec = INDEX_SIZE_ERR;
254         return;
255     }
256
257     if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
258         return;
259
260     // FIXME: Eliminate legacy editing positions
261     VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(baseNode, baseOffset), DOWNSTREAM);
262     VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
263
264     m_frame->selection().moveTo(visibleBase, visibleExtent);
265 }
266
267 void DOMSelection::setPosition(Node* node, int offset, ExceptionCode& ec)
268 {
269     if (!m_frame)
270         return;
271     if (offset < 0) {
272         ec = INDEX_SIZE_ERR;
273         return;
274     }
275
276     if (!isValidForPosition(node))
277         return;
278
279     // FIXME: Eliminate legacy editing positions
280     m_frame->selection().moveTo(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
281 }
282
283 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
284 {
285     if (!m_frame)
286         return;
287
288     FrameSelection::EAlteration alter;
289     if (equalIgnoringCase(alterString, "extend"))
290         alter = FrameSelection::AlterationExtend;
291     else if (equalIgnoringCase(alterString, "move"))
292         alter = FrameSelection::AlterationMove;
293     else
294         return;
295
296     SelectionDirection direction;
297     if (equalIgnoringCase(directionString, "forward"))
298         direction = DirectionForward;
299     else if (equalIgnoringCase(directionString, "backward"))
300         direction = DirectionBackward;
301     else if (equalIgnoringCase(directionString, "left"))
302         direction = DirectionLeft;
303     else if (equalIgnoringCase(directionString, "right"))
304         direction = DirectionRight;
305     else
306         return;
307
308     TextGranularity granularity;
309     if (equalIgnoringCase(granularityString, "character"))
310         granularity = CharacterGranularity;
311     else if (equalIgnoringCase(granularityString, "word"))
312         granularity = WordGranularity;
313     else if (equalIgnoringCase(granularityString, "sentence"))
314         granularity = SentenceGranularity;
315     else if (equalIgnoringCase(granularityString, "line"))
316         granularity = LineGranularity;
317     else if (equalIgnoringCase(granularityString, "paragraph"))
318         granularity = ParagraphGranularity;
319     else if (equalIgnoringCase(granularityString, "lineboundary"))
320         granularity = LineBoundary;
321     else if (equalIgnoringCase(granularityString, "sentenceboundary"))
322         granularity = SentenceBoundary;
323     else if (equalIgnoringCase(granularityString, "paragraphboundary"))
324         granularity = ParagraphBoundary;
325     else if (equalIgnoringCase(granularityString, "documentboundary"))
326         granularity = DocumentBoundary;
327     else
328         return;
329
330     m_frame->selection().modify(alter, direction, granularity);
331 }
332
333 void DOMSelection::extend(Node* node, int offset, ExceptionCode& ec)
334 {
335     if (!m_frame)
336         return;
337
338     if (!node) {
339         ec = TYPE_MISMATCH_ERR;
340         return;
341     }
342
343     if (offset < 0 || offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node->childNodeCount())) {
344         ec = INDEX_SIZE_ERR;
345         return;
346     }
347
348     if (!isValidForPosition(node))
349         return;
350
351     // FIXME: Eliminate legacy editing positions
352     m_frame->selection().setExtent(VisiblePosition(createLegacyEditingPosition(node, offset), DOWNSTREAM));
353 }
354
355 PassRefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionCode& ec)
356 {
357     if (!m_frame)
358         return 0;
359
360     if (index < 0 || index >= rangeCount()) {
361         ec = INDEX_SIZE_ERR;
362         return 0;
363     }
364
365     // If you're hitting this, you've added broken multi-range selection support
366     ASSERT(rangeCount() == 1);
367
368     if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
369         ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree();
370         int offset = shadowAncestor->nodeIndex();
371         return Range::create(&shadowAncestor->document(), container, offset, container, offset);
372     }
373
374     const VisibleSelection& selection = m_frame->selection().selection();
375     return selection.firstRange();
376 }
377
378 void DOMSelection::removeAllRanges()
379 {
380     if (!m_frame)
381         return;
382     m_frame->selection().clear();
383 }
384
385 void DOMSelection::addRange(Range* r)
386 {
387     if (!m_frame)
388         return;
389     if (!r)
390         return;
391
392     FrameSelection& selection = m_frame->selection();
393
394     if (selection.isNone()) {
395         selection.setSelection(VisibleSelection(r));
396         return;
397     }
398
399     RefPtr<Range> range = selection.selection().toNormalizedRange();
400     if (r->compareBoundaryPoints(Range::START_TO_START, range.get(), IGNORE_EXCEPTION) == -1) {
401         // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
402         if (r->compareBoundaryPoints(Range::START_TO_END, range.get(), IGNORE_EXCEPTION) > -1) {
403             if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), IGNORE_EXCEPTION) == -1)
404                 // The original range and r intersect.
405                 selection.setSelection(VisibleSelection(r->startPosition(), range->endPosition(), DOWNSTREAM));
406             else
407                 // r contains the original range.
408                 selection.setSelection(VisibleSelection(r));
409         }
410     } else {
411         // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
412         ExceptionCode ec = 0;
413         if (r->compareBoundaryPoints(Range::END_TO_START, range.get(), ec) < 1 && !ec) {
414             if (r->compareBoundaryPoints(Range::END_TO_END, range.get(), IGNORE_EXCEPTION) == -1)
415                 // The original range contains r.
416                 selection.setSelection(VisibleSelection(range.get()));
417             else
418                 // The original range and r intersect.
419                 selection.setSelection(VisibleSelection(range->startPosition(), r->endPosition(), DOWNSTREAM));
420         }
421     }
422 }
423
424 void DOMSelection::deleteFromDocument()
425 {
426     if (!m_frame)
427         return;
428
429     FrameSelection& selection = m_frame->selection();
430
431     if (selection.isNone())
432         return;
433
434     if (isCollapsed())
435         selection.modify(FrameSelection::AlterationExtend, DirectionBackward, CharacterGranularity);
436
437     RefPtr<Range> selectedRange = selection.selection().toNormalizedRange();
438     if (!selectedRange)
439         return;
440
441     selectedRange->deleteContents(ASSERT_NO_EXCEPTION);
442
443     setBaseAndExtent(selectedRange->startContainer(ASSERT_NO_EXCEPTION), selectedRange->startOffset(), selectedRange->startContainer(), selectedRange->startOffset(), ASSERT_NO_EXCEPTION);
444 }
445
446 bool DOMSelection::containsNode(Node* n, bool allowPartial) const
447 {
448     if (!m_frame)
449         return false;
450
451     FrameSelection& selection = m_frame->selection();
452
453     if (!n || m_frame->document() != &n->document() || selection.isNone())
454         return false;
455
456     RefPtr<Node> node = n;
457     RefPtr<Range> selectedRange = selection.selection().toNormalizedRange();
458
459     ContainerNode* parentNode = node->parentNode();
460     if (!parentNode || !parentNode->inDocument())
461         return false;
462     unsigned nodeIndex = node->nodeIndex();
463
464     ExceptionCode ec = 0;
465     bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->startContainer(), selectedRange->startOffset(), ec) >= 0 && !ec
466         && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->endContainer(), selectedRange->endOffset(), ec) <= 0 && !ec;
467     ASSERT(!ec);
468     if (nodeFullySelected)
469         return true;
470
471     bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, selectedRange->endContainer(), selectedRange->endOffset(), ec) > 0 && !ec)
472         || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange->startContainer(), selectedRange->startOffset(), ec) < 0 && !ec);
473     ASSERT(!ec);
474     if (nodeFullyUnselected)
475         return false;
476
477     return allowPartial || node->isTextNode();
478 }
479
480 void DOMSelection::selectAllChildren(Node* n, ExceptionCode& ec)
481 {
482     if (!n)
483         return;
484
485     // This doesn't (and shouldn't) select text node characters.
486     setBaseAndExtent(n, 0, n, n->childNodeCount(), ec);
487 }
488
489 String DOMSelection::toString()
490 {
491     if (!m_frame)
492         return String();
493
494     return plainText(m_frame->selection().selection().toNormalizedRange().get());
495 }
496
497 Node* DOMSelection::shadowAdjustedNode(const Position& position) const
498 {
499     if (position.isNull())
500         return 0;
501
502     Node* containerNode = position.containerNode();
503     Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
504
505     if (!adjustedNode)
506         return 0;
507
508     if (containerNode == adjustedNode)
509         return containerNode;
510
511     return adjustedNode->parentNodeGuaranteedHostFree();
512 }
513
514 int DOMSelection::shadowAdjustedOffset(const Position& position) const
515 {
516     if (position.isNull())
517         return 0;
518
519     Node* containerNode = position.containerNode();
520     Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
521
522     if (!adjustedNode)
523         return 0;
524
525     if (containerNode == adjustedNode)
526         return position.computeOffsetInContainerNode();
527
528     return adjustedNode->nodeIndex();
529 }
530
531 bool DOMSelection::isValidForPosition(Node* node) const
532 {
533     ASSERT(m_frame);
534     if (!node)
535         return true;
536     return &node->document() == m_frame->document();
537 }
538
539 } // namespace WebCore