1 | /* |
2 | * Copyright (C) 2006-2016 Apple Inc. All rights reserved. |
3 | * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) |
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 | * 1. Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * 2. Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * |
14 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
15 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
17 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
18 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
21 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
22 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 | */ |
26 | |
27 | #include "config.h" |
28 | #include "Editor.h" |
29 | |
30 | #include "AXObjectCache.h" |
31 | #include "AlternativeTextController.h" |
32 | #include "ApplyStyleCommand.h" |
33 | #include "CSSComputedStyleDeclaration.h" |
34 | #include "CSSPropertyNames.h" |
35 | #include "CSSValueList.h" |
36 | #include "CSSValuePool.h" |
37 | #include "CachedResourceLoader.h" |
38 | #include "ChangeListTypeCommand.h" |
39 | #include "ClipboardEvent.h" |
40 | #include "CompositionEvent.h" |
41 | #include "CreateLinkCommand.h" |
42 | #include "CustomUndoStep.h" |
43 | #include "DataTransfer.h" |
44 | #include "DeleteSelectionCommand.h" |
45 | #include "DictationAlternative.h" |
46 | #include "DictationCommand.h" |
47 | #include "DocumentFragment.h" |
48 | #include "DocumentMarkerController.h" |
49 | #include "Editing.h" |
50 | #include "EditorClient.h" |
51 | #include "EventHandler.h" |
52 | #include "EventNames.h" |
53 | #include "File.h" |
54 | #include "FocusController.h" |
55 | #include "FontAttributes.h" |
56 | #include "Frame.h" |
57 | #include "FrameLoader.h" |
58 | #include "FrameTree.h" |
59 | #include "FrameView.h" |
60 | #include "GraphicsContext.h" |
61 | #include "HTMLAttachmentElement.h" |
62 | #include "HTMLBRElement.h" |
63 | #include "HTMLCollection.h" |
64 | #include "HTMLFormControlElement.h" |
65 | #include "HTMLFrameOwnerElement.h" |
66 | #include "HTMLImageElement.h" |
67 | #include "HTMLInputElement.h" |
68 | #include "HTMLNames.h" |
69 | #include "HTMLOListElement.h" |
70 | #include "HTMLQuoteElement.h" |
71 | #include "HTMLSpanElement.h" |
72 | #include "HTMLUListElement.h" |
73 | #include "HitTestResult.h" |
74 | #include "IndentOutdentCommand.h" |
75 | #include "InputEvent.h" |
76 | #include "InsertEditableImageCommand.h" |
77 | #include "InsertListCommand.h" |
78 | #include "InsertTextCommand.h" |
79 | #include "KeyboardEvent.h" |
80 | #include "Logging.h" |
81 | #include "ModifySelectionListLevel.h" |
82 | #include "NodeList.h" |
83 | #include "NodeTraversal.h" |
84 | #include "Page.h" |
85 | #include "Pasteboard.h" |
86 | #include "Range.h" |
87 | #include "RemoveFormatCommand.h" |
88 | #include "RenderBlock.h" |
89 | #include "RenderTextControl.h" |
90 | #include "RenderedDocumentMarker.h" |
91 | #include "RenderedPosition.h" |
92 | #include "ReplaceRangeWithTextCommand.h" |
93 | #include "ReplaceSelectionCommand.h" |
94 | #include "RuntimeEnabledFeatures.h" |
95 | #include "SerializedAttachmentData.h" |
96 | #include "Settings.h" |
97 | #include "ShadowRoot.h" |
98 | #include "SharedBuffer.h" |
99 | #include "SimplifyMarkupCommand.h" |
100 | #include "SpellChecker.h" |
101 | #include "SpellingCorrectionCommand.h" |
102 | #include "StaticPasteboard.h" |
103 | #include "StyleProperties.h" |
104 | #include "TelephoneNumberDetector.h" |
105 | #include "Text.h" |
106 | #include "TextCheckerClient.h" |
107 | #include "TextCheckingHelper.h" |
108 | #include "TextEvent.h" |
109 | #include "TextIterator.h" |
110 | #include "TypingCommand.h" |
111 | #include "UserTypingGestureIndicator.h" |
112 | #include "VisibleUnits.h" |
113 | #include "markup.h" |
114 | #include <pal/FileSizeFormatter.h> |
115 | #include <pal/system/Sound.h> |
116 | #include <pal/text/KillRing.h> |
117 | #include <wtf/unicode/CharacterNames.h> |
118 | |
119 | #if PLATFORM(MAC) |
120 | #include "ServicesOverlayController.h" |
121 | #endif |
122 | |
123 | namespace WebCore { |
124 | |
125 | static bool dispatchBeforeInputEvent(Element& element, const AtomString& inputType, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { }, Event::IsCancelable cancelable = Event::IsCancelable::Yes) |
126 | { |
127 | if (!element.document().settings().inputEventsEnabled()) |
128 | return true; |
129 | |
130 | auto event = InputEvent::create(eventNames().beforeinputEvent, inputType, cancelable, element.document().windowProxy(), data, WTFMove(dataTransfer), targetRanges, 0); |
131 | element.dispatchEvent(event); |
132 | return !event->defaultPrevented(); |
133 | } |
134 | |
135 | static void dispatchInputEvent(Element& element, const AtomString& inputType, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { }) |
136 | { |
137 | if (element.document().settings().inputEventsEnabled()) { |
138 | // FIXME: We should not be dispatching to the scoped queue here. Normally, input events are dispatched in CompositeEditCommand::apply after the end of the scope, |
139 | // but TypingCommands are special in that existing TypingCommands that are applied again fire input events *from within* the scope by calling typingAddedToOpenCommand. |
140 | // Instead, TypingCommands should always dispatch events synchronously after the end of the scoped queue in CompositeEditCommand::apply. To work around this for the |
141 | // time being, just revert back to calling dispatchScopedEvent. |
142 | element.dispatchScopedEvent(InputEvent::create(eventNames().inputEvent, inputType, Event::IsCancelable::No, |
143 | element.document().windowProxy(), data, WTFMove(dataTransfer), targetRanges, 0)); |
144 | } else |
145 | element.dispatchInputEvent(); |
146 | } |
147 | |
148 | static String inputEventDataForEditingStyleAndAction(const StyleProperties* style, EditAction action) |
149 | { |
150 | if (!style) |
151 | return { }; |
152 | |
153 | switch (action) { |
154 | case EditAction::SetColor: |
155 | return style->getPropertyValue(CSSPropertyColor); |
156 | case EditAction::SetInlineWritingDirection: |
157 | case EditAction::SetBlockWritingDirection: |
158 | return style->getPropertyValue(CSSPropertyDirection); |
159 | default: |
160 | return { }; |
161 | } |
162 | } |
163 | |
164 | static String inputEventDataForEditingStyleAndAction(EditingStyle& style, EditAction action) |
165 | { |
166 | return inputEventDataForEditingStyleAndAction(style.style(), action); |
167 | } |
168 | |
169 | class ClearTextCommand : public DeleteSelectionCommand { |
170 | public: |
171 | ClearTextCommand(Document& document); |
172 | static void CreateAndApply(const RefPtr<Frame> frame); |
173 | |
174 | private: |
175 | EditAction editingAction() const override; |
176 | }; |
177 | |
178 | ClearTextCommand::ClearTextCommand(Document& document) |
179 | : DeleteSelectionCommand(document, false, true, false, false, true) |
180 | { |
181 | } |
182 | |
183 | EditAction ClearTextCommand::editingAction() const |
184 | { |
185 | return EditAction::Delete; |
186 | } |
187 | |
188 | void ClearTextCommand::CreateAndApply(const RefPtr<Frame> frame) |
189 | { |
190 | if (frame->selection().isNone()) |
191 | return; |
192 | |
193 | // Don't leave around stale composition state. |
194 | frame->editor().clear(); |
195 | |
196 | const VisibleSelection oldSelection = frame->selection().selection(); |
197 | frame->selection().selectAll(); |
198 | auto clearCommand = adoptRef(*new ClearTextCommand(*frame->document())); |
199 | clearCommand->setStartingSelection(oldSelection); |
200 | clearCommand->apply(); |
201 | } |
202 | |
203 | using namespace HTMLNames; |
204 | using namespace WTF::Unicode; |
205 | |
206 | TemporarySelectionChange::TemporarySelectionChange(Frame& frame, Optional<VisibleSelection> temporarySelection, OptionSet<TemporarySelectionOption> options) |
207 | : m_frame(frame) |
208 | , m_options(options) |
209 | , m_wasIgnoringSelectionChanges(frame.editor().ignoreSelectionChanges()) |
210 | #if PLATFORM(IOS_FAMILY) |
211 | , m_appearanceUpdatesWereEnabled(frame.selection().isUpdateAppearanceEnabled()) |
212 | #endif |
213 | { |
214 | #if PLATFORM(IOS_FAMILY) |
215 | if (options & TemporarySelectionOption::EnableAppearanceUpdates) |
216 | frame.selection().setUpdateAppearanceEnabled(true); |
217 | #endif |
218 | |
219 | if (options & TemporarySelectionOption::IgnoreSelectionChanges) |
220 | frame.editor().setIgnoreSelectionChanges(true); |
221 | |
222 | if (temporarySelection) { |
223 | m_selectionToRestore = frame.selection().selection(); |
224 | setSelection(temporarySelection.value()); |
225 | } |
226 | } |
227 | |
228 | TemporarySelectionChange::~TemporarySelectionChange() |
229 | { |
230 | if (m_selectionToRestore) |
231 | setSelection(m_selectionToRestore.value()); |
232 | |
233 | if (m_options & TemporarySelectionOption::IgnoreSelectionChanges) { |
234 | auto revealSelection = m_options & TemporarySelectionOption::RevealSelection ? Editor::RevealSelection::Yes : Editor::RevealSelection::No; |
235 | m_frame->editor().setIgnoreSelectionChanges(m_wasIgnoringSelectionChanges, revealSelection); |
236 | } |
237 | |
238 | #if PLATFORM(IOS_FAMILY) |
239 | if (m_options & TemporarySelectionOption::EnableAppearanceUpdates) |
240 | m_frame->selection().setUpdateAppearanceEnabled(m_appearanceUpdatesWereEnabled); |
241 | #endif |
242 | } |
243 | |
244 | void TemporarySelectionChange::setSelection(const VisibleSelection& selection) |
245 | { |
246 | auto options = FrameSelection::defaultSetSelectionOptions(); |
247 | if (m_options & TemporarySelectionOption::DoNotSetFocus) |
248 | options.add(FrameSelection::DoNotSetFocus); |
249 | m_frame->selection().setSelection(selection, options); |
250 | } |
251 | |
252 | // When an event handler has moved the selection outside of a text control |
253 | // we should use the target control's selection for this editing operation. |
254 | VisibleSelection Editor::selectionForCommand(Event* event) |
255 | { |
256 | auto selection = m_frame.selection().selection(); |
257 | if (!event) |
258 | return selection; |
259 | // If the target is a text control, and the current selection is outside of its shadow tree, |
260 | // then use the saved selection for that text control. |
261 | if (is<Element>(event->target()) && downcast<Element>(*event->target()).isTextField()) { |
262 | auto& target = downcast<HTMLTextFormControlElement>(*event->target()); |
263 | auto start = selection.start(); |
264 | if (start.isNull() || &target != enclosingTextFormControl(start)) { |
265 | if (auto range = target.selection()) |
266 | return { *range, DOWNSTREAM, selection.isDirectional() }; |
267 | } |
268 | } |
269 | return selection; |
270 | } |
271 | |
272 | // Function considers Mac editing behavior a fallback when Page or Settings is not available. |
273 | EditingBehavior Editor::behavior() const |
274 | { |
275 | return EditingBehavior(m_frame.settings().editingBehaviorType()); |
276 | } |
277 | |
278 | EditorClient* Editor::client() const |
279 | { |
280 | if (Page* page = m_frame.page()) |
281 | return &page->editorClient(); |
282 | return nullptr; |
283 | } |
284 | |
285 | TextCheckerClient* Editor::textChecker() const |
286 | { |
287 | if (EditorClient* owner = client()) |
288 | return owner->textChecker(); |
289 | return 0; |
290 | } |
291 | |
292 | void Editor::handleKeyboardEvent(KeyboardEvent& event) |
293 | { |
294 | if (auto* client = this->client()) |
295 | client->handleKeyboardEvent(event); |
296 | } |
297 | |
298 | void Editor::handleInputMethodKeydown(KeyboardEvent& event) |
299 | { |
300 | if (auto* client = this->client()) |
301 | client->handleInputMethodKeydown(event); |
302 | } |
303 | |
304 | bool Editor::handleTextEvent(TextEvent& event) |
305 | { |
306 | LOG(Editing, "Editor %p handleTextEvent (data %s)" , this, event.data().utf8().data()); |
307 | |
308 | // Default event handling for Drag and Drop will be handled by DragController |
309 | // so we leave the event for it. |
310 | if (event.isDrop()) |
311 | return false; |
312 | |
313 | if (event.isPaste()) { |
314 | if (event.pastingFragment()) { |
315 | #if PLATFORM(IOS_FAMILY) |
316 | if (client()->performsTwoStepPaste(event.pastingFragment())) |
317 | return true; |
318 | #endif |
319 | replaceSelectionWithFragment(*event.pastingFragment(), SelectReplacement::No, event.shouldSmartReplace() ? SmartReplace::Yes : SmartReplace::No, event.shouldMatchStyle() ? MatchStyle::Yes : MatchStyle::No, EditAction::Paste, event.mailBlockquoteHandling()); |
320 | } else |
321 | replaceSelectionWithText(event.data(), SelectReplacement::No, event.shouldSmartReplace() ? SmartReplace::Yes : SmartReplace::No, EditAction::Paste); |
322 | return true; |
323 | } |
324 | |
325 | String data = event.data(); |
326 | if (data == "\n" ) { |
327 | if (event.isLineBreak()) |
328 | return insertLineBreak(); |
329 | return insertParagraphSeparator(); |
330 | } |
331 | |
332 | return insertTextWithoutSendingTextEvent(data, false, &event); |
333 | } |
334 | |
335 | bool Editor::canEdit() const |
336 | { |
337 | return m_frame.selection().selection().rootEditableElement(); |
338 | } |
339 | |
340 | bool Editor::canEditRichly() const |
341 | { |
342 | return m_frame.selection().selection().isContentRichlyEditable(); |
343 | } |
344 | |
345 | enum class ClipboardEventKind { |
346 | Copy, |
347 | Cut, |
348 | Paste, |
349 | PasteAsPlainText, |
350 | PasteAsQuotation, |
351 | BeforeCopy, |
352 | BeforeCut, |
353 | BeforePaste, |
354 | }; |
355 | |
356 | static AtomString eventNameForClipboardEvent(ClipboardEventKind kind) |
357 | { |
358 | switch (kind) { |
359 | case ClipboardEventKind::Copy: |
360 | return eventNames().copyEvent; |
361 | case ClipboardEventKind::Cut: |
362 | return eventNames().cutEvent; |
363 | case ClipboardEventKind::Paste: |
364 | case ClipboardEventKind::PasteAsPlainText: |
365 | case ClipboardEventKind::PasteAsQuotation: |
366 | return eventNames().pasteEvent; |
367 | case ClipboardEventKind::BeforeCopy: |
368 | return eventNames().beforecopyEvent; |
369 | case ClipboardEventKind::BeforeCut: |
370 | return eventNames().beforecutEvent; |
371 | case ClipboardEventKind::BeforePaste: |
372 | return eventNames().beforepasteEvent; |
373 | } |
374 | ASSERT_NOT_REACHED(); |
375 | return { }; |
376 | } |
377 | |
378 | static Ref<DataTransfer> createDataTransferForClipboardEvent(Document& document, ClipboardEventKind kind) |
379 | { |
380 | switch (kind) { |
381 | case ClipboardEventKind::Copy: |
382 | case ClipboardEventKind::Cut: |
383 | return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::ReadWrite, std::make_unique<StaticPasteboard>()); |
384 | case ClipboardEventKind::PasteAsPlainText: |
385 | if (RuntimeEnabledFeatures::sharedFeatures().customPasteboardDataEnabled()) { |
386 | auto plainTextType = "text/plain"_s ; |
387 | auto plainText = Pasteboard::createForCopyAndPaste()->readString(plainTextType); |
388 | auto pasteboard = std::make_unique<StaticPasteboard>(); |
389 | pasteboard->writeString(plainTextType, plainText); |
390 | return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::Readonly, WTFMove(pasteboard)); |
391 | } |
392 | FALLTHROUGH; |
393 | case ClipboardEventKind::Paste: |
394 | case ClipboardEventKind::PasteAsQuotation: |
395 | return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::Readonly, Pasteboard::createForCopyAndPaste()); |
396 | case ClipboardEventKind::BeforeCopy: |
397 | case ClipboardEventKind::BeforeCut: |
398 | case ClipboardEventKind::BeforePaste: |
399 | return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::Invalid, std::make_unique<StaticPasteboard>()); |
400 | } |
401 | ASSERT_NOT_REACHED(); |
402 | return DataTransfer::createForCopyAndPaste(document, DataTransfer::StoreMode::Invalid, std::make_unique<StaticPasteboard>()); |
403 | } |
404 | |
405 | // Returns whether caller should continue with "the default processing", which is the same as |
406 | // the event handler NOT setting the return value to false |
407 | // https://w3c.github.io/clipboard-apis/#fire-a-clipboard-event |
408 | static bool dispatchClipboardEvent(RefPtr<Element>&& target, ClipboardEventKind kind) |
409 | { |
410 | // FIXME: Move the target selection code here. |
411 | if (!target) |
412 | return true; |
413 | |
414 | auto dataTransfer = createDataTransferForClipboardEvent(target->document(), kind); |
415 | |
416 | auto event = ClipboardEvent::create(eventNameForClipboardEvent(kind), dataTransfer.copyRef()); |
417 | |
418 | target->dispatchEvent(event); |
419 | bool noDefaultProcessing = event->defaultPrevented(); |
420 | if (noDefaultProcessing && (kind == ClipboardEventKind::Copy || kind == ClipboardEventKind::Cut)) { |
421 | auto pasteboard = Pasteboard::createForCopyAndPaste(); |
422 | pasteboard->clear(); |
423 | dataTransfer->commitToPasteboard(*pasteboard); |
424 | } |
425 | |
426 | dataTransfer->makeInvalidForSecurity(); |
427 | |
428 | return !noDefaultProcessing; |
429 | } |
430 | |
431 | // WinIE uses onbeforecut and onbeforepaste to enables the cut and paste menu items. They |
432 | // also send onbeforecopy, apparently for symmetry, but it doesn't affect the menu items. |
433 | // We need to use onbeforecopy as a real menu enabler because we allow elements that are not |
434 | // normally selectable to implement copy/paste (like divs, or a document body). |
435 | |
436 | bool Editor::canDHTMLCut() |
437 | { |
438 | if (m_frame.selection().selection().isInPasswordField()) |
439 | return false; |
440 | |
441 | return !dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::BeforeCut); |
442 | } |
443 | |
444 | bool Editor::canDHTMLCopy() |
445 | { |
446 | if (m_frame.selection().selection().isInPasswordField()) |
447 | return false; |
448 | return !dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::BeforeCopy); |
449 | } |
450 | |
451 | bool Editor::canDHTMLPaste() |
452 | { |
453 | return !dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::BeforePaste); |
454 | } |
455 | |
456 | bool Editor::canCut() const |
457 | { |
458 | return canCopy() && canDelete(); |
459 | } |
460 | |
461 | static HTMLImageElement* imageElementFromImageDocument(Document& document) |
462 | { |
463 | if (!document.isImageDocument()) |
464 | return nullptr; |
465 | |
466 | HTMLElement* body = document.bodyOrFrameset(); |
467 | if (!body) |
468 | return nullptr; |
469 | |
470 | Node* node = body->firstChild(); |
471 | if (!is<HTMLImageElement>(node)) |
472 | return nullptr; |
473 | return downcast<HTMLImageElement>(node); |
474 | } |
475 | |
476 | bool Editor::canCopy() const |
477 | { |
478 | if (imageElementFromImageDocument(document())) |
479 | return true; |
480 | const VisibleSelection& selection = m_frame.selection().selection(); |
481 | return selection.isRange() && !selection.isInPasswordField(); |
482 | } |
483 | |
484 | bool Editor::canPaste() const |
485 | { |
486 | if (m_frame.mainFrame().loader().shouldSuppressTextInputFromEditing()) |
487 | return false; |
488 | |
489 | return canEdit(); |
490 | } |
491 | |
492 | bool Editor::canDelete() const |
493 | { |
494 | const VisibleSelection& selection = m_frame.selection().selection(); |
495 | return selection.isRange() && selection.rootEditableElement(); |
496 | } |
497 | |
498 | bool Editor::canDeleteRange(Range* range) const |
499 | { |
500 | Node& startContainer = range->startContainer(); |
501 | Node& endContainer = range->endContainer(); |
502 | |
503 | if (!startContainer.hasEditableStyle() || !endContainer.hasEditableStyle()) |
504 | return false; |
505 | |
506 | if (range->collapsed()) { |
507 | VisiblePosition start(range->startPosition(), DOWNSTREAM); |
508 | VisiblePosition previous = start.previous(); |
509 | // FIXME: We sometimes allow deletions at the start of editable roots, like when the caret is in an empty list item. |
510 | if (previous.isNull() || previous.deepEquivalent().deprecatedNode()->rootEditableElement() != startContainer.rootEditableElement()) |
511 | return false; |
512 | } |
513 | return true; |
514 | } |
515 | |
516 | bool Editor::shouldSmartDelete() |
517 | { |
518 | if (behavior().shouldAlwaysSmartDelete()) |
519 | return true; |
520 | return m_frame.selection().granularity() == WordGranularity; |
521 | } |
522 | |
523 | bool Editor::smartInsertDeleteEnabled() |
524 | { |
525 | return client() && client()->smartInsertDeleteEnabled(); |
526 | } |
527 | |
528 | bool Editor::canSmartCopyOrDelete() |
529 | { |
530 | return client() && client()->smartInsertDeleteEnabled() && shouldSmartDelete(); |
531 | } |
532 | |
533 | bool Editor::isSelectTrailingWhitespaceEnabled() const |
534 | { |
535 | return client() && client()->isSelectTrailingWhitespaceEnabled(); |
536 | } |
537 | |
538 | bool Editor::deleteWithDirection(SelectionDirection direction, TextGranularity granularity, bool shouldAddToKillRing, bool isTypingAction) |
539 | { |
540 | if (!canEdit()) |
541 | return false; |
542 | |
543 | if (m_frame.selection().isRange()) { |
544 | if (isTypingAction) { |
545 | TypingCommand::deleteKeyPressed(document(), canSmartCopyOrDelete() ? TypingCommand::SmartDelete : 0, granularity); |
546 | revealSelectionAfterEditingOperation(); |
547 | } else { |
548 | if (shouldAddToKillRing) |
549 | addRangeToKillRing(*selectedRange().get(), KillRingInsertionMode::AppendText); |
550 | deleteSelectionWithSmartDelete(canSmartCopyOrDelete()); |
551 | // Implicitly calls revealSelectionAfterEditingOperation(). |
552 | } |
553 | } else { |
554 | TypingCommand::Options options = 0; |
555 | if (canSmartCopyOrDelete()) |
556 | options |= TypingCommand::SmartDelete; |
557 | if (shouldAddToKillRing) |
558 | options |= TypingCommand::AddsToKillRing; |
559 | switch (direction) { |
560 | case DirectionForward: |
561 | case DirectionRight: |
562 | TypingCommand::forwardDeleteKeyPressed(document(), options, granularity); |
563 | break; |
564 | case DirectionBackward: |
565 | case DirectionLeft: |
566 | TypingCommand::deleteKeyPressed(document(), options, granularity); |
567 | break; |
568 | } |
569 | revealSelectionAfterEditingOperation(); |
570 | } |
571 | |
572 | // FIXME: We should to move this down into deleteKeyPressed. |
573 | // clear the "start new kill ring sequence" setting, because it was set to true |
574 | // when the selection was updated by deleting the range |
575 | if (shouldAddToKillRing) |
576 | setStartNewKillRingSequence(false); |
577 | |
578 | return true; |
579 | } |
580 | |
581 | void Editor::deleteSelectionWithSmartDelete(bool smartDelete, EditAction editingAction) |
582 | { |
583 | if (m_frame.selection().isNone()) |
584 | return; |
585 | |
586 | DeleteSelectionCommand::create(document(), smartDelete, true, false, false, true, editingAction)->apply(); |
587 | } |
588 | |
589 | void Editor::clearText() |
590 | { |
591 | ClearTextCommand::CreateAndApply(&m_frame); |
592 | } |
593 | |
594 | void Editor::pasteAsPlainText(const String& pastingText, bool smartReplace) |
595 | { |
596 | Element* target = findEventTargetFromSelection(); |
597 | if (!target) |
598 | return; |
599 | target->dispatchEvent(TextEvent::createForPlainTextPaste(document().windowProxy(), pastingText, smartReplace)); |
600 | } |
601 | |
602 | void Editor::pasteAsFragment(Ref<DocumentFragment>&& pastingFragment, bool smartReplace, bool matchStyle, MailBlockquoteHandling respectsMailBlockquote) |
603 | { |
604 | Element* target = findEventTargetFromSelection(); |
605 | if (!target) |
606 | return; |
607 | target->dispatchEvent(TextEvent::createForFragmentPaste(document().windowProxy(), WTFMove(pastingFragment), smartReplace, matchStyle, respectsMailBlockquote)); |
608 | } |
609 | |
610 | void Editor::pasteAsPlainTextBypassingDHTML() |
611 | { |
612 | pasteAsPlainTextWithPasteboard(*Pasteboard::createForCopyAndPaste()); |
613 | } |
614 | |
615 | void Editor::pasteAsPlainTextWithPasteboard(Pasteboard& pasteboard) |
616 | { |
617 | String text = readPlainTextFromPasteboard(pasteboard); |
618 | if (client() && client()->shouldInsertText(text, selectedRange().get(), EditorInsertAction::Pasted)) |
619 | pasteAsPlainText(text, canSmartReplaceWithPasteboard(pasteboard)); |
620 | } |
621 | |
622 | String Editor::readPlainTextFromPasteboard(Pasteboard& pasteboard) |
623 | { |
624 | PasteboardPlainText text; |
625 | pasteboard.read(text); |
626 | return plainTextFromPasteboard(text); |
627 | } |
628 | |
629 | #if !PLATFORM(MAC) |
630 | |
631 | String Editor::plainTextFromPasteboard(const PasteboardPlainText& text) |
632 | { |
633 | return text.text; |
634 | } |
635 | |
636 | #endif |
637 | |
638 | bool Editor::canSmartReplaceWithPasteboard(Pasteboard& pasteboard) |
639 | { |
640 | return client() && client()->smartInsertDeleteEnabled() && pasteboard.canSmartReplace(); |
641 | } |
642 | |
643 | bool Editor::shouldInsertFragment(DocumentFragment& fragment, Range* replacingDOMRange, EditorInsertAction givenAction) |
644 | { |
645 | if (!client()) |
646 | return false; |
647 | |
648 | auto* child = fragment.firstChild(); |
649 | if (is<CharacterData>(child) && fragment.lastChild() == child) |
650 | return client()->shouldInsertText(downcast<CharacterData>(*child).data(), replacingDOMRange, givenAction); |
651 | |
652 | return client()->shouldInsertNode(&fragment, replacingDOMRange, givenAction); |
653 | } |
654 | |
655 | void Editor::replaceSelectionWithFragment(DocumentFragment& fragment, SelectReplacement selectReplacement, SmartReplace smartReplace, MatchStyle matchStyle, EditAction editingAction, MailBlockquoteHandling mailBlockquoteHandling) |
656 | { |
657 | VisibleSelection selection = m_frame.selection().selection(); |
658 | if (selection.isNone() || !selection.isContentEditable()) |
659 | return; |
660 | |
661 | AccessibilityReplacedText replacedText; |
662 | if (AXObjectCache::accessibilityEnabled() && editingAction == EditAction::Paste) |
663 | replacedText = AccessibilityReplacedText(selection); |
664 | |
665 | OptionSet<ReplaceSelectionCommand::CommandOption> options { ReplaceSelectionCommand::PreventNesting, ReplaceSelectionCommand::SanitizeFragment }; |
666 | if (selectReplacement == SelectReplacement::Yes) |
667 | options.add(ReplaceSelectionCommand::SelectReplacement); |
668 | if (smartReplace == SmartReplace::Yes) |
669 | options.add(ReplaceSelectionCommand::SmartReplace); |
670 | if (matchStyle == MatchStyle::Yes) |
671 | options.add(ReplaceSelectionCommand::MatchStyle); |
672 | if (mailBlockquoteHandling == MailBlockquoteHandling::IgnoreBlockquote) |
673 | options.add(ReplaceSelectionCommand::IgnoreMailBlockquote); |
674 | |
675 | auto command = ReplaceSelectionCommand::create(document(), &fragment, options, editingAction); |
676 | command->apply(); |
677 | revealSelectionAfterEditingOperation(); |
678 | |
679 | selection = m_frame.selection().selection(); |
680 | if (selection.isInPasswordField()) |
681 | return; |
682 | |
683 | if (AXObjectCache::accessibilityEnabled() && editingAction == EditAction::Paste) { |
684 | String text = AccessibilityObject::stringForVisiblePositionRange(command->visibleSelectionForInsertedText()); |
685 | replacedText.postTextStateChangeNotification(document().existingAXObjectCache(), AXTextEditTypePaste, text, m_frame.selection().selection()); |
686 | command->composition()->setRangeDeletedByUnapply(replacedText.replacedRange()); |
687 | } |
688 | |
689 | if (!isContinuousSpellCheckingEnabled()) |
690 | return; |
691 | |
692 | Node* nodeToCheck = selection.rootEditableElement(); |
693 | if (!nodeToCheck) |
694 | return; |
695 | |
696 | auto rangeToCheck = Range::create(document(), firstPositionInNode(nodeToCheck), lastPositionInNode(nodeToCheck)); |
697 | if (auto request = SpellCheckRequest::create(resolveTextCheckingTypeMask(*nodeToCheck, { TextCheckingType::Spelling, TextCheckingType::Grammar }), TextCheckingProcessBatch, rangeToCheck.copyRef(), rangeToCheck.copyRef(), rangeToCheck.copyRef())) |
698 | m_spellChecker->requestCheckingFor(request.releaseNonNull()); |
699 | } |
700 | |
701 | void Editor::replaceSelectionWithText(const String& text, SelectReplacement selectReplacement, SmartReplace smartReplace, EditAction editingAction) |
702 | { |
703 | RefPtr<Range> range = selectedRange(); |
704 | if (!range) |
705 | return; |
706 | |
707 | replaceSelectionWithFragment(createFragmentFromText(*range, text), selectReplacement, smartReplace, MatchStyle::Yes, editingAction); |
708 | } |
709 | |
710 | RefPtr<Range> Editor::selectedRange() |
711 | { |
712 | return m_frame.selection().toNormalizedRange(); |
713 | } |
714 | |
715 | bool Editor::shouldDeleteRange(Range* range) const |
716 | { |
717 | if (!range || range->collapsed()) |
718 | return false; |
719 | |
720 | if (!canDeleteRange(range)) |
721 | return false; |
722 | |
723 | return client() && client()->shouldDeleteRange(range); |
724 | } |
725 | |
726 | bool Editor::tryDHTMLCopy() |
727 | { |
728 | if (m_frame.selection().selection().isInPasswordField()) |
729 | return false; |
730 | |
731 | return !dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::Copy); |
732 | } |
733 | |
734 | bool Editor::tryDHTMLCut() |
735 | { |
736 | if (m_frame.selection().selection().isInPasswordField()) |
737 | return false; |
738 | |
739 | return !dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::Cut); |
740 | } |
741 | |
742 | bool Editor::shouldInsertText(const String& text, Range* range, EditorInsertAction action) const |
743 | { |
744 | if (m_frame.mainFrame().loader().shouldSuppressTextInputFromEditing() && action == EditorInsertAction::Typed) |
745 | return false; |
746 | |
747 | return client() && client()->shouldInsertText(text, range, action); |
748 | } |
749 | |
750 | void Editor::respondToChangedContents(const VisibleSelection& endingSelection) |
751 | { |
752 | if (AXObjectCache::accessibilityEnabled()) { |
753 | Node* node = endingSelection.start().deprecatedNode(); |
754 | if (AXObjectCache* cache = document().existingAXObjectCache()) |
755 | cache->postNotification(node, AXObjectCache::AXValueChanged, TargetObservableParent); |
756 | } |
757 | |
758 | updateMarkersForWordsAffectedByEditing(true); |
759 | |
760 | if (client()) |
761 | client()->respondToChangedContents(); |
762 | } |
763 | |
764 | bool Editor::hasBidiSelection() const |
765 | { |
766 | if (m_frame.selection().isNone()) |
767 | return false; |
768 | |
769 | Node* startNode; |
770 | if (m_frame.selection().isRange()) { |
771 | startNode = m_frame.selection().selection().start().downstream().deprecatedNode(); |
772 | Node* endNode = m_frame.selection().selection().end().upstream().deprecatedNode(); |
773 | if (enclosingBlock(startNode) != enclosingBlock(endNode)) |
774 | return false; |
775 | } else |
776 | startNode = m_frame.selection().selection().visibleStart().deepEquivalent().deprecatedNode(); |
777 | |
778 | if (!startNode) |
779 | return false; |
780 | |
781 | auto renderer = startNode->renderer(); |
782 | while (renderer && !is<RenderBlockFlow>(*renderer)) |
783 | renderer = renderer->parent(); |
784 | |
785 | if (!renderer) |
786 | return false; |
787 | |
788 | if (!renderer->style().isLeftToRightDirection()) |
789 | return true; |
790 | |
791 | return downcast<RenderBlockFlow>(*renderer).containsNonZeroBidiLevel(); |
792 | } |
793 | |
794 | TriState Editor::selectionUnorderedListState() const |
795 | { |
796 | if (m_frame.selection().isCaret()) { |
797 | if (enclosingElementWithTag(m_frame.selection().selection().start(), ulTag)) |
798 | return TrueTriState; |
799 | } else if (m_frame.selection().isRange()) { |
800 | auto* startNode = enclosingElementWithTag(m_frame.selection().selection().start(), ulTag); |
801 | auto* endNode = enclosingElementWithTag(m_frame.selection().selection().end(), ulTag); |
802 | if (startNode && endNode && startNode == endNode) |
803 | return TrueTriState; |
804 | } |
805 | |
806 | return FalseTriState; |
807 | } |
808 | |
809 | TriState Editor::selectionOrderedListState() const |
810 | { |
811 | if (m_frame.selection().isCaret()) { |
812 | if (enclosingElementWithTag(m_frame.selection().selection().start(), olTag)) |
813 | return TrueTriState; |
814 | } else if (m_frame.selection().isRange()) { |
815 | auto* startNode = enclosingElementWithTag(m_frame.selection().selection().start(), olTag); |
816 | auto* endNode = enclosingElementWithTag(m_frame.selection().selection().end(), olTag); |
817 | if (startNode && endNode && startNode == endNode) |
818 | return TrueTriState; |
819 | } |
820 | |
821 | return FalseTriState; |
822 | } |
823 | |
824 | RefPtr<Node> Editor::insertOrderedList() |
825 | { |
826 | if (!canEditRichly()) |
827 | return nullptr; |
828 | |
829 | auto newList = InsertListCommand::insertList(document(), InsertListCommand::Type::OrderedList); |
830 | revealSelectionAfterEditingOperation(); |
831 | return newList; |
832 | } |
833 | |
834 | RefPtr<Node> Editor::insertUnorderedList() |
835 | { |
836 | if (!canEditRichly()) |
837 | return nullptr; |
838 | |
839 | auto newList = InsertListCommand::insertList(document(), InsertListCommand::Type::UnorderedList); |
840 | revealSelectionAfterEditingOperation(); |
841 | return newList; |
842 | } |
843 | |
844 | bool Editor::canIncreaseSelectionListLevel() |
845 | { |
846 | return canEditRichly() && IncreaseSelectionListLevelCommand::canIncreaseSelectionListLevel(&document()); |
847 | } |
848 | |
849 | bool Editor::canDecreaseSelectionListLevel() |
850 | { |
851 | return canEditRichly() && DecreaseSelectionListLevelCommand::canDecreaseSelectionListLevel(&document()); |
852 | } |
853 | |
854 | RefPtr<Node> Editor::increaseSelectionListLevel() |
855 | { |
856 | if (!canEditRichly() || m_frame.selection().isNone()) |
857 | return nullptr; |
858 | |
859 | RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevel(&document()); |
860 | revealSelectionAfterEditingOperation(); |
861 | return newList; |
862 | } |
863 | |
864 | RefPtr<Node> Editor::increaseSelectionListLevelOrdered() |
865 | { |
866 | if (!canEditRichly() || m_frame.selection().isNone()) |
867 | return nullptr; |
868 | |
869 | RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelOrdered(&document()); |
870 | revealSelectionAfterEditingOperation(); |
871 | return newList; |
872 | } |
873 | |
874 | RefPtr<Node> Editor::increaseSelectionListLevelUnordered() |
875 | { |
876 | if (!canEditRichly() || m_frame.selection().isNone()) |
877 | return nullptr; |
878 | |
879 | RefPtr<Node> newList = IncreaseSelectionListLevelCommand::increaseSelectionListLevelUnordered(&document()); |
880 | revealSelectionAfterEditingOperation(); |
881 | return newList; |
882 | } |
883 | |
884 | void Editor::decreaseSelectionListLevel() |
885 | { |
886 | if (!canEditRichly() || m_frame.selection().isNone()) |
887 | return; |
888 | |
889 | DecreaseSelectionListLevelCommand::decreaseSelectionListLevel(&document()); |
890 | revealSelectionAfterEditingOperation(); |
891 | } |
892 | |
893 | void Editor::removeFormattingAndStyle() |
894 | { |
895 | RemoveFormatCommand::create(document())->apply(); |
896 | } |
897 | |
898 | void Editor::clearLastEditCommand() |
899 | { |
900 | m_lastEditCommand = nullptr; |
901 | } |
902 | |
903 | Element* Editor::findEventTargetFrom(const VisibleSelection& selection) const |
904 | { |
905 | Element* target = selection.start().element(); |
906 | if (!target) |
907 | target = document().bodyOrFrameset(); |
908 | if (!target) |
909 | return nullptr; |
910 | |
911 | return target; |
912 | } |
913 | |
914 | Element* Editor::findEventTargetFromSelection() const |
915 | { |
916 | return findEventTargetFrom(m_frame.selection().selection()); |
917 | } |
918 | |
919 | void Editor::applyStyle(StyleProperties* style, EditAction editingAction) |
920 | { |
921 | if (style) |
922 | applyStyle(EditingStyle::create(style), editingAction, ColorFilterMode::UseOriginalColor); |
923 | } |
924 | |
925 | void Editor::applyStyle(RefPtr<EditingStyle>&& style, EditAction editingAction, ColorFilterMode colorFilterMode) |
926 | { |
927 | if (!style) |
928 | return; |
929 | |
930 | auto selectionType = m_frame.selection().selection().selectionType(); |
931 | if (selectionType == VisibleSelection::NoSelection) |
932 | return; |
933 | |
934 | String inputTypeName = inputTypeNameForEditingAction(editingAction); |
935 | String inputEventData = inputEventDataForEditingStyleAndAction(*style, editingAction); |
936 | RefPtr<Element> element = m_frame.selection().selection().rootEditableElement(); |
937 | if (element && !dispatchBeforeInputEvent(*element, inputTypeName, inputEventData)) |
938 | return; |
939 | |
940 | Ref<EditingStyle> styleToApply = colorFilterMode == ColorFilterMode::InvertColor ? style->inverseTransformColorIfNeeded(*element) : style.releaseNonNull(); |
941 | |
942 | switch (selectionType) { |
943 | case VisibleSelection::CaretSelection: |
944 | computeAndSetTypingStyle(styleToApply.get(), editingAction); |
945 | break; |
946 | case VisibleSelection::RangeSelection: |
947 | ApplyStyleCommand::create(document(), styleToApply.ptr(), editingAction)->apply(); |
948 | break; |
949 | default: |
950 | break; |
951 | } |
952 | |
953 | client()->didApplyStyle(); |
954 | if (element) |
955 | dispatchInputEvent(*element, inputTypeName, inputEventData); |
956 | } |
957 | |
958 | bool Editor::shouldApplyStyle(StyleProperties* style, Range* range) |
959 | { |
960 | return client()->shouldApplyStyle(style, range); |
961 | } |
962 | |
963 | void Editor::applyParagraphStyle(StyleProperties* style, EditAction editingAction) |
964 | { |
965 | if (!style) |
966 | return; |
967 | |
968 | auto selectionType = m_frame.selection().selection().selectionType(); |
969 | if (selectionType == VisibleSelection::NoSelection) |
970 | return; |
971 | |
972 | String inputTypeName = inputTypeNameForEditingAction(editingAction); |
973 | String inputEventData = inputEventDataForEditingStyleAndAction(style, editingAction); |
974 | RefPtr<Element> element = m_frame.selection().selection().rootEditableElement(); |
975 | if (element && !dispatchBeforeInputEvent(*element, inputTypeName, inputEventData)) |
976 | return; |
977 | |
978 | ApplyStyleCommand::create(document(), EditingStyle::create(style).ptr(), editingAction, ApplyStyleCommand::ForceBlockProperties)->apply(); |
979 | client()->didApplyStyle(); |
980 | if (element) |
981 | dispatchInputEvent(*element, inputTypeName, inputEventData); |
982 | } |
983 | |
984 | void Editor::applyStyleToSelection(StyleProperties* style, EditAction editingAction) |
985 | { |
986 | if (!style || style->isEmpty() || !canEditRichly()) |
987 | return; |
988 | |
989 | if (!client() || !client()->shouldApplyStyle(style, m_frame.selection().toNormalizedRange().get())) |
990 | return; |
991 | applyStyle(style, editingAction); |
992 | } |
993 | |
994 | void Editor::applyStyleToSelection(Ref<EditingStyle>&& style, EditAction editingAction, ColorFilterMode colorFilterMode) |
995 | { |
996 | if (style->isEmpty() || !canEditRichly()) |
997 | return; |
998 | |
999 | // FIXME: This is wrong for text decorations since m_mutableStyle is empty. |
1000 | if (!client() || !client()->shouldApplyStyle(style->styleWithResolvedTextDecorations().ptr(), m_frame.selection().toNormalizedRange().get())) |
1001 | return; |
1002 | |
1003 | applyStyle(WTFMove(style), editingAction, colorFilterMode); |
1004 | } |
1005 | |
1006 | void Editor::applyParagraphStyleToSelection(StyleProperties* style, EditAction editingAction) |
1007 | { |
1008 | if (!style || style->isEmpty() || !canEditRichly()) |
1009 | return; |
1010 | |
1011 | if (client() && client()->shouldApplyStyle(style, m_frame.selection().toNormalizedRange().get())) |
1012 | applyParagraphStyle(style, editingAction); |
1013 | } |
1014 | |
1015 | bool Editor::selectionStartHasStyle(CSSPropertyID propertyID, const String& value) const |
1016 | { |
1017 | if (auto editingStyle = EditingStyle::styleAtSelectionStart(m_frame.selection().selection(), propertyID == CSSPropertyBackgroundColor)) |
1018 | return editingStyle->hasStyle(propertyID, value); |
1019 | return false; |
1020 | } |
1021 | |
1022 | TriState Editor::selectionHasStyle(CSSPropertyID propertyID, const String& value) const |
1023 | { |
1024 | return EditingStyle::create(propertyID, value)->triStateOfStyle(m_frame.selection().selection()); |
1025 | } |
1026 | |
1027 | String Editor::selectionStartCSSPropertyValue(CSSPropertyID propertyID) |
1028 | { |
1029 | RefPtr<EditingStyle> selectionStyle = EditingStyle::styleAtSelectionStart(m_frame.selection().selection(), |
1030 | propertyID == CSSPropertyBackgroundColor); |
1031 | if (!selectionStyle || !selectionStyle->style()) |
1032 | return String(); |
1033 | |
1034 | if (propertyID == CSSPropertyFontSize) |
1035 | return String::number(selectionStyle->legacyFontSize(document())); |
1036 | return selectionStyle->style()->getPropertyValue(propertyID); |
1037 | } |
1038 | |
1039 | void Editor::indent() |
1040 | { |
1041 | IndentOutdentCommand::create(document(), IndentOutdentCommand::Indent)->apply(); |
1042 | } |
1043 | |
1044 | void Editor::outdent() |
1045 | { |
1046 | IndentOutdentCommand::create(document(), IndentOutdentCommand::Outdent)->apply(); |
1047 | } |
1048 | |
1049 | static void notifyTextFromControls(Element* startRoot, Element* endRoot) |
1050 | { |
1051 | HTMLTextFormControlElement* startingTextControl = enclosingTextFormControl(firstPositionInOrBeforeNode(startRoot)); |
1052 | HTMLTextFormControlElement* endingTextControl = enclosingTextFormControl(firstPositionInOrBeforeNode(endRoot)); |
1053 | if (startingTextControl) |
1054 | startingTextControl->didEditInnerTextValue(); |
1055 | if (endingTextControl && startingTextControl != endingTextControl) |
1056 | endingTextControl->didEditInnerTextValue(); |
1057 | } |
1058 | |
1059 | static bool dispatchBeforeInputEvents(RefPtr<Element> startRoot, RefPtr<Element> endRoot, const AtomString& inputTypeName, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { }, Event::IsCancelable cancelable = Event::IsCancelable::Yes) |
1060 | { |
1061 | bool continueWithDefaultBehavior = true; |
1062 | if (startRoot) |
1063 | continueWithDefaultBehavior &= dispatchBeforeInputEvent(*startRoot, inputTypeName, data, WTFMove(dataTransfer), targetRanges, cancelable); |
1064 | if (endRoot && endRoot != startRoot) |
1065 | continueWithDefaultBehavior &= dispatchBeforeInputEvent(*endRoot, inputTypeName, data, WTFMove(dataTransfer), targetRanges, cancelable); |
1066 | return continueWithDefaultBehavior; |
1067 | } |
1068 | |
1069 | static void dispatchInputEvents(RefPtr<Element> startRoot, RefPtr<Element> endRoot, const AtomString& inputTypeName, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { }) |
1070 | { |
1071 | if (startRoot) |
1072 | dispatchInputEvent(*startRoot, inputTypeName, data, WTFMove(dataTransfer), targetRanges); |
1073 | if (endRoot && endRoot != startRoot) |
1074 | dispatchInputEvent(*endRoot, inputTypeName, data, WTFMove(dataTransfer), targetRanges); |
1075 | } |
1076 | |
1077 | bool Editor::willApplyEditing(CompositeEditCommand& command, Vector<RefPtr<StaticRange>>&& targetRanges) const |
1078 | { |
1079 | if (!command.shouldDispatchInputEvents()) |
1080 | return true; |
1081 | |
1082 | auto* composition = command.composition(); |
1083 | if (!composition) |
1084 | return true; |
1085 | |
1086 | return dispatchBeforeInputEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement(), command.inputEventTypeName(), |
1087 | command.inputEventData(), command.inputEventDataTransfer(), targetRanges, command.isBeforeInputEventCancelable() ? Event::IsCancelable::Yes : Event::IsCancelable::No); |
1088 | } |
1089 | |
1090 | void Editor::appliedEditing(CompositeEditCommand& command) |
1091 | { |
1092 | LOG(Editing, "Editor %p appliedEditing" , this); |
1093 | |
1094 | document().updateLayout(); |
1095 | |
1096 | ASSERT(command.composition()); |
1097 | auto& composition = *command.composition(); |
1098 | VisibleSelection newSelection(command.endingSelection()); |
1099 | |
1100 | notifyTextFromControls(composition.startingRootEditableElement(), composition.endingRootEditableElement()); |
1101 | |
1102 | if (command.isTopLevelCommand()) { |
1103 | // Don't clear the typing style with this selection change. We do those things elsewhere if necessary. |
1104 | OptionSet<FrameSelection::SetSelectionOption> options; |
1105 | if (command.isDictationCommand()) |
1106 | options.add(FrameSelection::DictationTriggered); |
1107 | |
1108 | changeSelectionAfterCommand(newSelection, options); |
1109 | } |
1110 | |
1111 | if (command.shouldDispatchInputEvents()) |
1112 | dispatchInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), command.inputEventTypeName(), command.inputEventData(), command.inputEventDataTransfer()); |
1113 | |
1114 | if (command.isTopLevelCommand()) { |
1115 | updateEditorUINowIfScheduled(); |
1116 | |
1117 | m_alternativeTextController->respondToAppliedEditing(&command); |
1118 | |
1119 | if (!command.preservesTypingStyle()) |
1120 | m_frame.selection().clearTypingStyle(); |
1121 | |
1122 | // Command will be equal to last edit command only in the case of typing |
1123 | if (m_lastEditCommand.get() == &command) |
1124 | ASSERT(command.isTypingCommand()); |
1125 | else { |
1126 | // Only register a new undo command if the command passed in is |
1127 | // different from the last command |
1128 | m_lastEditCommand = &command; |
1129 | if (client()) |
1130 | client()->registerUndoStep(m_lastEditCommand->ensureComposition()); |
1131 | } |
1132 | respondToChangedContents(newSelection); |
1133 | } |
1134 | } |
1135 | |
1136 | bool Editor::willUnapplyEditing(const EditCommandComposition& composition) const |
1137 | { |
1138 | return dispatchBeforeInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyUndo" ); |
1139 | } |
1140 | |
1141 | void Editor::unappliedEditing(EditCommandComposition& composition) |
1142 | { |
1143 | document().updateLayout(); |
1144 | |
1145 | notifyTextFromControls(composition.startingRootEditableElement(), composition.endingRootEditableElement()); |
1146 | |
1147 | VisibleSelection newSelection(composition.startingSelection()); |
1148 | changeSelectionAfterCommand(newSelection, FrameSelection::defaultSetSelectionOptions()); |
1149 | dispatchInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyUndo" ); |
1150 | |
1151 | updateEditorUINowIfScheduled(); |
1152 | |
1153 | m_alternativeTextController->respondToUnappliedEditing(&composition); |
1154 | |
1155 | m_lastEditCommand = nullptr; |
1156 | if (auto* client = this->client()) |
1157 | client->registerRedoStep(composition); |
1158 | respondToChangedContents(newSelection); |
1159 | } |
1160 | |
1161 | bool Editor::willReapplyEditing(const EditCommandComposition& composition) const |
1162 | { |
1163 | return dispatchBeforeInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyRedo" ); |
1164 | } |
1165 | |
1166 | void Editor::reappliedEditing(EditCommandComposition& composition) |
1167 | { |
1168 | document().updateLayout(); |
1169 | |
1170 | notifyTextFromControls(composition.startingRootEditableElement(), composition.endingRootEditableElement()); |
1171 | |
1172 | VisibleSelection newSelection(composition.endingSelection()); |
1173 | changeSelectionAfterCommand(newSelection, FrameSelection::defaultSetSelectionOptions()); |
1174 | dispatchInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), "historyRedo" ); |
1175 | |
1176 | updateEditorUINowIfScheduled(); |
1177 | |
1178 | m_lastEditCommand = nullptr; |
1179 | if (auto* client = this->client()) |
1180 | client->registerUndoStep(composition); |
1181 | respondToChangedContents(newSelection); |
1182 | } |
1183 | |
1184 | Editor::Editor(Frame& frame) |
1185 | : m_frame(frame) |
1186 | , m_killRing(std::make_unique<PAL::KillRing>()) |
1187 | , m_spellChecker(std::make_unique<SpellChecker>(frame)) |
1188 | , m_alternativeTextController(std::make_unique<AlternativeTextController>(frame)) |
1189 | , m_editorUIUpdateTimer(*this, &Editor::editorUIUpdateTimerFired) |
1190 | #if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS_FAMILY) |
1191 | , m_telephoneNumberDetectionUpdateTimer(*this, &Editor::scanSelectionForTelephoneNumbers) |
1192 | #endif |
1193 | { |
1194 | } |
1195 | |
1196 | Editor::~Editor() = default; |
1197 | |
1198 | void Editor::clear() |
1199 | { |
1200 | m_lastEditCommand = nullptr; |
1201 | if (m_compositionNode) { |
1202 | m_compositionNode = nullptr; |
1203 | if (EditorClient* client = this->client()) |
1204 | client->discardedComposition(&m_frame); |
1205 | } |
1206 | m_customCompositionUnderlines.clear(); |
1207 | m_shouldStyleWithCSS = false; |
1208 | m_defaultParagraphSeparator = EditorParagraphSeparatorIsDiv; |
1209 | m_mark = { }; |
1210 | m_oldSelectionForEditorUIUpdate = { }; |
1211 | m_editorUIUpdateTimer.stop(); |
1212 | |
1213 | #if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS_FAMILY) |
1214 | m_telephoneNumberDetectionUpdateTimer.stop(); |
1215 | m_detectedTelephoneNumberRanges.clear(); |
1216 | #endif |
1217 | } |
1218 | |
1219 | bool Editor::insertText(const String& text, Event* triggeringEvent, TextEventInputType inputType) |
1220 | { |
1221 | return m_frame.eventHandler().handleTextInputEvent(text, triggeringEvent, inputType); |
1222 | } |
1223 | |
1224 | bool Editor::insertTextForConfirmedComposition(const String& text) |
1225 | { |
1226 | return m_frame.eventHandler().handleTextInputEvent(text, 0, TextEventInputComposition); |
1227 | } |
1228 | |
1229 | bool Editor::insertDictatedText(const String& text, const Vector<DictationAlternative>& dictationAlternatives, Event* triggeringEvent) |
1230 | { |
1231 | return m_alternativeTextController->insertDictatedText(text, dictationAlternatives, triggeringEvent); |
1232 | } |
1233 | |
1234 | bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectInsertedText, TextEvent* triggeringEvent) |
1235 | { |
1236 | if (text.isEmpty()) |
1237 | return false; |
1238 | |
1239 | VisibleSelection selection = selectionForCommand(triggeringEvent); |
1240 | if (!selection.isContentEditable()) |
1241 | return false; |
1242 | RefPtr<Range> range = selection.toNormalizedRange(); |
1243 | |
1244 | if (!shouldInsertText(text, range.get(), EditorInsertAction::Typed)) |
1245 | return true; |
1246 | |
1247 | updateMarkersForWordsAffectedByEditing(isSpaceOrNewline(text[0])); |
1248 | |
1249 | bool shouldConsiderApplyingAutocorrection = false; |
1250 | if (text == " " || text == "\t" ) |
1251 | shouldConsiderApplyingAutocorrection = true; |
1252 | |
1253 | if (text.length() == 1 && u_ispunct(text[0]) && !isAmbiguousBoundaryCharacter(text[0])) |
1254 | shouldConsiderApplyingAutocorrection = true; |
1255 | |
1256 | bool autocorrectionWasApplied = shouldConsiderApplyingAutocorrection && m_alternativeTextController->applyAutocorrectionBeforeTypingIfAppropriate(); |
1257 | |
1258 | // Get the selection to use for the event that triggered this insertText. |
1259 | // If the event handler changed the selection, we may want to use a different selection |
1260 | // that is contained in the event target. |
1261 | selection = selectionForCommand(triggeringEvent); |
1262 | if (selection.isContentEditable()) { |
1263 | if (Node* selectionStart = selection.start().deprecatedNode()) { |
1264 | Ref<Document> document(selectionStart->document()); |
1265 | |
1266 | // Insert the text |
1267 | if (triggeringEvent && triggeringEvent->isDictation()) |
1268 | DictationCommand::insertText(document, text, triggeringEvent->dictationAlternatives(), selection); |
1269 | else { |
1270 | TypingCommand::Options options = 0; |
1271 | if (selectInsertedText) |
1272 | options |= TypingCommand::SelectInsertedText; |
1273 | if (autocorrectionWasApplied) |
1274 | options |= TypingCommand::RetainAutocorrectionIndicator; |
1275 | if (triggeringEvent && triggeringEvent->isAutocompletion()) |
1276 | options |= TypingCommand::IsAutocompletion; |
1277 | TypingCommand::insertText(document, text, selection, options, triggeringEvent && triggeringEvent->isComposition() ? TypingCommand::TextCompositionFinal : TypingCommand::TextCompositionNone); |
1278 | } |
1279 | |
1280 | // Reveal the current selection |
1281 | if (Frame* editedFrame = document->frame()) |
1282 | if (Page* page = editedFrame->page()) { |
1283 | SelectionRevealMode revealMode = SelectionRevealMode::Reveal; |
1284 | page->focusController().focusedOrMainFrame().selection().revealSelection(revealMode, ScrollAlignment::alignCenterIfNeeded); |
1285 | } |
1286 | } |
1287 | } |
1288 | |
1289 | return true; |
1290 | } |
1291 | |
1292 | bool Editor::insertLineBreak() |
1293 | { |
1294 | if (!canEdit()) |
1295 | return false; |
1296 | |
1297 | if (!shouldInsertText("\n" , m_frame.selection().toNormalizedRange().get(), EditorInsertAction::Typed)) |
1298 | return true; |
1299 | |
1300 | VisiblePosition caret = m_frame.selection().selection().visibleStart(); |
1301 | bool alignToEdge = isEndOfEditableOrNonEditableContent(caret); |
1302 | bool autocorrectionIsApplied = m_alternativeTextController->applyAutocorrectionBeforeTypingIfAppropriate(); |
1303 | TypingCommand::insertLineBreak(document(), autocorrectionIsApplied ? TypingCommand::RetainAutocorrectionIndicator : 0); |
1304 | revealSelectionAfterEditingOperation(alignToEdge ? ScrollAlignment::alignToEdgeIfNeeded : ScrollAlignment::alignCenterIfNeeded); |
1305 | |
1306 | return true; |
1307 | } |
1308 | |
1309 | bool Editor::insertParagraphSeparator() |
1310 | { |
1311 | if (!canEdit()) |
1312 | return false; |
1313 | |
1314 | if (!canEditRichly()) |
1315 | return insertLineBreak(); |
1316 | |
1317 | if (!shouldInsertText("\n" , m_frame.selection().toNormalizedRange().get(), EditorInsertAction::Typed)) |
1318 | return true; |
1319 | |
1320 | VisiblePosition caret = m_frame.selection().selection().visibleStart(); |
1321 | bool alignToEdge = isEndOfEditableOrNonEditableContent(caret); |
1322 | bool autocorrectionIsApplied = m_alternativeTextController->applyAutocorrectionBeforeTypingIfAppropriate(); |
1323 | TypingCommand::insertParagraphSeparator(document(), autocorrectionIsApplied ? TypingCommand::RetainAutocorrectionIndicator : 0); |
1324 | revealSelectionAfterEditingOperation(alignToEdge ? ScrollAlignment::alignToEdgeIfNeeded : ScrollAlignment::alignCenterIfNeeded); |
1325 | |
1326 | return true; |
1327 | } |
1328 | |
1329 | bool Editor::insertParagraphSeparatorInQuotedContent() |
1330 | { |
1331 | // FIXME: Why is this missing calls to canEdit, canEditRichly, etc.? |
1332 | TypingCommand::insertParagraphSeparatorInQuotedContent(document()); |
1333 | revealSelectionAfterEditingOperation(); |
1334 | return true; |
1335 | } |
1336 | |
1337 | void Editor::cut() |
1338 | { |
1339 | if (tryDHTMLCut()) |
1340 | return; // DHTML did the whole operation |
1341 | if (!canCut()) { |
1342 | PAL::systemBeep(); |
1343 | return; |
1344 | } |
1345 | |
1346 | performCutOrCopy(CutAction); |
1347 | } |
1348 | |
1349 | void Editor::copy() |
1350 | { |
1351 | if (tryDHTMLCopy()) |
1352 | return; // DHTML did the whole operation |
1353 | if (!canCopy()) { |
1354 | PAL::systemBeep(); |
1355 | return; |
1356 | } |
1357 | |
1358 | performCutOrCopy(CopyAction); |
1359 | } |
1360 | |
1361 | void Editor::postTextStateChangeNotificationForCut(const String& text, const VisibleSelection& selection) |
1362 | { |
1363 | if (!AXObjectCache::accessibilityEnabled()) |
1364 | return; |
1365 | if (!text.length()) |
1366 | return; |
1367 | AXObjectCache* cache = document().existingAXObjectCache(); |
1368 | if (!cache) |
1369 | return; |
1370 | cache->postTextStateChangeNotification(selection.start().anchorNode(), AXTextEditTypeCut, text, selection.start()); |
1371 | } |
1372 | |
1373 | void Editor::performCutOrCopy(EditorActionSpecifier action) |
1374 | { |
1375 | RefPtr<Range> selection = selectedRange(); |
1376 | willWriteSelectionToPasteboard(selection.get()); |
1377 | if (action == CutAction) { |
1378 | if (!shouldDeleteRange(selection.get())) |
1379 | return; |
1380 | |
1381 | updateMarkersForWordsAffectedByEditing(true); |
1382 | } |
1383 | |
1384 | if (enclosingTextFormControl(m_frame.selection().selection().start())) |
1385 | Pasteboard::createForCopyAndPaste()->writePlainText(selectedTextForDataTransfer(), canSmartCopyOrDelete() ? Pasteboard::CanSmartReplace : Pasteboard::CannotSmartReplace); |
1386 | else { |
1387 | HTMLImageElement* imageElement = nullptr; |
1388 | if (action == CopyAction) |
1389 | imageElement = imageElementFromImageDocument(document()); |
1390 | |
1391 | if (imageElement) { |
1392 | #if !PLATFORM(WIN) |
1393 | writeImageToPasteboard(*Pasteboard::createForCopyAndPaste(), *imageElement, document().url(), document().title()); |
1394 | #else |
1395 | // FIXME: Delete after <http://webkit.org/b/177618> lands. |
1396 | Pasteboard::createForCopyAndPaste()->writeImage(*imageElement, document().url(), document().title()); |
1397 | #endif |
1398 | } else { |
1399 | #if !PLATFORM(WIN) |
1400 | writeSelectionToPasteboard(*Pasteboard::createForCopyAndPaste()); |
1401 | #else |
1402 | // FIXME: Delete after <http://webkit.org/b/177618> lands. |
1403 | Pasteboard::createForCopyAndPaste()->writeSelection(*selection, canSmartCopyOrDelete(), m_frame, IncludeImageAltTextForDataTransfer); |
1404 | #endif |
1405 | } |
1406 | } |
1407 | |
1408 | didWriteSelectionToPasteboard(); |
1409 | if (action == CutAction) { |
1410 | String text; |
1411 | if (AXObjectCache::accessibilityEnabled()) |
1412 | text = AccessibilityObject::stringForVisiblePositionRange(m_frame.selection().selection()); |
1413 | deleteSelectionWithSmartDelete(canSmartCopyOrDelete(), EditAction::Cut); |
1414 | if (AXObjectCache::accessibilityEnabled()) |
1415 | postTextStateChangeNotificationForCut(text, m_frame.selection().selection()); |
1416 | } |
1417 | } |
1418 | |
1419 | void Editor::paste() |
1420 | { |
1421 | paste(*Pasteboard::createForCopyAndPaste()); |
1422 | } |
1423 | |
1424 | void Editor::paste(Pasteboard& pasteboard) |
1425 | { |
1426 | if (!dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::Paste)) |
1427 | return; // DHTML did the whole operation |
1428 | if (!canPaste()) |
1429 | return; |
1430 | updateMarkersForWordsAffectedByEditing(false); |
1431 | ResourceCacheValidationSuppressor validationSuppressor(document().cachedResourceLoader()); |
1432 | if (m_frame.selection().selection().isContentRichlyEditable()) |
1433 | pasteWithPasteboard(&pasteboard, { PasteOption::AllowPlainText }); |
1434 | else |
1435 | pasteAsPlainTextWithPasteboard(pasteboard); |
1436 | } |
1437 | |
1438 | void Editor::pasteAsPlainText() |
1439 | { |
1440 | if (!dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::PasteAsPlainText)) |
1441 | return; |
1442 | if (!canPaste()) |
1443 | return; |
1444 | updateMarkersForWordsAffectedByEditing(false); |
1445 | pasteAsPlainTextWithPasteboard(*Pasteboard::createForCopyAndPaste()); |
1446 | } |
1447 | |
1448 | void Editor::pasteAsQuotation() |
1449 | { |
1450 | if (!dispatchClipboardEvent(findEventTargetFromSelection(), ClipboardEventKind::PasteAsQuotation)) |
1451 | return; |
1452 | if (!canPaste()) |
1453 | return; |
1454 | updateMarkersForWordsAffectedByEditing(false); |
1455 | ResourceCacheValidationSuppressor validationSuppressor(document().cachedResourceLoader()); |
1456 | auto pasteboard = Pasteboard::createForCopyAndPaste(); |
1457 | if (m_frame.selection().selection().isContentRichlyEditable()) |
1458 | pasteWithPasteboard(pasteboard.get(), { PasteOption::AllowPlainText, PasteOption::AsQuotation }); |
1459 | else |
1460 | pasteAsPlainTextWithPasteboard(*pasteboard); |
1461 | } |
1462 | |
1463 | void Editor::quoteFragmentForPasting(DocumentFragment& fragment) |
1464 | { |
1465 | auto blockQuote = HTMLQuoteElement::create(blockquoteTag, document()); |
1466 | blockQuote->setAttributeWithoutSynchronization(typeAttr, AtomString("cite" )); |
1467 | blockQuote->setAttributeWithoutSynchronization(classAttr, AtomString(ApplePasteAsQuotation)); |
1468 | |
1469 | auto childNode = fragment.firstChild(); |
1470 | |
1471 | if (childNode) { |
1472 | while (childNode) { |
1473 | blockQuote->appendChild(*childNode); |
1474 | childNode = fragment.firstChild(); |
1475 | } |
1476 | } else |
1477 | blockQuote->appendChild(HTMLBRElement::create(document())); |
1478 | |
1479 | fragment.appendChild(blockQuote); |
1480 | } |
1481 | |
1482 | void Editor::performDelete() |
1483 | { |
1484 | if (!canDelete()) { |
1485 | PAL::systemBeep(); |
1486 | return; |
1487 | } |
1488 | |
1489 | addRangeToKillRing(*selectedRange().get(), KillRingInsertionMode::AppendText); |
1490 | deleteSelectionWithSmartDelete(canSmartCopyOrDelete()); |
1491 | |
1492 | // clear the "start new kill ring sequence" setting, because it was set to true |
1493 | // when the selection was updated by deleting the range |
1494 | setStartNewKillRingSequence(false); |
1495 | } |
1496 | |
1497 | void Editor::changeSelectionListType() |
1498 | { |
1499 | if (auto type = ChangeListTypeCommand::listConversionType(document())) |
1500 | ChangeListTypeCommand::create(document(), *type)->apply(); |
1501 | } |
1502 | |
1503 | void Editor::simplifyMarkup(Node* startNode, Node* endNode) |
1504 | { |
1505 | if (!startNode) |
1506 | return; |
1507 | if (endNode) { |
1508 | if (&startNode->document() != &endNode->document()) |
1509 | return; |
1510 | // check if start node is before endNode |
1511 | Node* node = startNode; |
1512 | while (node && node != endNode) |
1513 | node = NodeTraversal::next(*node); |
1514 | if (!node) |
1515 | return; |
1516 | } |
1517 | |
1518 | SimplifyMarkupCommand::create(document(), startNode, endNode ? NodeTraversal::next(*endNode) : nullptr)->apply(); |
1519 | } |
1520 | |
1521 | void Editor::copyURL(const URL& url, const String& title) |
1522 | { |
1523 | copyURL(url, title, *Pasteboard::createForCopyAndPaste()); |
1524 | } |
1525 | |
1526 | void Editor::copyURL(const URL& url, const String& title, Pasteboard& pasteboard) |
1527 | { |
1528 | PasteboardURL pasteboardURL; |
1529 | pasteboardURL.url = url; |
1530 | pasteboardURL.title = title; |
1531 | |
1532 | #if PLATFORM(MAC) |
1533 | pasteboardURL.userVisibleForm = userVisibleString(url); |
1534 | #endif |
1535 | |
1536 | pasteboard.write(pasteboardURL); |
1537 | } |
1538 | |
1539 | PasteboardWriterData::URLData Editor::pasteboardWriterURL(const URL& url, const String& title) |
1540 | { |
1541 | PasteboardWriterData::URLData result; |
1542 | |
1543 | result.url = url; |
1544 | result.title = title; |
1545 | #if PLATFORM(MAC) |
1546 | result.userVisibleForm = userVisibleString(url); |
1547 | #endif |
1548 | |
1549 | return result; |
1550 | } |
1551 | |
1552 | #if !PLATFORM(IOS_FAMILY) |
1553 | |
1554 | void Editor::copyImage(const HitTestResult& result) |
1555 | { |
1556 | Element* element = result.innerNonSharedElement(); |
1557 | if (!element) |
1558 | return; |
1559 | |
1560 | URL url = result.absoluteLinkURL(); |
1561 | if (url.isEmpty()) |
1562 | url = result.absoluteImageURL(); |
1563 | |
1564 | #if !PLATFORM(WIN) |
1565 | writeImageToPasteboard(*Pasteboard::createForCopyAndPaste(), *element, url, result.altDisplayString()); |
1566 | #else |
1567 | // FIXME: Delete after <http://webkit.org/b/177618> lands. |
1568 | Pasteboard::createForCopyAndPaste()->writeImage(*element, url, result.altDisplayString()); |
1569 | #endif |
1570 | } |
1571 | |
1572 | #endif |
1573 | |
1574 | bool Editor::isContinuousSpellCheckingEnabled() const |
1575 | { |
1576 | return client() && client()->isContinuousSpellCheckingEnabled(); |
1577 | } |
1578 | |
1579 | void Editor::toggleContinuousSpellChecking() |
1580 | { |
1581 | if (client()) |
1582 | client()->toggleContinuousSpellChecking(); |
1583 | } |
1584 | |
1585 | bool Editor::isGrammarCheckingEnabled() |
1586 | { |
1587 | return client() && client()->isGrammarCheckingEnabled(); |
1588 | } |
1589 | |
1590 | void Editor::toggleGrammarChecking() |
1591 | { |
1592 | if (client()) |
1593 | client()->toggleGrammarChecking(); |
1594 | } |
1595 | |
1596 | int Editor::spellCheckerDocumentTag() |
1597 | { |
1598 | return client() ? client()->spellCheckerDocumentTag() : 0; |
1599 | } |
1600 | |
1601 | #if USE(APPKIT) |
1602 | |
1603 | void Editor::uppercaseWord() |
1604 | { |
1605 | if (client()) |
1606 | client()->uppercaseWord(); |
1607 | } |
1608 | |
1609 | void Editor::lowercaseWord() |
1610 | { |
1611 | if (client()) |
1612 | client()->lowercaseWord(); |
1613 | } |
1614 | |
1615 | void Editor::capitalizeWord() |
1616 | { |
1617 | if (client()) |
1618 | client()->capitalizeWord(); |
1619 | } |
1620 | |
1621 | #endif |
1622 | |
1623 | #if USE(AUTOMATIC_TEXT_REPLACEMENT) |
1624 | |
1625 | void Editor::showSubstitutionsPanel() |
1626 | { |
1627 | if (!client()) { |
1628 | LOG_ERROR("No NSSpellChecker" ); |
1629 | return; |
1630 | } |
1631 | |
1632 | if (client()->substitutionsPanelIsShowing()) { |
1633 | client()->showSubstitutionsPanel(false); |
1634 | return; |
1635 | } |
1636 | client()->showSubstitutionsPanel(true); |
1637 | } |
1638 | |
1639 | bool Editor::substitutionsPanelIsShowing() |
1640 | { |
1641 | if (!client()) |
1642 | return false; |
1643 | return client()->substitutionsPanelIsShowing(); |
1644 | } |
1645 | |
1646 | void Editor::toggleSmartInsertDelete() |
1647 | { |
1648 | if (client()) |
1649 | client()->toggleSmartInsertDelete(); |
1650 | } |
1651 | |
1652 | bool Editor::isAutomaticQuoteSubstitutionEnabled() |
1653 | { |
1654 | return client() && client()->isAutomaticQuoteSubstitutionEnabled(); |
1655 | } |
1656 | |
1657 | void Editor::toggleAutomaticQuoteSubstitution() |
1658 | { |
1659 | if (client()) |
1660 | client()->toggleAutomaticQuoteSubstitution(); |
1661 | } |
1662 | |
1663 | bool Editor::isAutomaticLinkDetectionEnabled() |
1664 | { |
1665 | return client() && client()->isAutomaticLinkDetectionEnabled(); |
1666 | } |
1667 | |
1668 | void Editor::toggleAutomaticLinkDetection() |
1669 | { |
1670 | if (client()) |
1671 | client()->toggleAutomaticLinkDetection(); |
1672 | } |
1673 | |
1674 | bool Editor::isAutomaticDashSubstitutionEnabled() |
1675 | { |
1676 | return client() && client()->isAutomaticDashSubstitutionEnabled(); |
1677 | } |
1678 | |
1679 | void Editor::toggleAutomaticDashSubstitution() |
1680 | { |
1681 | if (client()) |
1682 | client()->toggleAutomaticDashSubstitution(); |
1683 | } |
1684 | |
1685 | bool Editor::isAutomaticTextReplacementEnabled() |
1686 | { |
1687 | return client() && client()->isAutomaticTextReplacementEnabled(); |
1688 | } |
1689 | |
1690 | void Editor::toggleAutomaticTextReplacement() |
1691 | { |
1692 | if (client()) |
1693 | client()->toggleAutomaticTextReplacement(); |
1694 | } |
1695 | |
1696 | bool Editor::isAutomaticSpellingCorrectionEnabled() |
1697 | { |
1698 | return m_alternativeTextController->isAutomaticSpellingCorrectionEnabled(); |
1699 | } |
1700 | |
1701 | void Editor::toggleAutomaticSpellingCorrection() |
1702 | { |
1703 | if (client()) |
1704 | client()->toggleAutomaticSpellingCorrection(); |
1705 | } |
1706 | |
1707 | #endif |
1708 | |
1709 | bool Editor::shouldEndEditing(Range* range) |
1710 | { |
1711 | return client() && client()->shouldEndEditing(range); |
1712 | } |
1713 | |
1714 | bool Editor::shouldBeginEditing(Range* range) |
1715 | { |
1716 | return client() && client()->shouldBeginEditing(range); |
1717 | } |
1718 | |
1719 | void Editor::clearUndoRedoOperations() |
1720 | { |
1721 | if (client()) |
1722 | client()->clearUndoRedoOperations(); |
1723 | } |
1724 | |
1725 | bool Editor::canUndo() const |
1726 | { |
1727 | return client() && client()->canUndo(); |
1728 | } |
1729 | |
1730 | void Editor::undo() |
1731 | { |
1732 | if (client()) |
1733 | client()->undo(); |
1734 | } |
1735 | |
1736 | bool Editor::canRedo() const |
1737 | { |
1738 | return client() && client()->canRedo(); |
1739 | } |
1740 | |
1741 | void Editor::redo() |
1742 | { |
1743 | if (client()) |
1744 | client()->redo(); |
1745 | } |
1746 | |
1747 | void Editor::registerCustomUndoStep(Ref<CustomUndoStep>&& undoStep) |
1748 | { |
1749 | ASSERT(RuntimeEnabledFeatures::sharedFeatures().undoManagerAPIEnabled()); |
1750 | if (auto* client = this->client()) |
1751 | client->registerUndoStep(WTFMove(undoStep)); |
1752 | } |
1753 | |
1754 | void Editor::didBeginEditing() |
1755 | { |
1756 | if (client()) |
1757 | client()->didBeginEditing(); |
1758 | } |
1759 | |
1760 | void Editor::didEndEditing() |
1761 | { |
1762 | if (client()) |
1763 | client()->didEndEditing(); |
1764 | } |
1765 | |
1766 | void Editor::willWriteSelectionToPasteboard(Range* range) |
1767 | { |
1768 | if (client()) |
1769 | client()->willWriteSelectionToPasteboard(range); |
1770 | } |
1771 | |
1772 | void Editor::didWriteSelectionToPasteboard() |
1773 | { |
1774 | if (client()) |
1775 | client()->didWriteSelectionToPasteboard(); |
1776 | } |
1777 | |
1778 | void Editor::toggleBold() |
1779 | { |
1780 | command("ToggleBold" ).execute(); |
1781 | } |
1782 | |
1783 | void Editor::toggleUnderline() |
1784 | { |
1785 | command("ToggleUnderline" ).execute(); |
1786 | } |
1787 | |
1788 | void Editor::setBaseWritingDirection(WritingDirection direction) |
1789 | { |
1790 | #if PLATFORM(IOS_FAMILY) |
1791 | if (inSameParagraph(m_frame.selection().selection().visibleStart(), m_frame.selection().selection().visibleEnd()) && |
1792 | baseWritingDirectionForSelectionStart() == direction) |
1793 | return; |
1794 | #endif |
1795 | |
1796 | Element* focusedElement = document().focusedElement(); |
1797 | if (focusedElement && focusedElement->isTextField()) { |
1798 | if (direction == WritingDirection::Natural) |
1799 | return; |
1800 | |
1801 | auto& focusedFormElement = downcast<HTMLTextFormControlElement>(*focusedElement); |
1802 | auto directionValue = direction == WritingDirection::LeftToRight ? "ltr" : "rtl" ; |
1803 | auto writingDirectionInputTypeName = inputTypeNameForEditingAction(EditAction::SetBlockWritingDirection); |
1804 | if (!dispatchBeforeInputEvent(focusedFormElement, writingDirectionInputTypeName, directionValue)) |
1805 | return; |
1806 | |
1807 | focusedFormElement.setAttributeWithoutSynchronization(dirAttr, directionValue); |
1808 | dispatchInputEvent(focusedFormElement, writingDirectionInputTypeName, directionValue); |
1809 | document().updateStyleIfNeeded(); |
1810 | return; |
1811 | } |
1812 | |
1813 | auto style = MutableStyleProperties::create(); |
1814 | style->setProperty(CSSPropertyDirection, direction == WritingDirection::LeftToRight ? "ltr" : direction == WritingDirection::RightToLeft ? "rtl" : "inherit" , false); |
1815 | applyParagraphStyleToSelection(style.ptr(), EditAction::SetBlockWritingDirection); |
1816 | } |
1817 | |
1818 | WritingDirection Editor::baseWritingDirectionForSelectionStart() const |
1819 | { |
1820 | auto result = WritingDirection::LeftToRight; |
1821 | |
1822 | Position pos = m_frame.selection().selection().visibleStart().deepEquivalent(); |
1823 | Node* node = pos.deprecatedNode(); |
1824 | if (!node) |
1825 | return result; |
1826 | |
1827 | auto renderer = node->renderer(); |
1828 | if (!renderer) |
1829 | return result; |
1830 | |
1831 | if (!renderer->isRenderBlockFlow()) { |
1832 | renderer = renderer->containingBlock(); |
1833 | if (!renderer) |
1834 | return result; |
1835 | } |
1836 | |
1837 | switch (renderer->style().direction()) { |
1838 | case TextDirection::LTR: |
1839 | return WritingDirection::LeftToRight; |
1840 | case TextDirection::RTL: |
1841 | return WritingDirection::RightToLeft; |
1842 | } |
1843 | |
1844 | return result; |
1845 | } |
1846 | |
1847 | void Editor::selectComposition() |
1848 | { |
1849 | RefPtr<Range> range = compositionRange(); |
1850 | if (!range) |
1851 | return; |
1852 | |
1853 | // The composition can start inside a composed character sequence, so we have to override checks. |
1854 | // See <http://bugs.webkit.org/show_bug.cgi?id=15781> |
1855 | VisibleSelection selection; |
1856 | selection.setWithoutValidation(range->startPosition(), range->endPosition()); |
1857 | m_frame.selection().setSelection(selection, { }); |
1858 | } |
1859 | |
1860 | void Editor::confirmComposition() |
1861 | { |
1862 | if (!m_compositionNode) |
1863 | return; |
1864 | setComposition(m_compositionNode->data().substring(m_compositionStart, m_compositionEnd - m_compositionStart), ConfirmComposition); |
1865 | } |
1866 | |
1867 | void Editor::cancelComposition() |
1868 | { |
1869 | if (!m_compositionNode) |
1870 | return; |
1871 | setComposition(emptyString(), CancelComposition); |
1872 | } |
1873 | |
1874 | bool Editor::cancelCompositionIfSelectionIsInvalid() |
1875 | { |
1876 | unsigned start; |
1877 | unsigned end; |
1878 | if (!hasComposition() || ignoreSelectionChanges() || getCompositionSelection(start, end)) |
1879 | return false; |
1880 | |
1881 | cancelComposition(); |
1882 | return true; |
1883 | } |
1884 | |
1885 | void Editor::confirmComposition(const String& text) |
1886 | { |
1887 | setComposition(text, ConfirmComposition); |
1888 | } |
1889 | |
1890 | class SetCompositionScope { |
1891 | public: |
1892 | SetCompositionScope(Frame& frame) |
1893 | : m_frame(frame) |
1894 | , m_typingGestureIndicator(frame) |
1895 | { |
1896 | m_frame->editor().setIgnoreSelectionChanges(true); |
1897 | } |
1898 | |
1899 | ~SetCompositionScope() |
1900 | { |
1901 | m_frame->editor().setIgnoreSelectionChanges(false); |
1902 | if (auto* editorClient = m_frame->editor().client()) |
1903 | editorClient->didUpdateComposition(); |
1904 | } |
1905 | |
1906 | Ref<Frame> m_frame; |
1907 | UserTypingGestureIndicator m_typingGestureIndicator; |
1908 | }; |
1909 | |
1910 | void Editor::setComposition(const String& text, SetCompositionMode mode) |
1911 | { |
1912 | ASSERT(mode == ConfirmComposition || mode == CancelComposition); |
1913 | SetCompositionScope setCompositionScope(m_frame); |
1914 | |
1915 | if (mode == CancelComposition) |
1916 | ASSERT(text == emptyString()); |
1917 | else |
1918 | selectComposition(); |
1919 | |
1920 | m_compositionNode = nullptr; |
1921 | m_customCompositionUnderlines.clear(); |
1922 | |
1923 | if (m_frame.selection().isNone()) |
1924 | return; |
1925 | |
1926 | // Always delete the current composition before inserting the finalized composition text if we're confirming our composition. |
1927 | // Our default behavior (if the beforeinput event is not prevented) is to insert the finalized composition text back in. |
1928 | // We pass TypingCommand::TextCompositionPending here to indicate that we are deleting the pending composition. |
1929 | if (mode != CancelComposition) |
1930 | TypingCommand::deleteSelection(document(), 0, TypingCommand::TextCompositionPending); |
1931 | |
1932 | insertTextForConfirmedComposition(text); |
1933 | |
1934 | if (auto* target = document().focusedElement()) |
1935 | target->dispatchEvent(CompositionEvent::create(eventNames().compositionendEvent, document().windowProxy(), text)); |
1936 | |
1937 | if (mode == CancelComposition) { |
1938 | // An open typing command that disagrees about current selection would cause issues with typing later on. |
1939 | TypingCommand::closeTyping(&m_frame); |
1940 | } |
1941 | } |
1942 | |
1943 | void Editor::setComposition(const String& text, const Vector<CompositionUnderline>& underlines, unsigned selectionStart, unsigned selectionEnd) |
1944 | { |
1945 | SetCompositionScope setCompositionScope(m_frame); |
1946 | |
1947 | // Updates styles before setting selection for composition to prevent |
1948 | // inserting the previous composition text into text nodes oddly. |
1949 | // See https://bugs.webkit.org/show_bug.cgi?id=46868 |
1950 | document().updateStyleIfNeeded(); |
1951 | |
1952 | selectComposition(); |
1953 | |
1954 | if (m_frame.selection().isNone()) |
1955 | return; |
1956 | |
1957 | String originalText = selectedText(); |
1958 | bool isStartingToRecomposeExistingRange = !text.isEmpty() && selectionStart < selectionEnd && !hasComposition(); |
1959 | if (isStartingToRecomposeExistingRange) { |
1960 | // We pass TypingCommand::TextCompositionFinal here to indicate that we are removing composition text that has been finalized. |
1961 | TypingCommand::deleteSelection(document(), 0, TypingCommand::TextCompositionFinal); |
1962 | const VisibleSelection& currentSelection = m_frame.selection().selection(); |
1963 | if (currentSelection.isRange()) { |
1964 | // If deletion was prevented, then we need to collapse the selection to the end so that the original text will not be recomposed. |
1965 | m_frame.selection().setSelection({ currentSelection.end(), currentSelection.end() }); |
1966 | } |
1967 | } |
1968 | |
1969 | #if PLATFORM(IOS_FAMILY) |
1970 | client()->startDelayingAndCoalescingContentChangeNotifications(); |
1971 | #endif |
1972 | |
1973 | Element* target = document().focusedElement(); |
1974 | if (target) { |
1975 | // Dispatch an appropriate composition event to the focused node. |
1976 | // We check the composition status and choose an appropriate composition event since this |
1977 | // function is used for three purposes: |
1978 | // 1. Starting a new composition. |
1979 | // Send a compositionstart and a compositionupdate event when this function creates |
1980 | // a new composition node, i.e. |
1981 | // m_compositionNode == 0 && !text.isEmpty(). |
1982 | // Sending a compositionupdate event at this time ensures that at least one |
1983 | // compositionupdate event is dispatched. |
1984 | // 2. Updating the existing composition node. |
1985 | // Send a compositionupdate event when this function updates the existing composition |
1986 | // node, i.e. m_compositionNode != 0 && !text.isEmpty(). |
1987 | // 3. Canceling the ongoing composition. |
1988 | // Send a compositionend event when function deletes the existing composition node, i.e. |
1989 | // m_compositionNode != 0 && test.isEmpty(). |
1990 | RefPtr<CompositionEvent> event; |
1991 | if (!m_compositionNode) { |
1992 | // We should send a compositionstart event only when the given text is not empty because this |
1993 | // function doesn't create a composition node when the text is empty. |
1994 | if (!text.isEmpty()) { |
1995 | target->dispatchEvent(CompositionEvent::create(eventNames().compositionstartEvent, document().windowProxy(), originalText)); |
1996 | event = CompositionEvent::create(eventNames().compositionupdateEvent, document().windowProxy(), text); |
1997 | } |
1998 | } else if (!text.isEmpty()) |
1999 | event = CompositionEvent::create(eventNames().compositionupdateEvent, document().windowProxy(), text); |
2000 | |
2001 | if (event) |
2002 | target->dispatchEvent(*event); |
2003 | } |
2004 | |
2005 | // If text is empty, then delete the old composition here. If text is non-empty, InsertTextCommand::input |
2006 | // will delete the old composition with an optimized replace operation. |
2007 | if (text.isEmpty()) { |
2008 | TypingCommand::deleteSelection(document(), TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionPending); |
2009 | if (target) |
2010 | target->dispatchEvent(CompositionEvent::create(eventNames().compositionendEvent, document().windowProxy(), text)); |
2011 | } |
2012 | |
2013 | m_compositionNode = nullptr; |
2014 | m_customCompositionUnderlines.clear(); |
2015 | |
2016 | if (!text.isEmpty()) { |
2017 | TypingCommand::insertText(document(), text, TypingCommand::SelectInsertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextCompositionPending); |
2018 | |
2019 | // Find out what node has the composition now. |
2020 | Position base = m_frame.selection().selection().base().downstream(); |
2021 | Position extent = m_frame.selection().selection().extent(); |
2022 | Node* baseNode = base.deprecatedNode(); |
2023 | unsigned baseOffset = base.deprecatedEditingOffset(); |
2024 | Node* extentNode = extent.deprecatedNode(); |
2025 | unsigned extentOffset = extent.deprecatedEditingOffset(); |
2026 | |
2027 | if (is<Text>(baseNode) && baseNode == extentNode && baseOffset + text.length() == extentOffset) { |
2028 | m_compositionNode = downcast<Text>(baseNode); |
2029 | m_compositionStart = baseOffset; |
2030 | m_compositionEnd = extentOffset; |
2031 | m_customCompositionUnderlines = underlines; |
2032 | for (auto& underline : m_customCompositionUnderlines) { |
2033 | underline.startOffset += baseOffset; |
2034 | underline.endOffset += baseOffset; |
2035 | } |
2036 | if (baseNode->renderer()) |
2037 | baseNode->renderer()->repaint(); |
2038 | |
2039 | unsigned start = std::min(baseOffset + selectionStart, extentOffset); |
2040 | unsigned end = std::min(std::max(start, baseOffset + selectionEnd), extentOffset); |
2041 | auto selectedRange = Range::create(baseNode->document(), baseNode, start, baseNode, end); |
2042 | m_frame.selection().setSelectedRange(selectedRange.ptr(), DOWNSTREAM, FrameSelection::ShouldCloseTyping::No); |
2043 | } |
2044 | } |
2045 | |
2046 | #if PLATFORM(IOS_FAMILY) |
2047 | client()->stopDelayingAndCoalescingContentChangeNotifications(); |
2048 | #endif |
2049 | } |
2050 | |
2051 | void Editor::ignoreSpelling() |
2052 | { |
2053 | if (!client()) |
2054 | return; |
2055 | |
2056 | RefPtr<Range> selectedRange = m_frame.selection().toNormalizedRange(); |
2057 | if (selectedRange) |
2058 | document().markers().removeMarkers(*selectedRange, DocumentMarker::Spelling); |
2059 | |
2060 | String text = selectedText(); |
2061 | ASSERT(text.length()); |
2062 | textChecker()->ignoreWordInSpellDocument(text); |
2063 | } |
2064 | |
2065 | void Editor::learnSpelling() |
2066 | { |
2067 | if (!client()) |
2068 | return; |
2069 | |
2070 | // FIXME: On Mac OS X, when use "learn" button on "Spelling and Grammar" panel, we don't call this function. It should remove misspelling markers around the learned word, see <rdar://problem/5396072>. |
2071 | |
2072 | RefPtr<Range> selectedRange = m_frame.selection().toNormalizedRange(); |
2073 | if (selectedRange) |
2074 | document().markers().removeMarkers(*selectedRange, DocumentMarker::Spelling); |
2075 | |
2076 | String text = selectedText(); |
2077 | ASSERT(text.length()); |
2078 | textChecker()->learnWord(text); |
2079 | } |
2080 | |
2081 | #if !PLATFORM(IOS_FAMILY) |
2082 | |
2083 | void Editor::advanceToNextMisspelling(bool startBeforeSelection) |
2084 | { |
2085 | Ref<Frame> protection(m_frame); |
2086 | |
2087 | // The basic approach is to search in two phases - from the selection end to the end of the doc, and |
2088 | // then we wrap and search from the doc start to (approximately) where we started. |
2089 | |
2090 | // Start at the end of the selection, search to edge of document. Starting at the selection end makes |
2091 | // repeated "check spelling" commands work. |
2092 | VisibleSelection selection(m_frame.selection().selection()); |
2093 | Ref<Range> spellingSearchRange = rangeOfContents(document()); |
2094 | |
2095 | bool startedWithSelection = false; |
2096 | if (selection.start().deprecatedNode()) { |
2097 | startedWithSelection = true; |
2098 | if (startBeforeSelection) { |
2099 | VisiblePosition start(selection.visibleStart()); |
2100 | // We match AppKit's rule: Start 1 character before the selection. |
2101 | VisiblePosition oneBeforeStart = start.previous(); |
2102 | setStart(spellingSearchRange.ptr(), oneBeforeStart.isNotNull() ? oneBeforeStart : start); |
2103 | } else |
2104 | setStart(spellingSearchRange.ptr(), selection.visibleEnd()); |
2105 | } |
2106 | |
2107 | Position position = spellingSearchRange->startPosition(); |
2108 | if (!isEditablePosition(position)) { |
2109 | // This shouldn't happen in very often because the Spelling menu items aren't enabled unless the |
2110 | // selection is editable. |
2111 | // This can happen in Mail for a mix of non-editable and editable content (like Stationary), |
2112 | // when spell checking the whole document before sending the message. |
2113 | // In that case the document might not be editable, but there are editable pockets that need to be spell checked. |
2114 | |
2115 | position = VisiblePosition(firstEditablePositionAfterPositionInRoot(position, document().documentElement())).deepEquivalent(); |
2116 | if (position.isNull()) |
2117 | return; |
2118 | |
2119 | Position rangeCompliantPosition = position.parentAnchoredEquivalent(); |
2120 | if (rangeCompliantPosition.deprecatedNode()) |
2121 | spellingSearchRange->setStart(*rangeCompliantPosition.deprecatedNode(), rangeCompliantPosition.deprecatedEditingOffset()); |
2122 | startedWithSelection = false; // won't need to wrap |
2123 | } |
2124 | |
2125 | // topNode defines the whole range we want to operate on |
2126 | auto* topNode = highestEditableRoot(position); |
2127 | // FIXME: lastOffsetForEditing() is wrong here if editingIgnoresContent(highestEditableRoot()) returns true (e.g. a <table>) |
2128 | if (topNode) |
2129 | spellingSearchRange->setEnd(*topNode, lastOffsetForEditing(*topNode)); |
2130 | |
2131 | // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking |
2132 | // at a word boundary. Going back by one char and then forward by a word does the trick. |
2133 | if (startedWithSelection) { |
2134 | VisiblePosition oneBeforeStart = startVisiblePosition(spellingSearchRange.ptr(), DOWNSTREAM).previous(); |
2135 | if (oneBeforeStart.isNotNull()) |
2136 | setStart(spellingSearchRange.ptr(), endOfWord(oneBeforeStart)); |
2137 | // else we were already at the start of the editable node |
2138 | } |
2139 | |
2140 | if (spellingSearchRange->collapsed()) |
2141 | return; // nothing to search in |
2142 | |
2143 | // Get the spell checker if it is available |
2144 | if (!client()) |
2145 | return; |
2146 | |
2147 | // We go to the end of our first range instead of the start of it, just to be sure |
2148 | // we don't get foiled by any word boundary problems at the start. It means we might |
2149 | // do a tiny bit more searching. |
2150 | Node& searchEndNodeAfterWrap = spellingSearchRange->endContainer(); |
2151 | int searchEndOffsetAfterWrap = spellingSearchRange->endOffset(); |
2152 | |
2153 | int misspellingOffset = 0; |
2154 | GrammarDetail grammarDetail; |
2155 | int grammarPhraseOffset = 0; |
2156 | RefPtr<Range> grammarSearchRange; |
2157 | String badGrammarPhrase; |
2158 | String misspelledWord; |
2159 | |
2160 | bool isSpelling = true; |
2161 | int foundOffset = 0; |
2162 | String foundItem; |
2163 | RefPtr<Range> firstMisspellingRange; |
2164 | if (unifiedTextCheckerEnabled()) { |
2165 | grammarSearchRange = spellingSearchRange->cloneRange(); |
2166 | foundItem = TextCheckingHelper(*client(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); |
2167 | if (isSpelling) { |
2168 | misspelledWord = foundItem; |
2169 | misspellingOffset = foundOffset; |
2170 | } else { |
2171 | badGrammarPhrase = foundItem; |
2172 | grammarPhraseOffset = foundOffset; |
2173 | } |
2174 | } else { |
2175 | misspelledWord = TextCheckingHelper(*client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); |
2176 | |
2177 | #if USE(GRAMMAR_CHECKING) |
2178 | grammarSearchRange = spellingSearchRange->cloneRange(); |
2179 | if (!misspelledWord.isEmpty()) { |
2180 | // Stop looking at start of next misspelled word |
2181 | CharacterIterator chars(*grammarSearchRange); |
2182 | chars.advance(misspellingOffset); |
2183 | grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset()); |
2184 | } |
2185 | |
2186 | if (isGrammarCheckingEnabled()) |
2187 | badGrammarPhrase = TextCheckingHelper(*client(), *grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false); |
2188 | #endif |
2189 | } |
2190 | |
2191 | // If we found neither bad grammar nor a misspelled word, wrap and try again (but don't bother if we started at the beginning of the |
2192 | // block rather than at a selection). |
2193 | if (startedWithSelection && !misspelledWord && !badGrammarPhrase) { |
2194 | if (topNode) |
2195 | spellingSearchRange->setStart(*topNode, 0); |
2196 | // going until the end of the very first chunk we tested is far enough |
2197 | spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap); |
2198 | |
2199 | if (unifiedTextCheckerEnabled()) { |
2200 | grammarSearchRange = spellingSearchRange->cloneRange(); |
2201 | foundItem = TextCheckingHelper(*client(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); |
2202 | if (isSpelling) { |
2203 | misspelledWord = foundItem; |
2204 | misspellingOffset = foundOffset; |
2205 | } else { |
2206 | badGrammarPhrase = foundItem; |
2207 | grammarPhraseOffset = foundOffset; |
2208 | } |
2209 | } else { |
2210 | misspelledWord = TextCheckingHelper(*client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); |
2211 | |
2212 | #if USE(GRAMMAR_CHECKING) |
2213 | grammarSearchRange = spellingSearchRange->cloneRange(); |
2214 | if (!misspelledWord.isEmpty()) { |
2215 | // Stop looking at start of next misspelled word |
2216 | CharacterIterator chars(*grammarSearchRange); |
2217 | chars.advance(misspellingOffset); |
2218 | grammarSearchRange->setEnd(chars.range()->startContainer(), chars.range()->startOffset()); |
2219 | } |
2220 | |
2221 | if (isGrammarCheckingEnabled()) |
2222 | badGrammarPhrase = TextCheckingHelper(*client(), *grammarSearchRange).findFirstBadGrammar(grammarDetail, grammarPhraseOffset, false); |
2223 | #endif |
2224 | } |
2225 | } |
2226 | |
2227 | #if !USE(GRAMMAR_CHECKING) |
2228 | ASSERT(badGrammarPhrase.isEmpty()); |
2229 | UNUSED_PARAM(grammarPhraseOffset); |
2230 | #else |
2231 | if (!badGrammarPhrase.isEmpty()) { |
2232 | // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar |
2233 | // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling |
2234 | // panel, and store a marker so we draw the green squiggle later. |
2235 | |
2236 | ASSERT(badGrammarPhrase.length() > 0); |
2237 | ASSERT(grammarDetail.location != -1 && grammarDetail.length > 0); |
2238 | |
2239 | // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph |
2240 | auto badGrammarRange = TextIterator::subrange(*grammarSearchRange, grammarPhraseOffset + grammarDetail.location, grammarDetail.length); |
2241 | m_frame.selection().setSelection(VisibleSelection(badGrammarRange, SEL_DEFAULT_AFFINITY)); |
2242 | m_frame.selection().revealSelection(); |
2243 | |
2244 | client()->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail); |
2245 | document().markers().addMarker(badGrammarRange, DocumentMarker::Grammar, grammarDetail.userDescription); |
2246 | } else |
2247 | #endif |
2248 | if (!misspelledWord.isEmpty()) { |
2249 | // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store |
2250 | // a marker so we draw the red squiggle later. |
2251 | |
2252 | auto misspellingRange = TextIterator::subrange(spellingSearchRange, misspellingOffset, misspelledWord.length()); |
2253 | m_frame.selection().setSelection(VisibleSelection(misspellingRange, DOWNSTREAM)); |
2254 | m_frame.selection().revealSelection(); |
2255 | |
2256 | client()->updateSpellingUIWithMisspelledWord(misspelledWord); |
2257 | document().markers().addMarker(misspellingRange, DocumentMarker::Spelling); |
2258 | } |
2259 | } |
2260 | |
2261 | #endif // !PLATFORM(IOS_FAMILY) |
2262 | |
2263 | String Editor::misspelledWordAtCaretOrRange(Node* clickedNode) const |
2264 | { |
2265 | if (!isContinuousSpellCheckingEnabled() || !clickedNode || !isSpellCheckingEnabledFor(clickedNode)) |
2266 | return String(); |
2267 | |
2268 | VisibleSelection selection = m_frame.selection().selection(); |
2269 | if (!selection.isContentEditable() || selection.isNone()) |
2270 | return String(); |
2271 | |
2272 | VisibleSelection wordSelection(selection.base()); |
2273 | wordSelection.expandUsingGranularity(WordGranularity); |
2274 | RefPtr<Range> wordRange = wordSelection.toNormalizedRange(); |
2275 | if (!wordRange) |
2276 | return String(); |
2277 | |
2278 | // In compliance with GTK+ applications, additionally allow to provide suggestions when the current |
2279 | // selection exactly match the word selection. |
2280 | if (selection.isRange() && !areRangesEqual(wordRange.get(), selection.toNormalizedRange().get())) |
2281 | return String(); |
2282 | |
2283 | String word = wordRange->text(); |
2284 | if (word.isEmpty() || !client()) |
2285 | return String(); |
2286 | |
2287 | int wordLength = word.length(); |
2288 | int misspellingLocation = -1; |
2289 | int misspellingLength = 0; |
2290 | textChecker()->checkSpellingOfString(word, &misspellingLocation, &misspellingLength); |
2291 | |
2292 | return misspellingLength == wordLength ? word : String(); |
2293 | } |
2294 | |
2295 | String Editor::misspelledSelectionString() const |
2296 | { |
2297 | String selectedString = selectedText(); |
2298 | int length = selectedString.length(); |
2299 | if (!length || !client()) |
2300 | return String(); |
2301 | |
2302 | int misspellingLocation = -1; |
2303 | int misspellingLength = 0; |
2304 | textChecker()->checkSpellingOfString(selectedString, &misspellingLocation, &misspellingLength); |
2305 | |
2306 | // The selection only counts as misspelled if the selected text is exactly one misspelled word |
2307 | if (misspellingLength != length) |
2308 | return String(); |
2309 | |
2310 | // Update the spelling panel to be displaying this error (whether or not the spelling panel is on screen). |
2311 | // This is necessary to make a subsequent call to [NSSpellChecker ignoreWord:inSpellDocumentWithTag:] work |
2312 | // correctly; that call behaves differently based on whether the spelling panel is displaying a misspelling |
2313 | // or a grammar error. |
2314 | client()->updateSpellingUIWithMisspelledWord(selectedString); |
2315 | |
2316 | return selectedString; |
2317 | } |
2318 | |
2319 | bool Editor::isSelectionUngrammatical() |
2320 | { |
2321 | #if USE(GRAMMAR_CHECKING) |
2322 | RefPtr<Range> range = m_frame.selection().toNormalizedRange(); |
2323 | if (!range || !client()) |
2324 | return false; |
2325 | return TextCheckingHelper(*client(), *range).isUngrammatical(); |
2326 | #else |
2327 | return false; |
2328 | #endif |
2329 | } |
2330 | |
2331 | Vector<String> Editor::guessesForMisspelledWord(const String& word) const |
2332 | { |
2333 | ASSERT(word.length()); |
2334 | |
2335 | Vector<String> guesses; |
2336 | if (client()) |
2337 | textChecker()->getGuessesForWord(word, String(), m_frame.selection().selection(), guesses); |
2338 | return guesses; |
2339 | } |
2340 | |
2341 | Vector<String> Editor::guessesForMisspelledOrUngrammatical(bool& misspelled, bool& ungrammatical) |
2342 | { |
2343 | if (unifiedTextCheckerEnabled()) { |
2344 | RefPtr<Range> range; |
2345 | VisibleSelection selection = m_frame.selection().selection(); |
2346 | if (selection.isCaret() && behavior().shouldAllowSpellingSuggestionsWithoutSelection()) { |
2347 | VisibleSelection wordSelection = VisibleSelection(selection.base()); |
2348 | wordSelection.expandUsingGranularity(WordGranularity); |
2349 | range = wordSelection.toNormalizedRange(); |
2350 | } else |
2351 | range = selection.toNormalizedRange(); |
2352 | if (!range || !client()) |
2353 | return Vector<String>(); |
2354 | return TextCheckingHelper(*client(), *range).guessesForMisspelledOrUngrammaticalRange(isGrammarCheckingEnabled(), misspelled, ungrammatical); |
2355 | } |
2356 | |
2357 | String misspelledWord = behavior().shouldAllowSpellingSuggestionsWithoutSelection() ? misspelledWordAtCaretOrRange(document().focusedElement()) : misspelledSelectionString(); |
2358 | misspelled = !misspelledWord.isEmpty(); |
2359 | // Only unified text checker supports guesses for ungrammatical phrases. |
2360 | ungrammatical = false; |
2361 | |
2362 | if (misspelled) |
2363 | return guessesForMisspelledWord(misspelledWord); |
2364 | return Vector<String>(); |
2365 | } |
2366 | |
2367 | void Editor::showSpellingGuessPanel() |
2368 | { |
2369 | if (!client()) { |
2370 | LOG_ERROR("No NSSpellChecker" ); |
2371 | return; |
2372 | } |
2373 | |
2374 | if (client()->spellingUIIsShowing()) { |
2375 | client()->showSpellingUI(false); |
2376 | return; |
2377 | } |
2378 | |
2379 | #if !PLATFORM(IOS_FAMILY) |
2380 | advanceToNextMisspelling(true); |
2381 | #endif |
2382 | client()->showSpellingUI(true); |
2383 | } |
2384 | |
2385 | bool Editor::spellingPanelIsShowing() |
2386 | { |
2387 | if (!client()) |
2388 | return false; |
2389 | return client()->spellingUIIsShowing(); |
2390 | } |
2391 | |
2392 | void Editor::clearMisspellingsAndBadGrammar(const VisibleSelection& movingSelection) |
2393 | { |
2394 | if (auto selectedRange = movingSelection.toNormalizedRange()) |
2395 | document().markers().removeMarkers(*selectedRange, { DocumentMarker::Spelling, DocumentMarker::Grammar }); |
2396 | } |
2397 | |
2398 | void Editor::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelection) |
2399 | { |
2400 | markMisspellingsAndBadGrammar(movingSelection, isContinuousSpellCheckingEnabled() && isGrammarCheckingEnabled(), movingSelection); |
2401 | } |
2402 | |
2403 | void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping, bool doReplacement) |
2404 | { |
2405 | Ref<Frame> protection(m_frame); |
2406 | |
2407 | if (platformDrivenTextCheckerEnabled()) |
2408 | return; |
2409 | |
2410 | #if PLATFORM(IOS_FAMILY) |
2411 | UNUSED_PARAM(selectionAfterTyping); |
2412 | UNUSED_PARAM(doReplacement); |
2413 | OptionSet<TextCheckingType> textCheckingOptions; |
2414 | if (isContinuousSpellCheckingEnabled()) |
2415 | textCheckingOptions.add(TextCheckingType::Spelling); |
2416 | if (!textCheckingOptions.contains(TextCheckingType::Spelling)) |
2417 | return; |
2418 | |
2419 | VisibleSelection adjacentWords = VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary)); |
2420 | auto adjacentWordRange = adjacentWords.toNormalizedRange(); |
2421 | markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, adjacentWordRange.copyRef(), adjacentWordRange.copyRef(), adjacentWordRange.copyRef()); |
2422 | #else |
2423 | #if !USE(AUTOMATIC_TEXT_REPLACEMENT) |
2424 | UNUSED_PARAM(doReplacement); |
2425 | #endif |
2426 | |
2427 | if (unifiedTextCheckerEnabled()) { |
2428 | m_alternativeTextController->applyPendingCorrection(selectionAfterTyping); |
2429 | |
2430 | OptionSet<TextCheckingType> textCheckingOptions; |
2431 | |
2432 | if (isContinuousSpellCheckingEnabled()) |
2433 | textCheckingOptions.add(TextCheckingType::Spelling); |
2434 | |
2435 | #if USE(AUTOMATIC_TEXT_REPLACEMENT) |
2436 | if (doReplacement |
2437 | && (isAutomaticQuoteSubstitutionEnabled() |
2438 | || isAutomaticLinkDetectionEnabled() |
2439 | || isAutomaticDashSubstitutionEnabled() |
2440 | || isAutomaticTextReplacementEnabled() |
2441 | || (textCheckingOptions.contains(TextCheckingType::Spelling) && isAutomaticSpellingCorrectionEnabled()))) |
2442 | textCheckingOptions.add(TextCheckingType::Replacement); |
2443 | #endif |
2444 | if (!textCheckingOptions.contains(TextCheckingType::Spelling) && !textCheckingOptions.contains(TextCheckingType::Replacement)) |
2445 | return; |
2446 | |
2447 | if (isGrammarCheckingEnabled()) |
2448 | textCheckingOptions.add(TextCheckingType::Grammar); |
2449 | |
2450 | auto sentenceStart = startOfSentence(wordStart); |
2451 | auto sentenceEnd = endOfSentence(wordStart); |
2452 | VisibleSelection fullSentence(sentenceStart, sentenceEnd); |
2453 | auto fullSentenceRange = fullSentence.toNormalizedRange(); |
2454 | if (!fullSentenceRange) |
2455 | return; |
2456 | |
2457 | auto spellCheckingStart = wordStart; |
2458 | auto spellCheckingEnd = wordStart; |
2459 | |
2460 | // FIXME: The following logic doesn't handle adding spelling markers due to retro sentence corrections when an |
2461 | // incorrectly spelled range is separated from the start of the current word by a text node inside an element |
2462 | // with spellcheck disabled. To fix this, we need to refactor markAllMisspellingsAndBadGrammarInRanges so that |
2463 | // it can handle a list of spelling ranges, alongside the grammar range. |
2464 | while (sentenceStart < spellCheckingStart) { |
2465 | auto previousPosition = spellCheckingStart.previous(CannotCrossEditingBoundary); |
2466 | if (previousPosition.isNull() || previousPosition == spellCheckingStart) |
2467 | break; |
2468 | |
2469 | auto* container = previousPosition.deepEquivalent().downstream().containerNode(); |
2470 | if (auto* containerElement = is<Element>(container) ? downcast<Element>(container) : container->parentElement()) { |
2471 | if (!containerElement->isSpellCheckingEnabled()) |
2472 | break; |
2473 | } |
2474 | |
2475 | spellCheckingStart = previousPosition; |
2476 | } |
2477 | |
2478 | while (spellCheckingEnd < sentenceEnd) { |
2479 | auto nextPosition = spellCheckingEnd.next(CannotCrossEditingBoundary); |
2480 | if (nextPosition.isNull() || nextPosition == spellCheckingEnd) |
2481 | break; |
2482 | |
2483 | auto* container = nextPosition.deepEquivalent().upstream().containerNode(); |
2484 | if (auto* containerElement = is<Element>(container) ? downcast<Element>(container) : container->parentElement()) { |
2485 | if (!containerElement->isSpellCheckingEnabled()) |
2486 | break; |
2487 | } |
2488 | |
2489 | spellCheckingEnd = nextPosition; |
2490 | } |
2491 | |
2492 | auto spellCheckingRange = VisibleSelection(spellCheckingStart, spellCheckingEnd).toNormalizedRange(); |
2493 | if (!spellCheckingRange) |
2494 | return; |
2495 | |
2496 | auto adjacentWordRange = VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary)).toNormalizedRange(); |
2497 | if (!adjacentWordRange) |
2498 | return; |
2499 | |
2500 | // The spelling and grammar markers in these ranges are recomputed. This is because typing a word may |
2501 | // cause any other part of the current sentence to lose or gain spelling correction markers, due to |
2502 | // sentence retro correction. As such, we expand the spell checking range to encompass as much of the |
2503 | // full sentence as we can, respecting boundaries where spellchecking is disabled. |
2504 | fullSentenceRange->ownerDocument().markers().removeMarkers(*fullSentenceRange, DocumentMarker::Grammar); |
2505 | spellCheckingRange->ownerDocument().markers().removeMarkers(*spellCheckingRange, DocumentMarker::Spelling); |
2506 | markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, WTFMove(spellCheckingRange), WTFMove(adjacentWordRange), WTFMove(fullSentenceRange)); |
2507 | return; |
2508 | } |
2509 | |
2510 | if (!isContinuousSpellCheckingEnabled()) |
2511 | return; |
2512 | |
2513 | // Check spelling of one word |
2514 | RefPtr<Range> misspellingRange; |
2515 | markMisspellings(VisibleSelection(startOfWord(wordStart, LeftWordIfOnBoundary), endOfWord(wordStart, RightWordIfOnBoundary)), misspellingRange); |
2516 | |
2517 | // Autocorrect the misspelled word. |
2518 | if (!misspellingRange) |
2519 | return; |
2520 | |
2521 | // Get the misspelled word. |
2522 | const String misspelledWord = plainText(misspellingRange.get()); |
2523 | String autocorrectedString = textChecker()->getAutoCorrectSuggestionForMisspelledWord(misspelledWord); |
2524 | |
2525 | // If autocorrected word is non empty, replace the misspelled word by this word. |
2526 | if (!autocorrectedString.isEmpty()) { |
2527 | VisibleSelection newSelection(*misspellingRange, DOWNSTREAM); |
2528 | if (newSelection != m_frame.selection().selection()) { |
2529 | if (!m_frame.selection().shouldChangeSelection(newSelection)) |
2530 | return; |
2531 | m_frame.selection().setSelection(newSelection); |
2532 | } |
2533 | |
2534 | if (!m_frame.editor().shouldInsertText(autocorrectedString, misspellingRange.get(), EditorInsertAction::Typed)) |
2535 | return; |
2536 | m_frame.editor().replaceSelectionWithText(autocorrectedString, SelectReplacement::No, SmartReplace::No, EditAction::Insert); |
2537 | |
2538 | // Reset the charet one character further. |
2539 | m_frame.selection().moveTo(m_frame.selection().selection().end()); |
2540 | m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity); |
2541 | } |
2542 | |
2543 | if (!isGrammarCheckingEnabled()) |
2544 | return; |
2545 | |
2546 | // Check grammar of entire sentence |
2547 | markBadGrammar(VisibleSelection(startOfSentence(wordStart), endOfSentence(wordStart))); |
2548 | #endif |
2549 | } |
2550 | |
2551 | void Editor::markMisspellingsOrBadGrammar(const VisibleSelection& selection, bool checkSpelling, RefPtr<Range>& firstMisspellingRange) |
2552 | { |
2553 | #if !PLATFORM(IOS_FAMILY) |
2554 | // This function is called with a selection already expanded to word boundaries. |
2555 | // Might be nice to assert that here. |
2556 | |
2557 | // This function is used only for as-you-type checking, so if that's off we do nothing. Note that |
2558 | // grammar checking can only be on if spell checking is also on. |
2559 | if (!isContinuousSpellCheckingEnabled()) |
2560 | return; |
2561 | |
2562 | RefPtr<Range> searchRange(selection.toNormalizedRange()); |
2563 | if (!searchRange) |
2564 | return; |
2565 | |
2566 | // If we're not in an editable node, bail. |
2567 | Node& editableNode = searchRange->startContainer(); |
2568 | if (!editableNode.hasEditableStyle()) |
2569 | return; |
2570 | |
2571 | if (!isSpellCheckingEnabledFor(&editableNode)) |
2572 | return; |
2573 | |
2574 | // Get the spell checker if it is available |
2575 | if (!client()) |
2576 | return; |
2577 | |
2578 | TextCheckingHelper checker(*client(), *searchRange); |
2579 | if (checkSpelling) |
2580 | checker.markAllMisspellings(firstMisspellingRange); |
2581 | else { |
2582 | #if USE(GRAMMAR_CHECKING) |
2583 | if (isGrammarCheckingEnabled()) |
2584 | checker.markAllBadGrammar(); |
2585 | #else |
2586 | ASSERT_NOT_REACHED(); |
2587 | #endif |
2588 | } |
2589 | #else |
2590 | UNUSED_PARAM(selection); |
2591 | UNUSED_PARAM(checkSpelling); |
2592 | UNUSED_PARAM(firstMisspellingRange); |
2593 | #endif // !PLATFORM(IOS_FAMILY) |
2594 | } |
2595 | |
2596 | bool Editor::isSpellCheckingEnabledFor(Node* node) const |
2597 | { |
2598 | if (!node) |
2599 | return false; |
2600 | Element* element = is<Element>(*node) ? downcast<Element>(node) : node->parentElement(); |
2601 | if (!element) |
2602 | return false; |
2603 | if (element->isInUserAgentShadowTree()) { |
2604 | if (HTMLTextFormControlElement* textControl = enclosingTextFormControl(firstPositionInOrBeforeNode(element))) |
2605 | return textControl->isSpellCheckingEnabled(); |
2606 | } |
2607 | return element->isSpellCheckingEnabled(); |
2608 | } |
2609 | |
2610 | bool Editor::isSpellCheckingEnabledInFocusedNode() const |
2611 | { |
2612 | return isSpellCheckingEnabledFor(m_frame.selection().selection().start().deprecatedNode()); |
2613 | } |
2614 | |
2615 | void Editor::markMisspellings(const VisibleSelection& selection, RefPtr<Range>& firstMisspellingRange) |
2616 | { |
2617 | markMisspellingsOrBadGrammar(selection, true, firstMisspellingRange); |
2618 | } |
2619 | |
2620 | void Editor::markBadGrammar(const VisibleSelection& selection) |
2621 | { |
2622 | #if USE(GRAMMAR_CHECKING) |
2623 | RefPtr<Range> firstMisspellingRange; |
2624 | markMisspellingsOrBadGrammar(selection, false, firstMisspellingRange); |
2625 | #else |
2626 | ASSERT_NOT_REACHED(); |
2627 | #endif |
2628 | } |
2629 | |
2630 | void Editor::markAllMisspellingsAndBadGrammarInRanges(OptionSet<TextCheckingType> textCheckingOptions, RefPtr<Range>&& spellingRange, RefPtr<Range>&& automaticReplacementRange, RefPtr<Range>&& grammarRange) |
2631 | { |
2632 | if (platformDrivenTextCheckerEnabled()) |
2633 | return; |
2634 | |
2635 | ASSERT(unifiedTextCheckerEnabled()); |
2636 | |
2637 | // There shouldn't be pending autocorrection at this moment. |
2638 | ASSERT(!m_alternativeTextController->hasPendingCorrection()); |
2639 | |
2640 | bool shouldMarkGrammar = textCheckingOptions.contains(TextCheckingType::Grammar); |
2641 | bool shouldShowCorrectionPanel = textCheckingOptions.contains(TextCheckingType::ShowCorrectionPanel); |
2642 | |
2643 | // This function is called with selections already expanded to word boundaries. |
2644 | if (!client() || !spellingRange || (shouldMarkGrammar && !grammarRange)) |
2645 | return; |
2646 | |
2647 | // If we're not in an editable node, bail. |
2648 | Node& editableNode = spellingRange->startContainer(); |
2649 | if (!editableNode.hasEditableStyle()) |
2650 | return; |
2651 | |
2652 | if (!isSpellCheckingEnabledFor(&editableNode)) |
2653 | return; |
2654 | |
2655 | auto rangeToCheck = shouldMarkGrammar ? grammarRange.releaseNonNull() : spellingRange.releaseNonNull(); |
2656 | TextCheckingParagraph paragraphToCheck(rangeToCheck.get()); |
2657 | if (paragraphToCheck.isEmpty()) |
2658 | return; |
2659 | |
2660 | bool asynchronous = m_frame.settings().asynchronousSpellCheckingEnabled() && !shouldShowCorrectionPanel; |
2661 | |
2662 | // In asynchronous mode, we intentionally check paragraph-wide sentence. |
2663 | const auto resolvedOptions = resolveTextCheckingTypeMask(editableNode, textCheckingOptions); |
2664 | auto textReplacementRange = automaticReplacementRange ? makeRef(*automaticReplacementRange) : rangeToCheck.copyRef(); |
2665 | auto request = SpellCheckRequest::create(resolvedOptions, TextCheckingProcessIncremental, asynchronous ? makeRef(paragraphToCheck.paragraphRange()) : WTFMove(rangeToCheck), WTFMove(textReplacementRange), paragraphToCheck.paragraphRange()); |
2666 | if (!request) |
2667 | return; |
2668 | |
2669 | if (asynchronous) { |
2670 | m_spellChecker->requestCheckingFor(request.releaseNonNull()); |
2671 | return; |
2672 | } |
2673 | |
2674 | Vector<TextCheckingResult> results; |
2675 | checkTextOfParagraph(*textChecker(), paragraphToCheck.text(), resolvedOptions, results, m_frame.selection().selection()); |
2676 | markAndReplaceFor(request.releaseNonNull(), results); |
2677 | } |
2678 | |
2679 | static bool isAutomaticTextReplacementType(TextCheckingType type) |
2680 | { |
2681 | switch (type) { |
2682 | case TextCheckingType::None: |
2683 | case TextCheckingType::Spelling: |
2684 | case TextCheckingType::Grammar: |
2685 | return false; |
2686 | case TextCheckingType::Link: |
2687 | case TextCheckingType::Quote: |
2688 | case TextCheckingType::Dash: |
2689 | case TextCheckingType::Replacement: |
2690 | case TextCheckingType::Correction: |
2691 | case TextCheckingType::ShowCorrectionPanel: |
2692 | return true; |
2693 | } |
2694 | ASSERT_NOT_REACHED(); |
2695 | return false; |
2696 | } |
2697 | |
2698 | void Editor::replaceRangeForSpellChecking(Range& rangeToReplace, const String& replacement) |
2699 | { |
2700 | SpellingCorrectionCommand::create(rangeToReplace, replacement)->apply(); |
2701 | } |
2702 | |
2703 | static void correctSpellcheckingPreservingTextCheckingParagraph(TextCheckingParagraph& paragraph, Range& rangeToReplace, const String& replacement, int resultLocation, int resultLength) |
2704 | { |
2705 | auto& scope = downcast<ContainerNode>(paragraph.paragraphRange().startContainer().rootNode()); |
2706 | |
2707 | size_t paragraphLocation; |
2708 | size_t paragraphLength; |
2709 | TextIterator::getLocationAndLengthFromRange(&scope, ¶graph.paragraphRange(), paragraphLocation, paragraphLength); |
2710 | |
2711 | SpellingCorrectionCommand::create(rangeToReplace, replacement)->apply(); |
2712 | |
2713 | // TextCheckingParagraph may be orphaned after SpellingCorrectionCommand mutated DOM. |
2714 | // See <rdar://10305315>, http://webkit.org/b/89526. |
2715 | |
2716 | RefPtr<Range> newParagraphRange = TextIterator::rangeFromLocationAndLength(&scope, paragraphLocation, paragraphLength + replacement.length() - resultLength); |
2717 | |
2718 | auto spellCheckingRange = TextIterator::subrange(*newParagraphRange, resultLocation, replacement.length()); |
2719 | paragraph = TextCheckingParagraph(spellCheckingRange.copyRef(), spellCheckingRange.copyRef(), newParagraphRange.get()); |
2720 | } |
2721 | |
2722 | void Editor::markAndReplaceFor(const SpellCheckRequest& request, const Vector<TextCheckingResult>& results) |
2723 | { |
2724 | Ref<Frame> protection(m_frame); |
2725 | |
2726 | auto textCheckingOptions = request.data().checkingTypes(); |
2727 | TextCheckingParagraph paragraph(request.checkingRange(), request.automaticReplacementRange(), &request.paragraphRange()); |
2728 | |
2729 | // FIXME: Mark this const once MSVC bug is fixed: <https://developercommunity.visualstudio.com/content/problem/316713/msvc-cant-compile-webkits-optionsetcontainsany.html>. |
2730 | bool shouldPerformReplacement = textCheckingOptions.containsAny({ TextCheckingType::Quote, TextCheckingType::Dash, TextCheckingType::Replacement }); |
2731 | const bool shouldMarkSpelling = textCheckingOptions.contains(TextCheckingType::Spelling); |
2732 | const bool shouldMarkGrammar = textCheckingOptions.contains(TextCheckingType::Grammar); |
2733 | const bool shouldMarkLink = textCheckingOptions.contains(TextCheckingType::Link); |
2734 | const bool shouldShowCorrectionPanel = textCheckingOptions.contains(TextCheckingType::ShowCorrectionPanel); |
2735 | const bool shouldCheckForCorrection = shouldShowCorrectionPanel || textCheckingOptions.contains(TextCheckingType::Correction); |
2736 | #if !USE(AUTOCORRECTION_PANEL) |
2737 | ASSERT(!shouldShowCorrectionPanel); |
2738 | #endif |
2739 | |
2740 | // Expand the range to encompass entire paragraphs, since text checking needs that much context. |
2741 | int selectionOffset = 0; |
2742 | bool useAmbiguousBoundaryOffset = false; |
2743 | bool selectionChanged = false; |
2744 | bool restoreSelectionAfterChange = false; |
2745 | bool adjustSelectionForParagraphBoundaries = false; |
2746 | |
2747 | if (shouldPerformReplacement || shouldMarkSpelling || shouldCheckForCorrection) { |
2748 | if (m_frame.selection().selection().selectionType() == VisibleSelection::CaretSelection) { |
2749 | // Attempt to save the caret position so we can restore it later if needed |
2750 | Position caretPosition = m_frame.selection().selection().end(); |
2751 | selectionOffset = paragraph.offsetTo(caretPosition).releaseReturnValue(); |
2752 | restoreSelectionAfterChange = true; |
2753 | if (selectionOffset > 0 && (selectionOffset > paragraph.textLength() || paragraph.textCharAt(selectionOffset - 1) == newlineCharacter)) |
2754 | adjustSelectionForParagraphBoundaries = true; |
2755 | if (selectionOffset > 0 && selectionOffset <= paragraph.textLength() && isAmbiguousBoundaryCharacter(paragraph.textCharAt(selectionOffset - 1))) |
2756 | useAmbiguousBoundaryOffset = true; |
2757 | } |
2758 | } |
2759 | |
2760 | int offsetDueToReplacement = 0; |
2761 | |
2762 | for (unsigned i = 0; i < results.size(); i++) { |
2763 | const int spellingRangeEndOffset = paragraph.checkingEnd() + offsetDueToReplacement; |
2764 | const TextCheckingType resultType = results[i].type; |
2765 | const int resultLocation = results[i].location + offsetDueToReplacement; |
2766 | const int resultLength = results[i].length; |
2767 | const int resultEndLocation = resultLocation + resultLength; |
2768 | const int automaticReplacementEndLocation = paragraph.automaticReplacementStart() + paragraph.automaticReplacementLength() + offsetDueToReplacement; |
2769 | const String& replacement = results[i].replacement; |
2770 | const bool resultEndsAtAmbiguousBoundary = useAmbiguousBoundaryOffset && selectionOffset - 1 <= resultEndLocation; |
2771 | |
2772 | // Only mark misspelling if: |
2773 | // 1. Current text checking isn't done for autocorrection, in which case shouldMarkSpelling is false. |
2774 | // 2. Result falls within spellingRange. |
2775 | // 3. The word in question doesn't end at an ambiguous boundary. For instance, we would not mark |
2776 | // "wouldn'" as misspelled right after apostrophe is typed. |
2777 | if (shouldMarkSpelling && !shouldShowCorrectionPanel && resultType == TextCheckingType::Spelling |
2778 | && resultLocation >= paragraph.checkingStart() && resultEndLocation <= spellingRangeEndOffset && !resultEndsAtAmbiguousBoundary) { |
2779 | ASSERT(resultLength > 0 && resultLocation >= 0); |
2780 | auto misspellingRange = paragraph.subrange(resultLocation, resultLength); |
2781 | if (!m_alternativeTextController->isSpellingMarkerAllowed(misspellingRange)) |
2782 | continue; |
2783 | misspellingRange->startContainer().document().markers().addMarker(misspellingRange, DocumentMarker::Spelling, replacement); |
2784 | } else if (shouldMarkGrammar && resultType == TextCheckingType::Grammar && paragraph.checkingRangeCovers(resultLocation, resultLength)) { |
2785 | ASSERT(resultLength > 0 && resultLocation >= 0); |
2786 | for (auto& detail : results[i].details) { |
2787 | ASSERT(detail.length > 0 && detail.location >= 0); |
2788 | if (paragraph.checkingRangeCovers(resultLocation + detail.location, detail.length)) { |
2789 | auto badGrammarRange = paragraph.subrange(resultLocation + detail.location, detail.length); |
2790 | badGrammarRange->startContainer().document().markers().addMarker(badGrammarRange, DocumentMarker::Grammar, detail.userDescription); |
2791 | } |
2792 | } |
2793 | } else if (resultEndLocation <= automaticReplacementEndLocation && resultEndLocation >= paragraph.automaticReplacementStart() |
2794 | && isAutomaticTextReplacementType(resultType)) { |
2795 | // In this case the result range just has to touch the automatic replacement range, so we can handle replacing non-word text such as punctuation. |
2796 | ASSERT(resultLength > 0 && resultLocation >= 0); |
2797 | |
2798 | if (shouldShowCorrectionPanel && (resultEndLocation < automaticReplacementEndLocation |
2799 | || (resultType != TextCheckingType::Replacement && resultType != TextCheckingType::Correction))) |
2800 | continue; |
2801 | |
2802 | // Apply replacement if: |
2803 | // 1. The replacement length is non-zero. |
2804 | // 2. The result doesn't end at an ambiguous boundary. |
2805 | // (FIXME: this is required until 6853027 is fixed and text checking can do this for us |
2806 | bool doReplacement = replacement.length() > 0 && !resultEndsAtAmbiguousBoundary; |
2807 | auto rangeToReplace = paragraph.subrange(resultLocation, resultLength); |
2808 | |
2809 | // Adding links should be done only immediately after they are typed. |
2810 | if (resultType == TextCheckingType::Link && selectionOffset != resultEndLocation + 1) |
2811 | continue; |
2812 | |
2813 | if (!(shouldPerformReplacement || shouldCheckForCorrection || shouldMarkLink) || !doReplacement) |
2814 | continue; |
2815 | |
2816 | String replacedString = plainText(rangeToReplace.ptr()); |
2817 | const bool existingMarkersPermitReplacement = m_alternativeTextController->processMarkersOnTextToBeReplacedByResult(results[i], rangeToReplace, replacedString); |
2818 | if (!existingMarkersPermitReplacement) |
2819 | continue; |
2820 | |
2821 | if (shouldShowCorrectionPanel) { |
2822 | if (resultEndLocation == automaticReplacementEndLocation) { |
2823 | // We only show the correction panel on the last word. |
2824 | m_alternativeTextController->show(rangeToReplace, replacement); |
2825 | break; |
2826 | } |
2827 | // If this function is called for showing correction panel, we ignore other correction or replacement. |
2828 | continue; |
2829 | } |
2830 | |
2831 | VisibleSelection selectionToReplace(rangeToReplace, DOWNSTREAM); |
2832 | if (selectionToReplace != m_frame.selection().selection()) { |
2833 | if (!m_frame.selection().shouldChangeSelection(selectionToReplace)) |
2834 | continue; |
2835 | } |
2836 | |
2837 | if (resultType == TextCheckingType::Link) { |
2838 | m_frame.selection().setSelection(selectionToReplace); |
2839 | selectionChanged = true; |
2840 | restoreSelectionAfterChange = false; |
2841 | if (canEditRichly()) |
2842 | CreateLinkCommand::create(document(), replacement)->apply(); |
2843 | } else if (canEdit() && shouldInsertText(replacement, rangeToReplace.ptr(), EditorInsertAction::Typed)) { |
2844 | correctSpellcheckingPreservingTextCheckingParagraph(paragraph, rangeToReplace, replacement, resultLocation, resultLength); |
2845 | |
2846 | if (AXObjectCache* cache = document().existingAXObjectCache()) { |
2847 | if (Element* root = m_frame.selection().selection().rootEditableElement()) |
2848 | cache->postNotification(root, AXObjectCache::AXAutocorrectionOccured); |
2849 | } |
2850 | |
2851 | // Skip all other results for the replaced text. |
2852 | while (i + 1 < results.size() && results[i + 1].location + offsetDueToReplacement <= resultLocation) |
2853 | i++; |
2854 | |
2855 | selectionChanged = true; |
2856 | offsetDueToReplacement += replacement.length() - resultLength; |
2857 | if (resultLocation < selectionOffset) |
2858 | selectionOffset += replacement.length() - resultLength; |
2859 | |
2860 | if (resultType == TextCheckingType::Correction) { |
2861 | auto replacementRange = paragraph.subrange(resultLocation, replacement.length()); |
2862 | m_alternativeTextController->recordAutocorrectionResponse(AutocorrectionResponse::Accepted, replacedString, replacementRange.ptr()); |
2863 | |
2864 | // Add a marker so that corrections can easily be undone and won't be re-corrected. |
2865 | m_alternativeTextController->markCorrection(replacementRange, replacedString); |
2866 | } |
2867 | } |
2868 | } |
2869 | } |
2870 | |
2871 | if (selectionChanged) { |
2872 | TextCheckingParagraph extendedParagraph(WTFMove(paragraph)); |
2873 | // Restore the caret position if we have made any replacements |
2874 | extendedParagraph.expandRangeToNextEnd(); |
2875 | if (restoreSelectionAfterChange && selectionOffset >= 0 && selectionOffset <= extendedParagraph.rangeLength()) { |
2876 | auto selectionRange = extendedParagraph.subrange(0, selectionOffset); |
2877 | m_frame.selection().moveTo(selectionRange->endPosition(), DOWNSTREAM); |
2878 | if (adjustSelectionForParagraphBoundaries) |
2879 | m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity); |
2880 | } else { |
2881 | // If this fails for any reason, the fallback is to go one position beyond the last replacement |
2882 | m_frame.selection().moveTo(m_frame.selection().selection().end()); |
2883 | m_frame.selection().modify(FrameSelection::AlterationMove, DirectionForward, CharacterGranularity); |
2884 | } |
2885 | } |
2886 | } |
2887 | |
2888 | void Editor::changeBackToReplacedString(const String& replacedString) |
2889 | { |
2890 | #if !PLATFORM(IOS_FAMILY) |
2891 | ASSERT(unifiedTextCheckerEnabled()); |
2892 | |
2893 | if (replacedString.isEmpty()) |
2894 | return; |
2895 | |
2896 | RefPtr<Range> selection = selectedRange(); |
2897 | if (!shouldInsertText(replacedString, selection.get(), EditorInsertAction::Pasted)) |
2898 | return; |
2899 | |
2900 | m_alternativeTextController->recordAutocorrectionResponse(AutocorrectionResponse::Reverted, replacedString, selection.get()); |
2901 | TextCheckingParagraph paragraph(*selection); |
2902 | replaceSelectionWithText(replacedString, SelectReplacement::No, SmartReplace::No, EditAction::Insert); |
2903 | auto changedRange = paragraph.subrange(paragraph.checkingStart(), replacedString.length()); |
2904 | changedRange->startContainer().document().markers().addMarker(changedRange, DocumentMarker::Replacement, String()); |
2905 | m_alternativeTextController->markReversed(changedRange); |
2906 | #else |
2907 | ASSERT_NOT_REACHED(); |
2908 | UNUSED_PARAM(replacedString); |
2909 | #endif // !PLATFORM(IOS_FAMILY) |
2910 | } |
2911 | |
2912 | |
2913 | void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection) |
2914 | { |
2915 | if (platformDrivenTextCheckerEnabled()) |
2916 | return; |
2917 | |
2918 | if (unifiedTextCheckerEnabled()) { |
2919 | if (!isContinuousSpellCheckingEnabled()) |
2920 | return; |
2921 | |
2922 | // markMisspellingsAndBadGrammar() is triggered by selection change, in which case we check spelling and grammar, but don't autocorrect misspellings. |
2923 | OptionSet<TextCheckingType> textCheckingOptions { TextCheckingType::Spelling }; |
2924 | if (markGrammar && isGrammarCheckingEnabled()) |
2925 | textCheckingOptions.add(TextCheckingType::Grammar); |
2926 | auto spellCheckingRange = spellingSelection.toNormalizedRange(); |
2927 | markAllMisspellingsAndBadGrammarInRanges(textCheckingOptions, spellCheckingRange.copyRef(), spellCheckingRange.copyRef(), grammarSelection.toNormalizedRange()); |
2928 | return; |
2929 | } |
2930 | |
2931 | RefPtr<Range> firstMisspellingRange; |
2932 | markMisspellings(spellingSelection, firstMisspellingRange); |
2933 | if (markGrammar) |
2934 | markBadGrammar(grammarSelection); |
2935 | } |
2936 | |
2937 | void Editor::unappliedSpellCorrection(const VisibleSelection& selectionOfCorrected, const String& corrected, const String& correction) |
2938 | { |
2939 | m_alternativeTextController->respondToUnappliedSpellCorrection(selectionOfCorrected, corrected, correction); |
2940 | } |
2941 | |
2942 | void Editor::updateMarkersForWordsAffectedByEditing(bool doNotRemoveIfSelectionAtWordBoundary) |
2943 | { |
2944 | if (!document().markers().hasMarkers()) |
2945 | return; |
2946 | |
2947 | if (!m_alternativeTextController->shouldRemoveMarkersUponEditing() && (!textChecker() || textChecker()->shouldEraseMarkersAfterChangeSelection(TextCheckingType::Spelling))) |
2948 | return; |
2949 | |
2950 | // We want to remove the markers from a word if an editing command will change the word. This can happen in one of |
2951 | // several scenarios: |
2952 | // 1. Insert in the middle of a word. |
2953 | // 2. Appending non whitespace at the beginning of word. |
2954 | // 3. Appending non whitespace at the end of word. |
2955 | // Note that, appending only whitespaces at the beginning or end of word won't change the word, so we don't need to |
2956 | // remove the markers on that word. |
2957 | // Of course, if current selection is a range, we potentially will edit two words that fall on the boundaries of |
2958 | // selection, and remove words between the selection boundaries. |
2959 | // |
2960 | VisiblePosition startOfSelection = m_frame.selection().selection().start(); |
2961 | VisiblePosition endOfSelection = m_frame.selection().selection().end(); |
2962 | if (startOfSelection.isNull()) |
2963 | return; |
2964 | // First word is the word that ends after or on the start of selection. |
2965 | VisiblePosition startOfFirstWord = startOfWord(startOfSelection, LeftWordIfOnBoundary); |
2966 | VisiblePosition endOfFirstWord = endOfWord(startOfSelection, LeftWordIfOnBoundary); |
2967 | // Last word is the word that begins before or on the end of selection |
2968 | VisiblePosition startOfLastWord = startOfWord(endOfSelection, RightWordIfOnBoundary); |
2969 | VisiblePosition endOfLastWord = endOfWord(endOfSelection, RightWordIfOnBoundary); |
2970 | |
2971 | if (startOfFirstWord.isNull()) { |
2972 | startOfFirstWord = startOfWord(startOfSelection, RightWordIfOnBoundary); |
2973 | endOfFirstWord = endOfWord(startOfSelection, RightWordIfOnBoundary); |
2974 | } |
2975 | |
2976 | if (endOfLastWord.isNull()) { |
2977 | startOfLastWord = startOfWord(endOfSelection, LeftWordIfOnBoundary); |
2978 | endOfLastWord = endOfWord(endOfSelection, LeftWordIfOnBoundary); |
2979 | } |
2980 | |
2981 | // If doNotRemoveIfSelectionAtWordBoundary is true, and first word ends at the start of selection, |
2982 | // we choose next word as the first word. |
2983 | if (doNotRemoveIfSelectionAtWordBoundary && endOfFirstWord == startOfSelection) { |
2984 | startOfFirstWord = nextWordPosition(startOfFirstWord); |
2985 | endOfFirstWord = endOfWord(startOfFirstWord, RightWordIfOnBoundary); |
2986 | if (startOfFirstWord == endOfSelection) |
2987 | return; |
2988 | } |
2989 | |
2990 | // If doNotRemoveIfSelectionAtWordBoundary is true, and last word begins at the end of selection, |
2991 | // we choose previous word as the last word. |
2992 | if (doNotRemoveIfSelectionAtWordBoundary && startOfLastWord == endOfSelection) { |
2993 | startOfLastWord = previousWordPosition(startOfLastWord); |
2994 | endOfLastWord = endOfWord(startOfLastWord, RightWordIfOnBoundary); |
2995 | if (endOfLastWord == startOfSelection) |
2996 | return; |
2997 | } |
2998 | |
2999 | if (startOfFirstWord.isNull() || endOfFirstWord.isNull() || startOfLastWord.isNull() || endOfLastWord.isNull()) |
3000 | return; |
3001 | |
3002 | // Now we remove markers on everything between startOfFirstWord and endOfLastWord. |
3003 | // However, if an autocorrection change a single word to multiple words, we want to remove correction mark from all the |
3004 | // resulted words even we only edit one of them. For example, assuming autocorrection changes "avantgarde" to "avant |
3005 | // garde", we will have CorrectionIndicator marker on both words and on the whitespace between them. If we then edit garde, |
3006 | // we would like to remove the marker from word "avant" and whitespace as well. So we need to get the continous range of |
3007 | // of marker that contains the word in question, and remove marker on that whole range. |
3008 | auto wordRange = Range::create(document(), startOfFirstWord.deepEquivalent(), endOfLastWord.deepEquivalent()); |
3009 | |
3010 | Vector<RenderedDocumentMarker*> markers = document().markers().markersInRange(wordRange, DocumentMarker::DictationAlternatives); |
3011 | for (auto* marker : markers) |
3012 | m_alternativeTextController->removeDictationAlternativesForMarker(*marker); |
3013 | |
3014 | OptionSet<DocumentMarker::MarkerType> markerTypesToRemove { |
3015 | DocumentMarker::CorrectionIndicator, |
3016 | DocumentMarker::DictationAlternatives, |
3017 | DocumentMarker::SpellCheckingExemption, |
3018 | DocumentMarker::Spelling, |
3019 | #if !PLATFORM(IOS_FAMILY) |
3020 | DocumentMarker::Grammar, |
3021 | #endif |
3022 | }; |
3023 | document().markers().removeMarkers(wordRange, markerTypesToRemove, DocumentMarkerController::RemovePartiallyOverlappingMarker); |
3024 | document().markers().clearDescriptionOnMarkersIntersectingRange(wordRange, DocumentMarker::Replacement); |
3025 | } |
3026 | |
3027 | void Editor::deletedAutocorrectionAtPosition(const Position& position, const String& originalString) |
3028 | { |
3029 | m_alternativeTextController->deletedAutocorrectionAtPosition(position, originalString); |
3030 | } |
3031 | |
3032 | RefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint) |
3033 | { |
3034 | Document* document = m_frame.documentAtPoint(windowPoint); |
3035 | if (!document) |
3036 | return nullptr; |
3037 | |
3038 | Frame* frame = document->frame(); |
3039 | ASSERT(frame); |
3040 | FrameView* frameView = frame->view(); |
3041 | if (!frameView) |
3042 | return nullptr; |
3043 | IntPoint framePoint = frameView->windowToContents(windowPoint); |
3044 | VisibleSelection selection(frame->visiblePositionForPoint(framePoint)); |
3045 | |
3046 | return selection.toNormalizedRange(); |
3047 | } |
3048 | |
3049 | void Editor::revealSelectionAfterEditingOperation(const ScrollAlignment& alignment, RevealExtentOption revealExtentOption) |
3050 | { |
3051 | if (m_ignoreSelectionChanges) |
3052 | return; |
3053 | |
3054 | SelectionRevealMode revealMode = SelectionRevealMode::Reveal; |
3055 | m_frame.selection().revealSelection(revealMode, alignment, revealExtentOption); |
3056 | } |
3057 | |
3058 | void Editor::setIgnoreSelectionChanges(bool ignore, RevealSelection shouldRevealExistingSelection) |
3059 | { |
3060 | if (m_ignoreSelectionChanges == ignore) |
3061 | return; |
3062 | |
3063 | m_ignoreSelectionChanges = ignore; |
3064 | #if PLATFORM(IOS_FAMILY) |
3065 | // FIXME: Should suppress selection change notifications during a composition change <https://webkit.org/b/38830> |
3066 | if (!ignore) |
3067 | respondToChangedSelection(m_frame.selection().selection(), { }); |
3068 | #endif |
3069 | if (!ignore && shouldRevealExistingSelection == RevealSelection::Yes) |
3070 | revealSelectionAfterEditingOperation(ScrollAlignment::alignToEdgeIfNeeded, RevealExtent); |
3071 | } |
3072 | |
3073 | RefPtr<Range> Editor::compositionRange() const |
3074 | { |
3075 | if (!m_compositionNode) |
3076 | return nullptr; |
3077 | unsigned length = m_compositionNode->length(); |
3078 | unsigned start = std::min(m_compositionStart, length); |
3079 | unsigned end = std::min(std::max(start, m_compositionEnd), length); |
3080 | if (start >= end) |
3081 | return nullptr; |
3082 | return Range::create(m_compositionNode->document(), m_compositionNode.get(), start, m_compositionNode.get(), end); |
3083 | } |
3084 | |
3085 | bool Editor::getCompositionSelection(unsigned& selectionStart, unsigned& selectionEnd) const |
3086 | { |
3087 | if (!m_compositionNode) |
3088 | return false; |
3089 | const VisibleSelection& selection = m_frame.selection().selection(); |
3090 | Position start = selection.start(); |
3091 | if (start.deprecatedNode() != m_compositionNode) |
3092 | return false; |
3093 | Position end = selection.end(); |
3094 | if (end.deprecatedNode() != m_compositionNode) |
3095 | return false; |
3096 | |
3097 | if (static_cast<unsigned>(start.deprecatedEditingOffset()) < m_compositionStart) |
3098 | return false; |
3099 | if (static_cast<unsigned>(end.deprecatedEditingOffset()) > m_compositionEnd) |
3100 | return false; |
3101 | |
3102 | selectionStart = start.deprecatedEditingOffset() - m_compositionStart; |
3103 | selectionEnd = start.deprecatedEditingOffset() - m_compositionEnd; |
3104 | return true; |
3105 | } |
3106 | |
3107 | void Editor::transpose() |
3108 | { |
3109 | if (!canEdit()) |
3110 | return; |
3111 | |
3112 | VisibleSelection selection = m_frame.selection().selection(); |
3113 | if (!selection.isCaret()) |
3114 | return; |
3115 | |
3116 | // Make a selection that goes back one character and forward two characters. |
3117 | VisiblePosition caret = selection.visibleStart(); |
3118 | VisiblePosition next = isEndOfParagraph(caret) ? caret : caret.next(); |
3119 | VisiblePosition previous = next.previous(); |
3120 | if (next == previous) |
3121 | return; |
3122 | previous = previous.previous(); |
3123 | if (!inSameParagraph(next, previous)) |
3124 | return; |
3125 | RefPtr<Range> range = makeRange(previous, next); |
3126 | if (!range) |
3127 | return; |
3128 | VisibleSelection newSelection(*range, DOWNSTREAM); |
3129 | |
3130 | // Transpose the two characters. |
3131 | String text = plainText(range.get()); |
3132 | if (text.length() != 2) |
3133 | return; |
3134 | String transposed = text.right(1) + text.left(1); |
3135 | |
3136 | // Select the two characters. |
3137 | if (newSelection != m_frame.selection().selection()) { |
3138 | if (!m_frame.selection().shouldChangeSelection(newSelection)) |
3139 | return; |
3140 | m_frame.selection().setSelection(newSelection); |
3141 | } |
3142 | |
3143 | // Insert the transposed characters. |
3144 | if (!shouldInsertText(transposed, range.get(), EditorInsertAction::Typed)) |
3145 | return; |
3146 | replaceSelectionWithText(transposed, SelectReplacement::No, SmartReplace::No, EditAction::Insert); |
3147 | } |
3148 | |
3149 | void Editor::addRangeToKillRing(const Range& range, KillRingInsertionMode mode) |
3150 | { |
3151 | addTextToKillRing(plainText(&range), mode); |
3152 | } |
3153 | |
3154 | void Editor::addTextToKillRing(const String& text, KillRingInsertionMode mode) |
3155 | { |
3156 | if (m_shouldStartNewKillRingSequence) |
3157 | killRing().startNewSequence(); |
3158 | |
3159 | m_shouldStartNewKillRingSequence = false; |
3160 | |
3161 | // If the kill was from a backwards motion, prepend to the kill ring. |
3162 | // This will ensure that alternating forward and backward kills will |
3163 | // build up the original string in the kill ring without permuting it. |
3164 | switch (mode) { |
3165 | case KillRingInsertionMode::PrependText: |
3166 | killRing().prepend(text); |
3167 | break; |
3168 | case KillRingInsertionMode::AppendText: |
3169 | killRing().append(text); |
3170 | break; |
3171 | } |
3172 | } |
3173 | |
3174 | void Editor::startAlternativeTextUITimer() |
3175 | { |
3176 | m_alternativeTextController->startAlternativeTextUITimer(AlternativeTextTypeCorrection); |
3177 | } |
3178 | |
3179 | void Editor::handleAlternativeTextUIResult(const String& correction) |
3180 | { |
3181 | m_alternativeTextController->handleAlternativeTextUIResult(correction); |
3182 | } |
3183 | |
3184 | |
3185 | void Editor::dismissCorrectionPanelAsIgnored() |
3186 | { |
3187 | m_alternativeTextController->dismiss(ReasonForDismissingAlternativeTextIgnored); |
3188 | } |
3189 | |
3190 | void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, OptionSet<FrameSelection::SetSelectionOption> options) |
3191 | { |
3192 | Ref<Frame> protection(m_frame); |
3193 | |
3194 | // If the new selection is orphaned, then don't update the selection. |
3195 | if (newSelection.start().isOrphan() || newSelection.end().isOrphan()) |
3196 | return; |
3197 | |
3198 | // If there is no selection change, don't bother sending shouldChangeSelection, but still call setSelection, |
3199 | // because there is work that it must do in this situation. |
3200 | // The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls. |
3201 | // See <rdar://problem/5729315> Some shouldChangeSelectedDOMRange contain Ranges for selections that are no longer valid |
3202 | bool selectionDidNotChangeDOMPosition = newSelection == m_frame.selection().selection(); |
3203 | if (selectionDidNotChangeDOMPosition || m_frame.selection().shouldChangeSelection(newSelection)) |
3204 | m_frame.selection().setSelection(newSelection, options); |
3205 | |
3206 | // Some editing operations change the selection visually without affecting its position within the DOM. |
3207 | // For example when you press return in the following (the caret is marked by ^): |
3208 | // <div contentEditable="true"><div>^Hello</div></div> |
3209 | // WebCore inserts <div><br></div> *before* the current block, which correctly moves the paragraph down but which doesn't |
3210 | // change the caret's DOM position (["hello", 0]). In these situations the above FrameSelection::setSelection call |
3211 | // does not call EditorClient::respondToChangedSelection(), which, on the Mac, sends selection change notifications and |
3212 | // starts a new kill ring sequence, but we want to do these things (matches AppKit). |
3213 | #if PLATFORM(IOS_FAMILY) |
3214 | // FIXME: Should suppress selection change notifications during a composition change <https://webkit.org/b/38830> |
3215 | if (m_ignoreSelectionChanges) |
3216 | return; |
3217 | #endif |
3218 | if (selectionDidNotChangeDOMPosition && client()) |
3219 | client()->respondToChangedSelection(&m_frame); |
3220 | } |
3221 | |
3222 | String Editor::selectedText() const |
3223 | { |
3224 | TextIteratorBehavior behavior = TextIteratorDefaultBehavior; |
3225 | if (m_frame.settings().selectionAcrossShadowBoundariesEnabled()) |
3226 | behavior |= TextIteratorTraversesFlatTree; |
3227 | return selectedText(behavior); |
3228 | } |
3229 | |
3230 | String Editor::selectedTextForDataTransfer() const |
3231 | { |
3232 | TextIteratorBehavior behavior = TextIteratorEmitsImageAltText; |
3233 | if (m_frame.settings().selectionAcrossShadowBoundariesEnabled()) |
3234 | behavior |= TextIteratorTraversesFlatTree; |
3235 | return selectedText(behavior); |
3236 | } |
3237 | |
3238 | String Editor::selectedText(TextIteratorBehavior behavior) const |
3239 | { |
3240 | // We remove '\0' characters because they are not visibly rendered to the user. |
3241 | auto& selection = m_frame.selection().selection(); |
3242 | return plainText(selection.start(), selection.end(), behavior).replaceWithLiteral('\0', "" ); |
3243 | } |
3244 | |
3245 | static inline void collapseCaretWidth(IntRect& rect) |
3246 | { |
3247 | // FIXME: Width adjustment doesn't work for rotated text. |
3248 | if (rect.width() == caretWidth) |
3249 | rect.setWidth(0); |
3250 | else if (rect.height() == caretWidth) |
3251 | rect.setHeight(0); |
3252 | } |
3253 | |
3254 | IntRect Editor::firstRectForRange(Range* range) const |
3255 | { |
3256 | VisiblePosition startVisiblePosition(range->startPosition(), DOWNSTREAM); |
3257 | |
3258 | if (range->collapsed()) { |
3259 | // FIXME: Getting caret rect and removing caret width is a very roundabout way to get collapsed range location. |
3260 | // In particular, width adjustment doesn't work for rotated text. |
3261 | IntRect startCaretRect = RenderedPosition(startVisiblePosition).absoluteRect(); |
3262 | collapseCaretWidth(startCaretRect); |
3263 | return startCaretRect; |
3264 | } |
3265 | |
3266 | VisiblePosition endVisiblePosition(range->endPosition(), UPSTREAM); |
3267 | |
3268 | if (inSameLine(startVisiblePosition, endVisiblePosition)) |
3269 | return enclosingIntRect(RenderObject::absoluteBoundingBoxRectForRange(range)); |
3270 | |
3271 | LayoutUnit ; |
3272 | IntRect startCaretRect = RenderedPosition(startVisiblePosition).absoluteRect(&extraWidthToEndOfLine); |
3273 | if (startCaretRect == IntRect()) |
3274 | return IntRect(); |
3275 | |
3276 | // When start and end aren't on the same line, we want to go from start to the end of its line. |
3277 | bool textIsHorizontal = startCaretRect.width() == caretWidth; |
3278 | return textIsHorizontal ? |
3279 | IntRect(startCaretRect.x(), |
3280 | startCaretRect.y(), |
3281 | startCaretRect.width() + extraWidthToEndOfLine, |
3282 | startCaretRect.height()) : |
3283 | IntRect(startCaretRect.x(), |
3284 | startCaretRect.y(), |
3285 | startCaretRect.width(), |
3286 | startCaretRect.height() + extraWidthToEndOfLine); |
3287 | } |
3288 | |
3289 | bool Editor::shouldChangeSelection(const VisibleSelection& oldSelection, const VisibleSelection& newSelection, EAffinity affinity, bool stillSelecting) const |
3290 | { |
3291 | #if PLATFORM(IOS_FAMILY) |
3292 | if (m_frame.selectionChangeCallbacksDisabled()) |
3293 | return true; |
3294 | #endif |
3295 | return client() && client()->shouldChangeSelectedRange(oldSelection.toNormalizedRange().get(), newSelection.toNormalizedRange().get(), affinity, stillSelecting); |
3296 | } |
3297 | |
3298 | void Editor::computeAndSetTypingStyle(EditingStyle& style, EditAction editingAction) |
3299 | { |
3300 | if (style.isEmpty()) { |
3301 | m_frame.selection().clearTypingStyle(); |
3302 | return; |
3303 | } |
3304 | |
3305 | // Calculate the current typing style. |
3306 | RefPtr<EditingStyle> typingStyle; |
3307 | if (auto existingTypingStyle = m_frame.selection().typingStyle()) |
3308 | typingStyle = existingTypingStyle->copy(); |
3309 | else |
3310 | typingStyle = EditingStyle::create(); |
3311 | typingStyle->overrideTypingStyleAt(style, m_frame.selection().selection().visibleStart().deepEquivalent()); |
3312 | |
3313 | // Handle block styles, substracting these from the typing style. |
3314 | RefPtr<EditingStyle> blockStyle = typingStyle->extractAndRemoveBlockProperties(); |
3315 | if (!blockStyle->isEmpty()) |
3316 | ApplyStyleCommand::create(document(), blockStyle.get(), editingAction)->apply(); |
3317 | |
3318 | // Set the remaining style as the typing style. |
3319 | m_frame.selection().setTypingStyle(WTFMove(typingStyle)); |
3320 | } |
3321 | |
3322 | void Editor::computeAndSetTypingStyle(StyleProperties& properties, EditAction editingAction) |
3323 | { |
3324 | return computeAndSetTypingStyle(EditingStyle::create(&properties), editingAction); |
3325 | } |
3326 | |
3327 | void Editor::textFieldDidBeginEditing(Element* e) |
3328 | { |
3329 | if (client()) |
3330 | client()->textFieldDidBeginEditing(e); |
3331 | } |
3332 | |
3333 | void Editor::textFieldDidEndEditing(Element* e) |
3334 | { |
3335 | dismissCorrectionPanelAsIgnored(); |
3336 | if (client()) |
3337 | client()->textFieldDidEndEditing(e); |
3338 | } |
3339 | |
3340 | void Editor::textDidChangeInTextField(Element* e) |
3341 | { |
3342 | if (client()) |
3343 | client()->textDidChangeInTextField(e); |
3344 | } |
3345 | |
3346 | bool Editor::doTextFieldCommandFromEvent(Element* e, KeyboardEvent* ke) |
3347 | { |
3348 | if (client()) |
3349 | return client()->doTextFieldCommandFromEvent(e, ke); |
3350 | |
3351 | return false; |
3352 | } |
3353 | |
3354 | void Editor::textWillBeDeletedInTextField(Element* input) |
3355 | { |
3356 | if (client()) |
3357 | client()->textWillBeDeletedInTextField(input); |
3358 | } |
3359 | |
3360 | void Editor::textDidChangeInTextArea(Element* e) |
3361 | { |
3362 | if (client()) |
3363 | client()->textDidChangeInTextArea(e); |
3364 | } |
3365 | |
3366 | void Editor::applyEditingStyleToBodyElement() const |
3367 | { |
3368 | auto collection = document().getElementsByTagName(HTMLNames::bodyTag->localName()); |
3369 | unsigned length = collection->length(); |
3370 | for (unsigned i = 0; i < length; ++i) |
3371 | applyEditingStyleToElement(collection->item(i)); |
3372 | } |
3373 | |
3374 | void Editor::applyEditingStyleToElement(Element* element) const |
3375 | { |
3376 | ASSERT(!element || is<StyledElement>(*element)); |
3377 | if (!is<StyledElement>(element)) |
3378 | return; |
3379 | |
3380 | // Mutate using the CSSOM wrapper so we get the same event behavior as a script. |
3381 | auto& style = downcast<StyledElement>(*element).cssomStyle(); |
3382 | style.setPropertyInternal(CSSPropertyWordWrap, "break-word" , false); |
3383 | style.setPropertyInternal(CSSPropertyWebkitNbspMode, "space" , false); |
3384 | style.setPropertyInternal(CSSPropertyLineBreak, "after-white-space" , false); |
3385 | } |
3386 | |
3387 | bool Editor::findString(const String& target, FindOptions options) |
3388 | { |
3389 | Ref<Frame> protection(m_frame); |
3390 | |
3391 | VisibleSelection selection = m_frame.selection().selection(); |
3392 | |
3393 | RefPtr<Range> resultRange = rangeOfString(target, selection.firstRange().get(), options); |
3394 | |
3395 | if (!resultRange) |
3396 | return false; |
3397 | |
3398 | m_frame.selection().setSelection(VisibleSelection(*resultRange, DOWNSTREAM)); |
3399 | |
3400 | if (!(options.contains(DoNotRevealSelection))) |
3401 | m_frame.selection().revealSelection(); |
3402 | |
3403 | return true; |
3404 | } |
3405 | |
3406 | RefPtr<Range> Editor::rangeOfString(const String& target, Range* referenceRange, FindOptions options) |
3407 | { |
3408 | if (target.isEmpty()) |
3409 | return nullptr; |
3410 | |
3411 | // Start from an edge of the reference range, if there's a reference range that's not in shadow content. Which edge |
3412 | // is used depends on whether we're searching forward or backward, and whether startInSelection is set. |
3413 | RefPtr<Range> searchRange(rangeOfContents(document())); |
3414 | |
3415 | bool forward = !options.contains(Backwards); |
3416 | bool startInReferenceRange = referenceRange && options.contains(StartInSelection); |
3417 | if (referenceRange) { |
3418 | if (forward) |
3419 | searchRange->setStart(startInReferenceRange ? referenceRange->startPosition() : referenceRange->endPosition()); |
3420 | else |
3421 | searchRange->setEnd(startInReferenceRange ? referenceRange->endPosition() : referenceRange->startPosition()); |
3422 | } |
3423 | |
3424 | RefPtr<ShadowRoot> shadowTreeRoot = referenceRange ? referenceRange->startContainer().containingShadowRoot() : nullptr; |
3425 | if (shadowTreeRoot) { |
3426 | if (forward) |
3427 | searchRange->setEnd(*shadowTreeRoot, shadowTreeRoot->countChildNodes()); |
3428 | else |
3429 | searchRange->setStart(*shadowTreeRoot, 0); |
3430 | } |
3431 | |
3432 | RefPtr<Range> resultRange = findPlainText(*searchRange, target, options); |
3433 | // If we started in the reference range and the found range exactly matches the reference range, find again. |
3434 | // Build a selection with the found range to remove collapsed whitespace. |
3435 | // Compare ranges instead of selection objects to ignore the way that the current selection was made. |
3436 | if (startInReferenceRange && areRangesEqual(VisibleSelection(*resultRange).toNormalizedRange().get(), referenceRange)) { |
3437 | searchRange = rangeOfContents(document()); |
3438 | if (forward) |
3439 | searchRange->setStart(referenceRange->endPosition()); |
3440 | else |
3441 | searchRange->setEnd(referenceRange->startPosition()); |
3442 | |
3443 | if (shadowTreeRoot) { |
3444 | if (forward) |
3445 | searchRange->setEnd(*shadowTreeRoot, shadowTreeRoot->countChildNodes()); |
3446 | else |
3447 | searchRange->setStart(*shadowTreeRoot, 0); |
3448 | } |
3449 | |
3450 | resultRange = findPlainText(*searchRange, target, options); |
3451 | } |
3452 | |
3453 | // If nothing was found in the shadow tree, search in main content following the shadow tree. |
3454 | if (resultRange->collapsed() && shadowTreeRoot) { |
3455 | searchRange = rangeOfContents(document()); |
3456 | if (shadowTreeRoot->shadowHost()) { |
3457 | if (forward) |
3458 | searchRange->setStartAfter(*shadowTreeRoot->shadowHost()); |
3459 | else |
3460 | searchRange->setEndBefore(*shadowTreeRoot->shadowHost()); |
3461 | } |
3462 | |
3463 | resultRange = findPlainText(*searchRange, target, options); |
3464 | } |
3465 | |
3466 | // If we didn't find anything and we're wrapping, search again in the entire document (this will |
3467 | // redundantly re-search the area already searched in some cases). |
3468 | if (resultRange->collapsed() && options.contains(WrapAround)) { |
3469 | searchRange = rangeOfContents(document()); |
3470 | resultRange = findPlainText(*searchRange, target, options); |
3471 | // We used to return false here if we ended up with the same range that we started with |
3472 | // (e.g., the reference range was already the only instance of this text). But we decided that |
3473 | // this should be a success case instead, so we'll just fall through in that case. |
3474 | } |
3475 | |
3476 | return resultRange->collapsed() ? nullptr : resultRange; |
3477 | } |
3478 | |
3479 | static bool isFrameInRange(Frame& frame, Range& range) |
3480 | { |
3481 | for (auto* ownerElement = frame.ownerElement(); ownerElement; ownerElement = ownerElement->document().ownerElement()) { |
3482 | if (&ownerElement->document() == &range.ownerDocument()) { |
3483 | auto result = range.intersectsNode(*ownerElement); |
3484 | return !result.hasException() && result.releaseReturnValue(); |
3485 | } |
3486 | } |
3487 | return false; |
3488 | } |
3489 | |
3490 | unsigned Editor::countMatchesForText(const String& target, Range* range, FindOptions options, unsigned limit, bool markMatches, Vector<RefPtr<Range>>* matches) |
3491 | { |
3492 | if (target.isEmpty()) |
3493 | return 0; |
3494 | |
3495 | RefPtr<Range> searchRange; |
3496 | if (range) { |
3497 | if (&range->ownerDocument() == &document()) |
3498 | searchRange = range; |
3499 | else if (!isFrameInRange(m_frame, *range)) |
3500 | return 0; |
3501 | } |
3502 | if (!searchRange) |
3503 | searchRange = rangeOfContents(document()); |
3504 | |
3505 | Node& originalEndContainer = searchRange->endContainer(); |
3506 | int originalEndOffset = searchRange->endOffset(); |
3507 | |
3508 | unsigned matchCount = 0; |
3509 | do { |
3510 | auto resultRange = findPlainText(*searchRange, target, options - Backwards); |
3511 | if (resultRange->collapsed()) { |
3512 | if (!resultRange->startContainer().isInShadowTree()) |
3513 | break; |
3514 | |
3515 | searchRange->setStartAfter(*resultRange->startContainer().shadowHost()); |
3516 | searchRange->setEnd(originalEndContainer, originalEndOffset); |
3517 | continue; |
3518 | } |
3519 | |
3520 | ++matchCount; |
3521 | if (matches) |
3522 | matches->append(resultRange.ptr()); |
3523 | |
3524 | if (markMatches) |
3525 | document().markers().addMarker(resultRange, DocumentMarker::TextMatch); |
3526 | |
3527 | // Stop looking if we hit the specified limit. A limit of 0 means no limit. |
3528 | if (limit > 0 && matchCount >= limit) |
3529 | break; |
3530 | |
3531 | // Set the new start for the search range to be the end of the previous |
3532 | // result range. There is no need to use a VisiblePosition here, |
3533 | // since findPlainText will use a TextIterator to go over the visible |
3534 | // text nodes. |
3535 | searchRange->setStart(resultRange->endContainer(), resultRange->endOffset()); |
3536 | |
3537 | Node* shadowTreeRoot = searchRange->shadowRoot(); |
3538 | if (searchRange->collapsed() && shadowTreeRoot) |
3539 | searchRange->setEnd(*shadowTreeRoot, shadowTreeRoot->countChildNodes()); |
3540 | } while (true); |
3541 | |
3542 | return matchCount; |
3543 | } |
3544 | |
3545 | void Editor::setMarkedTextMatchesAreHighlighted(bool flag) |
3546 | { |
3547 | if (flag == m_areMarkedTextMatchesHighlighted) |
3548 | return; |
3549 | |
3550 | m_areMarkedTextMatchesHighlighted = flag; |
3551 | document().markers().repaintMarkers(DocumentMarker::TextMatch); |
3552 | } |
3553 | |
3554 | #if !PLATFORM(MAC) |
3555 | void Editor::selectionWillChange() |
3556 | { |
3557 | } |
3558 | #endif |
3559 | |
3560 | void Editor::respondToChangedSelection(const VisibleSelection&, OptionSet<FrameSelection::SetSelectionOption> options) |
3561 | { |
3562 | #if PLATFORM(IOS_FAMILY) |
3563 | // FIXME: Should suppress selection change notifications during a composition change <https://webkit.org/b/38830> |
3564 | if (m_ignoreSelectionChanges) |
3565 | return; |
3566 | #endif |
3567 | |
3568 | if (client()) |
3569 | client()->respondToChangedSelection(&m_frame); |
3570 | |
3571 | #if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS_FAMILY) |
3572 | if (shouldDetectTelephoneNumbers()) |
3573 | m_telephoneNumberDetectionUpdateTimer.startOneShot(0_s); |
3574 | #endif |
3575 | |
3576 | setStartNewKillRingSequence(true); |
3577 | |
3578 | if (m_editorUIUpdateTimer.isActive()) |
3579 | return; |
3580 | |
3581 | // Don't check spelling and grammar if the change of selection is triggered by spelling correction itself. |
3582 | m_editorUIUpdateTimerShouldCheckSpellingAndGrammar = options.contains(FrameSelection::CloseTyping) && !options.contains(FrameSelection::SpellCorrectionTriggered); |
3583 | m_editorUIUpdateTimerWasTriggeredByDictation = options.contains(FrameSelection::DictationTriggered); |
3584 | scheduleEditorUIUpdate(); |
3585 | } |
3586 | |
3587 | #if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS_FAMILY) |
3588 | |
3589 | bool Editor::shouldDetectTelephoneNumbers() |
3590 | { |
3591 | if (!m_frame.document()) |
3592 | return false; |
3593 | return document().isTelephoneNumberParsingEnabled() && TelephoneNumberDetector::isSupported(); |
3594 | } |
3595 | |
3596 | void Editor::scanSelectionForTelephoneNumbers() |
3597 | { |
3598 | if (!shouldDetectTelephoneNumbers() || !client()) |
3599 | return; |
3600 | |
3601 | m_detectedTelephoneNumberRanges.clear(); |
3602 | |
3603 | Vector<RefPtr<Range>> markedRanges; |
3604 | |
3605 | FrameSelection& frameSelection = m_frame.selection(); |
3606 | if (!frameSelection.isRange()) { |
3607 | if (auto* page = m_frame.page()) |
3608 | page->servicesOverlayController().selectedTelephoneNumberRangesChanged(); |
3609 | return; |
3610 | } |
3611 | RefPtr<Range> selectedRange = frameSelection.toNormalizedRange(); |
3612 | |
3613 | // Extend the range a few characters in each direction to detect incompletely selected phone numbers. |
3614 | static const int charactersToExtend = 15; |
3615 | const VisibleSelection& visibleSelection = frameSelection.selection(); |
3616 | Position start = visibleSelection.start(); |
3617 | Position end = visibleSelection.end(); |
3618 | for (int i = 0; i < charactersToExtend; ++i) { |
3619 | start = start.previous(Character); |
3620 | end = end.next(Character); |
3621 | } |
3622 | |
3623 | FrameSelection extendedSelection; |
3624 | extendedSelection.setStart(start); |
3625 | extendedSelection.setEnd(end); |
3626 | RefPtr<Range> extendedRange = extendedSelection.toNormalizedRange(); |
3627 | |
3628 | if (!extendedRange) { |
3629 | if (auto* page = m_frame.page()) |
3630 | page->servicesOverlayController().selectedTelephoneNumberRangesChanged(); |
3631 | return; |
3632 | } |
3633 | |
3634 | scanRangeForTelephoneNumbers(*extendedRange, extendedRange->text(), markedRanges); |
3635 | |
3636 | // Only consider ranges with a detected telephone number if they overlap with the actual selection range. |
3637 | for (auto& range : markedRanges) { |
3638 | if (rangesOverlap(range.get(), selectedRange.get())) |
3639 | m_detectedTelephoneNumberRanges.append(range); |
3640 | } |
3641 | |
3642 | if (auto* page = m_frame.page()) |
3643 | page->servicesOverlayController().selectedTelephoneNumberRangesChanged(); |
3644 | } |
3645 | |
3646 | void Editor::scanRangeForTelephoneNumbers(Range& range, const StringView& stringView, Vector<RefPtr<Range>>& markedRanges) |
3647 | { |
3648 | // Don't scan for phone numbers inside editable regions. |
3649 | Node& startNode = range.startContainer(); |
3650 | if (startNode.hasEditableStyle()) |
3651 | return; |
3652 | |
3653 | // relativeStartPosition and relativeEndPosition are the endpoints of the phone number range, |
3654 | // relative to the scannerPosition |
3655 | unsigned length = stringView.length(); |
3656 | unsigned scannerPosition = 0; |
3657 | int relativeStartPosition = 0; |
3658 | int relativeEndPosition = 0; |
3659 | |
3660 | auto characters = stringView.upconvertedCharacters(); |
3661 | |
3662 | while (scannerPosition < length && TelephoneNumberDetector::find(&characters[scannerPosition], length - scannerPosition, &relativeStartPosition, &relativeEndPosition)) { |
3663 | // The convention in the Data Detectors framework is that the end position is the first character NOT in the phone number |
3664 | // (that is, the length of the range is relativeEndPosition - relativeStartPosition). So subtract 1 to get the same |
3665 | // convention as the old WebCore phone number parser (so that the rest of the code is still valid if we want to go back |
3666 | // to the old parser). |
3667 | --relativeEndPosition; |
3668 | |
3669 | ASSERT(scannerPosition + relativeEndPosition < length); |
3670 | |
3671 | unsigned subrangeOffset = scannerPosition + relativeStartPosition; |
3672 | unsigned subrangeLength = relativeEndPosition - relativeStartPosition + 1; |
3673 | |
3674 | auto subrange = TextIterator::subrange(range, subrangeOffset, subrangeLength); |
3675 | |
3676 | markedRanges.append(subrange.ptr()); |
3677 | range.ownerDocument().markers().addMarker(subrange, DocumentMarker::TelephoneNumber); |
3678 | |
3679 | scannerPosition += relativeEndPosition + 1; |
3680 | } |
3681 | } |
3682 | |
3683 | #endif // ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS_FAMILY) |
3684 | |
3685 | void Editor::updateEditorUINowIfScheduled() |
3686 | { |
3687 | if (!m_editorUIUpdateTimer.isActive()) |
3688 | return; |
3689 | m_editorUIUpdateTimer.stop(); |
3690 | editorUIUpdateTimerFired(); |
3691 | } |
3692 | |
3693 | void Editor::editorUIUpdateTimerFired() |
3694 | { |
3695 | VisibleSelection oldSelection = m_oldSelectionForEditorUIUpdate; |
3696 | |
3697 | m_alternativeTextController->stopPendingCorrection(oldSelection); |
3698 | |
3699 | bool isContinuousSpellCheckingEnabled = this->isContinuousSpellCheckingEnabled(); |
3700 | bool isContinuousGrammarCheckingEnabled = isContinuousSpellCheckingEnabled && isGrammarCheckingEnabled(); |
3701 | if (isContinuousSpellCheckingEnabled) { |
3702 | VisibleSelection newAdjacentWords; |
3703 | VisibleSelection newSelectedSentence; |
3704 | bool caretBrowsing = m_frame.settings().caretBrowsingEnabled(); |
3705 | if (m_frame.selection().selection().isContentEditable() || caretBrowsing) { |
3706 | VisiblePosition newStart(m_frame.selection().selection().visibleStart()); |
3707 | #if !PLATFORM(IOS_FAMILY) |
3708 | newAdjacentWords = VisibleSelection(startOfWord(newStart, LeftWordIfOnBoundary), endOfWord(newStart, RightWordIfOnBoundary)); |
3709 | #else |
3710 | // If this bug gets fixed, this PLATFORM(IOS_FAMILY) code could be removed: |
3711 | // <rdar://problem/7259611> Word boundary code on iPhone gives different results than desktop |
3712 | EWordSide startWordSide = LeftWordIfOnBoundary; |
3713 | UChar32 c = newStart.characterBefore(); |
3714 | // FIXME: VisiblePosition::characterAfter() and characterBefore() do not emit newlines the same |
3715 | // way as TextIterator, so we do an isStartOfParagraph check here. |
3716 | if (isSpaceOrNewline(c) || c == noBreakSpace || isStartOfParagraph(newStart)) { |
3717 | startWordSide = RightWordIfOnBoundary; |
3718 | } |
3719 | newAdjacentWords = VisibleSelection(startOfWord(newStart, startWordSide), endOfWord(newStart, RightWordIfOnBoundary)); |
3720 | #endif // !PLATFORM(IOS_FAMILY) |
3721 | if (isContinuousGrammarCheckingEnabled) |
3722 | newSelectedSentence = VisibleSelection(startOfSentence(newStart), endOfSentence(newStart)); |
3723 | } |
3724 | |
3725 | // When typing we check spelling elsewhere, so don't redo it here. |
3726 | // If this is a change in selection resulting from a delete operation, |
3727 | // oldSelection may no longer be in the document. |
3728 | if (m_editorUIUpdateTimerShouldCheckSpellingAndGrammar && oldSelection.isContentEditable() && oldSelection.start().deprecatedNode() && oldSelection.start().anchorNode()->isConnected()) { |
3729 | VisiblePosition oldStart(oldSelection.visibleStart()); |
3730 | VisibleSelection oldAdjacentWords = VisibleSelection(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary)); |
3731 | if (oldAdjacentWords != newAdjacentWords) { |
3732 | if (isContinuousGrammarCheckingEnabled) { |
3733 | VisibleSelection oldSelectedSentence = VisibleSelection(startOfSentence(oldStart), endOfSentence(oldStart)); |
3734 | markMisspellingsAndBadGrammar(oldAdjacentWords, oldSelectedSentence != newSelectedSentence, oldSelectedSentence); |
3735 | } else |
3736 | markMisspellingsAndBadGrammar(oldAdjacentWords, false, oldAdjacentWords); |
3737 | } |
3738 | } |
3739 | |
3740 | if (!textChecker() || textChecker()->shouldEraseMarkersAfterChangeSelection(TextCheckingType::Spelling)) { |
3741 | if (RefPtr<Range> wordRange = newAdjacentWords.toNormalizedRange()) |
3742 | document().markers().removeMarkers(*wordRange, DocumentMarker::Spelling); |
3743 | } |
3744 | if (!textChecker() || textChecker()->shouldEraseMarkersAfterChangeSelection(TextCheckingType::Grammar)) { |
3745 | if (RefPtr<Range> sentenceRange = newSelectedSentence.toNormalizedRange()) |
3746 | document().markers().removeMarkers(*sentenceRange, DocumentMarker::Grammar); |
3747 | } |
3748 | } |
3749 | |
3750 | // When continuous spell checking is off, existing markers disappear after the selection changes. |
3751 | if (!isContinuousSpellCheckingEnabled) |
3752 | document().markers().removeMarkers(DocumentMarker::Spelling); |
3753 | if (!isContinuousGrammarCheckingEnabled) |
3754 | document().markers().removeMarkers(DocumentMarker::Grammar); |
3755 | |
3756 | if (!m_editorUIUpdateTimerWasTriggeredByDictation) |
3757 | m_alternativeTextController->respondToChangedSelection(oldSelection); |
3758 | |
3759 | m_oldSelectionForEditorUIUpdate = m_frame.selection().selection(); |
3760 | |
3761 | #if ENABLE(ATTACHMENT_ELEMENT) |
3762 | notifyClientOfAttachmentUpdates(); |
3763 | #endif |
3764 | } |
3765 | |
3766 | static Node* findFirstMarkable(Node* node) |
3767 | { |
3768 | while (node) { |
3769 | if (!node->renderer()) |
3770 | return nullptr; |
3771 | if (node->renderer()->isTextOrLineBreak()) |
3772 | return node; |
3773 | if (is<Element>(*node) && downcast<Element>(*node).isTextField()) |
3774 | node = downcast<HTMLTextFormControlElement>(*node).visiblePositionForIndex(1).deepEquivalent().deprecatedNode(); |
3775 | else if (node->firstChild()) |
3776 | node = node->firstChild(); |
3777 | else |
3778 | node = node->nextSibling(); |
3779 | } |
3780 | |
3781 | return nullptr; |
3782 | } |
3783 | |
3784 | bool Editor::selectionStartHasMarkerFor(DocumentMarker::MarkerType markerType, int from, int length) const |
3785 | { |
3786 | Node* node = findFirstMarkable(m_frame.selection().selection().start().deprecatedNode()); |
3787 | if (!node) |
3788 | return false; |
3789 | |
3790 | unsigned int startOffset = static_cast<unsigned int>(from); |
3791 | unsigned int endOffset = static_cast<unsigned int>(from + length); |
3792 | Vector<RenderedDocumentMarker*> markers = document().markers().markersFor(*node); |
3793 | for (auto* marker : markers) { |
3794 | if (marker->startOffset() <= startOffset && endOffset <= marker->endOffset() && marker->type() == markerType) |
3795 | return true; |
3796 | } |
3797 | |
3798 | return false; |
3799 | } |
3800 | |
3801 | OptionSet<TextCheckingType> Editor::resolveTextCheckingTypeMask(const Node& rootEditableElement, OptionSet<TextCheckingType> textCheckingOptions) |
3802 | { |
3803 | #if USE(AUTOMATIC_TEXT_REPLACEMENT) && !PLATFORM(IOS_FAMILY) |
3804 | bool onlyAllowsTextReplacement = false; |
3805 | if (auto* host = rootEditableElement.shadowHost()) |
3806 | onlyAllowsTextReplacement = is<HTMLInputElement>(host) && downcast<HTMLInputElement>(*host).isSpellcheckDisabledExceptTextReplacement(); |
3807 | if (onlyAllowsTextReplacement) |
3808 | textCheckingOptions = textCheckingOptions & TextCheckingType::Replacement; |
3809 | #else |
3810 | UNUSED_PARAM(rootEditableElement); |
3811 | #endif |
3812 | |
3813 | bool shouldMarkSpelling = textCheckingOptions.contains(TextCheckingType::Spelling); |
3814 | bool shouldMarkGrammar = textCheckingOptions.contains(TextCheckingType::Grammar); |
3815 | #if !PLATFORM(IOS_FAMILY) |
3816 | bool shouldShowCorrectionPanel = textCheckingOptions.contains(TextCheckingType::ShowCorrectionPanel); |
3817 | bool shouldCheckForCorrection = shouldShowCorrectionPanel || textCheckingOptions.contains(TextCheckingType::Correction); |
3818 | #endif |
3819 | |
3820 | OptionSet<TextCheckingType> checkingTypes; |
3821 | if (shouldMarkSpelling) |
3822 | checkingTypes.add(TextCheckingType::Spelling); |
3823 | if (shouldMarkGrammar) |
3824 | checkingTypes.add(TextCheckingType::Grammar); |
3825 | #if !PLATFORM(IOS_FAMILY) |
3826 | if (shouldCheckForCorrection) |
3827 | checkingTypes.add(TextCheckingType::Correction); |
3828 | if (shouldShowCorrectionPanel) |
3829 | checkingTypes.add(TextCheckingType::ShowCorrectionPanel); |
3830 | |
3831 | #if USE(AUTOMATIC_TEXT_REPLACEMENT) |
3832 | bool shouldPerformReplacement = textCheckingOptions.contains(TextCheckingType::Replacement); |
3833 | if (shouldPerformReplacement) { |
3834 | if (!onlyAllowsTextReplacement) { |
3835 | if (isAutomaticLinkDetectionEnabled()) |
3836 | checkingTypes.add(TextCheckingType::Link); |
3837 | if (isAutomaticQuoteSubstitutionEnabled()) |
3838 | checkingTypes.add(TextCheckingType::Quote); |
3839 | if (isAutomaticDashSubstitutionEnabled()) |
3840 | checkingTypes.add(TextCheckingType::Dash); |
3841 | if (shouldMarkSpelling && isAutomaticSpellingCorrectionEnabled()) |
3842 | checkingTypes.add(TextCheckingType::Correction); |
3843 | } |
3844 | if (isAutomaticTextReplacementEnabled()) |
3845 | checkingTypes.add(TextCheckingType::Replacement); |
3846 | } |
3847 | #endif |
3848 | #endif // !PLATFORM(IOS_FAMILY) |
3849 | |
3850 | return checkingTypes; |
3851 | } |
3852 | |
3853 | static RefPtr<Range> candidateRangeForSelection(Frame& frame) |
3854 | { |
3855 | const VisibleSelection& selection = frame.selection().selection(); |
3856 | return selection.isCaret() ? wordRangeFromPosition(selection.start()) : frame.selection().toNormalizedRange(); |
3857 | } |
3858 | |
3859 | static bool candidateWouldReplaceText(const VisibleSelection& selection) |
3860 | { |
3861 | // If the character behind the caret in the current selection is anything but a space or a newline then we should |
3862 | // replace the whole current word with the candidate. |
3863 | UChar32 characterAfterSelection, characterBeforeSelection, twoCharacterBeforeSelection = 0; |
3864 | charactersAroundPosition(selection.visibleStart(), characterAfterSelection, characterBeforeSelection, twoCharacterBeforeSelection); |
3865 | return !(characterBeforeSelection == '\0' || characterBeforeSelection == '\n' || characterBeforeSelection == ' '); |
3866 | } |
3867 | |
3868 | String Editor::stringForCandidateRequest() const |
3869 | { |
3870 | const VisibleSelection& selection = m_frame.selection().selection(); |
3871 | RefPtr<Range> rangeForCurrentlyTypedString = candidateRangeForSelection(m_frame); |
3872 | if (rangeForCurrentlyTypedString && candidateWouldReplaceText(selection)) |
3873 | return plainText(rangeForCurrentlyTypedString.get()); |
3874 | |
3875 | return String(); |
3876 | } |
3877 | |
3878 | RefPtr<Range> Editor::contextRangeForCandidateRequest() const |
3879 | { |
3880 | const VisibleSelection& selection = m_frame.selection().selection(); |
3881 | return makeRange(startOfParagraph(selection.visibleStart()), endOfParagraph(selection.visibleEnd())); |
3882 | } |
3883 | |
3884 | RefPtr<Range> Editor::rangeForTextCheckingResult(const TextCheckingResult& result) const |
3885 | { |
3886 | if (!result.length) |
3887 | return nullptr; |
3888 | |
3889 | RefPtr<Range> = contextRangeForCandidateRequest(); |
3890 | if (!contextRange) |
3891 | return nullptr; |
3892 | |
3893 | return TextIterator::subrange(*contextRange, result.location, result.length); |
3894 | } |
3895 | |
3896 | void Editor::scheduleEditorUIUpdate() |
3897 | { |
3898 | m_editorUIUpdateTimer.startOneShot(0_s); |
3899 | } |
3900 | |
3901 | #if !PLATFORM(COCOA) |
3902 | |
3903 | void Editor::platformFontAttributesAtSelectionStart(FontAttributes&, const RenderStyle&) const |
3904 | { |
3905 | } |
3906 | |
3907 | #endif |
3908 | |
3909 | static Vector<TextList> editableTextListsAtPositionInDescendingOrder(const Position& position) |
3910 | { |
3911 | auto startContainer = makeRefPtr(position.containerNode()); |
3912 | if (!startContainer) |
3913 | return { }; |
3914 | |
3915 | auto* editableRoot = highestEditableRoot(firstPositionInOrBeforeNode(startContainer.get())); |
3916 | if (!editableRoot) |
3917 | return { }; |
3918 | |
3919 | Vector<Ref<HTMLElement>> enclosingLists; |
3920 | for (auto& ancestor : ancestorsOfType<HTMLElement>(*startContainer)) { |
3921 | if (&ancestor == editableRoot) |
3922 | break; |
3923 | |
3924 | auto* renderer = ancestor.renderer(); |
3925 | if (!renderer) |
3926 | continue; |
3927 | |
3928 | if (is<HTMLUListElement>(ancestor) || is<HTMLOListElement>(ancestor)) |
3929 | enclosingLists.append(ancestor); |
3930 | } |
3931 | |
3932 | Vector<TextList> textLists; |
3933 | textLists.reserveCapacity(enclosingLists.size()); |
3934 | for (auto iterator = enclosingLists.rbegin(); iterator != enclosingLists.rend(); ++iterator) { |
3935 | auto& list = iterator->get(); |
3936 | bool ordered = is<HTMLOListElement>(list); |
3937 | textLists.uncheckedAppend({ list.renderer()->style().listStyleType(), ordered ? downcast<HTMLOListElement>(list).start() : 1, ordered }); |
3938 | } |
3939 | |
3940 | return textLists; |
3941 | } |
3942 | |
3943 | FontAttributes Editor::fontAttributesAtSelectionStart() const |
3944 | { |
3945 | FontAttributes attributes; |
3946 | Node* nodeToRemove = nullptr; |
3947 | auto* style = styleForSelectionStart(&m_frame, nodeToRemove); |
3948 | if (!style) { |
3949 | if (nodeToRemove) |
3950 | nodeToRemove->remove(); |
3951 | return attributes; |
3952 | } |
3953 | |
3954 | platformFontAttributesAtSelectionStart(attributes, *style); |
3955 | |
3956 | // FIXME: for now, always report the colors after applying -apple-color-filter. In future not all clients |
3957 | // may want this, so we may have to add a setting to control it. See also editingAttributedStringFromRange(). |
3958 | auto backgroundColor = style->visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor); |
3959 | if (backgroundColor.isVisible()) |
3960 | attributes.backgroundColor = backgroundColor; |
3961 | |
3962 | auto foregroundColor = style->visitedDependentColorWithColorFilter(CSSPropertyColor); |
3963 | // FIXME: isBlackColor not suitable for dark mode. |
3964 | if (foregroundColor.isValid() && !Color::isBlackColor(foregroundColor)) |
3965 | attributes.foregroundColor = foregroundColor; |
3966 | |
3967 | if (auto* shadowData = style->textShadow()) |
3968 | attributes.fontShadow = { shadowData->color(), { static_cast<float>(shadowData->x()), static_cast<float>(shadowData->y()) }, static_cast<double>(shadowData->radius()) }; |
3969 | |
3970 | switch (style->verticalAlign()) { |
3971 | case VerticalAlign::Baseline: |
3972 | case VerticalAlign::Bottom: |
3973 | case VerticalAlign::BaselineMiddle: |
3974 | case VerticalAlign::Length: |
3975 | case VerticalAlign::Middle: |
3976 | case VerticalAlign::TextBottom: |
3977 | case VerticalAlign::TextTop: |
3978 | case VerticalAlign::Top: |
3979 | break; |
3980 | case VerticalAlign::Sub: |
3981 | attributes.subscriptOrSuperscript = FontAttributes::SubscriptOrSuperscript::Subscript; |
3982 | break; |
3983 | case VerticalAlign::Super: |
3984 | attributes.subscriptOrSuperscript = FontAttributes::SubscriptOrSuperscript::Superscript; |
3985 | break; |
3986 | } |
3987 | |
3988 | attributes.textLists = editableTextListsAtPositionInDescendingOrder(m_frame.selection().selection().start()); |
3989 | |
3990 | switch (style->textAlign()) { |
3991 | case TextAlignMode::Right: |
3992 | case TextAlignMode::WebKitRight: |
3993 | attributes.horizontalAlignment = FontAttributes::HorizontalAlignment::Right; |
3994 | break; |
3995 | case TextAlignMode::Left: |
3996 | case TextAlignMode::WebKitLeft: |
3997 | attributes.horizontalAlignment = FontAttributes::HorizontalAlignment::Left; |
3998 | break; |
3999 | case TextAlignMode::Center: |
4000 | case TextAlignMode::WebKitCenter: |
4001 | attributes.horizontalAlignment = FontAttributes::HorizontalAlignment::Center; |
4002 | break; |
4003 | case TextAlignMode::Justify: |
4004 | attributes.horizontalAlignment = FontAttributes::HorizontalAlignment::Justify; |
4005 | break; |
4006 | case TextAlignMode::Start: |
4007 | attributes.horizontalAlignment = FontAttributes::HorizontalAlignment::Natural; |
4008 | break; |
4009 | case TextAlignMode::End: |
4010 | attributes.horizontalAlignment = style->isLeftToRightDirection() ? FontAttributes::HorizontalAlignment::Right : FontAttributes::HorizontalAlignment::Left; |
4011 | break; |
4012 | } |
4013 | |
4014 | auto typingStyle = makeRefPtr(m_frame.selection().typingStyle()); |
4015 | if (typingStyle && typingStyle->style()) { |
4016 | auto value = typingStyle->style()->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect); |
4017 | if (value && value->isValueList()) { |
4018 | CSSValueList& valueList = downcast<CSSValueList>(*value); |
4019 | if (valueList.hasValue(CSSValuePool::singleton().createIdentifierValue(CSSValueLineThrough).ptr())) |
4020 | attributes.hasStrikeThrough = true; |
4021 | if (valueList.hasValue(CSSValuePool::singleton().createIdentifierValue(CSSValueUnderline).ptr())) |
4022 | attributes.hasUnderline = true; |
4023 | } |
4024 | } else { |
4025 | auto decoration = style->textDecorationsInEffect(); |
4026 | if (decoration & TextDecoration::LineThrough) |
4027 | attributes.hasStrikeThrough = true; |
4028 | if (decoration & TextDecoration::Underline) |
4029 | attributes.hasUnderline = true; |
4030 | } |
4031 | |
4032 | if (nodeToRemove) |
4033 | nodeToRemove->remove(); |
4034 | |
4035 | return attributes; |
4036 | } |
4037 | |
4038 | #if ENABLE(ATTACHMENT_ELEMENT) |
4039 | |
4040 | void Editor::registerAttachmentIdentifier(const String& identifier, const String& contentType, const String& preferredFileName, Ref<SharedBuffer>&& data) |
4041 | { |
4042 | if (auto* client = this->client()) |
4043 | client->registerAttachmentIdentifier(identifier, contentType, preferredFileName, WTFMove(data)); |
4044 | } |
4045 | |
4046 | void Editor::registerAttachmentIdentifier(const String& identifier, const String& contentType, const String& filePath) |
4047 | { |
4048 | if (auto* client = this->client()) |
4049 | client->registerAttachmentIdentifier(identifier, contentType, filePath); |
4050 | } |
4051 | |
4052 | void Editor::registerAttachments(Vector<SerializedAttachmentData>&& data) |
4053 | { |
4054 | if (auto* client = this->client()) |
4055 | client->registerAttachments(WTFMove(data)); |
4056 | } |
4057 | |
4058 | void Editor::registerAttachmentIdentifier(const String& identifier) |
4059 | { |
4060 | if (auto* client = this->client()) |
4061 | client->registerAttachmentIdentifier(identifier); |
4062 | } |
4063 | |
4064 | void Editor::cloneAttachmentData(const String& fromIdentifier, const String& toIdentifier) |
4065 | { |
4066 | if (auto* client = this->client()) |
4067 | client->cloneAttachmentData(fromIdentifier, toIdentifier); |
4068 | } |
4069 | |
4070 | void Editor::didInsertAttachmentElement(HTMLAttachmentElement& attachment) |
4071 | { |
4072 | auto identifier = attachment.uniqueIdentifier(); |
4073 | if (identifier.isEmpty()) |
4074 | return; |
4075 | |
4076 | if (!m_removedAttachmentIdentifiers.take(identifier)) |
4077 | m_insertedAttachmentIdentifiers.add(identifier); |
4078 | scheduleEditorUIUpdate(); |
4079 | } |
4080 | |
4081 | void Editor::didRemoveAttachmentElement(HTMLAttachmentElement& attachment) |
4082 | { |
4083 | auto identifier = attachment.uniqueIdentifier(); |
4084 | if (identifier.isEmpty()) |
4085 | return; |
4086 | |
4087 | if (!m_insertedAttachmentIdentifiers.take(identifier)) |
4088 | m_removedAttachmentIdentifiers.add(identifier); |
4089 | scheduleEditorUIUpdate(); |
4090 | } |
4091 | |
4092 | void Editor::notifyClientOfAttachmentUpdates() |
4093 | { |
4094 | auto removedAttachmentIdentifiers = WTFMove(m_removedAttachmentIdentifiers); |
4095 | auto insertedAttachmentIdentifiers = WTFMove(m_insertedAttachmentIdentifiers); |
4096 | if (!client()) |
4097 | return; |
4098 | |
4099 | for (auto& identifier : removedAttachmentIdentifiers) |
4100 | client()->didRemoveAttachmentWithIdentifier(identifier); |
4101 | |
4102 | auto* document = m_frame.document(); |
4103 | if (!document) |
4104 | return; |
4105 | |
4106 | for (auto& identifier : insertedAttachmentIdentifiers) { |
4107 | if (auto attachment = document->attachmentForIdentifier(identifier)) |
4108 | client()->didInsertAttachmentWithIdentifier(identifier, attachment->attributeWithoutSynchronization(HTMLNames::srcAttr), attachment->hasEnclosingImage()); |
4109 | else |
4110 | ASSERT_NOT_REACHED(); |
4111 | } |
4112 | } |
4113 | |
4114 | void Editor::insertAttachment(const String& identifier, Optional<uint64_t>&& fileSize, const String& fileName, const String& contentType) |
4115 | { |
4116 | auto attachment = HTMLAttachmentElement::create(HTMLNames::attachmentTag, document()); |
4117 | attachment->setUniqueIdentifier(identifier); |
4118 | attachment->updateAttributes(WTFMove(fileSize), contentType, fileName); |
4119 | |
4120 | auto fragmentToInsert = document().createDocumentFragment(); |
4121 | fragmentToInsert->appendChild(attachment.get()); |
4122 | |
4123 | replaceSelectionWithFragment(fragmentToInsert.get(), SelectReplacement::No, SmartReplace::No, MatchStyle::Yes); |
4124 | } |
4125 | |
4126 | #endif // ENABLE(ATTACHMENT_ELEMENT) |
4127 | |
4128 | void Editor::handleAcceptedCandidate(TextCheckingResult acceptedCandidate) |
4129 | { |
4130 | const VisibleSelection& selection = m_frame.selection().selection(); |
4131 | |
4132 | m_isHandlingAcceptedCandidate = true; |
4133 | |
4134 | if (auto range = rangeForTextCheckingResult(acceptedCandidate)) { |
4135 | if (shouldInsertText(acceptedCandidate.replacement, range.get(), EditorInsertAction::Typed)) |
4136 | ReplaceRangeWithTextCommand::create(range.get(), acceptedCandidate.replacement)->apply(); |
4137 | } else |
4138 | insertText(acceptedCandidate.replacement, nullptr); |
4139 | |
4140 | RefPtr<Range> insertedCandidateRange = rangeExpandedByCharactersInDirectionAtWordBoundary(selection.visibleStart(), acceptedCandidate.replacement.length(), DirectionBackward); |
4141 | if (insertedCandidateRange) |
4142 | insertedCandidateRange->startContainer().document().markers().addMarker(*insertedCandidateRange, DocumentMarker::AcceptedCandidate, acceptedCandidate.replacement); |
4143 | |
4144 | m_isHandlingAcceptedCandidate = false; |
4145 | } |
4146 | |
4147 | bool Editor::unifiedTextCheckerEnabled() const |
4148 | { |
4149 | return WebCore::unifiedTextCheckerEnabled(&m_frame); |
4150 | } |
4151 | |
4152 | Vector<String> Editor::dictationAlternativesForMarker(const DocumentMarker& marker) |
4153 | { |
4154 | return m_alternativeTextController->dictationAlternativesForMarker(marker); |
4155 | } |
4156 | |
4157 | void Editor::applyDictationAlternativelternative(const String& alternativeString) |
4158 | { |
4159 | m_alternativeTextController->applyDictationAlternative(alternativeString); |
4160 | } |
4161 | |
4162 | void Editor::toggleOverwriteModeEnabled() |
4163 | { |
4164 | m_overwriteModeEnabled = !m_overwriteModeEnabled; |
4165 | m_frame.selection().setShouldShowBlockCursor(m_overwriteModeEnabled); |
4166 | } |
4167 | |
4168 | Document& Editor::document() const |
4169 | { |
4170 | ASSERT(m_frame.document()); |
4171 | return *m_frame.document(); |
4172 | } |
4173 | |
4174 | RefPtr<Range> Editor::adjustedSelectionRange() |
4175 | { |
4176 | // FIXME: Why do we need to adjust the selection to include the anchor tag it's in? |
4177 | // Whoever wrote this code originally forgot to leave us a comment explaining the rationale. |
4178 | RefPtr<Range> range = selectedRange(); |
4179 | Node* commonAncestor = range->commonAncestorContainer(); |
4180 | ASSERT(commonAncestor); |
4181 | auto* enclosingAnchor = enclosingElementWithTag(firstPositionInNode(commonAncestor), HTMLNames::aTag); |
4182 | if (enclosingAnchor && comparePositions(firstPositionInOrBeforeNode(range->startPosition().anchorNode()), range->startPosition()) >= 0) |
4183 | range->setStart(*enclosingAnchor, 0); |
4184 | return range; |
4185 | } |
4186 | |
4187 | // FIXME: This figures out the current style by inserting a <span>! |
4188 | const RenderStyle* Editor::styleForSelectionStart(Frame* frame, Node*& nodeToRemove) |
4189 | { |
4190 | nodeToRemove = nullptr; |
4191 | |
4192 | if (frame->selection().isNone()) |
4193 | return nullptr; |
4194 | |
4195 | Position position = adjustedSelectionStartForStyleComputation(frame->selection().selection()); |
4196 | if (!position.isCandidate() || position.isNull()) |
4197 | return nullptr; |
4198 | |
4199 | RefPtr<EditingStyle> typingStyle = frame->selection().typingStyle(); |
4200 | if (!typingStyle || !typingStyle->style()) |
4201 | return &position.deprecatedNode()->renderer()->style(); |
4202 | |
4203 | auto styleElement = HTMLSpanElement::create(*frame->document()); |
4204 | |
4205 | String styleText = typingStyle->style()->asText() + " display: inline" ; |
4206 | styleElement->setAttribute(HTMLNames::styleAttr, styleText); |
4207 | |
4208 | styleElement->appendChild(frame->document()->createEditingTextNode(emptyString())); |
4209 | |
4210 | auto positionNode = position.deprecatedNode(); |
4211 | if (!positionNode || !positionNode->parentNode() || positionNode->parentNode()->appendChild(styleElement).hasException()) |
4212 | return nullptr; |
4213 | |
4214 | nodeToRemove = styleElement.ptr(); |
4215 | |
4216 | frame->document()->updateStyleIfNeeded(); |
4217 | return styleElement->renderer() ? &styleElement->renderer()->style() : nullptr; |
4218 | } |
4219 | |
4220 | const Font* Editor::fontForSelection(bool& hasMultipleFonts) const |
4221 | { |
4222 | hasMultipleFonts = false; |
4223 | |
4224 | if (!m_frame.selection().isRange()) { |
4225 | Node* nodeToRemove; |
4226 | auto* style = styleForSelectionStart(&m_frame, nodeToRemove); // sets nodeToRemove |
4227 | |
4228 | const Font* font = nullptr; |
4229 | if (style) { |
4230 | font = &style->fontCascade().primaryFont(); |
4231 | if (nodeToRemove) |
4232 | nodeToRemove->remove(); |
4233 | } |
4234 | |
4235 | return font; |
4236 | } |
4237 | |
4238 | RefPtr<Range> range = m_frame.selection().toNormalizedRange(); |
4239 | if (!range) |
4240 | return nullptr; |
4241 | |
4242 | Node* startNode = adjustedSelectionStartForStyleComputation(m_frame.selection().selection()).deprecatedNode(); |
4243 | if (!startNode) |
4244 | return nullptr; |
4245 | |
4246 | const Font* font = nullptr; |
4247 | Node* pastEnd = range->pastLastNode(); |
4248 | // In the loop below, node should eventually match pastEnd and not become null, but we've seen at least one |
4249 | // unreproducible case where this didn't happen, so check for null also. |
4250 | for (Node* node = startNode; node && node != pastEnd; node = NodeTraversal::next(*node)) { |
4251 | auto renderer = node->renderer(); |
4252 | if (!renderer) |
4253 | continue; |
4254 | // FIXME: Are there any node types that have renderers, but that we should be skipping? |
4255 | const Font& primaryFont = renderer->style().fontCascade().primaryFont(); |
4256 | if (!font) |
4257 | font = &primaryFont; |
4258 | else if (font != &primaryFont) { |
4259 | hasMultipleFonts = true; |
4260 | break; |
4261 | } |
4262 | } |
4263 | |
4264 | return font; |
4265 | } |
4266 | |
4267 | RefPtr<HTMLImageElement> Editor::insertEditableImage() |
4268 | { |
4269 | return InsertEditableImageCommand::insertEditableImage(document()); |
4270 | } |
4271 | |
4272 | bool Editor::canCopyExcludingStandaloneImages() const |
4273 | { |
4274 | auto& selection = m_frame.selection().selection(); |
4275 | return selection.isRange() && !selection.isInPasswordField(); |
4276 | } |
4277 | |
4278 | } // namespace WebCore |
4279 | |