Chromium Code Reviews| 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 CHECK(compositionType == TypingCommand::TextCompositionType::TextComposition Update || compositionType == TypingCommand::TextCompositionType::TextComposition Confirm); | |
|
yosin_UTC9
2016/05/20 07:58:12
Please add "<< compositionType" for ease of debugg
chongz
2016/05/20 17:44:48
Done. Also changed to DCHECK().
| |
| 78 if (!frame.document()) | |
| 79 return; | |
| 80 | |
| 81 Element* target = frame.document()->focusedElement(); | |
| 82 if (!target) | |
| 83 return; | |
| 84 | |
| 85 // TODO(chongz): Fire 'beforeinput' for the composed text being replaced/del eted. | |
| 86 | |
| 87 // Only the last confirmed text is cancelable. | |
| 88 InputEvent::EventCancelable beforeInputCancelable = (compositionType == Typi ngCommand::TextCompositionType::TextCompositionUpdate) ? InputEvent::EventCancel able::NotCancelable : InputEvent::EventCancelable::IsCancelable; | |
| 89 DispatchEventResult result = dispatchBeforeInputFromComposition(target, Inpu tEvent::InputType::InsertText, text, beforeInputCancelable); | |
| 90 | |
| 91 if (beforeInputCancelable == InputEvent::EventCancelable::IsCancelable && re sult != DispatchEventResult::NotCanceled) | |
| 92 return; | |
| 93 | |
| 94 // 'beforeinput' event handler may destroy document. | |
| 95 if (!frame.document()) | |
| 96 return; | |
|
chongz
2016/05/19 21:44:19
When would event handler destroy document...? I te
| |
| 97 | |
| 98 dispatchCompositionUpdateEvent(frame, text); | |
| 99 // 'compositionupdate' event handler may destroy document. | |
| 100 if (!frame.document()) | |
| 101 return; | |
| 102 | |
| 103 switch (compositionType) { | |
| 104 case TypingCommand::TextCompositionType::TextCompositionUpdate: | |
| 105 TypingCommand::insertText(*frame.document(), text, options, compositionT ype); | |
| 106 break; | |
| 107 case TypingCommand::TextCompositionType::TextCompositionConfirm: | |
| 108 // TODO(chongz): Use TypingCommand::insertText after TextEvent was remov ed. (Removed from spec since 2012) | |
| 109 // See TextEvent.idl. | |
| 110 frame.eventHandler().handleTextInputEvent(text, 0, TextEventInputComposi tion); | |
| 111 break; | |
| 112 default: | |
| 113 NOTREACHED(); | |
| 114 } | |
| 115 // TODO(chongz): Fire 'input' event. | |
| 116 } | |
| 117 | |
| 118 } // anonymous namespace | |
| 119 | |
| 47 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodC ontroller* inputMethodController) | 120 InputMethodController::SelectionOffsetsScope::SelectionOffsetsScope(InputMethodC ontroller* inputMethodController) |
| 48 : m_inputMethodController(inputMethodController) | 121 : m_inputMethodController(inputMethodController) |
| 49 , m_offsets(inputMethodController->getSelectionOffsets()) | 122 , m_offsets(inputMethodController->getSelectionOffsets()) |
| 50 { | 123 { |
| 51 } | 124 } |
| 52 | 125 |
| 53 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope() | 126 InputMethodController::SelectionOffsetsScope::~SelectionOffsetsScope() |
| 54 { | 127 { |
| 55 m_inputMethodController->setSelectionOffsets(m_offsets); | 128 m_inputMethodController->setSelectionOffsets(m_offsets); |
| 56 } | 129 } |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 89 frame().document()->markers().removeMarkers(DocumentMarker::Composition); | 162 frame().document()->markers().removeMarkers(DocumentMarker::Composition); |
| 90 m_isDirty = false; | 163 m_isDirty = false; |
| 91 } | 164 } |
| 92 | 165 |
| 93 void InputMethodController::documentDetached() | 166 void InputMethodController::documentDetached() |
| 94 { | 167 { |
| 95 clear(); | 168 clear(); |
| 96 m_compositionRange = nullptr; | 169 m_compositionRange = nullptr; |
| 97 } | 170 } |
| 98 | 171 |
| 99 bool InputMethodController::insertTextForConfirmedComposition(const String& text ) | |
| 100 { | |
| 101 return frame().eventHandler().handleTextInputEvent(text, 0, TextEventInputCo mposition); | |
| 102 } | |
| 103 | |
| 104 void InputMethodController::selectComposition() const | 172 void InputMethodController::selectComposition() const |
| 105 { | 173 { |
| 106 const EphemeralRange range = compositionEphemeralRange(); | 174 const EphemeralRange range = compositionEphemeralRange(); |
| 107 if (range.isNull()) | 175 if (range.isNull()) |
| 108 return; | 176 return; |
| 109 | 177 |
| 110 // The composition can start inside a composed character sequence, so we hav e to override checks. | 178 // 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> | 179 // See <http://bugs.webkit.org/show_bug.cgi?id=15781> |
| 112 VisibleSelection selection; | 180 VisibleSelection selection; |
| 113 selection.setWithoutValidation(range.startPosition(), range.endPosition()); | 181 selection.setWithoutValidation(range.startPosition(), range.endPosition()); |
| 114 frame().selection().setSelection(selection, 0); | 182 frame().selection().setSelection(selection, 0); |
| 115 } | 183 } |
| 116 | 184 |
| 117 bool InputMethodController::confirmComposition() | 185 bool InputMethodController::confirmComposition() |
| 118 { | 186 { |
| 119 return confirmComposition(composingText()); | 187 return confirmComposition(composingText()); |
| 120 } | 188 } |
| 121 | 189 |
| 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) | 190 bool InputMethodController::confirmComposition(const String& text, ConfirmCompos itionBehavior confirmBehavior) |
| 136 { | 191 { |
| 137 if (!hasComposition()) | 192 if (!hasComposition()) |
| 138 return false; | 193 return false; |
| 139 | 194 |
| 140 Optional<Editor::RevealSelectionScope> revealSelectionScope; | 195 Optional<Editor::RevealSelectionScope> revealSelectionScope; |
| 141 if (confirmBehavior == KeepSelection) | 196 if (confirmBehavior == KeepSelection) |
| 142 revealSelectionScope.emplace(&editor()); | 197 revealSelectionScope.emplace(&editor()); |
| 143 | 198 |
| 144 // If the composition was set from existing text and didn't change, then | 199 // 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 | 200 // there's nothing to do here (and we should avoid doing anything as that |
| 146 // may clobber multi-node styled text). | 201 // may clobber multi-node styled text). |
| 147 if (!m_isDirty && composingText() == text) { | 202 if (!m_isDirty && composingText() == text) { |
| 148 clear(); | 203 clear(); |
| 149 return true; | 204 return true; |
| 150 } | 205 } |
| 151 | 206 |
| 152 // Select the text that will be deleted or replaced. | 207 // Select the text that will be deleted or replaced. |
| 153 selectComposition(); | 208 selectComposition(); |
| 154 | 209 |
| 155 if (frame().selection().isNone()) | 210 if (frame().selection().isNone()) |
| 156 return false; | 211 return false; |
| 157 | 212 |
| 158 dispatchCompositionEndEvent(frame(), text); | |
| 159 | |
| 160 if (!frame().document()) | 213 if (!frame().document()) |
| 161 return false; | 214 return false; |
| 162 | 215 |
| 163 // If text is empty, then delete the old composition here. If text is | 216 // If text is empty, then delete the old composition here. If text is |
| 164 // non-empty, InsertTextCommand::input will delete the old composition with | 217 // non-empty, InsertTextCommand::input will delete the old composition with |
| 165 // an optimized replace operation. | 218 // an optimized replace operation. |
| 166 if (text.isEmpty()) | 219 if (text.isEmpty()) |
| 167 TypingCommand::deleteSelection(*frame().document(), 0); | 220 TypingCommand::deleteSelection(*frame().document(), 0); |
| 168 | 221 |
| 169 clear(); | 222 clear(); |
| 170 | 223 |
| 171 // TODO(chongz): DOM update should happen before 'compositionend' and along with 'compositionupdate'. | 224 insertTextDuringCompositionWithEvents(frame(), text, 0, TypingCommand::TextC ompositionType::TextCompositionConfirm); |
| 172 // https://crbug.com/575294 | 225 // Event handler might destroy document. |
| 173 if (dispatchBeforeInputInsertText(frame().document()->focusedElement(), text ) != DispatchEventResult::NotCanceled) | 226 if (!frame().document()) |
| 174 return false; | 227 return false; |
| 175 | 228 |
| 176 insertTextForConfirmedComposition(text); | 229 // No DOM update after 'compositionend'. |
| 230 dispatchCompositionEndEvent(frame(), text); | |
| 177 | 231 |
| 178 return true; | 232 return true; |
| 179 } | 233 } |
| 180 | 234 |
| 181 bool InputMethodController::confirmCompositionOrInsertText(const String& text, C onfirmCompositionBehavior confirmBehavior) | 235 bool InputMethodController::confirmCompositionOrInsertText(const String& text, C onfirmCompositionBehavior confirmBehavior) |
| 182 { | 236 { |
| 183 if (!hasComposition()) { | 237 if (!hasComposition()) { |
| 184 if (!text.length()) | 238 if (!text.length()) |
| 185 return false; | 239 return false; |
| 186 | 240 |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 206 void InputMethodController::cancelComposition() | 260 void InputMethodController::cancelComposition() |
| 207 { | 261 { |
| 208 if (!hasComposition()) | 262 if (!hasComposition()) |
| 209 return; | 263 return; |
| 210 | 264 |
| 211 Editor::RevealSelectionScope revealSelectionScope(&editor()); | 265 Editor::RevealSelectionScope revealSelectionScope(&editor()); |
| 212 | 266 |
| 213 if (frame().selection().isNone()) | 267 if (frame().selection().isNone()) |
| 214 return; | 268 return; |
| 215 | 269 |
| 216 dispatchCompositionEndEvent(frame(), emptyString()); | |
| 217 clear(); | 270 clear(); |
| 218 insertTextForConfirmedComposition(emptyString()); | 271 |
| 272 // TODO(chongz): Update InputType::DeleteComposedCharacter with latest discu ssion. | |
| 273 dispatchBeforeInputFromComposition(frame().document()->focusedElement(), Inp utEvent::InputType::DeleteComposedCharacter, emptyString(), InputEvent::EventCan celable::NotCancelable); | |
| 274 dispatchCompositionUpdateEvent(frame(), emptyString()); | |
| 275 insertTextDuringCompositionWithEvents(frame(), emptyString(), 0, TypingComma nd::TextCompositionType::TextCompositionConfirm); | |
| 276 // Event handler might destroy document. | |
| 277 if (!frame().document()) | |
| 278 return; | |
| 219 | 279 |
| 220 // An open typing command that disagrees about current selection would cause | 280 // An open typing command that disagrees about current selection would cause |
| 221 // issues with typing later on. | 281 // issues with typing later on. |
| 222 TypingCommand::closeTyping(m_frame); | 282 TypingCommand::closeTyping(m_frame); |
| 283 | |
| 284 // No DOM update after 'compositionend'. | |
| 285 dispatchCompositionEndEvent(frame(), emptyString()); | |
| 223 } | 286 } |
| 224 | 287 |
| 225 void InputMethodController::cancelCompositionIfSelectionIsInvalid() | 288 void InputMethodController::cancelCompositionIfSelectionIsInvalid() |
| 226 { | 289 { |
| 227 if (!hasComposition() || editor().preventRevealSelection()) | 290 if (!hasComposition() || editor().preventRevealSelection()) |
| 228 return; | 291 return; |
| 229 | 292 |
| 230 // Check if selection start and selection end are valid. | 293 // Check if selection start and selection end are valid. |
| 231 FrameSelection& selection = frame().selection(); | 294 FrameSelection& selection = frame().selection(); |
| 232 if (!selection.isNone() && !m_compositionRange->collapsed()) { | 295 if (!selection.isNone() && !m_compositionRange->collapsed()) { |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 246 // Updates styles before setting selection for composition to prevent | 309 // Updates styles before setting selection for composition to prevent |
| 247 // inserting the previous composition text into text nodes oddly. | 310 // inserting the previous composition text into text nodes oddly. |
| 248 // See https://bugs.webkit.org/show_bug.cgi?id=46868 | 311 // See https://bugs.webkit.org/show_bug.cgi?id=46868 |
| 249 frame().document()->updateStyleAndLayoutTree(); | 312 frame().document()->updateStyleAndLayoutTree(); |
| 250 | 313 |
| 251 selectComposition(); | 314 selectComposition(); |
| 252 | 315 |
| 253 if (frame().selection().isNone()) | 316 if (frame().selection().isNone()) |
| 254 return; | 317 return; |
| 255 | 318 |
| 256 if (Element* target = frame().document()->focusedElement()) { | 319 Element* target = frame().document()->focusedElement(); |
| 257 // Dispatch an appropriate composition event to the focused node. | 320 if (!target) |
| 258 // We check the composition status and choose an appropriate composition event since this | 321 return; |
| 259 // function is used for three purposes: | 322 |
| 260 // 1. Starting a new composition. | 323 // Dispatch an appropriate composition event to the focused node. |
| 261 // Send a compositionstart and a compositionupdate event when this fu nction creates | 324 // We check the composition status and choose an appropriate composition eve nt since this |
| 262 // a new composition node, i.e. | 325 // function is used for three purposes: |
| 263 // !hasComposition() && !text.isEmpty(). | 326 // 1. Starting a new composition. |
| 264 // Sending a compositionupdate event at this time ensures that at lea st one | 327 // Send a compositionstart and a compositionupdate event when this functi on creates |
| 265 // compositionupdate event is dispatched. | 328 // a new composition node, i.e. |
| 266 // 2. Updating the existing composition node. | 329 // !hasComposition() && !text.isEmpty(). |
| 267 // Send a compositionupdate event when this function updates the exis ting composition | 330 // Sending a compositionupdate event at this time ensures that at least o ne |
| 268 // node, i.e. hasComposition() && !text.isEmpty(). | 331 // compositionupdate event is dispatched. |
| 269 // 3. Canceling the ongoing composition. | 332 // 2. Updating the existing composition node. |
| 270 // Send a compositionend event when function deletes the existing com position node, i.e. | 333 // Send a compositionupdate event when this function updates the existing composition |
| 271 // !hasComposition() && test.isEmpty(). | 334 // node, i.e. hasComposition() && !text.isEmpty(). |
| 272 CompositionEvent* event = nullptr; | 335 // 3. Canceling the ongoing composition. |
| 273 if (!hasComposition()) { | 336 // 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 | 337 // !hasComposition() && test.isEmpty(). |
| 275 // function doesn't create a composition node when the text is empty . | 338 if (text.isEmpty()) { |
| 276 if (!text.isEmpty()) { | 339 if (hasComposition()) { |
| 277 target->dispatchEvent(CompositionEvent::create(EventTypeNames::c ompositionstart, frame().domWindow(), frame().selectedText())); | 340 confirmComposition(emptyString()); |
| 278 event = CompositionEvent::create(EventTypeNames::compositionupda te, frame().domWindow(), text); | 341 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 } | 342 } |
| 286 if (event) { | 343 // It's weird to call |setComposition()| with empty text outside composi tion, however some IME |
| 287 // TODO(chongz): Support canceling IME composition. | 344 // (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. | 345 TypingCommand::deleteSelection(*frame().document(), TypingCommand::Preve ntSpellChecking); |
| 289 if (event->type() == EventTypeNames::compositionupdate) | 346 return; |
| 290 dispatchBeforeInputFromComposition(target, InputEvent::InputType ::InsertText, text); | |
| 291 target->dispatchEvent(event); | |
| 292 } | |
| 293 } | 347 } |
| 294 | 348 |
| 295 // If text is empty, then delete the old composition here. If text is non-em pty, InsertTextCommand::input | 349 // 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. | 350 // function doesn't create a composition node when the text is empty. |
| 297 if (text.isEmpty()) { | 351 if (!hasComposition()) { |
| 298 DCHECK(frame().document()); | 352 target->dispatchEvent(CompositionEvent::create(EventTypeNames::compositi onstart, frame().domWindow(), frame().selectedText())); |
| 299 TypingCommand::deleteSelection(*frame().document(), TypingCommand::Preve ntSpellChecking); | 353 if (!frame().document()) |
| 354 return; | |
| 300 } | 355 } |
| 301 | 356 |
| 357 CHECK(!text.isEmpty()); | |
|
yosin_UTC9
2016/05/20 07:58:12
Do we really need to use |CHECK()|?
chongz
2016/05/20 17:44:48
Probably overkill since I already filtered it out.
yosin_UTC9
2016/05/23 04:57:28
I supposed to use |DCHECK()| instead of |CHECK()|.
| |
| 358 CHECK(frame().document()); | |
|
yosin_UTC9
2016/05/20 07:58:12
This CHECK() makes L364 redundant. Which one shoul
chongz
2016/05/20 17:44:48
L364 was trying to handle the case you mentioned b
yosin_UTC9
2016/05/23 04:57:28
ClusterFuzz will find test case. ;-)
| |
| 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 |