/*
 * Copyright (C) 2006, 2007 Apple, Inc.  All rights reserved.
 * Copyright (C) 2012 Google, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "EditorClientImpl.h"

#include "HTMLNames.h"
#include "WebAutofillClient.h"
#include "WebEditingAction.h"
#include "WebElement.h"
#include "WebFrameClient.h"
#include "WebFrameImpl.h"
#include "WebInputElement.h"
#include "WebInputEventConversion.h"
#include "WebKit.h"
#include "WebNode.h"
#include "WebPermissionClient.h"
#include "WebRange.h"
#include "WebSpellCheckClient.h"
#include "WebTextAffinity.h"
#include "WebTextCheckingCompletionImpl.h"
#include "WebTextCheckingResult.h"
#include "WebViewClient.h"
#include "WebViewImpl.h"
#include "core/dom/Document.h"
#include "core/dom/DocumentMarkerController.h"
#include "core/dom/EventNames.h"
#include "core/dom/KeyboardEvent.h"
#include "core/editing/Editor.h"
#include "core/editing/SpellCheckRequester.h"
#include "core/editing/TextCheckingHelper.h"
#include "core/editing/UndoStep.h"
#include "core/html/HTMLInputElement.h"
#include "core/page/EventHandler.h"
#include "core/page/Frame.h"
#include "core/page/Page.h"
#include "core/page/Settings.h"
#include "core/platform/NotImplemented.h"
#include "core/platform/PlatformKeyboardEvent.h"
#include "core/platform/chromium/KeyboardCodes.h"
#include "core/rendering/RenderObject.h"
#include "wtf/text/WTFString.h"

using namespace WebCore;

namespace WebKit {

// Arbitrary depth limit for the undo stack, to keep it from using
// unbounded memory.  This is the maximum number of distinct undoable
// actions -- unbroken stretches of typed characters are coalesced
// into a single action.
static const size_t maximumUndoStackDepth = 1000;

EditorClientImpl::EditorClientImpl(WebViewImpl* webview)
    : m_webView(webview)
    , m_inRedo(false)
    , m_spellCheckThisFieldStatus(SpellCheckAutomatic)
{
}

EditorClientImpl::~EditorClientImpl()
{
}

bool EditorClientImpl::smartInsertDeleteEnabled()
{
    if (m_webView->page())
        return m_webView->page()->settings().smartInsertDeleteEnabled();
    return false;
}

bool EditorClientImpl::isSelectTrailingWhitespaceEnabled()
{
    if (m_webView->page())
        return m_webView->page()->settings().selectTrailingWhitespaceEnabled();
    return false;
}

bool EditorClientImpl::shouldSpellcheckByDefault()
{
    // Spellcheck should be enabled for all editable areas (such as textareas,
    // contentEditable regions, designMode docs and inputs).
    const Frame* frame = m_webView->focusedWebCoreFrame();
    if (!frame)
        return false;
    const Editor& editor = frame->editor();
    if (editor.isSpellCheckingEnabledInFocusedNode())
        return true;
    const Document* document = frame->document();
    if (!document)
        return false;
    const Element* element = document->focusedElement();
    // If |element| is null, we default to allowing spellchecking. This is done
    // in order to mitigate the issue when the user clicks outside the textbox,
    // as a result of which |element| becomes null, resulting in all the spell
    // check markers being deleted. Also, the Frame will decide not to do
    // spellchecking if the user can't edit - so returning true here will not
    // cause any problems to the Frame's behavior.
    if (!element)
        return true;
    const RenderObject* renderer = element->renderer();
    if (!renderer)
        return false;

    return true;
}

bool EditorClientImpl::isContinuousSpellCheckingEnabled()
{
    if (m_spellCheckThisFieldStatus == SpellCheckForcedOff)
        return false;
    if (m_spellCheckThisFieldStatus == SpellCheckForcedOn)
        return true;
    return shouldSpellcheckByDefault();
}

void EditorClientImpl::toggleContinuousSpellChecking()
{
    if (isContinuousSpellCheckingEnabled()) {
        m_spellCheckThisFieldStatus = SpellCheckForcedOff;
        if (Page* page = m_webView->page()) {
            for (Frame* frame = page->mainFrame(); frame && frame->document(); frame = frame->tree()->traverseNext()) {
                frame->document()->markers()->removeMarkers(DocumentMarker::MisspellingMarkers());
            }
        }
    } else {
        m_spellCheckThisFieldStatus = SpellCheckForcedOn;
        if (Frame* frame = m_webView->focusedWebCoreFrame()) {
            VisibleSelection frameSelection = frame->selection().selection();
            // If a selection is in an editable element spell check its content.
            if (Element* rootEditableElement = frameSelection.rootEditableElement()) {
                frame->editor().elementDidBeginEditing(rootEditableElement);
            }
        }
    }
}

bool EditorClientImpl::isGrammarCheckingEnabled()
{
    const Frame* frame = m_webView->focusedWebCoreFrame();
    return frame && frame->settings() && (frame->settings()->asynchronousSpellCheckingEnabled() || frame->settings()->unifiedTextCheckerEnabled());
}

bool EditorClientImpl::shouldBeginEditing(Range* range)
{
    if (m_webView->client())
        return m_webView->client()->shouldBeginEditing(WebRange(range));
    return true;
}

bool EditorClientImpl::shouldEndEditing(Range* range)
{
    if (m_webView->client())
        return m_webView->client()->shouldEndEditing(WebRange(range));
    return true;
}

bool EditorClientImpl::shouldInsertNode(Node* node,
                                        Range* range,
                                        EditorInsertAction action)
{
    if (m_webView->client()) {
        return m_webView->client()->shouldInsertNode(WebNode(node),
                                                     WebRange(range),
                                                     static_cast<WebEditingAction>(action));
    }
    return true;
}

bool EditorClientImpl::shouldInsertText(const String& text,
                                        Range* range,
                                        EditorInsertAction action)
{
    if (m_webView->client()) {
        return m_webView->client()->shouldInsertText(WebString(text),
                                                     WebRange(range),
                                                     static_cast<WebEditingAction>(action));
    }
    return true;
}


bool EditorClientImpl::shouldDeleteRange(Range* range)
{
    if (m_webView->client())
        return m_webView->client()->shouldDeleteRange(WebRange(range));
    return true;
}

bool EditorClientImpl::shouldChangeSelectedRange(Range* fromRange,
                                                 Range* toRange,
                                                 EAffinity affinity,
                                                 bool stillSelecting)
{
    if (m_webView->client()) {
        return m_webView->client()->shouldChangeSelectedRange(WebRange(fromRange),
                                                              WebRange(toRange),
                                                              static_cast<WebTextAffinity>(affinity),
                                                              stillSelecting);
    }
    return true;
}

bool EditorClientImpl::shouldApplyStyle(StylePropertySet* style, Range* range)
{
    if (m_webView->client()) {
        // FIXME: Pass a reference to the CSSStyleDeclaration somehow.
        return m_webView->client()->shouldApplyStyle(WebString(), WebRange(range));
    }
    return true;
}

void EditorClientImpl::didBeginEditing()
{
    if (m_webView->client())
        m_webView->client()->didBeginEditing();
}

void EditorClientImpl::respondToChangedSelection(Frame* frame)
{
    if (m_webView->client() && frame)
        m_webView->client()->didChangeSelection(!frame->selection().isRange());
}

void EditorClientImpl::respondToChangedContents()
{
    if (m_webView->client())
        m_webView->client()->didChangeContents();
}

void EditorClientImpl::didEndEditing()
{
    if (m_webView->client())
        m_webView->client()->didEndEditing();
}

void EditorClientImpl::didCancelCompositionOnSelectionChange()
{
    if (m_webView->client())
        m_webView->client()->didCancelCompositionOnSelectionChange();
}

void EditorClientImpl::registerUndoStep(PassRefPtr<UndoStep> step)
{
    if (m_undoStack.size() == maximumUndoStackDepth)
        m_undoStack.removeFirst(); // drop oldest item off the far end
    if (!m_inRedo)
        m_redoStack.clear();
    m_undoStack.append(step);
}

void EditorClientImpl::registerRedoStep(PassRefPtr<UndoStep> step)
{
    m_redoStack.append(step);
}

void EditorClientImpl::clearUndoRedoOperations()
{
    NoEventDispatchAssertion assertNoEventDispatch;
    m_undoStack.clear();
    m_redoStack.clear();
}

bool EditorClientImpl::canCopyCut(Frame* frame, bool defaultValue) const
{
    if (!m_webView->permissionClient())
        return defaultValue;
    return m_webView->permissionClient()->allowWriteToClipboard(WebFrameImpl::fromFrame(frame), defaultValue);
}

bool EditorClientImpl::canPaste(Frame* frame, bool defaultValue) const
{
    if (!m_webView->permissionClient())
        return defaultValue;
    return m_webView->permissionClient()->allowReadFromClipboard(WebFrameImpl::fromFrame(frame), defaultValue);
}

bool EditorClientImpl::canUndo() const
{
    return !m_undoStack.isEmpty();
}

bool EditorClientImpl::canRedo() const
{
    return !m_redoStack.isEmpty();
}

void EditorClientImpl::undo()
{
    if (canUndo()) {
        UndoManagerStack::iterator back = --m_undoStack.end();
        RefPtr<UndoStep> step(*back);
        m_undoStack.remove(back);
        step->unapply();
        // unapply will call us back to push this command onto the redo stack.
    }
}

void EditorClientImpl::redo()
{
    if (canRedo()) {
        UndoManagerStack::iterator back = --m_redoStack.end();
        RefPtr<UndoStep> step(*back);
        m_redoStack.remove(back);

        ASSERT(!m_inRedo);
        m_inRedo = true;
        step->reapply();
        // reapply will call us back to push this command onto the undo stack.
        m_inRedo = false;
    }
}

//
// The below code was adapted from the WebKit file webview.cpp
//

static const unsigned CtrlKey = 1 << 0;
static const unsigned AltKey = 1 << 1;
static const unsigned ShiftKey = 1 << 2;
static const unsigned MetaKey = 1 << 3;
#if OS(MACOSX)
// Aliases for the generic key defintions to make kbd shortcuts definitions more
// readable on OS X.
static const unsigned OptionKey  = AltKey;

// Do not use this constant for anything but cursor movement commands. Keys
// with cmd set have their |isSystemKey| bit set, so chances are the shortcut
// will not be executed. Another, less important, reason is that shortcuts
// defined in the renderer do not blink the menu item that they triggered.  See
// http://crbug.com/25856 and the bugs linked from there for details.
static const unsigned CommandKey = MetaKey;
#endif

// Keys with special meaning. These will be delegated to the editor using
// the execCommand() method
struct KeyDownEntry {
    unsigned virtualKey;
    unsigned modifiers;
    const char* name;
};

struct KeyPressEntry {
    unsigned charCode;
    unsigned modifiers;
    const char* name;
};

static const KeyDownEntry keyDownEntries[] = {
    { VKEY_LEFT,   0,                  "MoveLeft"                             },
    { VKEY_LEFT,   ShiftKey,           "MoveLeftAndModifySelection"           },
#if OS(MACOSX)
    { VKEY_LEFT,   OptionKey,          "MoveWordLeft"                         },
    { VKEY_LEFT,   OptionKey | ShiftKey,
        "MoveWordLeftAndModifySelection"                                      },
#else
    { VKEY_LEFT,   CtrlKey,            "MoveWordLeft"                         },
    { VKEY_LEFT,   CtrlKey | ShiftKey,
        "MoveWordLeftAndModifySelection"                                      },
#endif
    { VKEY_RIGHT,  0,                  "MoveRight"                            },
    { VKEY_RIGHT,  ShiftKey,           "MoveRightAndModifySelection"          },
#if OS(MACOSX)
    { VKEY_RIGHT,  OptionKey,          "MoveWordRight"                        },
    { VKEY_RIGHT,  OptionKey | ShiftKey,
      "MoveWordRightAndModifySelection"                                       },
#else
    { VKEY_RIGHT,  CtrlKey,            "MoveWordRight"                        },
    { VKEY_RIGHT,  CtrlKey | ShiftKey,
      "MoveWordRightAndModifySelection"                                       },
#endif
    { VKEY_UP,     0,                  "MoveUp"                               },
    { VKEY_UP,     ShiftKey,           "MoveUpAndModifySelection"             },
    { VKEY_PRIOR,  ShiftKey,           "MovePageUpAndModifySelection"         },
    { VKEY_DOWN,   0,                  "MoveDown"                             },
    { VKEY_DOWN,   ShiftKey,           "MoveDownAndModifySelection"           },
    { VKEY_NEXT,   ShiftKey,           "MovePageDownAndModifySelection"       },
#if !OS(MACOSX)
    { VKEY_UP,     CtrlKey,            "MoveParagraphBackward"                },
    { VKEY_UP,     CtrlKey | ShiftKey, "MoveParagraphBackwardAndModifySelection" },
    { VKEY_DOWN,   CtrlKey,            "MoveParagraphForward"                },
    { VKEY_DOWN,   CtrlKey | ShiftKey, "MoveParagraphForwardAndModifySelection" },
    { VKEY_PRIOR,  0,                  "MovePageUp"                           },
    { VKEY_NEXT,   0,                  "MovePageDown"                         },
#endif
    { VKEY_HOME,   0,                  "MoveToBeginningOfLine"                },
    { VKEY_HOME,   ShiftKey,
        "MoveToBeginningOfLineAndModifySelection"                             },
#if OS(MACOSX)
    { VKEY_LEFT,   CommandKey,         "MoveToBeginningOfLine"                },
    { VKEY_LEFT,   CommandKey | ShiftKey,
      "MoveToBeginningOfLineAndModifySelection"                               },
    { VKEY_PRIOR,  OptionKey,          "MovePageUp"                           },
    { VKEY_NEXT,   OptionKey,          "MovePageDown"                         },
#endif
#if OS(MACOSX)
    { VKEY_UP,     CommandKey,         "MoveToBeginningOfDocument"            },
    { VKEY_UP,     CommandKey | ShiftKey,
        "MoveToBeginningOfDocumentAndModifySelection"                         },
#else
    { VKEY_HOME,   CtrlKey,            "MoveToBeginningOfDocument"            },
    { VKEY_HOME,   CtrlKey | ShiftKey,
        "MoveToBeginningOfDocumentAndModifySelection"                         },
#endif
    { VKEY_END,    0,                  "MoveToEndOfLine"                      },
    { VKEY_END,    ShiftKey,           "MoveToEndOfLineAndModifySelection"    },
#if OS(MACOSX)
    { VKEY_DOWN,   CommandKey,         "MoveToEndOfDocument"                  },
    { VKEY_DOWN,   CommandKey | ShiftKey,
        "MoveToEndOfDocumentAndModifySelection"                               },
#else
    { VKEY_END,    CtrlKey,            "MoveToEndOfDocument"                  },
    { VKEY_END,    CtrlKey | ShiftKey,
        "MoveToEndOfDocumentAndModifySelection"                               },
#endif
#if OS(MACOSX)
    { VKEY_RIGHT,  CommandKey,         "MoveToEndOfLine"                      },
    { VKEY_RIGHT,  CommandKey | ShiftKey,
        "MoveToEndOfLineAndModifySelection"                                   },
#endif
    { VKEY_BACK,   0,                  "DeleteBackward"                       },
    { VKEY_BACK,   ShiftKey,           "DeleteBackward"                       },
    { VKEY_DELETE, 0,                  "DeleteForward"                        },
#if OS(MACOSX)
    { VKEY_BACK,   OptionKey,          "DeleteWordBackward"                   },
    { VKEY_DELETE, OptionKey,          "DeleteWordForward"                    },
#else
    { VKEY_BACK,   CtrlKey,            "DeleteWordBackward"                   },
    { VKEY_DELETE, CtrlKey,            "DeleteWordForward"                    },
#endif
    { 'B',         CtrlKey,            "ToggleBold"                           },
    { 'I',         CtrlKey,            "ToggleItalic"                         },
    { 'U',         CtrlKey,            "ToggleUnderline"                      },
    { VKEY_ESCAPE, 0,                  "Cancel"                               },
    { VKEY_OEM_PERIOD, CtrlKey,        "Cancel"                               },
    { VKEY_TAB,    0,                  "InsertTab"                            },
    { VKEY_TAB,    ShiftKey,           "InsertBacktab"                        },
    { VKEY_RETURN, 0,                  "InsertNewline"                        },
    { VKEY_RETURN, CtrlKey,            "InsertNewline"                        },
    { VKEY_RETURN, AltKey,             "InsertNewline"                        },
    { VKEY_RETURN, AltKey | ShiftKey,  "InsertNewline"                        },
    { VKEY_RETURN, ShiftKey,           "InsertLineBreak"                      },
    { VKEY_INSERT, CtrlKey,            "Copy"                                 },
    { VKEY_INSERT, ShiftKey,           "Paste"                                },
    { VKEY_DELETE, ShiftKey,           "Cut"                                  },
#if !OS(MACOSX)
    // On OS X, we pipe these back to the browser, so that it can do menu item
    // blinking.
    { 'C',         CtrlKey,            "Copy"                                 },
    { 'V',         CtrlKey,            "Paste"                                },
    { 'V',         CtrlKey | ShiftKey, "PasteAndMatchStyle"                   },
    { 'X',         CtrlKey,            "Cut"                                  },
    { 'A',         CtrlKey,            "SelectAll"                            },
    { 'Z',         CtrlKey,            "Undo"                                 },
    { 'Z',         CtrlKey | ShiftKey, "Redo"                                 },
    { 'Y',         CtrlKey,            "Redo"                                 },
#endif
    { VKEY_INSERT, 0,                  "OverWrite"                            },
};

static const KeyPressEntry keyPressEntries[] = {
    { '\t',   0,                  "InsertTab"                                 },
    { '\t',   ShiftKey,           "InsertBacktab"                             },
    { '\r',   0,                  "InsertNewline"                             },
    { '\r',   CtrlKey,            "InsertNewline"                             },
    { '\r',   ShiftKey,           "InsertLineBreak"                           },
    { '\r',   AltKey,             "InsertNewline"                             },
    { '\r',   AltKey | ShiftKey,  "InsertNewline"                             },
};

const char* EditorClientImpl::interpretKeyEvent(const KeyboardEvent* evt)
{
    const PlatformKeyboardEvent* keyEvent = evt->keyEvent();
    if (!keyEvent)
        return "";

    static HashMap<int, const char*>* keyDownCommandsMap = 0;
    static HashMap<int, const char*>* keyPressCommandsMap = 0;

    if (!keyDownCommandsMap) {
        keyDownCommandsMap = new HashMap<int, const char*>;
        keyPressCommandsMap = new HashMap<int, const char*>;

        for (unsigned i = 0; i < arraysize(keyDownEntries); i++) {
            keyDownCommandsMap->set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey,
                                    keyDownEntries[i].name);
        }

        for (unsigned i = 0; i < arraysize(keyPressEntries); i++) {
            keyPressCommandsMap->set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode,
                                     keyPressEntries[i].name);
        }
    }

    unsigned modifiers = 0;
    if (keyEvent->shiftKey())
        modifiers |= ShiftKey;
    if (keyEvent->altKey())
        modifiers |= AltKey;
    if (keyEvent->ctrlKey())
        modifiers |= CtrlKey;
    if (keyEvent->metaKey())
        modifiers |= MetaKey;

    if (keyEvent->type() == PlatformEvent::RawKeyDown) {
        int mapKey = modifiers << 16 | evt->keyCode();
        return mapKey ? keyDownCommandsMap->get(mapKey) : 0;
    }

    int mapKey = modifiers << 16 | evt->charCode();
    return mapKey ? keyPressCommandsMap->get(mapKey) : 0;
}

bool EditorClientImpl::handleEditingKeyboardEvent(KeyboardEvent* evt)
{
    const PlatformKeyboardEvent* keyEvent = evt->keyEvent();
    // do not treat this as text input if it's a system key event
    if (!keyEvent || keyEvent->isSystemKey())
        return false;

    Frame* frame = evt->target()->toNode()->document().frame();
    if (!frame)
        return false;

    String commandName = interpretKeyEvent(evt);
    Editor::Command command = frame->editor().command(commandName);

    if (keyEvent->type() == PlatformEvent::RawKeyDown) {
        // WebKit doesn't have enough information about mode to decide how
        // commands that just insert text if executed via Editor should be treated,
        // so we leave it upon WebCore to either handle them immediately
        // (e.g. Tab that changes focus) or let a keypress event be generated
        // (e.g. Tab that inserts a Tab character, or Enter).
        if (command.isTextInsertion() || commandName.isEmpty())
            return false;
        if (command.execute(evt)) {
            if (m_webView->client())
                m_webView->client()->didExecuteCommand(WebString(commandName));
            return true;
        }
        return false;
    }

    if (command.execute(evt)) {
        if (m_webView->client())
            m_webView->client()->didExecuteCommand(WebString(commandName));
        return true;
    }

    // Here we need to filter key events.
    // On Gtk/Linux, it emits key events with ASCII text and ctrl on for ctrl-<x>.
    // In Webkit, EditorClient::handleKeyboardEvent in
    // WebKit/gtk/WebCoreSupport/EditorClientGtk.cpp drop such events.
    // On Mac, it emits key events with ASCII text and meta on for Command-<x>.
    // These key events should not emit text insert event.
    // Alt key would be used to insert alternative character, so we should let
    // through. Also note that Ctrl-Alt combination equals to AltGr key which is
    // also used to insert alternative character.
    // http://code.google.com/p/chromium/issues/detail?id=10846
    // Windows sets both alt and meta are on when "Alt" key pressed.
    // http://code.google.com/p/chromium/issues/detail?id=2215
    // Also, we should not rely on an assumption that keyboards don't
    // send ASCII characters when pressing a control key on Windows,
    // which may be configured to do it so by user.
    // See also http://en.wikipedia.org/wiki/Keyboard_Layout
    // FIXME(ukai): investigate more detail for various keyboard layout.
    if (evt->keyEvent()->text().length() == 1) {
        UChar ch = evt->keyEvent()->text()[0U];

        // Don't insert null or control characters as they can result in
        // unexpected behaviour
        if (ch < ' ')
            return false;
#if !OS(WIN)
        // Don't insert ASCII character if ctrl w/o alt or meta is on.
        // On Mac, we should ignore events when meta is on (Command-<x>).
        if (ch < 0x80) {
            if (evt->keyEvent()->ctrlKey() && !evt->keyEvent()->altKey())
                return false;
#if OS(MACOSX)
            if (evt->keyEvent()->metaKey())
            return false;
#endif
        }
#endif
    }

    if (!frame->editor().canEdit())
        return false;

    return frame->editor().insertText(evt->keyEvent()->text(), evt);
}

void EditorClientImpl::handleKeyboardEvent(KeyboardEvent* evt)
{
    // Give the embedder a chance to handle the keyboard event.
    if ((m_webView->client()
         && m_webView->client()->handleCurrentKeyboardEvent())
        || handleEditingKeyboardEvent(evt))
        evt->setDefaultHandled();
}

void EditorClientImpl::textFieldDidEndEditing(Element* element)
{
    if (m_webView->autofillClient() && element->hasTagName(HTMLNames::inputTag))
        m_webView->autofillClient()->textFieldDidEndEditing(WebInputElement(toHTMLInputElement(element)));

    // Notification that focus was lost.  Be careful with this, it's also sent
    // when the page is being closed.

    // Hide any showing popup.
    m_webView->hideAutofillPopup();
}

void EditorClientImpl::textDidChangeInTextField(Element* element)
{
    HTMLInputElement* inputElement = toHTMLInputElement(element);
    if (m_webView->autofillClient())
        m_webView->autofillClient()->textFieldDidChange(WebInputElement(inputElement));
}

bool EditorClientImpl::doTextFieldCommandFromEvent(Element* element,
                                                   KeyboardEvent* event)
{
    if (m_webView->autofillClient() && element->hasTagName(HTMLNames::inputTag)) {
        m_webView->autofillClient()->textFieldDidReceiveKeyDown(WebInputElement(toHTMLInputElement(element)),
                                                                WebKeyboardEventBuilder(*event));
    }

    // The Mac code appears to use this method as a hook to implement special
    // keyboard commands specific to Safari's auto-fill implementation.  We
    // just return false to allow the default action.
    return false;
}

bool EditorClientImpl::shouldEraseMarkersAfterChangeSelection(TextCheckingType type) const
{
    const Frame* frame = m_webView->focusedWebCoreFrame();
    return !frame || !frame->settings() || (!frame->settings()->asynchronousSpellCheckingEnabled() && !frame->settings()->unifiedTextCheckerEnabled());
}

void EditorClientImpl::checkSpellingOfString(const String& text,
                                             int* misspellingLocation,
                                             int* misspellingLength)
{
    // SpellCheckWord will write (0, 0) into the output vars, which is what our
    // caller expects if the word is spelled correctly.
    int spellLocation = -1;
    int spellLength = 0;

    // Check to see if the provided text is spelled correctly.
    if (m_webView->spellCheckClient())
        m_webView->spellCheckClient()->spellCheck(text, spellLocation, spellLength, 0);
    else {
        spellLocation = 0;
        spellLength = 0;
    }

    // Note: the Mac code checks if the pointers are null before writing to them,
    // so we do too.
    if (misspellingLocation)
        *misspellingLocation = spellLocation;
    if (misspellingLength)
        *misspellingLength = spellLength;
}

void EditorClientImpl::requestCheckingOfString(WTF::PassRefPtr<WebCore::TextCheckingRequest> request)
{
    if (m_webView->spellCheckClient()) {
        const String& text = request->data().text();
        const Vector<uint32_t>& markers = request->data().markers();
        const Vector<unsigned>& markerOffsets = request->data().offsets();
        m_webView->spellCheckClient()->requestCheckingOfText(text, markers, markerOffsets, new WebTextCheckingCompletionImpl(request));
    }
}

String EditorClientImpl::getAutoCorrectSuggestionForMisspelledWord(const String& misspelledWord)
{
    if (!(isContinuousSpellCheckingEnabled() && m_webView->client()))
        return String();

    // Do not autocorrect words with capital letters in it except the
    // first letter. This will remove cases changing "IMB" to "IBM".
    for (size_t i = 1; i < misspelledWord.length(); i++) {
        if (u_isupper(static_cast<UChar32>(misspelledWord[i])))
            return String();
    }

    if (m_webView->spellCheckClient())
        return m_webView->spellCheckClient()->autoCorrectWord(WebString(misspelledWord));
    return String();
}

void EditorClientImpl::checkGrammarOfString(const String& text, WTF::Vector<GrammarDetail>& details, int* badGrammarLocation, int* badGrammarLength)
{
    if (badGrammarLocation)
        *badGrammarLocation = -1;
    if (badGrammarLength)
        *badGrammarLength = 0;

    if (!m_webView->spellCheckClient())
        return;
    WebVector<WebTextCheckingResult> webResults;
    m_webView->spellCheckClient()->checkTextOfParagraph(text, WebTextCheckingTypeGrammar, &webResults);
    if (!webResults.size())
        return;

    // Convert a list of WebTextCheckingResults to a list of GrammarDetails. If
    // the converted vector of GrammarDetails has grammar errors, we set
    // badGrammarLocation and badGrammarLength to tell WebKit that the input
    // text has grammar errors.
    for (size_t i = 0; i < webResults.size(); ++i) {
        if (webResults[i].type == WebTextCheckingTypeGrammar) {
            GrammarDetail detail;
            detail.location = webResults[i].location;
            detail.length = webResults[i].length;
            detail.userDescription = webResults[i].replacement;
            details.append(detail);
        }
    }
    if (!details.size())
        return;
    if (badGrammarLocation)
        *badGrammarLocation = 0;
    if (badGrammarLength)
        *badGrammarLength = text.length();
}

void EditorClientImpl::updateSpellingUIWithMisspelledWord(const String& misspelledWord)
{
    if (m_webView->spellCheckClient())
        m_webView->spellCheckClient()->updateSpellingUIWithMisspelledWord(WebString(misspelledWord));
}

void EditorClientImpl::showSpellingUI(bool show)
{
    if (m_webView->spellCheckClient())
        m_webView->spellCheckClient()->showSpellingUI(show);
}

bool EditorClientImpl::spellingUIIsShowing()
{
    if (m_webView->spellCheckClient())
        return m_webView->spellCheckClient()->isShowingSpellingUI();
    return false;
}

bool EditorClientImpl::supportsGlobalSelection()
{
#if OS(WIN) || OS(MACOSX)
    return false;
#else
    return true;
#endif
}

void EditorClientImpl::willSetInputMethodState()
{
    if (m_webView->client())
        m_webView->client()->resetInputMethod();
}

} // namesace WebKit
