Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(137)

Side by Side Diff: Source/core/page/DOMSelection.cpp

Issue 467503002: Move DOMSelection.[cpp/h] from core/page to core/editing (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « Source/core/page/DOMSelection.h ('k') | Source/core/page/Selection.idl » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« no previous file with comments | « Source/core/page/DOMSelection.h ('k') | Source/core/page/Selection.idl » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698