OLD | NEW |
| (Empty) |
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 "core/page/DOMSelection.h" | |
33 | |
34 #include "bindings/core/v8/ExceptionMessages.h" | |
35 #include "bindings/core/v8/ExceptionState.h" | |
36 #include "bindings/core/v8/ExceptionStatePlaceholder.h" | |
37 #include "core/dom/Document.h" | |
38 #include "core/dom/ExceptionCode.h" | |
39 #include "core/dom/Node.h" | |
40 #include "core/dom/Range.h" | |
41 #include "core/dom/TreeScope.h" | |
42 #include "core/editing/FrameSelection.h" | |
43 #include "core/editing/TextIterator.h" | |
44 #include "core/editing/htmlediting.h" | |
45 #include "core/frame/LocalFrame.h" | |
46 #include "wtf/text/WTFString.h" | |
47 | |
48 namespace blink { | |
49 | |
50 static Node* selectionShadowAncestor(LocalFrame* frame) | |
51 { | |
52 Node* node = frame->selection().selection().base().anchorNode(); | |
53 if (!node) | |
54 return 0; | |
55 | |
56 if (!node->isInShadowTree()) | |
57 return 0; | |
58 | |
59 return frame->document()->ancestorInThisScope(node); | |
60 } | |
61 | |
62 DOMSelection::DOMSelection(const TreeScope* treeScope) | |
63 : DOMWindowProperty(treeScope->rootNode().document().frame()) | |
64 , m_treeScope(treeScope) | |
65 { | |
66 ScriptWrappable::init(this); | |
67 } | |
68 | |
69 void DOMSelection::clearTreeScope() | |
70 { | |
71 m_treeScope = nullptr; | |
72 } | |
73 | |
74 const VisibleSelection& DOMSelection::visibleSelection() const | |
75 { | |
76 ASSERT(m_frame); | |
77 return m_frame->selection().selection(); | |
78 } | |
79 | |
80 static Position anchorPosition(const VisibleSelection& selection) | |
81 { | |
82 Position anchor = selection.isBaseFirst() ? selection.start() : selection.en
d(); | |
83 return anchor.parentAnchoredEquivalent(); | |
84 } | |
85 | |
86 static Position focusPosition(const VisibleSelection& selection) | |
87 { | |
88 Position focus = selection.isBaseFirst() ? selection.end() : selection.start
(); | |
89 return focus.parentAnchoredEquivalent(); | |
90 } | |
91 | |
92 static Position basePosition(const VisibleSelection& selection) | |
93 { | |
94 return selection.base().parentAnchoredEquivalent(); | |
95 } | |
96 | |
97 static Position extentPosition(const VisibleSelection& selection) | |
98 { | |
99 return selection.extent().parentAnchoredEquivalent(); | |
100 } | |
101 | |
102 Node* DOMSelection::anchorNode() const | |
103 { | |
104 if (!m_frame) | |
105 return 0; | |
106 | |
107 return shadowAdjustedNode(anchorPosition(visibleSelection())); | |
108 } | |
109 | |
110 int DOMSelection::anchorOffset() const | |
111 { | |
112 if (!m_frame) | |
113 return 0; | |
114 | |
115 return shadowAdjustedOffset(anchorPosition(visibleSelection())); | |
116 } | |
117 | |
118 Node* DOMSelection::focusNode() const | |
119 { | |
120 if (!m_frame) | |
121 return 0; | |
122 | |
123 return shadowAdjustedNode(focusPosition(visibleSelection())); | |
124 } | |
125 | |
126 int DOMSelection::focusOffset() const | |
127 { | |
128 if (!m_frame) | |
129 return 0; | |
130 | |
131 return shadowAdjustedOffset(focusPosition(visibleSelection())); | |
132 } | |
133 | |
134 Node* DOMSelection::baseNode() const | |
135 { | |
136 if (!m_frame) | |
137 return 0; | |
138 | |
139 return shadowAdjustedNode(basePosition(visibleSelection())); | |
140 } | |
141 | |
142 int DOMSelection::baseOffset() const | |
143 { | |
144 if (!m_frame) | |
145 return 0; | |
146 | |
147 return shadowAdjustedOffset(basePosition(visibleSelection())); | |
148 } | |
149 | |
150 Node* DOMSelection::extentNode() const | |
151 { | |
152 if (!m_frame) | |
153 return 0; | |
154 | |
155 return shadowAdjustedNode(extentPosition(visibleSelection())); | |
156 } | |
157 | |
158 int DOMSelection::extentOffset() const | |
159 { | |
160 if (!m_frame) | |
161 return 0; | |
162 | |
163 return shadowAdjustedOffset(extentPosition(visibleSelection())); | |
164 } | |
165 | |
166 bool DOMSelection::isCollapsed() const | |
167 { | |
168 if (!m_frame || selectionShadowAncestor(m_frame)) | |
169 return true; | |
170 return !m_frame->selection().isRange(); | |
171 } | |
172 | |
173 String DOMSelection::type() const | |
174 { | |
175 if (!m_frame) | |
176 return String(); | |
177 | |
178 FrameSelection& selection = m_frame->selection(); | |
179 | |
180 // This is a WebKit DOM extension, incompatible with an IE extension | |
181 // IE has this same attribute, but returns "none", "text" and "control" | |
182 // http://msdn.microsoft.com/en-us/library/ms534692(VS.85).aspx | |
183 if (selection.isNone()) | |
184 return "None"; | |
185 if (selection.isCaret()) | |
186 return "Caret"; | |
187 return "Range"; | |
188 } | |
189 | |
190 int DOMSelection::rangeCount() const | |
191 { | |
192 if (!m_frame) | |
193 return 0; | |
194 return m_frame->selection().isNone() ? 0 : 1; | |
195 } | |
196 | |
197 void DOMSelection::collapse(Node* node, int offset, ExceptionState& exceptionSta
te) | |
198 { | |
199 ASSERT(node); | |
200 if (!m_frame) | |
201 return; | |
202 | |
203 if (offset < 0) { | |
204 exceptionState.throwDOMException(IndexSizeError, String::number(offset)
+ " is not a valid offset."); | |
205 return; | |
206 } | |
207 | |
208 if (!isValidForPosition(node)) | |
209 return; | |
210 RefPtrWillBeRawPtr<Range> range = Range::create(node->document()); | |
211 range->setStart(node, offset, exceptionState); | |
212 if (exceptionState.hadException()) | |
213 return; | |
214 range->setEnd(node, offset, exceptionState); | |
215 if (exceptionState.hadException()) | |
216 return; | |
217 m_frame->selection().setSelectedRange(range.get(), DOWNSTREAM, m_frame->sele
ction().isDirectional() ? FrameSelection::Directional : FrameSelection::NonDirec
tional); | |
218 } | |
219 | |
220 void DOMSelection::collapse(Node* node, ExceptionState& exceptionState) | |
221 { | |
222 collapse(node, 0, exceptionState); | |
223 } | |
224 | |
225 void DOMSelection::collapseToEnd(ExceptionState& exceptionState) | |
226 { | |
227 if (!m_frame) | |
228 return; | |
229 | |
230 const VisibleSelection& selection = m_frame->selection().selection(); | |
231 | |
232 if (selection.isNone()) { | |
233 exceptionState.throwDOMException(InvalidStateError, "there is no selecti
on."); | |
234 return; | |
235 } | |
236 | |
237 m_frame->selection().moveTo(VisiblePosition(selection.end(), DOWNSTREAM)); | |
238 } | |
239 | |
240 void DOMSelection::collapseToStart(ExceptionState& exceptionState) | |
241 { | |
242 if (!m_frame) | |
243 return; | |
244 | |
245 const VisibleSelection& selection = m_frame->selection().selection(); | |
246 | |
247 if (selection.isNone()) { | |
248 exceptionState.throwDOMException(InvalidStateError, "there is no selecti
on."); | |
249 return; | |
250 } | |
251 | |
252 m_frame->selection().moveTo(VisiblePosition(selection.start(), DOWNSTREAM)); | |
253 } | |
254 | |
255 void DOMSelection::empty() | |
256 { | |
257 if (!m_frame) | |
258 return; | |
259 m_frame->selection().clear(); | |
260 } | |
261 | |
262 void DOMSelection::setBaseAndExtent(Node* baseNode, int baseOffset, Node* extent
Node, int extentOffset, ExceptionState& exceptionState) | |
263 { | |
264 if (!m_frame) | |
265 return; | |
266 | |
267 if (baseOffset < 0) { | |
268 exceptionState.throwDOMException(IndexSizeError, String::number(baseOffs
et) + " is not a valid base offset."); | |
269 return; | |
270 } | |
271 | |
272 if (extentOffset < 0) { | |
273 exceptionState.throwDOMException(IndexSizeError, String::number(extentOf
fset) + " is not a valid extent offset."); | |
274 return; | |
275 } | |
276 | |
277 if (!isValidForPosition(baseNode) || !isValidForPosition(extentNode)) | |
278 return; | |
279 | |
280 // FIXME: Eliminate legacy editing positions | |
281 VisiblePosition visibleBase = VisiblePosition(createLegacyEditingPosition(ba
seNode, baseOffset), DOWNSTREAM); | |
282 VisiblePosition visibleExtent = VisiblePosition(createLegacyEditingPosition(
extentNode, extentOffset), DOWNSTREAM); | |
283 | |
284 m_frame->selection().moveTo(visibleBase, visibleExtent); | |
285 } | |
286 | |
287 void DOMSelection::modify(const String& alterString, const String& directionStri
ng, const String& granularityString) | |
288 { | |
289 if (!m_frame) | |
290 return; | |
291 | |
292 FrameSelection::EAlteration alter; | |
293 if (equalIgnoringCase(alterString, "extend")) | |
294 alter = FrameSelection::AlterationExtend; | |
295 else if (equalIgnoringCase(alterString, "move")) | |
296 alter = FrameSelection::AlterationMove; | |
297 else | |
298 return; | |
299 | |
300 SelectionDirection direction; | |
301 if (equalIgnoringCase(directionString, "forward")) | |
302 direction = DirectionForward; | |
303 else if (equalIgnoringCase(directionString, "backward")) | |
304 direction = DirectionBackward; | |
305 else if (equalIgnoringCase(directionString, "left")) | |
306 direction = DirectionLeft; | |
307 else if (equalIgnoringCase(directionString, "right")) | |
308 direction = DirectionRight; | |
309 else | |
310 return; | |
311 | |
312 TextGranularity granularity; | |
313 if (equalIgnoringCase(granularityString, "character")) | |
314 granularity = CharacterGranularity; | |
315 else if (equalIgnoringCase(granularityString, "word")) | |
316 granularity = WordGranularity; | |
317 else if (equalIgnoringCase(granularityString, "sentence")) | |
318 granularity = SentenceGranularity; | |
319 else if (equalIgnoringCase(granularityString, "line")) | |
320 granularity = LineGranularity; | |
321 else if (equalIgnoringCase(granularityString, "paragraph")) | |
322 granularity = ParagraphGranularity; | |
323 else if (equalIgnoringCase(granularityString, "lineboundary")) | |
324 granularity = LineBoundary; | |
325 else if (equalIgnoringCase(granularityString, "sentenceboundary")) | |
326 granularity = SentenceBoundary; | |
327 else if (equalIgnoringCase(granularityString, "paragraphboundary")) | |
328 granularity = ParagraphBoundary; | |
329 else if (equalIgnoringCase(granularityString, "documentboundary")) | |
330 granularity = DocumentBoundary; | |
331 else | |
332 return; | |
333 | |
334 m_frame->selection().modify(alter, direction, granularity); | |
335 } | |
336 | |
337 void DOMSelection::extend(Node* node, int offset, ExceptionState& exceptionState
) | |
338 { | |
339 ASSERT(node); | |
340 | |
341 if (!m_frame) | |
342 return; | |
343 | |
344 if (offset < 0) { | |
345 exceptionState.throwDOMException(IndexSizeError, String::number(offset)
+ " is not a valid offset."); | |
346 return; | |
347 } | |
348 if (offset > (node->offsetInCharacters() ? caretMaxOffset(node) : (int)node-
>countChildren())) { | |
349 exceptionState.throwDOMException(IndexSizeError, String::number(offset)
+ " is larger than the given node's length."); | |
350 return; | |
351 } | |
352 | |
353 if (!isValidForPosition(node)) | |
354 return; | |
355 | |
356 // FIXME: Eliminate legacy editing positions | |
357 m_frame->selection().setExtent(VisiblePosition(createLegacyEditingPosition(n
ode, offset), DOWNSTREAM)); | |
358 } | |
359 | |
360 void DOMSelection::extend(Node* node, ExceptionState& exceptionState) | |
361 { | |
362 // This default value implementation differs from the spec, which says |offs
et| is not optional. | |
363 // FIXME: Specify this default value in Selection.idl. | |
364 extend(node, 0, exceptionState); | |
365 } | |
366 | |
367 PassRefPtrWillBeRawPtr<Range> DOMSelection::getRangeAt(int index, ExceptionState
& exceptionState) | |
368 { | |
369 if (!m_frame) | |
370 return nullptr; | |
371 | |
372 if (index < 0 || index >= rangeCount()) { | |
373 exceptionState.throwDOMException(IndexSizeError, String::number(index) +
" is not a valid index."); | |
374 return nullptr; | |
375 } | |
376 | |
377 // If you're hitting this, you've added broken multi-range selection support | |
378 ASSERT(rangeCount() == 1); | |
379 | |
380 if (Node* shadowAncestor = selectionShadowAncestor(m_frame)) { | |
381 ASSERT(!shadowAncestor->isShadowRoot()); | |
382 ContainerNode* container = shadowAncestor->parentOrShadowHostNode(); | |
383 int offset = shadowAncestor->nodeIndex(); | |
384 return Range::create(shadowAncestor->document(), container, offset, cont
ainer, offset); | |
385 } | |
386 | |
387 return m_frame->selection().firstRange(); | |
388 } | |
389 | |
390 void DOMSelection::removeAllRanges() | |
391 { | |
392 if (!m_frame) | |
393 return; | |
394 m_frame->selection().clear(); | |
395 } | |
396 | |
397 void DOMSelection::addRange(Range* newRange) | |
398 { | |
399 if (!m_frame) | |
400 return; | |
401 | |
402 // FIXME: Should we throw DOMException for error cases below? | |
403 if (!newRange) { | |
404 addConsoleError("The given range is null."); | |
405 return; | |
406 } | |
407 | |
408 if (!newRange->startContainer()) { | |
409 addConsoleError("The given range has no container. Perhaps 'detach()' ha
s been invoked on it?"); | |
410 return; | |
411 } | |
412 | |
413 FrameSelection& selection = m_frame->selection(); | |
414 | |
415 if (selection.isNone()) { | |
416 selection.setSelectedRange(newRange, VP_DEFAULT_AFFINITY); | |
417 return; | |
418 } | |
419 | |
420 RefPtrWillBeRawPtr<Range> originalRange = selection.firstRange(); | |
421 | |
422 if (originalRange->startContainer()->document() != newRange->startContainer(
)->document()) { | |
423 addConsoleError("The given range does not belong to the current selectio
n's document."); | |
424 return; | |
425 } | |
426 if (originalRange->startContainer()->treeScope() != newRange->startContainer
()->treeScope()) { | |
427 addConsoleError("The given range and the current selection belong to two
different document fragments."); | |
428 return; | |
429 } | |
430 | |
431 if (originalRange->compareBoundaryPoints(Range::START_TO_END, newRange, ASSE
RT_NO_EXCEPTION) < 0 | |
432 || newRange->compareBoundaryPoints(Range::START_TO_END, originalRange.ge
t(), ASSERT_NO_EXCEPTION) < 0) { | |
433 addConsoleError("Discontiguous selection is not supported."); | |
434 return; | |
435 } | |
436 | |
437 // FIXME: "Merge the ranges if they intersect" is Blink-specific behavior; o
ther browsers supporting discontiguous | |
438 // selection (obviously) keep each Range added and return it in getRangeAt()
. But it's unclear if we can really | |
439 // do the same, since we don't support discontiguous selection. Further disc
ussions at | |
440 // <https://code.google.com/p/chromium/issues/detail?id=353069>. | |
441 | |
442 Range* start = originalRange->compareBoundaryPoints(Range::START_TO_START, n
ewRange, ASSERT_NO_EXCEPTION) < 0 ? originalRange.get() : newRange; | |
443 Range* end = originalRange->compareBoundaryPoints(Range::END_TO_END, newRang
e, ASSERT_NO_EXCEPTION) < 0 ? newRange : originalRange.get(); | |
444 RefPtrWillBeRawPtr<Range> merged = Range::create(originalRange->startContain
er()->document(), start->startContainer(), start->startOffset(), end->endContain
er(), end->endOffset()); | |
445 EAffinity affinity = selection.selection().affinity(); | |
446 selection.setSelectedRange(merged.get(), affinity); | |
447 } | |
448 | |
449 void DOMSelection::deleteFromDocument() | |
450 { | |
451 if (!m_frame) | |
452 return; | |
453 | |
454 FrameSelection& selection = m_frame->selection(); | |
455 | |
456 if (selection.isNone()) | |
457 return; | |
458 | |
459 RefPtrWillBeRawPtr<Range> selectedRange = selection.selection().toNormalized
Range(); | |
460 if (!selectedRange) | |
461 return; | |
462 | |
463 selectedRange->deleteContents(ASSERT_NO_EXCEPTION); | |
464 | |
465 setBaseAndExtent(selectedRange->startContainer(), selectedRange->startOffset
(), selectedRange->startContainer(), selectedRange->startOffset(), ASSERT_NO_EXC
EPTION); | |
466 } | |
467 | |
468 bool DOMSelection::containsNode(const Node* n, bool allowPartial) const | |
469 { | |
470 if (!m_frame) | |
471 return false; | |
472 | |
473 FrameSelection& selection = m_frame->selection(); | |
474 | |
475 if (!n || m_frame->document() != n->document() || selection.isNone()) | |
476 return false; | |
477 | |
478 unsigned nodeIndex = n->nodeIndex(); | |
479 RefPtrWillBeRawPtr<Range> selectedRange = selection.selection().toNormalized
Range(); | |
480 | |
481 ContainerNode* parentNode = n->parentNode(); | |
482 if (!parentNode) | |
483 return false; | |
484 | |
485 TrackExceptionState exceptionState; | |
486 bool nodeFullySelected = Range::compareBoundaryPoints(parentNode, nodeIndex,
selectedRange->startContainer(), selectedRange->startOffset(), exceptionState)
>= 0 && !exceptionState.hadException() | |
487 && Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRange
->endContainer(), selectedRange->endOffset(), exceptionState) <= 0 && !exception
State.hadException(); | |
488 if (exceptionState.hadException()) | |
489 return false; | |
490 if (nodeFullySelected) | |
491 return true; | |
492 | |
493 bool nodeFullyUnselected = (Range::compareBoundaryPoints(parentNode, nodeInd
ex, selectedRange->endContainer(), selectedRange->endOffset(), exceptionState) >
0 && !exceptionState.hadException()) | |
494 || (Range::compareBoundaryPoints(parentNode, nodeIndex + 1, selectedRang
e->startContainer(), selectedRange->startOffset(), exceptionState) < 0 && !excep
tionState.hadException()); | |
495 ASSERT(!exceptionState.hadException()); | |
496 if (nodeFullyUnselected) | |
497 return false; | |
498 | |
499 return allowPartial || n->isTextNode(); | |
500 } | |
501 | |
502 void DOMSelection::selectAllChildren(Node* n, ExceptionState& exceptionState) | |
503 { | |
504 if (!n) | |
505 return; | |
506 | |
507 // This doesn't (and shouldn't) select text node characters. | |
508 setBaseAndExtent(n, 0, n, n->countChildren(), exceptionState); | |
509 } | |
510 | |
511 String DOMSelection::toString() | |
512 { | |
513 if (!m_frame) | |
514 return String(); | |
515 | |
516 return plainText(m_frame->selection().selection().toNormalizedRange().get())
; | |
517 } | |
518 | |
519 Node* DOMSelection::shadowAdjustedNode(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 containerNode; | |
532 | |
533 ASSERT(!adjustedNode->isShadowRoot()); | |
534 return adjustedNode->parentOrShadowHostNode(); | |
535 } | |
536 | |
537 int DOMSelection::shadowAdjustedOffset(const Position& position) const | |
538 { | |
539 if (position.isNull()) | |
540 return 0; | |
541 | |
542 Node* containerNode = position.containerNode(); | |
543 Node* adjustedNode = m_treeScope->ancestorInThisScope(containerNode); | |
544 | |
545 if (!adjustedNode) | |
546 return 0; | |
547 | |
548 if (containerNode == adjustedNode) | |
549 return position.computeOffsetInContainerNode(); | |
550 | |
551 return adjustedNode->nodeIndex(); | |
552 } | |
553 | |
554 bool DOMSelection::isValidForPosition(Node* node) const | |
555 { | |
556 ASSERT(m_frame); | |
557 if (!node) | |
558 return true; | |
559 return node->document() == m_frame->document(); | |
560 } | |
561 | |
562 void DOMSelection::addConsoleError(const String& message) | |
563 { | |
564 if (m_treeScope) | |
565 m_treeScope->document().addConsoleMessage(JSMessageSource, ErrorMessageL
evel, message); | |
566 } | |
567 | |
568 } // namespace blink | |
OLD | NEW |