917d5e5aaddce25031fffedb02a60e7f587acf25
[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 int 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 int 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 int 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 int 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 int 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, int offset, ExceptionCode& ec)
191 {
192     if (!m_frame)
193         return;
194
195     if (offset < 0) {
196         ec = INDEX_SIZE_ERR;
197         return;
198     }
199
200     if (!isValidForPosition(node))
201         return;
202
203     // FIXME: Eliminate legacy editing positions
204     m_frame->selection().moveTo(createLegacyEditingPosition(node, offset), DOWNSTREAM);
205 }
206
207 void DOMSelection::collapseToEnd(ExceptionCode& ec)
208 {
209     if (!m_frame)
210         return;
211
212     const VisibleSelection& selection = m_frame->selection().selection();
213
214     if (selection.isNone()) {
215         ec = INVALID_STATE_ERR;
216         return;
217     }
218
219     m_frame->selection().moveTo(selection.end(), DOWNSTREAM);
220 }
221
222 void DOMSelection::collapseToStart(ExceptionCode& ec)
223 {
224     if (!m_frame)
225         return;
226
227     const VisibleSelection& selection = m_frame->selection().selection();
228
229     if (selection.isNone()) {
230         ec = INVALID_STATE_ERR;
231         return;
232     }
233
234     m_frame->selection().moveTo(selection.start(), DOWNSTREAM);
235 }
236
237 void DOMSelection::empty()
238 {
239     if (!m_frame)
240         return;
241     m_frame->selection().clear();
242 }
243
244 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extentNode, int extentOffset, ExceptionCode& ec)
245 {
246     if (!m_frame)
247         return;
248
249     if (baseOffset < 0 || extentOffset < 0) {
250         ec = INDEX_SIZE_ERR;
251         return;
252     }
253
254     if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode))
255         return;
256
257     // FIXME: Eliminate legacy editing positions
258     m_frame->selection().moveTo(createLegacyEditingPosition(baseNode, baseOffset), createLegacyEditingPosition(extentNode, extentOffset), DOWNSTREAM);
259 }
260
261 void DOMSelection::setPosition(Node* node, int offset, ExceptionCode& ec)
262 {
263     if (!m_frame)
264         return;
265     if (offset < 0) {
266         ec = INDEX_SIZE_ERR;
267         return;
268     }
269
270     if (!isValidForPosition(node))
271         return;
272
273     // FIXME: Eliminate legacy editing positions
274     m_frame->selection().moveTo(createLegacyEditingPosition(node, offset), DOWNSTREAM);
275 }
276
277 void DOMSelection::modify(const String& alterString, const String& directionString, const String& granularityString)
278 {
279     if (!m_frame)
280         return;
281
282     FrameSelection::EAlteration alter;
283     if (equalLettersIgnoringASCIICase(alterString, "extend"))
284         alter = FrameSelection::AlterationExtend;
285     else if (equalLettersIgnoringASCIICase(alterString, "move"))
286         alter = FrameSelection::AlterationMove;
287     else
288         return;
289
290     SelectionDirection direction;
291     if (equalLettersIgnoringASCIICase(directionString, "forward"))
292         direction = DirectionForward;
293     else if (equalLettersIgnoringASCIICase(directionString, "backward"))
294         direction = DirectionBackward;
295     else if (equalLettersIgnoringASCIICase(directionString, "left"))
296         direction = DirectionLeft;
297     else if (equalLettersIgnoringASCIICase(directionString, "right"))
298         direction = DirectionRight;
299     else
300         return;
301
302     TextGranularity granularity;
303     if (equalLettersIgnoringASCIICase(granularityString, "character"))
304         granularity = CharacterGranularity;
305     else if (equalLettersIgnoringASCIICase(granularityString, "word"))
306         granularity = WordGranularity;
307     else if (equalLettersIgnoringASCIICase(granularityString, "sentence"))
308         granularity = SentenceGranularity;
309     else if (equalLettersIgnoringASCIICase(granularityString, "line"))
310         granularity = LineGranularity;
311     else if (equalLettersIgnoringASCIICase(granularityString, "paragraph"))
312         granularity = ParagraphGranularity;
313     else if (equalLettersIgnoringASCIICase(granularityString, "lineboundary"))
314         granularity = LineBoundary;
315     else if (equalLettersIgnoringASCIICase(granularityString, "sentenceboundary"))
316         granularity = SentenceBoundary;
317     else if (equalLettersIgnoringASCIICase(granularityString, "paragraphboundary"))
318         granularity = ParagraphBoundary;
319     else if (equalLettersIgnoringASCIICase(granularityString, "documentboundary"))
320         granularity = DocumentBoundary;
321     else
322         return;
323
324     m_frame->selection().modify(alter, direction, granularity);
325 }
326
327 void DOMSelection::extend(Node& node, int offset, ExceptionCode& ec)
328 {
329     if (!m_frame)
330         return;
331
332     if (offset < 0 || offset > (node.offsetInCharacters() ? caretMaxOffset(node) : static_cast<int>(node.countChildNodes()))) {
333         ec = INDEX_SIZE_ERR;
334         return;
335     }
336
337     if (!isValidForPosition(&node))
338         return;
339
340     // FIXME: Eliminate legacy editing positions
341     m_frame->selection().setExtent(createLegacyEditingPosition(&node, offset), DOWNSTREAM);
342 }
343
344 RefPtr<Range> DOMSelection::getRangeAt(int index, ExceptionCode& ec)
345 {
346     if (!m_frame)
347         return nullptr;
348
349     if (index < 0 || index >= rangeCount()) {
350         ec = INDEX_SIZE_ERR;
351         return nullptr;
352     }
353
354     // If you're hitting this, you've added broken multi-range selection support
355     ASSERT(rangeCount() == 1);
356
357     if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) {
358         ContainerNode* container = shadowAncestor->parentNodeGuaranteedHostFree();
359         unsigned offset = shadowAncestor->computeNodeIndex();
360         return Range::create(shadowAncestor->document(), container, offset, container, offset);
361     }
362
363     return m_frame->selection().selection().firstRange();
364 }
365
366 void DOMSelection::removeAllRanges()
367 {
368     if (!m_frame)
369         return;
370     m_frame->selection().clear();
371 }
372
373 void DOMSelection::addRange(Range* r)
374 {
375     if (!m_frame)
376         return;
377     if (!r)
378         return;
379
380     FrameSelection& selection = m_frame->selection();
381
382     if (selection.isNone()) {
383         selection.moveTo(r);
384         return;
385     }
386
387     RefPtr<Range> range = selection.selection().toNormalizedRange();
388     if (!range)
389         return;
390
391     if (r->compareBoundaryPoints(Range::START_TO_START, *range, IGNORE_EXCEPTION) == -1) {
392         // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
393         if (r->compareBoundaryPoints(Range::START_TO_END, *range, IGNORE_EXCEPTION) > -1) {
394             if (r->compareBoundaryPoints(Range::END_TO_END, *range, IGNORE_EXCEPTION) == -1) {
395                 // The original range and r intersect.
396                 selection.moveTo(r->startPosition(), range->endPosition(), DOWNSTREAM);
397             } else {
398                 // r contains the original range.
399                 selection.moveTo(r);
400             }
401         }
402     } else {
403         // We don't support discontiguous selection. We don't do anything if r and range don't intersect.
404         ExceptionCode ec = 0;
405         if (r->compareBoundaryPoints(Range::END_TO_START, *range, ec) < 1 && !ec) {
406             if (r->compareBoundaryPoints(Range::END_TO_END, *range, IGNORE_EXCEPTION) == -1) {
407                 // The original range contains r.
408                 selection.moveTo(range.get());
409             } else {
410                 // The original range and r intersect.
411                 selection.moveTo(range->startPosition(), r->endPosition(), DOWNSTREAM);
412             }
413         }
414     }
415 }
416
417 void DOMSelection::deleteFromDocument()
418 {
419     if (!m_frame)
420         return;
421
422     FrameSelection& selection = m_frame->selection();
423
424     if (selection.isNone())
425         return;
426
427     RefPtr<Range> selectedRange = selection.selection().toNormalizedRange();
428     if (!selectedRange)
429         return;
430
431     selectedRange->deleteContents(ASSERT_NO_EXCEPTION);
432
433     setBaseAndExtent(&selectedRange->startContainer(), selectedRange->startOffset(), &selectedRange->startContainer(), selectedRange->startOffset(), ASSERT_NO_EXCEPTION);
434 }
435
436 bool DOMSelection::containsNode(Node* n, bool allowPartial) const
437 {
438     if (!m_frame)
439         return false;
440
441     FrameSelection& selection = m_frame->selection();
442
443     if (!n || m_frame->document() != &n->document() || selection.isNone())
444         return false;
445
446     RefPtr<Node> node = n;
447     RefPtr<Range> selectedRange = selection.selection().toNormalizedRange();
448
449     ContainerNode* parentNode = node->parentNode();
450     if (!parentNode || !parentNode->inDocument())
451         return false;
452     unsigned nodeIndex = node->computeNodeIndex();
453
454     ExceptionCode ec = 0;
455     bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex, &selectedRange->startContainer(), selectedRange->startOffset(), ec) >= 0 && !ec
456         && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, &selectedRange->endContainer(), selectedRange->endOffset(), ec) <= 0 && !ec;
457     ASSERT(!ec);
458     if (nodeFullySelected)
459         return true;
460
461     bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeIndex, &selectedRange->endContainer(), selectedRange->endOffset(), ec) > 0 && !ec)
462         || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, &selectedRange->startContainer(), selectedRange->startOffset(), ec) < 0 && !ec);
463     ASSERT(!ec);
464     if (nodeFullyUnselected)
465         return false;
466
467     return allowPartial || node->isTextNode();
468 }
469
470 void DOMSelection::selectAllChildren(Node* n, ExceptionCode& ec)
471 {
472     if (!n)
473         return;
474
475     // This doesn't (and shouldn't) select text node characters.
476     setBaseAndExtent(n, 0, n, n->countChildNodes(), ec);
477 }
478
479 String DOMSelection::toString()
480 {
481     if (!m_frame)
482         return String();
483
484     return plainText(m_frame->selection().selection().toNormalizedRange().get());
485 }
486
487 Node* DOMSelection::shadowAdjustedNode(const Position& position) const
488 {
489     if (position.isNull())
490         return 0;
491
492     Node* containerNode = position.containerNode();
493     Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
494
495     if (!adjustedNode)
496         return 0;
497
498     if (containerNode == adjustedNode)
499         return containerNode;
500
501     return adjustedNode->parentNodeGuaranteedHostFree();
502 }
503
504 int DOMSelection::shadowAdjustedOffset(const Position& position) const
505 {
506     if (position.isNull())
507         return 0;
508
509     Node* containerNode = position.containerNode();
510     Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode);
511
512     if (!adjustedNode)
513         return 0;
514
515     if (containerNode == adjustedNode)
516         return position.computeOffsetInContainerNode();
517
518     return adjustedNode->computeNodeIndex();
519 }
520
521 bool DOMSelection::isValidForPosition(Node* node) const
522 {
523     ASSERT(m_frame);
524     if (!node)
525         return true;
526     return &node->document() == m_frame->document();
527 }
528
529 } // namespace WebCore