// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // The Mac interface forwards most of these commands to the application layer, // and I'm not really sure what to do about most of them. #include "config.h" #include "base/compiler_specific.h" MSVC_PUSH_WARNING_LEVEL(0); #include "Document.h" #include "EditCommand.h" #include "Editor.h" #include "EventHandler.h" #include "EventNames.h" #include "Frame.h" #include "KeyboardCodes.h" #include "HTMLInputElement.h" #include "HTMLNames.h" #include "Frame.h" #include "KeyboardEvent.h" #include "PlatformKeyboardEvent.h" #include "PlatformString.h" MSVC_POP_WARNING(); #undef LOG #include "base/message_loop.h" #include "base/string_util.h" #include "webkit/glue/editor_client_impl.h" #include "webkit/glue/glue_util.h" #include "webkit/glue/webkit_glue.h" #include "webkit/glue/webview.h" #include "webkit/glue/webview_impl.h" // 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 kMaximumUndoStackDepth = 1000; // The size above which we stop triggering autofill for an input text field // (so to avoid sending long strings through IPC). static const size_t kMaximumTextSizeForAutofill = 1000; namespace { // Record an editor command from the keyDownEntries[] below. We ignore the // Move* and Insert* commands because they're not that interesting. void MaybeRecordCommand(WebViewDelegate* d, const char* command_name) { if (!d) return; const char* move_prefix = "Move"; const char* insert_prefix = "Insert"; const char* delete_prefix = "Delete"; // Ignore all the Move*, Insert*, and Delete* commands. if (0 == strncmp(command_name, move_prefix, sizeof(move_prefix)) || 0 == strncmp(command_name, insert_prefix, sizeof(insert_prefix)) || 0 == strncmp(command_name, delete_prefix, sizeof(delete_prefix))) { return; } d->UserMetricsRecordComputedAction(UTF8ToWide(command_name)); } } EditorClientImpl::EditorClientImpl(WebView* web_view) : web_view_(static_cast(web_view)), use_editor_delegate_(false), in_redo_(false), backspace_pressed_(false), spell_check_this_field_status_(SPELLCHECK_AUTOMATIC), // Don't complain about using "this" in initializer list. MSVC_PUSH_DISABLE_WARNING(4355) autofill_factory_(this) { MSVC_POP_WARNING() } EditorClientImpl::~EditorClientImpl() { } void EditorClientImpl::pageDestroyed() { // Called by the Page (which owns the editor client) when the page is going // away. This should cause us to delete ourselves, which is stupid. The page // should just delete us when it's going away. Oh well. delete this; } bool EditorClientImpl::shouldShowDeleteInterface(WebCore::HTMLElement* elem) { // Normally, we don't care to show WebCore's deletion UI, so we only enable // it if in testing mode and the test specifically requests it by using this // magic class name. return webkit_glue::IsLayoutTestMode() && elem->className() == "needsDeletionUI"; } bool EditorClientImpl::smartInsertDeleteEnabled() { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) return d->SmartInsertDeleteEnabled(); } return true; } bool EditorClientImpl::isSelectTrailingWhitespaceEnabled() { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) return d->IsSelectTrailingWhitespaceEnabled(); } return true; } bool EditorClientImpl::ShouldSpellcheckByDefault() { const WebCore::Frame* frame = web_view_->GetFocusedWebCoreFrame(); if (!frame) return false; const WebCore::Editor* editor = frame->editor(); if (!editor) return false; const WebCore::Document* document = frame->document(); if (!document) return false; const WebCore::Node* node = document->focusedNode(); if (!node) return false; const WebCore::RenderObject* renderer = node->renderer(); if (!renderer) return false; // We should also retrieve the contenteditable attribute of this element to // determine if this element needs spell-checking. const WebCore::EUserModify user_modify = renderer->style()->userModify(); return (renderer->isTextArea() && editor->canEdit()) || user_modify == WebCore::READ_WRITE || user_modify == WebCore::READ_WRITE_PLAINTEXT_ONLY; } bool EditorClientImpl::isContinuousSpellCheckingEnabled() { if (spell_check_this_field_status_ == SPELLCHECK_FORCED_OFF) return false; else if (spell_check_this_field_status_ == SPELLCHECK_FORCED_ON) return true; else return ShouldSpellcheckByDefault(); } void EditorClientImpl::toggleContinuousSpellChecking() { if (isContinuousSpellCheckingEnabled()) spell_check_this_field_status_ = SPELLCHECK_FORCED_OFF; else spell_check_this_field_status_ = SPELLCHECK_FORCED_ON; } bool EditorClientImpl::isGrammarCheckingEnabled() { return false; } void EditorClientImpl::toggleGrammarChecking() { NOTIMPLEMENTED(); } int EditorClientImpl::spellCheckerDocumentTag() { NOTIMPLEMENTED(); return 0; } bool EditorClientImpl::isEditable() { return false; } bool EditorClientImpl::shouldBeginEditing(WebCore::Range* range) { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) return d->ShouldBeginEditing(web_view_, Describe(range)); } return true; } bool EditorClientImpl::shouldEndEditing(WebCore::Range* range) { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) return d->ShouldEndEditing(web_view_, Describe(range)); } return true; } bool EditorClientImpl::shouldInsertNode(WebCore::Node* node, WebCore::Range* range, WebCore::EditorInsertAction action) { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) { return d->ShouldInsertNode(web_view_, Describe(node), Describe(range), Describe(action)); } } return true; } bool EditorClientImpl::shouldInsertText(const WebCore::String& text, WebCore::Range* range, WebCore::EditorInsertAction action) { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) { std::wstring wstr = webkit_glue::StringToStdWString(text); return d->ShouldInsertText(web_view_, wstr, Describe(range), Describe(action)); } } return true; } bool EditorClientImpl::shouldDeleteRange(WebCore::Range* range) { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) return d->ShouldDeleteRange(web_view_, Describe(range)); } return true; } bool EditorClientImpl::shouldChangeSelectedRange(WebCore::Range* from_range, WebCore::Range* to_range, WebCore::EAffinity affinity, bool still_selecting) { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) { return d->ShouldChangeSelectedRange(web_view_, Describe(from_range), Describe(to_range), Describe(affinity), still_selecting); } } return true; } bool EditorClientImpl::shouldApplyStyle(WebCore::CSSStyleDeclaration* style, WebCore::Range* range) { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) return d->ShouldApplyStyle(web_view_, Describe(style), Describe(range)); } return true; } bool EditorClientImpl::shouldMoveRangeAfterDelete( WebCore::Range* /*range*/, WebCore::Range* /*rangeToBeReplaced*/) { return true; } void EditorClientImpl::didBeginEditing() { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) d->DidBeginEditing(); } } void EditorClientImpl::respondToChangedSelection() { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) { WebCore::Frame* frame = web_view_->GetFocusedWebCoreFrame(); if (frame) d->DidChangeSelection(!frame->selection()->isRange()); } } } void EditorClientImpl::respondToChangedContents() { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) d->DidChangeContents(); } } void EditorClientImpl::didEndEditing() { if (use_editor_delegate_) { WebViewDelegate* d = web_view_->delegate(); if (d) d->DidEndEditing(); } } void EditorClientImpl::didWriteSelectionToPasteboard() { } void EditorClientImpl::didSetSelectionTypesForPasteboard() { } void EditorClientImpl::registerCommandForUndo( PassRefPtr command) { if (undo_stack_.size() == kMaximumUndoStackDepth) undo_stack_.pop_front(); // drop oldest item off the far end if (!in_redo_) redo_stack_.clear(); undo_stack_.push_back(command); } void EditorClientImpl::registerCommandForRedo( PassRefPtr command) { redo_stack_.push_back(command); } void EditorClientImpl::clearUndoRedoOperations() { undo_stack_.clear(); redo_stack_.clear(); } bool EditorClientImpl::canUndo() const { return !undo_stack_.empty(); } bool EditorClientImpl::canRedo() const { return !redo_stack_.empty(); } void EditorClientImpl::undo() { if (canUndo()) { RefPtr command(undo_stack_.back()); undo_stack_.pop_back(); command->unapply(); // unapply will call us back to push this command onto the redo stack. } } void EditorClientImpl::redo() { if (canRedo()) { RefPtr command(redo_stack_.back()); redo_stack_.pop_back(); ASSERT(!in_redo_); in_redo_ = true; command->reapply(); // reapply will call us back to push this command onto the undo stack. in_redo_ = false; } } // The below code was adapted from the WebKit file webview.cpp // provided by Apple, Inc, and is subject to the following copyright // notice and disclaimer. /* * Copyright (C) 2006, 2007 Apple, 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. */ 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 defined(OS_MACOSX) // Aliases for the generic key defintions to make kbd shortcuts definitions more // readable on OS X. static const unsigned OptionKey = AltKey; 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[] = { { WebCore::VKEY_LEFT, 0, "MoveLeft" }, { WebCore::VKEY_LEFT, ShiftKey, "MoveLeftAndModifySelection" }, #if defined(OS_MACOSX) { WebCore::VKEY_LEFT, OptionKey, "MoveWordLeft" }, { WebCore::VKEY_LEFT, OptionKey | ShiftKey, "MoveWordLeftAndModifySelection" }, #else { WebCore::VKEY_LEFT, CtrlKey, "MoveWordLeft" }, { WebCore::VKEY_LEFT, CtrlKey | ShiftKey, "MoveWordLeftAndModifySelection"}, #endif { WebCore::VKEY_RIGHT, 0, "MoveRight" }, { WebCore::VKEY_RIGHT, ShiftKey, "MoveRightAndModifySelection" }, #if defined(OS_MACOSX) { WebCore::VKEY_RIGHT, OptionKey, "MoveWordRight" }, { WebCore::VKEY_RIGHT, OptionKey | ShiftKey, "MoveWordRightAndModifySelection" }, #else { WebCore::VKEY_RIGHT, CtrlKey, "MoveWordRight" }, { WebCore::VKEY_RIGHT, CtrlKey | ShiftKey, "MoveWordRightAndModifySelection" }, #endif { WebCore::VKEY_UP, 0, "MoveUp" }, { WebCore::VKEY_UP, ShiftKey, "MoveUpAndModifySelection" }, { WebCore::VKEY_PRIOR, ShiftKey, "MovePageUpAndModifySelection" }, { WebCore::VKEY_DOWN, 0, "MoveDown" }, { WebCore::VKEY_DOWN, ShiftKey, "MoveDownAndModifySelection" }, { WebCore::VKEY_NEXT, ShiftKey, "MovePageDownAndModifySelection"}, { WebCore::VKEY_PRIOR, 0, "MovePageUp" }, { WebCore::VKEY_NEXT, 0, "MovePageDown" }, { WebCore::VKEY_HOME, 0, "MoveToBeginningOfLine" }, { WebCore::VKEY_HOME, ShiftKey, "MoveToBeginningOfLineAndModifySelection" }, #if defined(OS_MACOSX) { WebCore::VKEY_LEFT, CommandKey, "MoveToBeginningOfLine" }, { WebCore::VKEY_LEFT, CommandKey | ShiftKey, "MoveToBeginningOfLineAndModifySelection" }, #endif #if defined(OS_MACOSX) { WebCore::VKEY_UP, CommandKey, "MoveToBeginningOfDocument" }, { WebCore::VKEY_UP, CommandKey | ShiftKey, "MoveToBeginningOfDocumentAndModifySelection" }, #else { WebCore::VKEY_HOME, CtrlKey, "MoveToBeginningOfDocument" }, { WebCore::VKEY_HOME, CtrlKey | ShiftKey, "MoveToBeginningOfDocumentAndModifySelection" }, #endif { WebCore::VKEY_END, 0, "MoveToEndOfLine" }, { WebCore::VKEY_END, ShiftKey, "MoveToEndOfLineAndModifySelection" }, #if defined(OS_MACOSX) { WebCore::VKEY_DOWN, CommandKey, "MoveToEndOfDocument" }, { WebCore::VKEY_DOWN, CommandKey | ShiftKey, "MoveToEndOfDocumentAndModifySelection" }, #else { WebCore::VKEY_END, CtrlKey, "MoveToEndOfDocument" }, { WebCore::VKEY_END, CtrlKey | ShiftKey, "MoveToEndOfDocumentAndModifySelection" }, #endif #if defined(OS_MACOSX) { WebCore::VKEY_RIGHT, CommandKey, "MoveToEndOfLine" }, { WebCore::VKEY_RIGHT, CommandKey | ShiftKey, "MoveToEndOfLineAndModifySelection" }, #endif { WebCore::VKEY_BACK, 0, "DeleteBackward" }, { WebCore::VKEY_BACK, ShiftKey, "DeleteBackward" }, { WebCore::VKEY_DELETE, 0, "DeleteForward" }, #if defined(OS_MACOSX) { WebCore::VKEY_BACK, OptionKey, "DeleteWordBackward" }, { WebCore::VKEY_DELETE, OptionKey, "DeleteWordForward" }, #else { WebCore::VKEY_BACK, CtrlKey, "DeleteWordBackward" }, { WebCore::VKEY_DELETE, CtrlKey, "DeleteWordForward" }, #endif { 'B', CtrlKey, "ToggleBold" }, { 'I', CtrlKey, "ToggleItalic" }, { 'U', CtrlKey, "ToggleUnderline" }, { WebCore::VKEY_ESCAPE, 0, "Cancel" }, { WebCore::VKEY_OEM_PERIOD, CtrlKey, "Cancel" }, { WebCore::VKEY_TAB, 0, "InsertTab" }, { WebCore::VKEY_TAB, ShiftKey, "InsertBacktab" }, { WebCore::VKEY_RETURN, 0, "InsertNewline" }, { WebCore::VKEY_RETURN, CtrlKey, "InsertNewline" }, { WebCore::VKEY_RETURN, AltKey, "InsertNewline" }, { WebCore::VKEY_RETURN, AltKey | ShiftKey, "InsertNewline" }, { WebCore::VKEY_RETURN, ShiftKey, "InsertLineBreak" }, { WebCore::VKEY_INSERT, CtrlKey, "Copy" }, { WebCore::VKEY_INSERT, ShiftKey, "Paste" }, { WebCore::VKEY_DELETE, ShiftKey, "Cut" }, #if defined(OS_MACOSX) { 'C', CommandKey, "Copy" }, { 'V', CommandKey, "Paste" }, { 'V', CommandKey | ShiftKey, "PasteAndMatchStyle" }, { 'X', CommandKey, "Cut" }, { 'A', CommandKey, "SelectAll" }, { 'Z', CommandKey, "Undo" }, { 'Z', CommandKey | ShiftKey, "Redo" }, { 'Y', CommandKey, "Redo" }, #else { '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 }; 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 WebCore::KeyboardEvent* evt) { const WebCore::PlatformKeyboardEvent* keyEvent = evt->keyEvent(); if (!keyEvent) return ""; static HashMap* keyDownCommandsMap = 0; static HashMap* keyPressCommandsMap = 0; if (!keyDownCommandsMap) { keyDownCommandsMap = new HashMap; keyPressCommandsMap = new HashMap; 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() == WebCore::PlatformKeyboardEvent::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( WebCore::KeyboardEvent* evt) { const WebCore::PlatformKeyboardEvent* keyEvent = evt->keyEvent(); // do not treat this as text input if it's a system key event if (!keyEvent || keyEvent->isSystemKey()) return false; WebCore::Frame* frame = evt->target()->toNode()->document()->frame(); if (!frame) return false; const char* command_name = interpretKeyEvent(evt); WebCore::Editor::Command command = frame->editor()->command(command_name); if (keyEvent->type() == WebCore::PlatformKeyboardEvent::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() || !command_name) return false; if (command.execute(evt)) { WebViewDelegate* d = web_view_->delegate(); MaybeRecordCommand(d, command_name); return true; } return false; } if (command.execute(evt)) { WebViewDelegate* d = web_view_->delegate(); MaybeRecordCommand(d, command_name); return true; } 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; } return frame->editor()->insertText(evt->keyEvent()->text(), evt); } // // End of code block subject to Apple, Inc. copyright // void EditorClientImpl::handleKeyboardEvent(WebCore::KeyboardEvent* evt) { if (evt->keyCode() == WebCore::VKEY_DOWN || evt->keyCode() == WebCore::VKEY_UP) { DCHECK(evt->target()->toNode()); ShowAutofillForNode(evt->target()->toNode()); } if (handleEditingKeyboardEvent(evt)) evt->setDefaultHandled(); } void EditorClientImpl::handleInputMethodKeydown(WebCore::KeyboardEvent* keyEvent) { // We handle IME within chrome. } void EditorClientImpl::textFieldDidBeginEditing(WebCore::Element*) { } void EditorClientImpl::textFieldDidEndEditing(WebCore::Element*) { // Notification that focus was lost. Be careful with this, it's also sent // when the page is being closed. // Cancel any pending DoAutofill calls. autofill_factory_.RevokeAll(); // Hide any showing popup. web_view_->HideAutoCompletePopup(); } void EditorClientImpl::textDidChangeInTextField(WebCore::Element* element) { DCHECK(element->hasLocalName(WebCore::HTMLNames::inputTag)); Autofill(static_cast(element), false); } void EditorClientImpl::ShowAutofillForNode(WebCore::Node* node) { if (node->nodeType() == WebCore::Node::ELEMENT_NODE) { WebCore::Element* element = static_cast(node); if (element->hasLocalName(WebCore::HTMLNames::inputTag)) { WebCore::HTMLInputElement* input_element = static_cast(element); if (input_element->value().isEmpty()) Autofill(input_element, true); } } } void EditorClientImpl::Autofill(WebCore::HTMLInputElement* input_element, bool autofill_on_empty_value) { // Cancel any pending DoAutofill calls. autofill_factory_.RevokeAll(); // Let's try to trigger autofill for that field, if applicable. if (!input_element->isEnabled() || !input_element->isTextField() || input_element->isPasswordField() || !input_element->autoComplete()) { return; } std::wstring name = webkit_glue::StringToStdWString(input_element->name()); if (name.empty()) // If the field has no name, then we won't have values. return; // Don't attempt to autofill with values that are too large. if (input_element->value().length() > kMaximumTextSizeForAutofill) return; // We post a task for doing the autofill as the caret position is not set // properly at this point ( http://bugs.webkit.org/show_bug.cgi?id=16976) // and we need it to determine whether or not to trigger autofill. std::wstring value = webkit_glue::StringToStdWString(input_element->value()); MessageLoop::current()->PostTask( FROM_HERE, autofill_factory_.NewRunnableMethod(&EditorClientImpl::DoAutofill, input_element, autofill_on_empty_value, backspace_pressed_)); } void EditorClientImpl::DoAutofill(WebCore::HTMLInputElement* input_element, bool autofill_on_empty_value, bool backspace) { std::wstring value = webkit_glue::StringToStdWString(input_element->value()); // Only autofill when there is some text and the caret is at the end. bool caret_at_end = input_element->selectionStart() == input_element->selectionEnd() && input_element->selectionEnd() == static_cast(value.length()); if ((!autofill_on_empty_value && value.empty()) || !caret_at_end) { web_view_->HideAutoCompletePopup(); return; } // First let's see if there is a password listener for that element. WebFrameImpl* webframe = WebFrameImpl::FromFrame(input_element->document()->frame()); webkit_glue::PasswordAutocompleteListener* listener = webframe->GetPasswordListener(input_element); if (listener) { if (backspace) // No autocomplete for password on backspace. return; listener->OnInlineAutocompleteNeeded(input_element, value); return; } // Then trigger form autofill. std::wstring name = webkit_glue::StringToStdWString(input_element-> name().string()); web_view_->delegate()->QueryFormFieldAutofill(name, value, reinterpret_cast(input_element)); } bool EditorClientImpl::doTextFieldCommandFromEvent( WebCore::Element* element, WebCore::KeyboardEvent* event) { // Remember if backspace was pressed for the autofill. It is not clear how to // find if backspace was pressed from textFieldDidBeginEditing and // textDidChangeInTextField as when these methods are called the value of the // input element already contains the type character. backspace_pressed_ = (event->keyCode() == WebCore::VKEY_BACK); // 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; } void EditorClientImpl::textWillBeDeletedInTextField(WebCore::Element*) { } void EditorClientImpl::textDidChangeInTextArea(WebCore::Element*) { } void EditorClientImpl::ignoreWordInSpellDocument(const WebCore::String&) { NOTIMPLEMENTED(); } void EditorClientImpl::learnWord(const WebCore::String&) { NOTIMPLEMENTED(); } void EditorClientImpl::checkSpellingOfString(const UChar* str, int length, 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 spell_location = -1; int spell_length = 0; WebViewDelegate* d = web_view_->delegate(); if (isContinuousSpellCheckingEnabled() && d) { std::wstring word = webkit_glue::StringToStdWString(WebCore::String(str, length)); d->SpellCheck(word, spell_location, spell_length); } else { spell_location = 0; spell_length = 0; } // Note: the Mac code checks if the pointers are NULL before writing to them, // so we do too. if (misspellingLocation) *misspellingLocation = spell_location; if (misspellingLength) *misspellingLength = spell_length; } void EditorClientImpl::checkGrammarOfString(const UChar*, int length, WTF::Vector&, int* badGrammarLocation, int* badGrammarLength) { NOTIMPLEMENTED(); if (badGrammarLocation) *badGrammarLocation = 0; if (badGrammarLength) *badGrammarLength = 0; } void EditorClientImpl::updateSpellingUIWithGrammarString(const WebCore::String&, const WebCore::GrammarDetail& detail) { NOTIMPLEMENTED(); } void EditorClientImpl::updateSpellingUIWithMisspelledWord(const WebCore::String&) { NOTIMPLEMENTED(); } void EditorClientImpl::showSpellingUI(bool show) { NOTIMPLEMENTED(); } bool EditorClientImpl::spellingUIIsShowing() { return false; } void EditorClientImpl::getGuessesForWord(const WebCore::String&, WTF::Vector& guesses) { NOTIMPLEMENTED(); } void EditorClientImpl::setInputMethodState(bool enabled) { WebViewDelegate* d = web_view_->delegate(); if (d) { d->SetInputMethodState(enabled); } } std::wstring EditorClientImpl::DescribeOrError(int number, WebCore::ExceptionCode ec) { if (ec) return L"ERROR"; return IntToWString(number); } std::wstring EditorClientImpl::DescribeOrError(WebCore::Node* node, WebCore::ExceptionCode ec) { if (ec) return L"ERROR"; return Describe(node); } // These Describe() functions match the output expected by the layout tests. std::wstring EditorClientImpl::Describe(WebCore::Range* range) { if (range) { WebCore::ExceptionCode exception = 0; std::wstring str = L"range from "; int offset = range->startOffset(exception); str.append(DescribeOrError(offset, exception)); str.append(L" of "); WebCore::Node* container = range->startContainer(exception); str.append(DescribeOrError(container, exception)); str.append(L" to "); offset = range->endOffset(exception); str.append(DescribeOrError(offset, exception)); str.append(L" of "); container = range->endContainer(exception); str.append(DescribeOrError(container, exception)); return str; } return L"(null)"; } // See comment for Describe(), above. std::wstring EditorClientImpl::Describe(WebCore::Node* node) { if (node) { std::wstring str = webkit_glue::StringToStdWString(node->nodeName()); WebCore::Node* parent = node->parentNode(); if (parent) { str.append(L" > "); str.append(Describe(parent)); } return str; } return L"(null)"; } // See comment for Describe(), above. std::wstring EditorClientImpl::Describe(WebCore::EditorInsertAction action) { switch (action) { case WebCore::EditorInsertActionTyped: return L"WebViewInsertActionTyped"; case WebCore::EditorInsertActionPasted: return L"WebViewInsertActionPasted"; case WebCore::EditorInsertActionDropped: return L"WebViewInsertActionDropped"; } return L"(UNKNOWN ACTION)"; } // See comment for Describe(), above. std::wstring EditorClientImpl::Describe(WebCore::EAffinity affinity) { switch (affinity) { case WebCore::UPSTREAM: return L"NSSelectionAffinityUpstream"; case WebCore::DOWNSTREAM: return L"NSSelectionAffinityDownstream"; } return L"(UNKNOWN AFFINITY)"; } std::wstring EditorClientImpl::Describe(WebCore::CSSStyleDeclaration* style) { // TODO(pamg): Implement me. It's not clear what WebKit produces for this // (their [style description] method), and none of the layout tests provide // an example. But because none of them use it, it's not yet important. return std::wstring(); }