Align Selection API with the specification
[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 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 "Range.h"
39 #include "TextIterator.h"
40 #include "htmlediting.h"
41
42 namespace WebCore {
43
44 static Node* selectionShadowAncestor(Frame* frame)
45 {
46     Node* node = frame->selection().selection().base().anchorNode();
47     if (!node)
48         return 0;
49
50     if (!node->isInShadowTree())
51         return 0;
52
53     return frame->document()->ancestorInThisScope(node);
54 }
55
56 DOMSelection::DOMSelection(const TreeScope* treeScope)
57     : DOMWindowProperty(treeScope->rootNode().document().frame())
58     , m_treeScope(treeScope)
59 {
60 }
61
62 void DOMSelection::clearTreeScope()
63 {
64     m_treeScope = nullptr;
65 }
66
67 const VisibleSelection& DOMSelection::visibleSelection() const
68 {
69     ASSERT(m_frame);
70     return m_frame->selection().selection();
71 }
72
73 static Position anchorPosition(const VisibleSelection& selection)
74 {
75     Position anchor = selection.isBaseFirst() ? selection.start() : selection.end();
76     return anchor.parentAnchoredEquivalent();
77 }
78
79 static Position focusPosition(const VisibleSelection& selection)
80 {
81     Position focus = selection.isBaseFirst() ? selection.end() : selection.start();
82     return focus.parentAnchoredEquivalent();
83 }
84
85 static Position basePosition(const VisibleSelection& selection)
86 {
87     return selection.base().parentAnchoredEquivalent();
88 }
89
90 static Position extentPosition(const VisibleSelection& selection)
91 {
92     return selection.extent().parentAnchoredEquivalent();
93 }
94
95 Node* DOMSelection::anchorNode() const
96 {
97     if (!m_frame)
98         return 0;
99
100     return shadowAdjustedNode(anchorPosition(visibleSelection()));
101 }
102
103 unsigned DOMSelection::anchorOffset() const
104 {
105     if (!m_frame)
106         return 0;
107
108     return shadowAdjustedOffset(anchorPosition(visibleSelection()));
109 }
110
111 Node* DOMSelection::focusNode() const
112 {
113     if (!m_frame)
114         return 0;
115
116     return shadowAdjustedNode(focusPosition(visibleSelection()));
117 }
118
119 unsigned DOMSelection::focusOffset() const
120 {
121     if (!m_frame)
122         return 0;
123
124     return shadowAdjustedOffset(focusPosition(visibleSelection()));
125 }
126
127 Node* DOMSelection::baseNode() const
128 {
129     if (!m_frame)
130         return 0;
131
132     return shadowAdjustedNode(basePosition(visibleSelection()));
133 }
134
135 unsigned DOMSelection::baseOffset() const
136 {
137     if (!m_frame)
138         return 0;
139
140     return shadowAdjustedOffset(basePosition(visibleSelection()));
141 }
142
143 Node* DOMSelection::extentNode() const
144 {
145     if (!m_frame)
146         return 0;
147
148     return shadowAdjustedNode(extentPosition(visibleSelection()));
149 }
150
151 unsigned DOMSelection::extentOffset() const
152 {
153     if (!m_frame)
154         return 0;
155
156     return shadowAdjustedOffset(extentPosition(visibleSelection()));
157 }
158
159 bool DOMSelection::isCollapsed() const
160 {
161     if (!m_frame || selectionShadowAncestor(m_frame))
162         return true;
163     return !m_frame->selection().isRange();
164 }
165
166 String DOMSelection::type() const
167 {
168     if (!m_frame)
169         return String();
170
171     FrameSelection& selection = m_frame->selection();
172
173     // This is a WebKit DOM extension, incompatible with an IE extension
174     // IE has this same attribute, but returns "none", "text" and "control"
175     // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx
176     if (selection.isNone())
177         return "None";
178     if (selection.isCaret())
179         return "Caret";
180     return "Range";
181 }
182
183 unsigned DOMSelection::rangeCount() const
184 {
185     if (!m_frame)
186         return 0;
187     return m_frame->selection().isNone() ? 0 : 1;
188 }
189
190 void DOMSelection::collapse(Node* node, unsigned offset)
191 {
192     if (!m_frame)
193         return;
194
195     if (!isValidForPosition(node))
196         return;
197
198     // FIXME: Eliminate legacy editing positions
199     m_frame->selection().moveTo(createLegacyEditingPosition(node, offset), DOWNSTREAM);
200 }
201
202 void DOMSelection::collapseToEnd(ExceptionCode& ec)
203 {
204     if (!m_frame)
205         return;
206
207     const VisibleSelection& selection = m_frame->selection().selection();
208
209     if (selection.isNone()) {
210         ec = INVALID_STATE_ERR;
211         return;
212     }
213
214     m_frame->selection().moveTo(selection.end(), DOWNSTREAM);
215 }
216
217 void DOMSelection::collapseToStart(ExceptionCode& ec)
218 {
219     if (!m_frame)
220         return;
221
222     const VisibleSelection& selection = m_frame->selection().selection();
223
224     if (selection.isNone()) {
225         ec = INVALID_STATE_ERR;
226         return;
227     }
228
229     m_frame->selection().moveTo(selection.start(), DOWNSTREAM);
230 }
231
232 void DOMSelection::empty()
233 {
234     if (!m_frame)
235         return;
236     m_frame->selection().clear();
237 }
238
239 void DOMSelection::setBaseAndExtent(Node* baseNode, unsigned baseOffset, Node* extentNode, unsigned extentOffset)
240 {
241     if (!m_frame)
242         return;
243
244     if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
245         return;
246
247     // FIXME: Eliminate legacy editing positions
248     m_frame->selection().moveTo(createLegacyEditingPosition(baseNode, baseOffset), createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
249 }
250
251 void DOMSelection::setPosition(Node* node, unsigned offset)
252 {
253     if (!m_frame)
254         return;
255
256     if (!isValidForPosition(node))
257         return;
258
259     // FIXME: Eliminate legacy editing positions
260     m_frame->selection().moveTo(createLegacyEditingPosition(node, offset), DOWNSTREAM);
261 }
262
263 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
264 {
265     if (!m_frame)
266         return;
267
268     FrameSelection::EAlteration alter;
269     if (equalLettersIgnoringASCIICase(alterString, "extend"))
270         alter = FrameSelection::AlterationExtend;
271     else if (equalLettersIgnoringASCIICase(alterString, "move"))
272         alter = FrameSelection::AlterationMove;
273     else
274         return;
275
276     SelectionDirection direction;
277     if (equalLettersIgnoringASCIICase(directionString, "forward"))
278         direction = DirectionForward;
279     else if (equalLettersIgnoringASCIICase(directionString, "backward"))
280         direction = DirectionBackward;
281     else if (equalLettersIgnoringASCIICase(directionString, "left"))
282         direction = DirectionLeft;
283     else if (equalLettersIgnoringASCIICase(directionString, "right"))
284         direction = DirectionRight;
285     else
286         return;
287
288     TextGranularity granularity;
289     if (equalLettersIgnoringASCIICase(granularityString, "character"))
290         granularity = CharacterGranularity;
291     else if (equalLettersIgnoringASCIICase(granularityString, "word"))
292         granularity = WordGranularity;
293     else if (equalLettersIgnoringASCIICase(granularityString, "sentence"))
294         granularity = SentenceGranularity;
295     else if (equalLettersIgnoringASCIICase(granularityString, "line"))
296         granularity = LineGranularity;
297     else if (equalLettersIgnoringASCIICase(granularityString, "paragraph"))
298         granularity = ParagraphGranularity;
299     else if (equalLettersIgnoringASCIICase(granularityString, "lineboundary"))
300         granularity = LineBoundary;
301     else if (equalLettersIgnoringASCIICase(granularityString, "sentenceboundary"))
302         granularity = SentenceBoundary;
303     else if (equalLettersIgnoringASCIICase(granularityString, "paragraphboundary"))
304         granularity = ParagraphBoundary;
305     else if (equalLettersIgnoringASCIICase(granularityString, "documentboundary"))
306         granularity = DocumentBoundary;
307     else
308         return;
309
310     m_frame->selection().modify(alter, direction, granularity);
311 }
312
313 void DOMSelection::extend(Node& node, unsigned offset, ExceptionCode& ec)
314 {
315     if (!m_frame)
316         return;
317
318     if (offset > (node.offsetInCharacters() ? caretMaxOffset(node) : node.countChildNodes())) {
319         ec = INDEX_SIZE_ERR;
320         return;
321     }
322
323     if (!isValidForPosition(&node))
324         return;
325
326     // FIXME: Eliminate legacy editing positions
327     m_frame->selection().setExtent(createLegacyEditingPosition(&node, offset), DOWNSTREAM);
328 }
329
330 RefPtr<Range> DOMSelection::getRangeAt(unsigned index, ExceptionCode& ec)
331 {
332     if (!m_frame)
333         return nullptr;
334
335     if (index >= rangeCount()) {
336         ec = INDEX_SIZE_ERR;
337         return nullptr;
338     }
339
340     // If you're hitting this, you've added broken multi-range selection support
341     ASSERT(rangeCount() == 1);
342
343     if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
344         ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree();
345         unsigned offset = shadowAncestor->computeNodeIndex();
346         return Range::create(shadowAncestor->document(), container, offset, container, offset);
347     }
348
349     return m_frame->selection().selection().firstRange();
350 }
351
352 void DOMSelection::removeAllRanges()
353 {
354     if (!m_frame)
355         return;
356     m_frame->selection().clear();
357 }
358
359 void DOMSelection::addRange(Range& range)
360 {
361     if (!m_frame)
362         return;
363
364     FrameSelection& selection = m_frame->selection();
365     if (selection.isNone()) {
366         selection.moveTo(&range);
367         return;
368     }
369
370     RefPtr<Range> normalizedRange = selection.selection().toNormalizedRange();
371     if (!normalizedRange)
372         return;
373
374     if (range.compareBoundaryPoints(Range::START_TO_START, *normalizedRange, IGNORE_EXCEPTION) == -1) {
375         // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
376         if (range.compareBoundaryPoints(Range::START_TO_END, *normalizedRange, IGNORE_EXCEPTION) > -1) {
377             if (range.compareBoundaryPoints(Range::END_TO_END, *normalizedRange, IGNORE_EXCEPTION) == -1) {
378                 // The original range and r intersect.
379                 selection.moveTo(range.startPosition(), normalizedRange->endPosition(), DOWNSTREAM);
380             } else {
381                 // r contains the original range.
382                 selection.moveTo(&range);
383             }
384         }
385     } else {
386         // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
387         ExceptionCode ec = 0;
388         if (range.compareBoundaryPoints(Range::END_TO_START, *normalizedRange, ec) < 1 && !ec) {
389             if (range.compareBoundaryPoints(Range::END_TO_END, *normalizedRange, IGNORE_EXCEPTION) == -1) {
390                 // The original range contains r.
391                 selection.moveTo(normalizedRange.get());
392             } else {
393                 // The original range and r intersect.
394                 selection.moveTo(normalizedRange->startPosition(), range.endPosition(), DOWNSTREAM);
395             }
396         }
397     }
398 }
399
400 void DOMSelection::deleteFromDocument()
401 {
402     if (!m_frame)
403         return;
404
405     FrameSelection& selection = m_frame->selection();
406
407     if (selection.isNone())
408         return;
409
410     RefPtr<Range> selectedRange = selection.selection().toNormalizedRange();
411     if (!selectedRange)
412         return;
413
414     selectedRange->deleteContents(ASSERT_NO_EXCEPTION);
415
416     setBaseAndExtent(&selectedRange->startContainer(), selectedRange->startOffset(), &selectedRange->startContainer(), selectedRange->startOffset());
417 }
418
419 bool DOMSelection::containsNode(Node& node, bool allowPartial) const
420 {
421     if (!m_frame)
422         return false;
423
424     FrameSelection& selection = m_frame->selection();
425
426     if (m_frame->document() != &node.document() || selection.isNone())
427         return false;
428
429     Ref<Node> protectedNode(node);
430     RefPtr<Range> selectedRange = selection.selection().toNormalizedRange();
431
432     ContainerNode* parentNode = node.parentNode();
433     if (!parentNode || !parentNode->inDocument())
434         return false;
435     unsigned nodeIndex = node.computeNodeIndex();
436
437     ExceptionCode ec = 0;
438     bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, &selectedRange->startContainer(), selectedRange->startOffset(), ec) >= 0 && !ec
439         && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, &selectedRange->endContainer(), selectedRange->endOffset(), ec) <= 0 && !ec;
440     ASSERT(!ec);
441     if (nodeFullySelected)
442         return true;
443
444     bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, &selectedRange->endContainer(), selectedRange->endOffset(), ec) > 0 && !ec)
445         || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, &selectedRange->startContainer(), selectedRange->startOffset(), ec) < 0 && !ec);
446     ASSERT(!ec);
447     if (nodeFullyUnselected)
448         return false;
449
450     return allowPartial || node.isTextNode();
451 }
452
453 void DOMSelection::selectAllChildren(Node& node)
454 {
455     // This doesn't (and shouldn't) select text node characters.
456     setBaseAndExtent(&node, 0, &node, node.countChildNodes());
457 }
458
459 String DOMSelection::toString()
460 {
461     if (!m_frame)
462         return String();
463
464     return plainText(m_frame->selection().selection().toNormalizedRange().get());
465 }
466
467 Node* DOMSelection::shadowAdjustedNode(const Position& position) const
468 {
469     if (position.isNull())
470         return 0;
471
472     Node* containerNode = position.containerNode();
473     Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
474
475     if (!adjustedNode)
476         return 0;
477
478     if (containerNode == adjustedNode)
479         return containerNode;
480
481     return adjustedNode->parentNodeGuaranteedHostFree();
482 }
483
484 unsigned DOMSelection::shadowAdjustedOffset(const Position& position) const
485 {
486     if (position.isNull())
487         return 0;
488
489     Node* containerNode = position.containerNode();
490     Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
491
492     if (!adjustedNode)
493         return 0;
494
495     if (containerNode == adjustedNode)
496         return position.computeOffsetInContainerNode();
497
498     return adjustedNode->computeNodeIndex();
499 }
500
501 bool DOMSelection::isValidForPosition(Node* node) const
502 {
503     ASSERT(m_frame);
504     if (!node)
505         return true;
506     return &node->document() == m_frame->document();
507 }
508
509 } // namespace WebCore