| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved. | 2 * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved. |
| 3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) | 3 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) |
| 4 * | 4 * |
| 5 * Redistribution and use in source and binary forms, with or without | 5 * Redistribution and use in source and binary forms, with or without |
| 6 * modification, are permitted provided that the following conditions | 6 * modification, are permitted provided that the following conditions |
| 7 * are met: | 7 * are met: |
| 8 * 1. Redistributions of source code must retain the above copyright | 8 * 1. Redistributions of source code must retain the above copyright |
| 9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
| 10 * 2. Redistributions in binary form must reproduce the above copyright | 10 * 2. Redistributions in binary form must reproduce the above copyright |
| (...skipping 26 matching lines...) Expand all Loading... |
| 37 #include "core/frame/LocalFrame.h" | 37 #include "core/frame/LocalFrame.h" |
| 38 #include "core/html/HTMLTextAreaElement.h" | 38 #include "core/html/HTMLTextAreaElement.h" |
| 39 #include "core/input/EventHandler.h" | 39 #include "core/input/EventHandler.h" |
| 40 #include "core/layout/LayoutObject.h" | 40 #include "core/layout/LayoutObject.h" |
| 41 #include "core/layout/LayoutTheme.h" | 41 #include "core/layout/LayoutTheme.h" |
| 42 #include "core/page/ChromeClient.h" | 42 #include "core/page/ChromeClient.h" |
| 43 #include "wtf/Optional.h" | 43 #include "wtf/Optional.h" |
| 44 | 44 |
| 45 namespace blink { | 45 namespace blink { |
| 46 | 46 |
| 47 namespace { |
| 48 |
| 49 void dispatchCompositionUpdateEvent(LocalFrame& frame, const String& text) |
| 50 { |
| 51 Element* target = frame.document()->focusedElement(); |
| 52 if (!target) |
| 53 return; |
| 54 |
| 55 CompositionEvent* event = CompositionEvent::create(EventTypeNames::compositi
onupdate, frame.domWindow(), text); |
| 56 target->dispatchEvent(event); |
| 57 } |
| 58 |
| 59 void dispatchCompositionEndEvent(LocalFrame& frame, const String& text) |
| 60 { |
| 61 Element* target = frame.document()->focusedElement(); |
| 62 if (!target) |
| 63 return; |
| 64 |
| 65 CompositionEvent* event = CompositionEvent::create(EventTypeNames::compositi
onend, frame.domWindow(), text); |
| 66 target->dispatchEvent(event); |
| 67 } |
| 68 |
| 69 // Used to insert/replace text during composition update and confirm composition
. |
| 70 // Procedure: |
| 71 // 1. Fire 'beforeinput' event for (TODO(chongz): deleted composed text) and i
nserted text |
| 72 // 2. Fire 'compositionupdate' event |
| 73 // 3. Fire TextEvent and modify DOM |
| 74 // TODO(chongz): 4. Fire 'input' event |
| 75 void insertTextDuringCompositionWithEvents(LocalFrame& frame, const String& text
, TypingCommand::Options options, TypingCommand::TextCompositionType composition
Type) |
| 76 { |
| 77 DCHECK(compositionType == TypingCommand::TextCompositionType::TextCompositio
nUpdate || compositionType == TypingCommand::TextCompositionType::TextCompositio
nConfirm) |
| 78 << "compositionType should be TextCompositionUpdate or TextCompositionCo
nfirm, but got " << static_cast<int>(compositionType); |
| 79 if (!frame.document()) |
| 80 return; |
| 81 |
| 82 Element* target = frame.document()->focusedElement(); |
| 83 if (!target) |
| 84 return; |
| 85 |
| 86 // TODO(chongz): Fire 'beforeinput' for the composed text being replaced/del
eted. |
| 87 |
| 88 // Only the last confirmed text is cancelable. |
| 89 InputEvent::EventCancelable beforeInputCancelable = (compositionType == Typi
ngCommand::TextCompositionType::TextCompositionUpdate) ? InputEvent::EventCancel
able::NotCancelable : InputEvent::EventCancelable::IsCancelable; |
| 90 DispatchEventResult result = dispatchBeforeInputFromComposition(target, Inpu
tEvent::InputType::InsertText, text, beforeInputCancelable); |
| 91 |
| 92 if (beforeInputCancelable == InputEvent::EventCancelable::IsCancelable && re
sult != DispatchEventResult::NotCanceled) |
| 93 return; |
| 94 |
| 95 // 'beforeinput' event handler may destroy document. |
| 96 if (!frame.document()) |
| 97 return; |
| 98 |
| 99 dispatchCompositionUpdateEvent(frame, text); |
| 100 // 'compositionupdate' event handler may destroy document. |
| 101 if (!frame.document()) |
| 102 return; |
| 103 |
| 104 switch (compositionType) { |
| 105 case TypingCommand::TextCompositionType::TextCompositionUpdate: |
| 106 TypingCommand::insertText(*frame.document(), text, options, compositionT
ype); |
| 107 break; |
| 108 case TypingCommand::TextCompositionType::TextCompositionConfirm: |
| 109 // TODO(chongz): Use TypingCommand::insertText after TextEvent was remov
ed. (Removed from spec since 2012) |
| 110 // See TextEvent.idl. |
| 111 frame.eventHandler().handleTextInputEvent(text, 0, TextEventInputComposi
tion); |
| 112 break; |
| 113 default: |
| 114 NOTREACHED(); |
| 115 } |
| 116 // TODO(chongz): Fire 'input' event. |
| 117 } |
| 118 |
| 119 } // anonymous namespace |
| 120 |
| 47 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodC
ontroller* inputMethodController) | 121 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodC
ontroller* inputMethodController) |
| 48 : m_inputMethodController(inputMethodController) | 122 : m_inputMethodController(inputMethodController) |
| 49 , m_offsets(inputMethodController->getSelectionOffsets()) | 123 , m_offsets(inputMethodController->getSelectionOffsets()) |
| 50 { | 124 { |
| 51 } | 125 } |
| 52 | 126 |
| 53 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope() | 127 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope() |
| 54 { | 128 { |
| 55 m_inputMethodController->setSelectionOffsets(m_offsets); | 129 m_inputMethodController->setSelectionOffsets(m_offsets); |
| 56 } | 130 } |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 89 frame().document()->markers().removeMarkers(DocumentMarker::Composition); | 163 frame().document()->markers().removeMarkers(DocumentMarker::Composition); |
| 90 m_isDirty = false; | 164 m_isDirty = false; |
| 91 } | 165 } |
| 92 | 166 |
| 93 void InputMethodController::documentDetached() | 167 void InputMethodController::documentDetached() |
| 94 { | 168 { |
| 95 clear(); | 169 clear(); |
| 96 m_compositionRange = nullptr; | 170 m_compositionRange = nullptr; |
| 97 } | 171 } |
| 98 | 172 |
| 99 bool InputMethodController::insertTextForConfirmedComposition(const String& text
) | |
| 100 { | |
| 101 return frame().eventHandler().handleTextInputEvent(text, 0, TextEventInputCo
mposition); | |
| 102 } | |
| 103 | |
| 104 void InputMethodController::selectComposition() const | 173 void InputMethodController::selectComposition() const |
| 105 { | 174 { |
| 106 const EphemeralRange range = compositionEphemeralRange(); | 175 const EphemeralRange range = compositionEphemeralRange(); |
| 107 if (range.isNull()) | 176 if (range.isNull()) |
| 108 return; | 177 return; |
| 109 | 178 |
| 110 // The composition can start inside a composed character sequence, so we hav
e to override checks. | 179 // The composition can start inside a composed character sequence, so we hav
e to override checks. |
| 111 // See <http://bugs.webkit.org/show_bug.cgi?id=15781> | 180 // See <http://bugs.webkit.org/show_bug.cgi?id=15781> |
| 112 VisibleSelection selection; | 181 VisibleSelection selection; |
| 113 selection.setWithoutValidation(range.startPosition(), range.endPosition()); | 182 selection.setWithoutValidation(range.startPosition(), range.endPosition()); |
| 114 frame().selection().setSelection(selection, 0); | 183 frame().selection().setSelection(selection, 0); |
| 115 } | 184 } |
| 116 | 185 |
| 117 bool InputMethodController::confirmComposition() | 186 bool InputMethodController::confirmComposition() |
| 118 { | 187 { |
| 119 return confirmComposition(composingText()); | 188 return confirmComposition(composingText()); |
| 120 } | 189 } |
| 121 | 190 |
| 122 static void dispatchCompositionEndEvent(LocalFrame& frame, const String& text) | |
| 123 { | |
| 124 // We should send this event before sending a TextEvent as written in | |
| 125 // Section 6.2.2 and 6.2.3 of the DOM Event specification. | |
| 126 Element* target = frame.document()->focusedElement(); | |
| 127 if (!target) | |
| 128 return; | |
| 129 | |
| 130 CompositionEvent* event = | |
| 131 CompositionEvent::create(EventTypeNames::compositionend, frame.domWindow
(), text); | |
| 132 target->dispatchEvent(event); | |
| 133 } | |
| 134 | |
| 135 bool InputMethodController::confirmComposition(const String& text, ConfirmCompos
itionBehavior confirmBehavior) | 191 bool InputMethodController::confirmComposition(const String& text, ConfirmCompos
itionBehavior confirmBehavior) |
| 136 { | 192 { |
| 137 if (!hasComposition()) | 193 if (!hasComposition()) |
| 138 return false; | 194 return false; |
| 139 | 195 |
| 140 Optional<Editor::RevealSelectionScope> revealSelectionScope; | 196 Optional<Editor::RevealSelectionScope> revealSelectionScope; |
| 141 if (confirmBehavior == KeepSelection) | 197 if (confirmBehavior == KeepSelection) |
| 142 revealSelectionScope.emplace(&editor()); | 198 revealSelectionScope.emplace(&editor()); |
| 143 | 199 |
| 144 // If the composition was set from existing text and didn't change, then | 200 // If the composition was set from existing text and didn't change, then |
| 145 // there's nothing to do here (and we should avoid doing anything as that | 201 // there's nothing to do here (and we should avoid doing anything as that |
| 146 // may clobber multi-node styled text). | 202 // may clobber multi-node styled text). |
| 147 if (!m_isDirty && composingText() == text) { | 203 if (!m_isDirty && composingText() == text) { |
| 148 clear(); | 204 clear(); |
| 149 return true; | 205 return true; |
| 150 } | 206 } |
| 151 | 207 |
| 152 // Select the text that will be deleted or replaced. | 208 // Select the text that will be deleted or replaced. |
| 153 selectComposition(); | 209 selectComposition(); |
| 154 | 210 |
| 155 if (frame().selection().isNone()) | 211 if (frame().selection().isNone()) |
| 156 return false; | 212 return false; |
| 157 | 213 |
| 158 dispatchCompositionEndEvent(frame(), text); | |
| 159 | |
| 160 if (!frame().document()) | 214 if (!frame().document()) |
| 161 return false; | 215 return false; |
| 162 | 216 |
| 163 // If text is empty, then delete the old composition here. If text is | 217 // If text is empty, then delete the old composition here. If text is |
| 164 // non-empty, InsertTextCommand::input will delete the old composition with | 218 // non-empty, InsertTextCommand::input will delete the old composition with |
| 165 // an optimized replace operation. | 219 // an optimized replace operation. |
| 166 if (text.isEmpty()) | 220 if (text.isEmpty()) |
| 167 TypingCommand::deleteSelection(*frame().document(), 0); | 221 TypingCommand::deleteSelection(*frame().document(), 0); |
| 168 | 222 |
| 169 clear(); | 223 clear(); |
| 170 | 224 |
| 171 // TODO(chongz): DOM update should happen before 'compositionend' and along
with 'compositionupdate'. | 225 insertTextDuringCompositionWithEvents(frame(), text, 0, TypingCommand::TextC
ompositionType::TextCompositionConfirm); |
| 172 // https://crbug.com/575294 | 226 // Event handler might destroy document. |
| 173 if (dispatchBeforeInputInsertText(frame().document()->focusedElement(), text
) != DispatchEventResult::NotCanceled) | 227 if (!frame().document()) |
| 174 return false; | 228 return false; |
| 175 | 229 |
| 176 insertTextForConfirmedComposition(text); | 230 // No DOM update after 'compositionend'. |
| 231 dispatchCompositionEndEvent(frame(), text); |
| 177 | 232 |
| 178 return true; | 233 return true; |
| 179 } | 234 } |
| 180 | 235 |
| 181 bool InputMethodController::confirmCompositionOrInsertText(const String& text, C
onfirmCompositionBehavior confirmBehavior) | 236 bool InputMethodController::confirmCompositionOrInsertText(const String& text, C
onfirmCompositionBehavior confirmBehavior) |
| 182 { | 237 { |
| 183 if (!hasComposition()) { | 238 if (!hasComposition()) { |
| 184 if (!text.length()) | 239 if (!text.length()) |
| 185 return false; | 240 return false; |
| 186 | 241 |
| (...skipping 19 matching lines...) Expand all Loading... |
| 206 void InputMethodController::cancelComposition() | 261 void InputMethodController::cancelComposition() |
| 207 { | 262 { |
| 208 if (!hasComposition()) | 263 if (!hasComposition()) |
| 209 return; | 264 return; |
| 210 | 265 |
| 211 Editor::RevealSelectionScope revealSelectionScope(&editor()); | 266 Editor::RevealSelectionScope revealSelectionScope(&editor()); |
| 212 | 267 |
| 213 if (frame().selection().isNone()) | 268 if (frame().selection().isNone()) |
| 214 return; | 269 return; |
| 215 | 270 |
| 216 dispatchCompositionEndEvent(frame(), emptyString()); | |
| 217 clear(); | 271 clear(); |
| 218 insertTextForConfirmedComposition(emptyString()); | 272 |
| 273 // TODO(chongz): Update InputType::DeleteComposedCharacter with latest discu
ssion. |
| 274 dispatchBeforeInputFromComposition(frame().document()->focusedElement(), Inp
utEvent::InputType::DeleteComposedCharacter, emptyString(), InputEvent::EventCan
celable::NotCancelable); |
| 275 dispatchCompositionUpdateEvent(frame(), emptyString()); |
| 276 insertTextDuringCompositionWithEvents(frame(), emptyString(), 0, TypingComma
nd::TextCompositionType::TextCompositionConfirm); |
| 277 // Event handler might destroy document. |
| 278 if (!frame().document()) |
| 279 return; |
| 219 | 280 |
| 220 // An open typing command that disagrees about current selection would cause | 281 // An open typing command that disagrees about current selection would cause |
| 221 // issues with typing later on. | 282 // issues with typing later on. |
| 222 TypingCommand::closeTyping(m_frame); | 283 TypingCommand::closeTyping(m_frame); |
| 284 |
| 285 // No DOM update after 'compositionend'. |
| 286 dispatchCompositionEndEvent(frame(), emptyString()); |
| 223 } | 287 } |
| 224 | 288 |
| 225 void InputMethodController::cancelCompositionIfSelectionIsInvalid() | 289 void InputMethodController::cancelCompositionIfSelectionIsInvalid() |
| 226 { | 290 { |
| 227 if (!hasComposition() || editor().preventRevealSelection()) | 291 if (!hasComposition() || editor().preventRevealSelection()) |
| 228 return; | 292 return; |
| 229 | 293 |
| 230 // Check if selection start and selection end are valid. | 294 // Check if selection start and selection end are valid. |
| 231 FrameSelection& selection = frame().selection(); | 295 FrameSelection& selection = frame().selection(); |
| 232 if (!selection.isNone() && !m_compositionRange->collapsed()) { | 296 if (!selection.isNone() && !m_compositionRange->collapsed()) { |
| (...skipping 13 matching lines...) Expand all Loading... |
| 246 // Updates styles before setting selection for composition to prevent | 310 // Updates styles before setting selection for composition to prevent |
| 247 // inserting the previous composition text into text nodes oddly. | 311 // inserting the previous composition text into text nodes oddly. |
| 248 // See https://bugs.webkit.org/show_bug.cgi?id=46868 | 312 // See https://bugs.webkit.org/show_bug.cgi?id=46868 |
| 249 frame().document()->updateStyleAndLayoutTree(); | 313 frame().document()->updateStyleAndLayoutTree(); |
| 250 | 314 |
| 251 selectComposition(); | 315 selectComposition(); |
| 252 | 316 |
| 253 if (frame().selection().isNone()) | 317 if (frame().selection().isNone()) |
| 254 return; | 318 return; |
| 255 | 319 |
| 256 if (Element* target = frame().document()->focusedElement()) { | 320 Element* target = frame().document()->focusedElement(); |
| 257 // Dispatch an appropriate composition event to the focused node. | 321 if (!target) |
| 258 // We check the composition status and choose an appropriate composition
event since this | 322 return; |
| 259 // function is used for three purposes: | 323 |
| 260 // 1. Starting a new composition. | 324 // Dispatch an appropriate composition event to the focused node. |
| 261 // Send a compositionstart and a compositionupdate event when this fu
nction creates | 325 // We check the composition status and choose an appropriate composition eve
nt since this |
| 262 // a new composition node, i.e. | 326 // function is used for three purposes: |
| 263 // !hasComposition() && !text.isEmpty(). | 327 // 1. Starting a new composition. |
| 264 // Sending a compositionupdate event at this time ensures that at lea
st one | 328 // Send a compositionstart and a compositionupdate event when this functi
on creates |
| 265 // compositionupdate event is dispatched. | 329 // a new composition node, i.e. |
| 266 // 2. Updating the existing composition node. | 330 // !hasComposition() && !text.isEmpty(). |
| 267 // Send a compositionupdate event when this function updates the exis
ting composition | 331 // Sending a compositionupdate event at this time ensures that at least o
ne |
| 268 // node, i.e. hasComposition() && !text.isEmpty(). | 332 // compositionupdate event is dispatched. |
| 269 // 3. Canceling the ongoing composition. | 333 // 2. Updating the existing composition node. |
| 270 // Send a compositionend event when function deletes the existing com
position node, i.e. | 334 // Send a compositionupdate event when this function updates the existing
composition |
| 271 // !hasComposition() && test.isEmpty(). | 335 // node, i.e. hasComposition() && !text.isEmpty(). |
| 272 CompositionEvent* event = nullptr; | 336 // 3. Canceling the ongoing composition. |
| 273 if (!hasComposition()) { | 337 // Send a compositionend event when function deletes the existing composi
tion node, i.e. |
| 274 // We should send a compositionstart event only when the given text
is not empty because this | 338 // !hasComposition() && test.isEmpty(). |
| 275 // function doesn't create a composition node when the text is empty
. | 339 if (text.isEmpty()) { |
| 276 if (!text.isEmpty()) { | 340 if (hasComposition()) { |
| 277 target->dispatchEvent(CompositionEvent::create(EventTypeNames::c
ompositionstart, frame().domWindow(), frame().selectedText())); | 341 confirmComposition(emptyString()); |
| 278 event = CompositionEvent::create(EventTypeNames::compositionupda
te, frame().domWindow(), text); | 342 return; |
| 279 } | |
| 280 } else { | |
| 281 if (!text.isEmpty()) | |
| 282 event = CompositionEvent::create(EventTypeNames::compositionupda
te, frame().domWindow(), text); | |
| 283 else | |
| 284 event = CompositionEvent::create(EventTypeNames::compositionend,
frame().domWindow(), text); | |
| 285 } | 343 } |
| 286 if (event) { | 344 // It's weird to call |setComposition()| with empty text outside composi
tion, however some IME |
| 287 // TODO(chongz): Support canceling IME composition. | 345 // (e.g. Japanese IBus-Anthy) did this, so we simply delete selection wi
thout sending extra events. |
| 288 // TODO(chongz): Should fire InsertText or DeleteComposedCharacter b
ased on action. | 346 TypingCommand::deleteSelection(*frame().document(), TypingCommand::Preve
ntSpellChecking); |
| 289 if (event->type() == EventTypeNames::compositionupdate) | 347 return; |
| 290 dispatchBeforeInputFromComposition(target, InputEvent::InputType
::InsertText, text); | |
| 291 target->dispatchEvent(event); | |
| 292 } | |
| 293 } | 348 } |
| 294 | 349 |
| 295 // If text is empty, then delete the old composition here. If text is non-em
pty, InsertTextCommand::input | 350 // We should send a 'compositionstart' event only when the given text is not
empty because this |
| 296 // will delete the old composition with an optimized replace operation. | 351 // function doesn't create a composition node when the text is empty. |
| 297 if (text.isEmpty()) { | 352 if (!hasComposition()) { |
| 298 DCHECK(frame().document()); | 353 target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositi
onstart, frame().domWindow(), frame().selectedText())); |
| 299 TypingCommand::deleteSelection(*frame().document(), TypingCommand::Preve
ntSpellChecking); | 354 if (!frame().document()) |
| 355 return; |
| 300 } | 356 } |
| 301 | 357 |
| 358 DCHECK(!text.isEmpty()); |
| 359 |
| 302 clear(); | 360 clear(); |
| 303 | 361 |
| 304 if (text.isEmpty()) | 362 insertTextDuringCompositionWithEvents(frame(), text, TypingCommand::SelectIn
sertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextComposition
Update); |
| 363 // Event handlers might destroy document. |
| 364 if (!frame().document()) |
| 305 return; | 365 return; |
| 306 DCHECK(frame().document()); | |
| 307 TypingCommand::insertText(*frame().document(), text, TypingCommand::SelectIn
sertedText | TypingCommand::PreventSpellChecking, TypingCommand::TextComposition
Update); | |
| 308 | 366 |
| 309 // Find out what node has the composition now. | 367 // Find out what node has the composition now. |
| 310 Position base = mostForwardCaretPosition(frame().selection().base()); | 368 Position base = mostForwardCaretPosition(frame().selection().base()); |
| 311 Node* baseNode = base.anchorNode(); | 369 Node* baseNode = base.anchorNode(); |
| 312 if (!baseNode || !baseNode->isTextNode()) | 370 if (!baseNode || !baseNode->isTextNode()) |
| 313 return; | 371 return; |
| 314 | 372 |
| 315 Position extent = frame().selection().extent(); | 373 Position extent = frame().selection().extent(); |
| 316 Node* extentNode = extent.anchorNode(); | 374 Node* extentNode = extent.anchorNode(); |
| 317 if (baseNode != extentNode) | 375 if (baseNode != extentNode) |
| (...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 489 TypingCommand::deleteSelection(*frame().document()); | 547 TypingCommand::deleteSelection(*frame().document()); |
| 490 } | 548 } |
| 491 | 549 |
| 492 DEFINE_TRACE(InputMethodController) | 550 DEFINE_TRACE(InputMethodController) |
| 493 { | 551 { |
| 494 visitor->trace(m_frame); | 552 visitor->trace(m_frame); |
| 495 visitor->trace(m_compositionRange); | 553 visitor->trace(m_compositionRange); |
| 496 } | 554 } |
| 497 | 555 |
| 498 } // namespace blink | 556 } // namespace blink |
| OLD | NEW |