Home | History | Annotate | Download | only in WebCoreSupport
      1 /*
      2  *  Copyright (C) 2007 Alp Toker <alp (at) atoker.com>
      3  *  Copyright (C) 2008 Nuanti Ltd.
      4  *  Copyright (C) 2009 Diego Escalante Urrelo <diegoe (at) gnome.org>
      5  *  Copyright (C) 2006, 2007 Apple Inc.  All rights reserved.
      6  *  Copyright (C) 2009, Igalia S.L.
      7  *
      8  *  This library is free software; you can redistribute it and/or
      9  *  modify it under the terms of the GNU Lesser General Public
     10  *  License as published by the Free Software Foundation; either
     11  *  version 2 of the License, or (at your option) any later version.
     12  *
     13  *  This library is distributed in the hope that it will be useful,
     14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     16  *  Lesser General Public License for more details.
     17  *
     18  *  You should have received a copy of the GNU Lesser General Public
     19  *  License along with this library; if not, write to the Free Software
     20  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
     21  */
     22 
     23 #include "config.h"
     24 #include "EditorClientGtk.h"
     25 
     26 #include "CString.h"
     27 #include "DataObjectGtk.h"
     28 #include "EditCommand.h"
     29 #include "Editor.h"
     30 #include <enchant.h>
     31 #include "EventNames.h"
     32 #include "FocusController.h"
     33 #include "Frame.h"
     34 #include <glib.h>
     35 #include "KeyboardCodes.h"
     36 #include "KeyboardEvent.h"
     37 #include "NotImplemented.h"
     38 #include "Page.h"
     39 #include "PasteboardHelperGtk.h"
     40 #include "PlatformKeyboardEvent.h"
     41 #include "markup.h"
     42 #include "webkitprivate.h"
     43 
     44 // Arbitrary depth limit for the undo stack, to keep it from using
     45 // unbounded memory.  This is the maximum number of distinct undoable
     46 // actions -- unbroken stretches of typed characters are coalesced
     47 // into a single action.
     48 #define maximumUndoStackDepth 1000
     49 
     50 using namespace WebCore;
     51 
     52 namespace WebKit {
     53 
     54 static gchar* pendingComposition = 0;
     55 static gchar* pendingPreedit = 0;
     56 
     57 static void setPendingComposition(gchar* newComposition)
     58 {
     59     g_free(pendingComposition);
     60     pendingComposition = newComposition;
     61 }
     62 
     63 static void setPendingPreedit(gchar* newPreedit)
     64 {
     65     g_free(pendingPreedit);
     66     pendingPreedit = newPreedit;
     67 }
     68 
     69 static void clearPendingIMData()
     70 {
     71     setPendingComposition(0);
     72     setPendingPreedit(0);
     73 }
     74 static void imContextCommitted(GtkIMContext* context, const gchar* str, EditorClient* client)
     75 {
     76     // This signal will fire during a keydown event. We want the contents of the
     77     // field to change right before the keyup event, so we wait until then to actually
     78     // commit this composition.
     79     setPendingComposition(g_strdup(str));
     80 }
     81 
     82 static void imContextPreeditChanged(GtkIMContext* context, EditorClient* client)
     83 {
     84     // We ignore the provided PangoAttrList for now.
     85     gchar* newPreedit = 0;
     86     gtk_im_context_get_preedit_string(context, &newPreedit, NULL, NULL);
     87     setPendingPreedit(newPreedit);
     88 }
     89 
     90 void EditorClient::setInputMethodState(bool active)
     91 {
     92     WebKitWebViewPrivate* priv = m_webView->priv;
     93 
     94     if (active)
     95         gtk_im_context_focus_in(priv->imContext);
     96     else
     97         gtk_im_context_focus_out(priv->imContext);
     98 
     99 #ifdef MAEMO_CHANGES
    100     if (active)
    101         hildon_gtk_im_context_show(priv->imContext);
    102     else
    103         hildon_gtk_im_context_hide(priv->imContext);
    104 #endif
    105 }
    106 
    107 bool EditorClient::shouldDeleteRange(Range*)
    108 {
    109     notImplemented();
    110     return true;
    111 }
    112 
    113 bool EditorClient::shouldShowDeleteInterface(HTMLElement*)
    114 {
    115     return false;
    116 }
    117 
    118 bool EditorClient::isContinuousSpellCheckingEnabled()
    119 {
    120     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
    121 
    122     gboolean enabled;
    123     g_object_get(settings, "enable-spell-checking", &enabled, NULL);
    124 
    125     return enabled;
    126 }
    127 
    128 bool EditorClient::isGrammarCheckingEnabled()
    129 {
    130     notImplemented();
    131     return false;
    132 }
    133 
    134 int EditorClient::spellCheckerDocumentTag()
    135 {
    136     notImplemented();
    137     return 0;
    138 }
    139 
    140 bool EditorClient::shouldBeginEditing(WebCore::Range*)
    141 {
    142     clearPendingIMData();
    143 
    144     notImplemented();
    145     return true;
    146 }
    147 
    148 bool EditorClient::shouldEndEditing(WebCore::Range*)
    149 {
    150     clearPendingIMData();
    151 
    152     notImplemented();
    153     return true;
    154 }
    155 
    156 bool EditorClient::shouldInsertText(const String&, Range*, EditorInsertAction)
    157 {
    158     notImplemented();
    159     return true;
    160 }
    161 
    162 bool EditorClient::shouldChangeSelectedRange(Range*, Range*, EAffinity, bool)
    163 {
    164     notImplemented();
    165     return true;
    166 }
    167 
    168 bool EditorClient::shouldApplyStyle(WebCore::CSSStyleDeclaration*, WebCore::Range*)
    169 {
    170     notImplemented();
    171     return true;
    172 }
    173 
    174 bool EditorClient::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*)
    175 {
    176     notImplemented();
    177     return true;
    178 }
    179 
    180 void EditorClient::didBeginEditing()
    181 {
    182     notImplemented();
    183 }
    184 
    185 void EditorClient::respondToChangedContents()
    186 {
    187     notImplemented();
    188 }
    189 
    190 void EditorClient::respondToChangedSelection()
    191 {
    192     WebKitWebViewPrivate* priv = m_webView->priv;
    193     WebCore::Page* corePage = core(m_webView);
    194     Frame* targetFrame = corePage->focusController()->focusedOrMainFrame();
    195 
    196     if (!targetFrame)
    197         return;
    198 
    199     if (targetFrame->editor()->ignoreCompositionSelectionChange())
    200         return;
    201 
    202 #if PLATFORM(X11)
    203     GtkClipboard* clipboard = gtk_widget_get_clipboard(GTK_WIDGET(m_webView), GDK_SELECTION_PRIMARY);
    204     DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard);
    205 
    206     if (targetFrame->selection()->isRange()) {
    207         dataObject->clear();
    208         dataObject->setRange(targetFrame->selection()->toNormalizedRange());
    209         pasteboardHelperInstance()->writeClipboardContents(clipboard, m_webView);
    210     }
    211 #endif
    212 
    213     if (!targetFrame->editor()->hasComposition())
    214         return;
    215 
    216     unsigned start;
    217     unsigned end;
    218     if (!targetFrame->editor()->getCompositionSelection(start, end)) {
    219         // gtk_im_context_reset() clears the composition for us.
    220         gtk_im_context_reset(priv->imContext);
    221         targetFrame->editor()->confirmCompositionWithoutDisturbingSelection();
    222     }
    223 }
    224 
    225 void EditorClient::didEndEditing()
    226 {
    227     notImplemented();
    228 }
    229 
    230 void EditorClient::didWriteSelectionToPasteboard()
    231 {
    232     notImplemented();
    233 }
    234 
    235 void EditorClient::didSetSelectionTypesForPasteboard()
    236 {
    237     notImplemented();
    238 }
    239 
    240 bool EditorClient::isEditable()
    241 {
    242     return webkit_web_view_get_editable(m_webView);
    243 }
    244 
    245 void EditorClient::registerCommandForUndo(WTF::PassRefPtr<WebCore::EditCommand> command)
    246 {
    247     if (undoStack.size() == maximumUndoStackDepth)
    248         undoStack.removeFirst();
    249     if (!m_isInRedo)
    250         redoStack.clear();
    251     undoStack.append(command);
    252 }
    253 
    254 void EditorClient::registerCommandForRedo(WTF::PassRefPtr<WebCore::EditCommand> command)
    255 {
    256     redoStack.append(command);
    257 }
    258 
    259 void EditorClient::clearUndoRedoOperations()
    260 {
    261     undoStack.clear();
    262     redoStack.clear();
    263 }
    264 
    265 bool EditorClient::canUndo() const
    266 {
    267     return !undoStack.isEmpty();
    268 }
    269 
    270 bool EditorClient::canRedo() const
    271 {
    272     return !redoStack.isEmpty();
    273 }
    274 
    275 void EditorClient::undo()
    276 {
    277     if (canUndo()) {
    278         RefPtr<WebCore::EditCommand> command(*(--undoStack.end()));
    279         undoStack.remove(--undoStack.end());
    280         // unapply will call us back to push this command onto the redo stack.
    281         command->unapply();
    282     }
    283 }
    284 
    285 void EditorClient::redo()
    286 {
    287     if (canRedo()) {
    288         RefPtr<WebCore::EditCommand> command(*(--redoStack.end()));
    289         redoStack.remove(--redoStack.end());
    290 
    291         ASSERT(!m_isInRedo);
    292         m_isInRedo = true;
    293         // reapply will call us back to push this command onto the undo stack.
    294         command->reapply();
    295         m_isInRedo = false;
    296     }
    297 }
    298 
    299 bool EditorClient::shouldInsertNode(Node*, Range*, EditorInsertAction)
    300 {
    301     notImplemented();
    302     return true;
    303 }
    304 
    305 void EditorClient::pageDestroyed()
    306 {
    307     delete this;
    308 }
    309 
    310 bool EditorClient::smartInsertDeleteEnabled()
    311 {
    312     notImplemented();
    313     return false;
    314 }
    315 
    316 bool EditorClient::isSelectTrailingWhitespaceEnabled()
    317 {
    318     notImplemented();
    319     return false;
    320 }
    321 
    322 void EditorClient::toggleContinuousSpellChecking()
    323 {
    324     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
    325 
    326     gboolean enabled;
    327     g_object_get(settings, "enable-spell-checking", &enabled, NULL);
    328 
    329     g_object_set(settings, "enable-spell-checking", !enabled, NULL);
    330 }
    331 
    332 void EditorClient::toggleGrammarChecking()
    333 {
    334 }
    335 
    336 static const unsigned CtrlKey = 1 << 0;
    337 static const unsigned AltKey = 1 << 1;
    338 static const unsigned ShiftKey = 1 << 2;
    339 
    340 struct KeyDownEntry {
    341     unsigned virtualKey;
    342     unsigned modifiers;
    343     const char* name;
    344 };
    345 
    346 struct KeyPressEntry {
    347     unsigned charCode;
    348     unsigned modifiers;
    349     const char* name;
    350 };
    351 
    352 static const KeyDownEntry keyDownEntries[] = {
    353     { VK_LEFT,   0,                  "MoveLeft"                                    },
    354     { VK_LEFT,   ShiftKey,           "MoveLeftAndModifySelection"                  },
    355     { VK_LEFT,   CtrlKey,            "MoveWordLeft"                                },
    356     { VK_LEFT,   CtrlKey | ShiftKey, "MoveWordLeftAndModifySelection"              },
    357     { VK_RIGHT,  0,                  "MoveRight"                                   },
    358     { VK_RIGHT,  ShiftKey,           "MoveRightAndModifySelection"                 },
    359     { VK_RIGHT,  CtrlKey,            "MoveWordRight"                               },
    360     { VK_RIGHT,  CtrlKey | ShiftKey, "MoveWordRightAndModifySelection"             },
    361     { VK_UP,     0,                  "MoveUp"                                      },
    362     { VK_UP,     ShiftKey,           "MoveUpAndModifySelection"                    },
    363     { VK_PRIOR,  ShiftKey,           "MovePageUpAndModifySelection"                },
    364     { VK_DOWN,   0,                  "MoveDown"                                    },
    365     { VK_DOWN,   ShiftKey,           "MoveDownAndModifySelection"                  },
    366     { VK_NEXT,   ShiftKey,           "MovePageDownAndModifySelection"              },
    367     { VK_PRIOR,  0,                  "MovePageUp"                                  },
    368     { VK_NEXT,   0,                  "MovePageDown"                                },
    369     { VK_HOME,   0,                  "MoveToBeginningOfLine"                       },
    370     { VK_HOME,   ShiftKey,           "MoveToBeginningOfLineAndModifySelection"     },
    371     { VK_HOME,   CtrlKey,            "MoveToBeginningOfDocument"                   },
    372     { VK_HOME,   CtrlKey | ShiftKey, "MoveToBeginningOfDocumentAndModifySelection" },
    373 
    374     { VK_END,    0,                  "MoveToEndOfLine"                             },
    375     { VK_END,    ShiftKey,           "MoveToEndOfLineAndModifySelection"           },
    376     { VK_END,    CtrlKey,            "MoveToEndOfDocument"                         },
    377     { VK_END,    CtrlKey | ShiftKey, "MoveToEndOfDocumentAndModifySelection"       },
    378 
    379     { VK_BACK,   0,                  "DeleteBackward"                              },
    380     { VK_BACK,   ShiftKey,           "DeleteBackward"                              },
    381     { VK_DELETE, 0,                  "DeleteForward"                               },
    382     { VK_BACK,   CtrlKey,            "DeleteWordBackward"                          },
    383     { VK_DELETE, CtrlKey,            "DeleteWordForward"                           },
    384 
    385     { 'B',       CtrlKey,            "ToggleBold"                                  },
    386     { 'I',       CtrlKey,            "ToggleItalic"                                },
    387 
    388     { VK_ESCAPE, 0,                  "Cancel"                                      },
    389     { VK_OEM_PERIOD, CtrlKey,        "Cancel"                                      },
    390     { VK_TAB,    0,                  "InsertTab"                                   },
    391     { VK_TAB,    ShiftKey,           "InsertBacktab"                               },
    392     { VK_RETURN, 0,                  "InsertNewline"                               },
    393     { VK_RETURN, CtrlKey,            "InsertNewline"                               },
    394     { VK_RETURN, AltKey,             "InsertNewline"                               },
    395     { VK_RETURN, AltKey | ShiftKey,  "InsertNewline"                               },
    396 };
    397 
    398 static const KeyPressEntry keyPressEntries[] = {
    399     { '\t',   0,                  "InsertTab"                                   },
    400     { '\t',   ShiftKey,           "InsertBacktab"                               },
    401     { '\r',   0,                  "InsertNewline"                               },
    402     { '\r',   CtrlKey,            "InsertNewline"                               },
    403     { '\r',   AltKey,             "InsertNewline"                               },
    404     { '\r',   AltKey | ShiftKey,  "InsertNewline"                               },
    405 };
    406 
    407 static const char* interpretEditorCommandKeyEvent(const KeyboardEvent* evt)
    408 {
    409     ASSERT(evt->type() == eventNames().keydownEvent || evt->type() == eventNames().keypressEvent);
    410 
    411     static HashMap<int, const char*>* keyDownCommandsMap = 0;
    412     static HashMap<int, const char*>* keyPressCommandsMap = 0;
    413 
    414     if (!keyDownCommandsMap) {
    415         keyDownCommandsMap = new HashMap<int, const char*>;
    416         keyPressCommandsMap = new HashMap<int, const char*>;
    417 
    418         for (unsigned i = 0; i < G_N_ELEMENTS(keyDownEntries); i++)
    419             keyDownCommandsMap->set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name);
    420 
    421         for (unsigned i = 0; i < G_N_ELEMENTS(keyPressEntries); i++)
    422             keyPressCommandsMap->set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name);
    423     }
    424 
    425     unsigned modifiers = 0;
    426     if (evt->shiftKey())
    427         modifiers |= ShiftKey;
    428     if (evt->altKey())
    429         modifiers |= AltKey;
    430     if (evt->ctrlKey())
    431         modifiers |= CtrlKey;
    432 
    433     if (evt->type() == eventNames().keydownEvent) {
    434         int mapKey = modifiers << 16 | evt->keyCode();
    435         return mapKey ? keyDownCommandsMap->get(mapKey) : 0;
    436     }
    437 
    438     int mapKey = modifiers << 16 | evt->charCode();
    439     return mapKey ? keyPressCommandsMap->get(mapKey) : 0;
    440 }
    441 
    442 void EditorClient::handleKeyboardEvent(KeyboardEvent* event)
    443 {
    444     Node* node = event->target()->toNode();
    445     ASSERT(node);
    446     Frame* frame = node->document()->frame();
    447     ASSERT(frame);
    448 
    449     const PlatformKeyboardEvent* platformEvent = event->keyEvent();
    450     if (!platformEvent)
    451         return;
    452 
    453     // Don't allow editor commands or text insertion for nodes that
    454     // cannot edit, unless we are in caret mode.
    455     if (!frame->editor()->canEdit() && !(frame->settings() && frame->settings()->caretBrowsingEnabled()))
    456         return;
    457 
    458     const gchar* editorCommandString = interpretEditorCommandKeyEvent(event);
    459     if (editorCommandString) {
    460         Editor::Command command = frame->editor()->command(editorCommandString);
    461 
    462         // On editor commands from key down events, we only want to let the event bubble up to
    463         // the DOM if it inserts text. If it doesn't insert text (e.g. Tab that changes focus)
    464         // we just want WebKit to handle it immediately without a DOM event.
    465         if (platformEvent->type() == PlatformKeyboardEvent::RawKeyDown) {
    466             if (!command.isTextInsertion() && command.execute(event))
    467                 event->setDefaultHandled();
    468 
    469             return;
    470         } else if (command.execute(event)) {
    471             event->setDefaultHandled();
    472             return;
    473         }
    474     }
    475 
    476     // This is just a normal text insertion, so wait to execute the insertion
    477     // until a keypress event happens. This will ensure that the insertion will not
    478     // be reflected in the contents of the field until the keyup DOM event.
    479     if (event->type() == eventNames().keypressEvent) {
    480 
    481         if (pendingComposition) {
    482             String compositionString = String::fromUTF8(pendingComposition);
    483             frame->editor()->confirmComposition(compositionString);
    484 
    485             clearPendingIMData();
    486             event->setDefaultHandled();
    487 
    488         } else if (pendingPreedit) {
    489             String preeditString = String::fromUTF8(pendingPreedit);
    490 
    491             // Don't use an empty preedit as it will destroy the current
    492             // selection, even if the composition is cancelled or fails later on.
    493             if (!preeditString.isEmpty()) {
    494                 Vector<CompositionUnderline> underlines;
    495                 underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
    496                 frame->editor()->setComposition(preeditString, underlines, 0, 0);
    497             }
    498 
    499             clearPendingIMData();
    500             event->setDefaultHandled();
    501 
    502         } else {
    503             // Don't insert null or control characters as they can result in unexpected behaviour
    504             if (event->charCode() < ' ')
    505                 return;
    506 
    507             // Don't insert anything if a modifier is pressed
    508             if (platformEvent->ctrlKey() || platformEvent->altKey())
    509                 return;
    510 
    511             if (frame->editor()->insertText(platformEvent->text(), event))
    512                 event->setDefaultHandled();
    513         }
    514     }
    515 }
    516 
    517 void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
    518 {
    519     Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
    520     if (!targetFrame || !targetFrame->editor()->canEdit())
    521         return;
    522 
    523     // TODO: We need to decide which filtered keystrokes should be treated as IM
    524     // events and which should not.
    525     WebKitWebViewPrivate* priv = m_webView->priv;
    526     gtk_im_context_filter_keypress(priv->imContext, event->keyEvent()->gdkEventKey());
    527 }
    528 
    529 EditorClient::EditorClient(WebKitWebView* webView)
    530     : m_isInRedo(false)
    531     , m_webView(webView)
    532 {
    533     WebKitWebViewPrivate* priv = m_webView->priv;
    534     g_signal_connect(priv->imContext, "commit", G_CALLBACK(imContextCommitted), this);
    535     g_signal_connect(priv->imContext, "preedit-changed", G_CALLBACK(imContextPreeditChanged), this);
    536 }
    537 
    538 EditorClient::~EditorClient()
    539 {
    540     WebKitWebViewPrivate* priv = m_webView->priv;
    541     g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextCommitted, this);
    542     g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextPreeditChanged, this);
    543 }
    544 
    545 void EditorClient::textFieldDidBeginEditing(Element*)
    546 {
    547 }
    548 
    549 void EditorClient::textFieldDidEndEditing(Element*)
    550 {
    551 }
    552 
    553 void EditorClient::textDidChangeInTextField(Element*)
    554 {
    555 }
    556 
    557 bool EditorClient::doTextFieldCommandFromEvent(Element*, KeyboardEvent*)
    558 {
    559     return false;
    560 }
    561 
    562 void EditorClient::textWillBeDeletedInTextField(Element*)
    563 {
    564     notImplemented();
    565 }
    566 
    567 void EditorClient::textDidChangeInTextArea(Element*)
    568 {
    569     notImplemented();
    570 }
    571 
    572 void EditorClient::ignoreWordInSpellDocument(const String& text)
    573 {
    574     GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
    575 
    576     for (; langs; langs = langs->next) {
    577         SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
    578 
    579         enchant_dict_add_to_session(lang->speller, text.utf8().data(), -1);
    580     }
    581 }
    582 
    583 void EditorClient::learnWord(const String& text)
    584 {
    585     GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
    586 
    587     for (; langs; langs = langs->next) {
    588         SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
    589 
    590         enchant_dict_add_to_personal(lang->speller, text.utf8().data(), -1);
    591     }
    592 }
    593 
    594 void EditorClient::checkSpellingOfString(const UChar* text, int length, int* misspellingLocation, int* misspellingLength)
    595 {
    596     GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
    597     if (!langs)
    598         return;
    599 
    600     gchar* ctext = g_utf16_to_utf8(const_cast<gunichar2*>(text), length, 0, 0, 0);
    601     int utflen = g_utf8_strlen(ctext, -1);
    602 
    603     PangoLanguage* language = pango_language_get_default();
    604     PangoLogAttr* attrs = g_new(PangoLogAttr, utflen+1);
    605 
    606     // pango_get_log_attrs uses an aditional position at the end of the text.
    607     pango_get_log_attrs(ctext, -1, -1, language, attrs, utflen+1);
    608 
    609     for (int i = 0; i < length+1; i++) {
    610         // We go through each character until we find an is_word_start,
    611         // then we get into an inner loop to find the is_word_end corresponding
    612         // to it.
    613         if (attrs[i].is_word_start) {
    614             int start = i;
    615             int end = i;
    616             int wordLength;
    617 
    618             while (attrs[end].is_word_end < 1)
    619                 end++;
    620 
    621             wordLength = end - start;
    622             // Set the iterator to be at the current word end, so we don't
    623             // check characters twice.
    624             i = end;
    625 
    626             for (; langs; langs = langs->next) {
    627                 SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
    628                 gchar* cstart = g_utf8_offset_to_pointer(ctext, start);
    629                 gint bytes = static_cast<gint>(g_utf8_offset_to_pointer(ctext, end) - cstart);
    630                 gchar* word = g_new0(gchar, bytes+1);
    631                 int result;
    632 
    633                 g_utf8_strncpy(word, cstart, end - start);
    634 
    635                 result = enchant_dict_check(lang->speller, word, -1);
    636                 g_free(word);
    637                 if (result) {
    638                     *misspellingLocation = start;
    639                     *misspellingLength = wordLength;
    640                 } else {
    641                     // Stop checking, this word is ok in at least one dict.
    642                     *misspellingLocation = -1;
    643                     *misspellingLength = 0;
    644                     break;
    645                 }
    646             }
    647         }
    648     }
    649 
    650     g_free(attrs);
    651     g_free(ctext);
    652 }
    653 
    654 String EditorClient::getAutoCorrectSuggestionForMisspelledWord(const String& inputWord)
    655 {
    656     // This method can be implemented using customized algorithms for the particular browser.
    657     // Currently, it computes an empty string.
    658     return String();
    659 }
    660 
    661 void EditorClient::checkGrammarOfString(const UChar*, int, Vector<GrammarDetail>&, int*, int*)
    662 {
    663     notImplemented();
    664 }
    665 
    666 void EditorClient::updateSpellingUIWithGrammarString(const String&, const GrammarDetail&)
    667 {
    668     notImplemented();
    669 }
    670 
    671 void EditorClient::updateSpellingUIWithMisspelledWord(const String&)
    672 {
    673     notImplemented();
    674 }
    675 
    676 void EditorClient::showSpellingUI(bool)
    677 {
    678     notImplemented();
    679 }
    680 
    681 bool EditorClient::spellingUIIsShowing()
    682 {
    683     notImplemented();
    684     return false;
    685 }
    686 
    687 void EditorClient::getGuessesForWord(const String& word, WTF::Vector<String>& guesses)
    688 {
    689     GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
    690     guesses.clear();
    691 
    692     for (; langs; langs = langs->next) {
    693         size_t numberOfSuggestions;
    694         size_t i;
    695 
    696         SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
    697         gchar** suggestions = enchant_dict_suggest(lang->speller, word.utf8().data(), -1, &numberOfSuggestions);
    698 
    699         for (i = 0; i < numberOfSuggestions && i < 10; i++)
    700             guesses.append(String::fromUTF8(suggestions[i]));
    701 
    702         if (numberOfSuggestions > 0)
    703             enchant_dict_free_suggestions(lang->speller, suggestions);
    704     }
    705 }
    706 
    707 }
    708