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