Redo spellchecking of a field if the layout has changed
[WebKit-https.git] / Source / WebKit / blackberry / WebCoreSupport / EditorClientBlackBerry.cpp
1 /*
2  * Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/
3  * Copyright (C) 2009, 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19
20 #include "config.h"
21 #include "EditorClientBlackBerry.h"
22
23 #include "AutofillManager.h"
24 #include "DOMSupport.h"
25 #include "DumpRenderTreeClient.h"
26 #include "EditCommand.h"
27 #include "Frame.h"
28 #include "HTMLInputElement.h"
29 #include "HTMLNames.h"
30 #include "InputHandler.h"
31 #include "KeyboardEvent.h"
32 #include "NotImplemented.h"
33 #include "Page.h"
34 #include "PlatformKeyboardEvent.h"
35 #include "SelectionHandler.h"
36 #include "Settings.h"
37 #include "SpellChecker.h"
38 #include "WebPage_p.h"
39 #include "WindowsKeyboardCodes.h"
40
41 using namespace BlackBerry::WebKit;
42
43 namespace WebCore {
44
45 // Arbitrary depth limit for the undo stack, to keep it from using
46 // unbounded memory. This is the maximum number of distinct undoable
47 // actions -- unbroken stretches of typed characters are coalesced
48 // into a single action only when not interrupted by string replacements
49 // triggered by replaceText calls.
50 static const size_t maximumUndoStackDepth = 1000;
51
52 EditorClientBlackBerry::EditorClientBlackBerry(WebPagePrivate* webPagePrivate)
53     : m_webPagePrivate(webPagePrivate)
54     , m_waitingForCursorFocus(false)
55     , m_spellCheckState(SpellCheckDefault)
56     , m_inRedo(false)
57 {
58 }
59
60 void EditorClientBlackBerry::pageDestroyed()
61 {
62     delete this;
63 }
64
65 bool EditorClientBlackBerry::shouldDeleteRange(Range* range)
66 {
67     if (m_webPagePrivate->m_dumpRenderTree)
68         return m_webPagePrivate->m_dumpRenderTree->shouldDeleteDOMRange(range);
69     return true;
70 }
71
72 bool EditorClientBlackBerry::smartInsertDeleteEnabled()
73 {
74     Page* page = WebPagePrivate::core(m_webPagePrivate->m_webPage);
75     if (!page)
76         return false;
77     return page->settings()->smartInsertDeleteEnabled();
78 }
79
80 bool EditorClientBlackBerry::isSelectTrailingWhitespaceEnabled()
81 {
82     Page* page = WebPagePrivate::core(m_webPagePrivate->m_webPage);
83     if (!page)
84         return false;
85     return page->settings()->selectTrailingWhitespaceEnabled();
86 }
87
88 void EditorClientBlackBerry::enableSpellChecking(bool enable)
89 {
90     m_spellCheckState = enable ? SpellCheckDefault : SpellCheckOff;
91 }
92
93 bool EditorClientBlackBerry::shouldSpellCheckFocusedField()
94 {
95     const Frame* frame = m_webPagePrivate->focusedOrMainFrame();
96     if (!frame || !frame->document() || !frame->editor())
97         return false;
98
99     const Node* node = frame->document()->focusedNode();
100     // NOTE: This logic is taken from EditorClientImpl::shouldSpellcheckByDefault
101     // If |node| is null, we default to allowing spellchecking. This is done in
102     // order to mitigate the issue when the user clicks outside the textbox, as a
103     // result of which |node| becomes null, resulting in all the spell check
104     // markers being deleted. Also, the Frame will decide not to do spellchecking
105     // if the user can't edit - so returning true here will not cause any problems
106     // to the Frame's behavior.
107     if (!node)
108         return true;
109
110     // If the field does not support autocomplete, do not do spellchecking.
111     if (node->isElementNode()) {
112         const Element* element = toElement(node);
113         if (element->hasTagName(HTMLNames::inputTag) && !DOMSupport::elementSupportsAutocomplete(element))
114             return false;
115     }
116
117     // Check if the node disables spell checking directly.
118     return frame->editor()->isSpellCheckingEnabledInFocusedNode();
119 }
120
121 bool EditorClientBlackBerry::isContinuousSpellCheckingEnabled()
122 {
123     if (m_spellCheckState == SpellCheckOff)
124         return false;
125     if (m_spellCheckState == SpellCheckOn)
126         return true;
127     return shouldSpellCheckFocusedField();
128 }
129
130 void EditorClientBlackBerry::toggleContinuousSpellChecking()
131 {
132     // Use the current state to determine how to toggle, if it hasn't
133     // been explicitly set, it will toggle based on the field type.
134     if (isContinuousSpellCheckingEnabled())
135         m_spellCheckState = SpellCheckOff;
136     else
137         m_spellCheckState = SpellCheckOn;
138 }
139
140 bool EditorClientBlackBerry::isGrammarCheckingEnabled()
141 {
142     notImplemented();
143     return false;
144 }
145
146 void EditorClientBlackBerry::toggleGrammarChecking()
147 {
148     notImplemented();
149 }
150
151 int EditorClientBlackBerry::spellCheckerDocumentTag()
152 {
153     notImplemented();
154     return 0;
155 }
156
157 bool EditorClientBlackBerry::shouldBeginEditing(Range* range)
158 {
159     if (m_webPagePrivate->m_dumpRenderTree)
160         return m_webPagePrivate->m_dumpRenderTree->shouldBeginEditingInDOMRange(range);
161
162     return true;
163 }
164
165 bool EditorClientBlackBerry::shouldEndEditing(Range* range)
166 {
167     if (m_webPagePrivate->m_dumpRenderTree)
168         return m_webPagePrivate->m_dumpRenderTree->shouldEndEditingInDOMRange(range);
169     return true;
170 }
171
172 bool EditorClientBlackBerry::shouldInsertNode(Node* node, Range* range, EditorInsertAction insertAction)
173 {
174     if (m_webPagePrivate->m_dumpRenderTree)
175         return m_webPagePrivate->m_dumpRenderTree->shouldInsertNode(node, range, static_cast<int>(insertAction));
176     return true;
177 }
178
179 bool EditorClientBlackBerry::shouldInsertText(const WTF::String& text, Range* range, EditorInsertAction insertAction)
180 {
181     if (m_webPagePrivate->m_dumpRenderTree)
182         return m_webPagePrivate->m_dumpRenderTree->shouldInsertText(text, range, static_cast<int>(insertAction));
183     return true;
184 }
185
186 bool EditorClientBlackBerry::shouldChangeSelectedRange(Range* fromRange, Range* toRange, EAffinity affinity, bool stillSelecting)
187 {
188     if (m_webPagePrivate->m_dumpRenderTree)
189         return m_webPagePrivate->m_dumpRenderTree->shouldChangeSelectedDOMRangeToDOMRangeAffinityStillSelecting(fromRange, toRange, static_cast<int>(affinity), stillSelecting);
190
191     Frame* frame = m_webPagePrivate->focusedOrMainFrame();
192     if (frame && frame->document()) {
193         if (Node* focusedNode = frame->document()->focusedNode()) {
194             if (focusedNode->hasTagName(HTMLNames::selectTag))
195                 return false;
196             if (focusedNode->isElementNode() && DOMSupport::isPopupInputField(toElement(focusedNode)))
197                 return false;
198         }
199
200         // Check if this change does not represent a focus change and input is active and if so ensure the keyboard is visible.
201         if (m_webPagePrivate->m_inputHandler->isInputMode() && fromRange && toRange && (fromRange->startContainer() == toRange->startContainer()))
202             m_webPagePrivate->m_inputHandler->notifyClientOfKeyboardVisibilityChange(true);
203     }
204
205     return true;
206 }
207
208 bool EditorClientBlackBerry::shouldApplyStyle(StylePropertySet*, Range*)
209 {
210     notImplemented();
211     return true;
212 }
213
214 bool EditorClientBlackBerry::shouldMoveRangeAfterDelete(Range*, Range*)
215 {
216     notImplemented();
217     return true;
218 }
219
220 void EditorClientBlackBerry::didBeginEditing()
221 {
222     if (m_webPagePrivate->m_dumpRenderTree)
223         m_webPagePrivate->m_dumpRenderTree->didBeginEditing();
224 }
225
226 void EditorClientBlackBerry::respondToChangedContents()
227 {
228     if (m_webPagePrivate->m_dumpRenderTree)
229         m_webPagePrivate->m_dumpRenderTree->didChange();
230 }
231
232 void EditorClientBlackBerry::respondToChangedSelection(Frame* frame)
233 {
234     if (m_waitingForCursorFocus)
235         m_waitingForCursorFocus = false;
236     else
237         m_webPagePrivate->selectionChanged(frame);
238
239     if (m_webPagePrivate->m_dumpRenderTree)
240         m_webPagePrivate->m_dumpRenderTree->didChangeSelection();
241 }
242
243 void EditorClientBlackBerry::didEndEditing()
244 {
245     if (m_webPagePrivate->m_dumpRenderTree)
246         m_webPagePrivate->m_dumpRenderTree->didEndEditing();
247 }
248
249 void EditorClientBlackBerry::respondToSelectionAppearanceChange()
250 {
251     m_webPagePrivate->m_selectionHandler->selectionPositionChanged();
252 }
253
254 void EditorClientBlackBerry::didWriteSelectionToPasteboard()
255 {
256     notImplemented();
257 }
258
259 void EditorClientBlackBerry::willWriteSelectionToPasteboard(WebCore::Range*)
260 {
261     notImplemented();
262 }
263
264 void EditorClientBlackBerry::getClientPasteboardDataForRange(WebCore::Range*, Vector<String>&, Vector<RefPtr<WebCore::SharedBuffer> >&)
265 {
266     notImplemented();
267 }
268
269 void EditorClientBlackBerry::didSetSelectionTypesForPasteboard()
270 {
271     notImplemented();
272 }
273
274 void EditorClientBlackBerry::registerUndoStep(PassRefPtr<UndoStep> step)
275 {
276     // Remove the oldest item if we've reached the maximum capacity for the stack.
277     if (m_undoStack.size() == maximumUndoStackDepth)
278         m_undoStack.removeFirst();
279
280     if (!m_inRedo)
281         m_redoStack.clear();
282
283     m_undoStack.append(step);
284 }
285
286 void EditorClientBlackBerry::registerRedoStep(PassRefPtr<UndoStep> step)
287 {
288     m_redoStack.append(step);
289 }
290
291 void EditorClientBlackBerry::clearUndoRedoOperations()
292 {
293     m_undoStack.clear();
294     m_redoStack.clear();
295 }
296
297 bool EditorClientBlackBerry::canUndo() const
298 {
299     return !m_undoStack.isEmpty();
300 }
301
302 bool EditorClientBlackBerry::canRedo() const
303 {
304     return !m_redoStack.isEmpty();
305 }
306
307 bool EditorClientBlackBerry::canCopyCut(Frame*, bool defaultValue) const
308 {
309     return defaultValue;
310 }
311
312 bool EditorClientBlackBerry::canPaste(Frame*, bool defaultValue) const
313 {
314     return defaultValue;
315 }
316
317 void EditorClientBlackBerry::undo()
318 {
319     if (canUndo()) {
320         EditCommandStack::iterator back = --m_undoStack.end();
321         RefPtr<UndoStep> command(*back);
322         m_undoStack.remove(back);
323
324         // Unapply will call us back to push this command onto the redo stack.
325         command->unapply();
326     }
327 }
328
329 void EditorClientBlackBerry::redo()
330 {
331     if (canRedo()) {
332         EditCommandStack::iterator back = --m_redoStack.end();
333         RefPtr<UndoStep> command(*back);
334         m_redoStack.remove(back);
335
336         ASSERT(!m_inRedo);
337         m_inRedo = true;
338
339         // Reapply will call us back to push this command onto the undo stack.
340         command->reapply();
341         m_inRedo = false;
342     }
343 }
344
345 static const unsigned CtrlKey = 1 << 0;
346 static const unsigned AltKey = 1 << 1;
347 static const unsigned ShiftKey = 1 << 2;
348
349 struct KeyDownEntry {
350     unsigned virtualKey;
351     unsigned modifiers;
352     const char* name;
353 };
354
355 struct KeyPressEntry {
356     unsigned charCode;
357     unsigned modifiers;
358     const char* name;
359 };
360
361 static const KeyDownEntry keyDownEntries[] = {
362     { VK_LEFT,   0,                  "MoveLeft"                                    },
363     { VK_LEFT,   ShiftKey,           "MoveLeftAndModifySelection"                  },
364     { VK_LEFT,   CtrlKey,            "MoveWordLeft"                                },
365     { VK_LEFT,   CtrlKey | ShiftKey, "MoveWordLeftAndModifySelection"              },
366     { VK_RIGHT,  0,                  "MoveRight"                                   },
367     { VK_RIGHT,  ShiftKey,           "MoveRightAndModifySelection"                 },
368     { VK_RIGHT,  CtrlKey,            "MoveWordRight"                               },
369     { VK_RIGHT,  CtrlKey | ShiftKey, "MoveWordRightAndModifySelection"             },
370     { VK_UP,     0,                  "MoveUp"                                      },
371     { VK_UP,     ShiftKey,           "MoveUpAndModifySelection"                    },
372     { VK_DOWN,   0,                  "MoveDown"                                    },
373     { VK_DOWN,   ShiftKey,           "MoveDownAndModifySelection"                  },
374     { VK_PRIOR,  0,                  "MovePageUp"                                  },
375     { VK_PRIOR,  ShiftKey,           "MovePageUpAndModifySelection"                },
376     { VK_NEXT,   0,                  "MovePageDown"                                },
377     { VK_NEXT,   ShiftKey,           "MovePageDownAndModifySelection"              },
378     { VK_HOME,   0,                  "MoveToBeginningOfLine"                       },
379     { VK_HOME,   ShiftKey,           "MoveToBeginningOfLineAndModifySelection"     },
380     { VK_HOME,   CtrlKey,            "MoveToBeginningOfDocument"                   },
381     { VK_HOME,   CtrlKey | ShiftKey, "MoveToBeginningOfDocumentAndModifySelection" },
382     { VK_END,    0,                  "MoveToEndOfLine"                             },
383     { VK_END,    ShiftKey,           "MoveToEndOfLineAndModifySelection"           },
384     { VK_END,    CtrlKey,            "MoveToEndOfDocument"                         },
385     { VK_END,    CtrlKey | ShiftKey, "MoveToEndOfDocumentAndModifySelection"       },
386
387     { 'B',       CtrlKey,            "ToggleBold"                                  },
388     { 'I',       CtrlKey,            "ToggleItalic"                                },
389     { 'U',       CtrlKey,            "ToggleUnderline"                             },
390
391     { VK_BACK,   0,                  "DeleteBackward"                              },
392     { VK_BACK,   ShiftKey,           "DeleteBackward"                              },
393     { VK_DELETE, 0,                  "DeleteForward"                               },
394     { VK_BACK,   CtrlKey,            "DeleteWordBackward"                          },
395     { VK_DELETE, CtrlKey,            "DeleteWordForward"                           },
396
397     { 'C',       CtrlKey,            "Copy"                                        },
398     { 'V',       CtrlKey,            "Paste"                                       },
399     { 'V',       CtrlKey | ShiftKey, "PasteAndMatchStyle"                          },
400     { 'X',       CtrlKey,            "Cut"                                         },
401     { VK_INSERT, CtrlKey,            "Copy"                                        },
402     { VK_DELETE, ShiftKey,           "Cut"                                         },
403     { VK_INSERT, ShiftKey,           "Paste"                                       },
404
405     { 'A',       CtrlKey,            "SelectAll"                                   },
406     { 'Z',       CtrlKey,            "Undo"                                        },
407     { 'Z',       CtrlKey | ShiftKey, "Redo"                                        },
408     { 'Y',       CtrlKey,            "Redo"                                        },
409 };
410
411 static const KeyPressEntry keyPressEntries[] = {
412     { '\t',   0,                  "InsertTab"                                   },
413     { '\t',   ShiftKey,           "InsertBacktab"                               },
414     { '\r',   0,                  "InsertNewline"                               },
415     { '\r',   CtrlKey,            "InsertNewline"                               },
416     { '\r',   AltKey,             "InsertNewline"                               },
417     { '\r',   ShiftKey,           "InsertLineBreak"                             },
418     { '\r',   AltKey | ShiftKey,  "InsertNewline"                               },
419 };
420
421
422 const char* EditorClientBlackBerry::interpretKeyEvent(const KeyboardEvent* event)
423 {
424     ASSERT(event->type() == eventNames().keydownEvent || event->type() == eventNames().keypressEvent);
425
426     static HashMap<int, const char*>* keyDownCommandsMap = 0;
427     static HashMap<int, const char*>* keyPressCommandsMap = 0;
428
429     if (!keyDownCommandsMap) {
430         keyDownCommandsMap = new HashMap<int, const char*>;
431         keyPressCommandsMap = new HashMap<int, const char*>;
432
433         for (size_t i = 0; i < WTF_ARRAY_LENGTH(keyDownEntries); ++i)
434             keyDownCommandsMap->set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name);
435
436         for (size_t i = 0; i < WTF_ARRAY_LENGTH(keyPressEntries); ++i)
437             keyPressCommandsMap->set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name);
438     }
439
440     unsigned modifiers = 0;
441     if (event->shiftKey())
442         modifiers |= ShiftKey;
443     if (event->altKey())
444         modifiers |= AltKey;
445     if (event->ctrlKey())
446         modifiers |= CtrlKey;
447
448     if (event->type() == eventNames().keydownEvent) {
449         int mapKey = modifiers << 16 | event->keyCode();
450         return mapKey ? keyDownCommandsMap->get(mapKey) : 0;
451     }
452
453     int mapKey = modifiers << 16 | event->charCode();
454     return mapKey ? keyPressCommandsMap->get(mapKey) : 0;
455 }
456
457 void EditorClientBlackBerry::handleKeyboardEvent(KeyboardEvent* event)
458 {
459     ASSERT(event);
460
461     const PlatformKeyboardEvent* platformEvent = event->keyEvent();
462     if (!platformEvent)
463         return;
464
465     ASSERT(event->target()->toNode());
466     Frame* frame = event->target()->toNode()->document()->frame();
467     ASSERT(frame);
468
469     String commandName = interpretKeyEvent(event);
470
471     // Check to see we are not trying to insert text on key down.
472     ASSERT(!(event->type() == eventNames().keydownEvent && frame->editor()->command(commandName).isTextInsertion()));
473
474     if (!commandName.isEmpty()) {
475         // Hot key handling. Cancel processing mode.
476         if (commandName != "DeleteBackward")
477             m_webPagePrivate->m_inputHandler->setProcessingChange(false);
478
479         if (frame->editor()->command(commandName).execute())
480             event->setDefaultHandled();
481         return;
482     }
483
484     if (!frame->editor()->canEdit())
485         return;
486
487     // Text insertion commands should only be triggered from keypressEvent.
488     // There is an assert guaranteeing this in
489     // EventHandler::handleTextInputEvent. Note that windowsVirtualKeyCode
490     // is not set for keypressEvent: special keys should have been already
491     // handled in keydownEvent, which is called first.
492     if (event->type() != eventNames().keypressEvent)
493         return;
494
495     // Don't insert null or control characters as they can result in unexpected behaviour.
496     if (event->charCode() < ' ')
497         return;
498
499     // Don't insert anything if a modifier is pressed.
500     if (event->ctrlKey() || event->altKey())
501         return;
502
503     if (!platformEvent->text().isEmpty()) {
504         if (frame->editor()->insertText(platformEvent->text(), event))
505             event->setDefaultHandled();
506     }
507 }
508
509 void EditorClientBlackBerry::handleInputMethodKeydown(KeyboardEvent*)
510 {
511     notImplemented();
512 }
513
514 void EditorClientBlackBerry::textFieldDidBeginEditing(Element*)
515 {
516     notImplemented();
517 }
518
519 void EditorClientBlackBerry::textFieldDidEndEditing(Element* element)
520 {
521     if (m_webPagePrivate->m_webSettings->isFormAutofillEnabled()) {
522         if (HTMLInputElement* inputElement = element->toInputElement())
523             m_webPagePrivate->m_autofillManager->textFieldDidEndEditing(inputElement);
524     }
525 }
526
527 void EditorClientBlackBerry::textDidChangeInTextField(Element* element)
528 {
529     if (m_webPagePrivate->m_webSettings->isFormAutofillEnabled()) {
530         if (HTMLInputElement* inputElement = element->toInputElement())
531             m_webPagePrivate->m_autofillManager->didChangeInTextField(inputElement);
532     }
533 }
534
535 bool EditorClientBlackBerry::doTextFieldCommandFromEvent(Element*, KeyboardEvent*)
536 {
537     notImplemented();
538     return false;
539 }
540
541 void EditorClientBlackBerry::textWillBeDeletedInTextField(Element*)
542 {
543     notImplemented();
544 }
545
546 void EditorClientBlackBerry::textDidChangeInTextArea(Element*)
547 {
548     notImplemented();
549 }
550
551 bool EditorClientBlackBerry::shouldEraseMarkersAfterChangeSelection(TextCheckingType) const
552 {
553     return true;
554 }
555
556 void EditorClientBlackBerry::ignoreWordInSpellDocument(const WTF::String&)
557 {
558     notImplemented();
559 }
560
561 void EditorClientBlackBerry::learnWord(const WTF::String&)
562 {
563     notImplemented();
564 }
565
566 void EditorClientBlackBerry::checkSpellingOfString(const UChar* text, int textLength, int* misspellLocation, int* misspellLength)
567 {
568     notImplemented();
569 }
570
571 WTF::String EditorClientBlackBerry::getAutoCorrectSuggestionForMisspelledWord(const WTF::String& misspelledWord)
572 {
573     notImplemented();
574     return WTF::String();
575 }
576
577 void EditorClientBlackBerry::checkGrammarOfString(const UChar*, int, WTF::Vector<GrammarDetail, 0u>&, int*, int*)
578 {
579     notImplemented();
580 }
581
582 void EditorClientBlackBerry::requestCheckingOfString(PassRefPtr<TextCheckingRequest> testCheckingRequest)
583 {
584     RefPtr<SpellCheckRequest> spellCheckRequest = static_cast<SpellCheckRequest*>(textCheckingRequest.get());
585     m_webPagePrivate->m_inputHandler->requestCheckingOfString(spellCheckRequest);
586 }
587
588 void EditorClientBlackBerry::checkTextOfParagraph(const UChar*, int, TextCheckingTypeMask, Vector<TextCheckingResult>&)
589 {
590     notImplemented();
591 }
592
593 TextCheckerClient* EditorClientBlackBerry::textChecker()
594 {
595     return this;
596 }
597
598 void EditorClientBlackBerry::updateSpellingUIWithGrammarString(const WTF::String&, const GrammarDetail&)
599 {
600     notImplemented();
601 }
602
603 void EditorClientBlackBerry::updateSpellingUIWithMisspelledWord(const WTF::String&)
604 {
605     notImplemented();
606 }
607
608 void EditorClientBlackBerry::showSpellingUI(bool)
609 {
610     notImplemented();
611 }
612
613 bool EditorClientBlackBerry::spellingUIIsShowing()
614 {
615     notImplemented();
616     return false;
617 }
618
619 void EditorClientBlackBerry::getGuessesForWord(const WTF::String&, WTF::Vector<WTF::String, 0u>&)
620 {
621     notImplemented();
622 }
623
624 void EditorClientBlackBerry::getGuessesForWord(const String&, const String&, Vector<String>&)
625 {
626     notImplemented();
627 }
628
629 void EditorClientBlackBerry::willSetInputMethodState()
630 {
631     notImplemented();
632 }
633
634 void EditorClientBlackBerry::setInputMethodState(bool)
635 {
636     m_webPagePrivate->m_inputHandler->focusedNodeChanged();
637 }
638
639 } // namespace WebCore