diff options
21 files changed, 1914 insertions, 48 deletions
@@ -101,6 +101,20 @@ ifneq ($(HTTP_STACK),chrome) endif endif +# Read the environment variable to determine if Autofill is enabled. +# The default is off. Chrome HTTP stack must be used when Autofill +# is turned on. + +ifneq ($(ENABLE_AUTOFILL),true) + ENABLE_AUTOFILL=false +endif + +ifneq ($(HTTP_STACK),chrome) + ifeq ($(ENABLE_AUTOFILL),true) + ENABLE_AUTOFILL = false + endif +endif + BASE_PATH := $(call my-dir) include $(CLEAR_VARS) @@ -218,6 +232,16 @@ LOCAL_C_INCLUDES := $(LOCAL_C_INCLUDES) \ $(base_intermediates)/WebCore/html \ $(base_intermediates)/WebCore/platform +# The following includes are needed by the AutoFill feature. +LOCAL_C_INCLUDES := $(LOCAL_C_INCLUDES) \ + $(LOCAL_PATH)/WebKit/chromium \ + $(LOCAL_PATH)/WebKit/chromium/public \ + external/chromium/chrome/browser \ + external/chromium/chrome/renderer \ + external/chromium/android \ + external/chromium/chrome \ + external/skia + ifeq ($(JAVASCRIPT_ENGINE),v8) # Include WTF source file. d := JavaScriptCore @@ -364,6 +388,10 @@ LOCAL_STATIC_LIBRARIES += libchromium_net LOCAL_SHARED_LIBRARIES += libcrypto libssl libz endif # HTTP_STACK == chrome +ifeq ($(ENABLE_AUTOFILL),true) +LOCAL_SHARED_LIBRARIES += libexpat +endif + # Redefine LOCAL_SRC_FILES to be all the WebKit source files LOCAL_SRC_FILES := $(WEBKIT_SRC_FILES) @@ -428,6 +456,19 @@ LOCAL_C_INCLUDES := $(WEBKIT_C_INCLUDES) LOCAL_PATH := $(BASE_PATH) LOCAL_SRC_FILES := \ WebKit/android/jni/WebCoreJniOnLoad.cpp + +ifeq ($(ENABLE_AUTOFILL),true) +# AutoFill requires some cpp files from Chromium to link with +# libchromium_net. We cannot compile them into libchromium_net +# because they have cpp file extensions, not .cc. +LOCAL_CFLAGS += -DWEBKIT_IMPLEMENTATION=1 +LOCAL_SRC_FILES += \ + WebKit/android/WebCoreSupport/autofill/MainThreadProxy.cpp \ + WebKit/chromium/src/WebCString.cpp \ + WebKit/chromium/src/WebRegularExpression.cpp \ + WebKit/chromium/src/WebString.cpp +endif + # Do this dependency by hand. The reason we have to do this is because the # headers that this file pulls in are generated during the build of webcore. # We make all of our object files depend on those files so that they are built diff --git a/WebCore/Android.derived.mk b/WebCore/Android.derived.mk index e913a48..e2cf096 100644 --- a/WebCore/Android.derived.mk +++ b/WebCore/Android.derived.mk @@ -176,3 +176,51 @@ $(GEN): xlink_attrs := $(LOCAL_PATH)/svg/xlinkattrs.in $(GEN): $(LOCAL_PATH)/dom/make_names.pl $(xlink_attrs) $(transform-generated-source) LOCAL_GENERATED_SOURCES += $(GEN) + +ifeq ($(ENABLE_AUTOFILL),true) +# This is really ugly, but necessary. The following rules are taken from +# external/chromium/Android.mk. +# Chromium uses several third party libraries and headers that are already +# present on Android, but in different include paths. Generate a set of +# forwarding headers in the location that Chromium expects. We need to do +# this in both the Chromium and WebCore projects because of the +# WebKit <-> Chromium bindings which include headers from the Chromuim project. + +THIRD_PARTY = $(intermediates)/third_party +CHROMIUM_SRC_DIR := $(LOCAL_PATH)/../../chromium +SCRIPT := $(CHROMIUM_SRC_DIR)/android/generateAndroidForwardingHeader.pl + +GEN := $(THIRD_PARTY)/expat/files/lib/expat.h +$(GEN): $(SCRIPT) +$(GEN): + perl $(SCRIPT) $@ "lib/expat.h" +LOCAL_GENERATED_SOURCES += $(GEN) + +GEN := $(THIRD_PARTY)/skia/include/core/SkBitmap.h +$(GEN): $(SCRIPT) +$(GEN): + perl $(SCRIPT) $@ "include/core/SkBitmap.h" +LOCAL_GENERATED_SOURCES += $(GEN) + +GEN := $(THIRD_PARTY)/WebKit/WebKit/chromium/public/WebFormControlElement.h +$(GEN): $(SCRIPT) +$(GEN): + perl $(SCRIPT) $@ "public/WebFormControlElement.h" +LOCAL_GENERATED_SOURCES += $(GEN) + +GEN := $(THIRD_PARTY)/WebKit/WebKit/chromium/public/WebRegularExpression.h +$(GEN): $(SCRIPT) +$(GEN): + perl $(SCRIPT) $@ "public/WebRegularExpression.h" +LOCAL_GENERATED_SOURCES += $(GEN) + +GEN := $(THIRD_PARTY)/WebKit/WebKit/chromium/public/WebString.h +$(GEN): $(SCRIPT) +$(GEN): + perl $(SCRIPT) $@ "public/WebString.h" +LOCAL_GENERATED_SOURCES += $(GEN) + +endif + +LOCAL_SRC_FILES += $(LOCAL_GENERATED_SOURCES) + diff --git a/WebKit/Android.mk b/WebKit/Android.mk index 01ede0f..a361974 100644 --- a/WebKit/Android.mk +++ b/WebKit/Android.mk @@ -106,3 +106,14 @@ LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \ android/wds/Command.cpp \ android/wds/Connection.cpp \ android/wds/DebugServer.cpp + +# Needed for autofill. +ifeq ($(ENABLE_AUTOFILL),true) +LOCAL_CFLAGS += -DENABLE_WEB_AUTOFILL + +LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \ + android/WebCoreSupport/autofill/AutoFillHostAndroid.cpp \ + android/WebCoreSupport/autofill/FormFieldAndroid.cpp \ + android/WebCoreSupport/autofill/FormManagerAndroid.cpp \ + android/WebCoreSupport/autofill/WebAutoFill.cpp +endif # ENABLE_AUTOFILL == true diff --git a/WebKit/android/WebCoreSupport/ChromiumIncludes.h b/WebKit/android/WebCoreSupport/ChromiumIncludes.h index a7abef1..1a69ff1 100644 --- a/WebKit/android/WebCoreSupport/ChromiumIncludes.h +++ b/WebKit/android/WebCoreSupport/ChromiumIncludes.h @@ -26,6 +26,8 @@ #ifndef ChromiumIncludes_h #define ChromiumIncludes_h +#include "config.h" + // Include all external/chromium files in this file so the problems with the LOG // and LOG_ASSERT defines can be handled in one place. @@ -64,6 +66,20 @@ #include <net/url_request/url_request.h> #include <net/url_request/url_request_context.h> +#if ENABLE(WEB_AUTOFILL) +#include <autofill/autofill_manager.h> +#include <autofill/autofill_profile.h> +#include <autofill/personal_data_manager.h> +#include <base/logging.h> +#include <base/scoped_vector.h> +#include <base/string16.h> +#include <base/utf_string_conversions.h> +#include <chrome/browser/autofill/autofill_host.h> +#include <chrome/browser/profile.h> +#include <chrome/browser/tab_contents/tab_contents.h> +#include <webkit/glue/form_data.h> +#endif + #undef LOG #if defined(LOG_WAS_DEFINED) && defined(LOG_PRI) #define LOG(priority, tag, ...) LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) diff --git a/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp b/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp index 45498c0..f5f8211 100644 --- a/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp +++ b/WebKit/android/WebCoreSupport/EditorClientAndroid.cpp @@ -32,6 +32,7 @@ #include "EventNames.h" #include "FocusController.h" #include "Frame.h" +#include "HTMLNames.h" #include "KeyboardEvent.h" #include "NotImplemented.h" #include "PlatformKeyboardEvent.h" @@ -39,6 +40,8 @@ #include "WebViewCore.h" #include "WindowsKeyboardCodes.h" +using namespace WebCore::HTMLNames; + namespace android { void EditorClientAndroid::pageDestroyed() { @@ -235,6 +238,19 @@ void EditorClientAndroid::respondToChangedSelection() { Frame* frame = m_page->focusController()->focusedOrMainFrame(); if (!frame || !frame->view()) return; + +#if ENABLE(WEB_AUTOFILL) + WebCore::Node* focusedNode = frame->document()->focusedNode(); + if (focusedNode && focusedNode->hasTagName(inputTag)) { + WebCore::HTMLInputElement* element = static_cast<WebCore::HTMLInputElement*>(focusedNode); + // TODO: If it's a text field, inform AutoFill that it should get AutoFill suggestions for + // the form it belongs to. AutoFill can also work with select-one elements (i.e. <select> + // without the "multiple" attribute set. Is it safe to call this function with select elements? + // How should AutoFill communicate select suggestions to Java? + if (element->isTextField()) + m_autoFill->formFieldFocused(static_cast<HTMLFormControlElement*>(focusedNode)); + } +#endif WebViewCore* webViewCore = WebViewCore::getWebViewCore(frame->view()); webViewCore->updateTextSelection(); } @@ -261,4 +277,15 @@ void EditorClientAndroid::willSetInputMethodState() { notImplemented(); } + +#if ENABLE(WEB_AUTOFILL) +WebAutoFill* EditorClientAndroid::getAutoFill() +{ + if (!m_autoFill) + m_autoFill.set(new WebAutoFill()); + + return m_autoFill.get(); +} +#endif + } diff --git a/WebKit/android/WebCoreSupport/EditorClientAndroid.h b/WebKit/android/WebCoreSupport/EditorClientAndroid.h index 6d49f48..9aed88c 100644 --- a/WebKit/android/WebCoreSupport/EditorClientAndroid.h +++ b/WebKit/android/WebCoreSupport/EditorClientAndroid.h @@ -26,8 +26,14 @@ #ifndef EditorClientAndroid_h #define EditorClientAndroid_h +#include "config.h" + + #include "EditorClient.h" #include "Page.h" +#include "autofill/WebAutoFill.h" + +#include <wtf/OwnPtr.h> using namespace WebCore; @@ -111,10 +117,16 @@ public: void setPage(Page* page) { m_page = page; } void setShouldChangeSelectedRange(bool shouldChangeSelectedRange) { m_shouldChangeSelectedRange = shouldChangeSelectedRange; } void setUiGeneratedSelectionChange(bool uiGenerated) { m_uiGeneratedSelectionChange = uiGenerated; } +#if ENABLE(WEB_AUTOFILL) + WebAutoFill* getAutoFill(); +#endif private: Page* m_page; bool m_shouldChangeSelectedRange; bool m_uiGeneratedSelectionChange; +#if ENABLE(WEB_AUTOFILL) + OwnPtr<WebAutoFill> m_autoFill; +#endif }; } diff --git a/WebKit/android/WebCoreSupport/WebRequestContext.cpp b/WebKit/android/WebCoreSupport/WebRequestContext.cpp index dc9feae..0dda816 100644 --- a/WebKit/android/WebCoreSupport/WebRequestContext.cpp +++ b/WebKit/android/WebCoreSupport/WebRequestContext.cpp @@ -28,6 +28,7 @@ #include "WebRequestContext.h" #include "JNIUtility.h" +#include "WebUrlLoaderClient.h" #include "jni.h" #include <dirent.h> #include <sys/types.h> @@ -121,7 +122,8 @@ scoped_refptr<WebRequestContext> WebRequestContext::GetAndroidContextForPath(con scoped_refptr<WebRequestContext> androidContext = new WebRequestContext(); androidContext->host_resolver_ = net::CreateSystemHostResolver(0); - scoped_refptr<base::MessageLoopProxy> cacheMessageLoopProxy = base::MessageLoopProxy::CreateForCurrentThread(); + base::Thread* ioThread = WebUrlLoaderClient::ioThread(); + scoped_refptr<base::MessageLoopProxy> cacheMessageLoopProxy = ioThread->message_loop_proxy(); // Todo: check if the context takes ownership of the cache net::HttpCache::DefaultBackend* defaultBackend = new net::HttpCache::DefaultBackend(net::DISK_CACHE, cachePath, 20 * 1024 * 1024, cacheMessageLoopProxy); androidContext->http_transaction_factory_ = new net::HttpCache(androidContext->host_resolver(), net::ProxyService::CreateNull(), net::SSLConfigService::CreateSystemSSLConfigService(), net::HttpAuthHandlerFactory::CreateDefault(), 0, 0, defaultBackend); diff --git a/WebKit/android/WebCoreSupport/WebUrlLoaderClient.h b/WebKit/android/WebCoreSupport/WebUrlLoaderClient.h index 1a8118c..92c7bb5 100644 --- a/WebKit/android/WebCoreSupport/WebUrlLoaderClient.h +++ b/WebKit/android/WebCoreSupport/WebUrlLoaderClient.h @@ -83,6 +83,9 @@ public: static void didFail(void*); static void willSendRequest(void*); + // Handle to the chrome IO thread + static base::Thread* ioThread(); + private: void finish(); RefPtr<WebCore::ResourceHandle> m_resourceHandle; @@ -93,9 +96,6 @@ private: // Not an OwnPtr since it should be deleted on another thread WebRequest* m_request; - // Handle to the chrome IO thread - static base::Thread* ioThread(); - // Check if a request is active bool isActive() const; diff --git a/WebKit/android/WebCoreSupport/autofill/AutoFillHostAndroid.cpp b/WebKit/android/WebCoreSupport/autofill/AutoFillHostAndroid.cpp new file mode 100644 index 0000000..51f7a93 --- /dev/null +++ b/WebKit/android/WebCoreSupport/autofill/AutoFillHostAndroid.cpp @@ -0,0 +1,50 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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 "AutoFillHostAndroid.h" + +#include "autofill/WebAutoFill.h" + +namespace android { + +AutoFillHostAndroid::AutoFillHostAndroid(WebAutoFill* autoFill) + : mAutoFill(autoFill) +{ +} + +void AutoFillHostAndroid::AutoFillSuggestionsReturned(int queryId, const std::vector<string16>& names, const std::vector<string16>& labels, const std::vector<int>& uniqueIds) +{ + if (mAutoFill) + mAutoFill->fillFormFields(queryId, names[0], labels[0], uniqueIds[0]); +} + +void AutoFillHostAndroid::AutoFillFormDataFilled(int queryId, const webkit_glue::FormData& form) +{ + if (mAutoFill) + mAutoFill->fillFormInPage(queryId, form); +} + +} diff --git a/WebKit/android/WebCoreSupport/autofill/AutoFillHostAndroid.h b/WebKit/android/WebCoreSupport/autofill/AutoFillHostAndroid.h new file mode 100644 index 0000000..1984098 --- /dev/null +++ b/WebKit/android/WebCoreSupport/autofill/AutoFillHostAndroid.h @@ -0,0 +1,54 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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. + */ + +#ifndef AutoFillHostAndroid_h +#define AutoFillHostAndroid_h + +#include "ChromiumIncludes.h" + +#include <vector> + +namespace webkit_glue { +class FormData; +} + +namespace android { +class WebAutoFill; + +// This class receives the callbacks from the AutoFillManager in the Chromium code. +class AutoFillHostAndroid : public AutoFillHost { +public: + AutoFillHostAndroid(WebAutoFill* autoFill); + virtual ~AutoFillHostAndroid() { } + + virtual void AutoFillSuggestionsReturned(int queryId, const std::vector<string16>& names, const std::vector<string16>& labels, const std::vector<int>& uniqueIds); + virtual void AutoFillFormDataFilled(int queryId, const webkit_glue::FormData&); + +private: + WebAutoFill* mAutoFill; +}; +} + +#endif diff --git a/WebKit/android/WebCoreSupport/autofill/FormFieldAndroid.cpp b/WebKit/android/WebCoreSupport/autofill/FormFieldAndroid.cpp new file mode 100644 index 0000000..00213a1 --- /dev/null +++ b/WebKit/android/WebCoreSupport/autofill/FormFieldAndroid.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2010 The Chromium Authors. All rights reserved. + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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 "FormFieldAndroid.h" + +#include "ChromiumIncludes.h" +#include "HTMLFormControlElement.h" +#include "HTMLInputElement.h" +#include "HTMLSelectElement.h" + +// TODO: This file is taken from chromium/webkit/glue/form_field.h and +// customised to use WebCore types rather than WebKit API types. It would +// be nice and would ease future merge pain if the two could be combined. + +namespace webkit_glue { +FormField::FormField() + : size_(0) { +} + +FormField::FormField(WebCore::HTMLFormControlElement& element) + : size_(0) { + name_ = string16(element.name().characters()); + + // TODO: Extract the field label. For now we just use the field + // name. + label_ = name_; + + form_control_type_ = string16(element.type().characters()); + if (form_control_type_ == ASCIIToUTF16("text")) { + WebCore::HTMLInputElement* input_element = static_cast<WebCore::HTMLInputElement*>(&element); + value_ = string16(input_element->value().characters()); + size_ = input_element->size(); + } else if (form_control_type_ == ASCIIToUTF16("select-one")) { + WebCore::HTMLSelectElement* select_element = static_cast<WebCore::HTMLSelectElement*>(&element); + value_ = string16(select_element->value().characters()); + } + + TrimWhitespace(value_, TRIM_LEADING, &value_); +} + +FormField::FormField(const string16& label, + const string16& name, + const string16& value, + const string16& form_control_type, + int size) + : label_(label), + name_(name), + value_(value), + form_control_type_(form_control_type), + size_(size) { +} + +bool FormField::operator!=(const FormField& field) const { + return !operator==(field); +} +} // namespace webkit_glue diff --git a/WebKit/android/WebCoreSupport/autofill/FormFieldAndroid.h b/WebKit/android/WebCoreSupport/autofill/FormFieldAndroid.h new file mode 100644 index 0000000..51bb24b --- /dev/null +++ b/WebKit/android/WebCoreSupport/autofill/FormFieldAndroid.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2010 The Chromium Authors. All rights reserved. + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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. + */ + +#ifndef FormFieldAndroid_h +#define FormFieldAndroid_h + +#include <base/string16.h> +#include <vector> + +// TODO: This file is taken from chromium/webkit/glue/form_field.cc and +// customised to use WebCore types rather than WebKit API types. It would +// be nice and would ease future merge pain if the two could be combined. + +namespace WebCore { +class HTMLFormControlElement; +} + +namespace webkit_glue { + +// Stores information about a field in a form. +class FormField { +public: + FormField(); + explicit FormField(WebCore::HTMLFormControlElement& element); + FormField(const string16& label, + const string16& name, + const string16& value, + const string16& form_control_type, + int size); + + const string16& label() const { return label_; } + const string16& name() const { return name_; } + const string16& value() const { return value_; } + const string16& form_control_type() const { return form_control_type_; } + int size() const { return size_; } + // Returns option string for elements for which they make sense (select-one, + // for example) for the rest of elements return an empty array. + const std::vector<string16>& option_strings() const { + return option_strings_; + } + + + void set_label(const string16& label) { label_ = label; } + void set_name(const string16& name) { name_ = name; } + void set_value(const string16& value) { value_ = value; } + void set_form_control_type(const string16& form_control_type) { + form_control_type_ = form_control_type; + } + void set_size(int size) { size_ = size; } + void set_option_strings(const std::vector<string16>& strings) { + option_strings_ = strings; + } + + bool operator==(const FormField& field) const; + bool operator!=(const FormField& field) const; + +private: + string16 label_; + string16 name_; + string16 value_; + string16 form_control_type_; + int size_; + std::vector<string16> option_strings_; +}; + +inline bool FormField::operator==(const FormField& field) const { + // A FormField stores a value, but the value is not part of the identity of + // the field, so we don't want to compare the values. + return (label_ == field.label_ && + name_ == field.name_ && + form_control_type_ == field.form_control_type_); + +} + +} // namespace webkit_glue + +#endif // WEBKIT_GLUE_FORM_FIELD_H_ diff --git a/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.cpp b/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.cpp new file mode 100644 index 0000000..c97a2a8 --- /dev/null +++ b/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.cpp @@ -0,0 +1,637 @@ +/* + * Copyright (c) 2010 The Chromium Authors. All rights reserved. + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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 "FormManagerAndroid.h" + +#include "HTMLFormControlElement.h" +#include "HTMLFormElement.h" +#include "HTMLTextAreaElement.h" +#include "HTMLInputElement.h" +#include "HTMLLabelElement.h" +#include "HTMLNames.h" +#include "HTMLSelectElement.h" +#include "NodeList.h" +#include "HTMLCollection.h" +#include "FormFieldAndroid.h" +#include "QualifiedName.h" + + +// TODO: This file is taken from chromium/chrome/renderer/form_manager.cc and +// customised to use WebCore types rather than WebKit API types. It would be +// nice and would ease future merge pain if the two could be combined. + +using webkit_glue::FormData; +using webkit_glue::FormField; +using WebCore::HTMLElement; +using WebCore::HTMLFormControlElement; +using WebCore::HTMLFormElement; +using WebCore::HTMLLabelElement; +using WebCore::HTMLSelectElement; +using WebCore::Node; +using WebCore::NodeList; + +using namespace WebCore::HTMLNames; + +namespace { + +// The number of fields required by AutoFill. Ideally we could send the forms +// to AutoFill no matter how many fields are in the forms; however, finding the +// label for each field is a costly operation and we can't spare the cycles if +// it's not necessary. +// Note the on ANDROID we reduce this from Chromium's 3 as it allows us to +// autofill simple name/email forms for example. This improves the mobile +// device experience where form filling can be time consuming and frustrating. +const size_t kRequiredAutoFillFields = 2; + +string16 WTFStringToString16(const WTF::String& wtfString) +{ + WTF::String str = wtfString; + + if (str.charactersWithNullTermination()) + return string16(str.charactersWithNullTermination()); + else + return string16(); +} + +string16 S(const WTF::AtomicString& str) +{ + return WTFStringToString16(str.string()); +} + +string16 S(const WTF::String& string) +{ + return WTFStringToString16(string); +} + +// This is a helper function for the FindChildText() function. +// Returns the node value of the descendant or sibling of |node| that is a +// non-empty text node. This is a faster alternative to |innerText()| for +// performance critical operations. It does a full depth-first search so +// can be used when the structure is not directly known. It does not aggregate +// the text of multiple nodes, it just returns the value of the first found. +// "Non-empty" in this case means non-empty after the whitespace has been +// stripped. +string16 FindChildTextInner(const Node* node) { + string16 element_text; + if (!node) + return element_text; + + element_text = S(node->nodeValue()); + TrimWhitespace(element_text, TRIM_ALL, &element_text); + if (!element_text.empty()) + return element_text; + + element_text = FindChildTextInner(node->firstChild()); + if (!element_text.empty()) + return element_text; + + element_text = FindChildTextInner(node->nextSibling()); + if (!element_text.empty()) + return element_text; + + return element_text; +} + +// Returns the node value of the first decendant of |element| that is a +// non-empty text node. "Non-empty" in this case means non-empty after the +// whitespace has been stripped. +string16 FindChildText(const HTMLElement* element) { + Node* child = element->firstChild(); + return FindChildTextInner(child); +} + +} // namespace + +namespace android { + +FormManager::FormManager() { +} + +FormManager::~FormManager() { + Reset(); +} + +// static +void FormManager::HTMLFormControlElementToFormField( + const HTMLFormControlElement& element, bool get_value, FormField* field) { + ASSERT(field); + + // The label is not officially part of a HTMLFormControlElement; however, the + // labels for all form control elements are scraped from the DOM and set in + // WebFormElementToFormData. + field->set_name(S(element.name())); + field->set_form_control_type(S(element.type())); + + if (!get_value) + return; + + string16 value; + if (element.type() == WTF::AtomicString("text")) { + const WebCore::HTMLTextAreaElement* input_element = static_cast<const WebCore::HTMLTextAreaElement*>(&element); + if (input_element->renderer()) + value = S(input_element->value()); + } else if (element.type() == WTF::AtomicString("select-one")) { + // TODO: This is ugly. SelectElement::value() is a non-const + // method. Look into fixing this on the WebKit side. + HTMLFormControlElement& e = const_cast<HTMLFormControlElement&>(element); + HTMLSelectElement& select_element = static_cast<HTMLSelectElement&>(e); + value = S(select_element.value()); + } + field->set_value(value); +} + +// static +string16 FormManager::LabelForElement(const HTMLFormControlElement& element) { + RefPtr<NodeList> labels = element.document()->getElementsByTagName("label"); + for (unsigned i = 0; i < labels->length(); ++i) { + Node* e = labels->item(i); + if (e->hasTagName(labelTag)) { + HTMLLabelElement* label = static_cast<HTMLLabelElement*>(e); + if (label->control() == &element) + return FindChildText(label); + } + } + + // Infer the label from context if not found in label element. + return FormManager::InferLabelForElement(element); +} + +// static +bool FormManager::HTMLFormElementToFormData(HTMLFormElement& element, + RequirementsMask requirements, + bool get_values, + FormData* form) { + ASSERT(form); + + const WebCore::Document* document = element.document(); + if (!document) + return false; + + if (requirements & REQUIRE_AUTOCOMPLETE && !element.autoComplete()) + return false; + + form->name = S(element.name()); + form->method = S(element.method()); + form->origin = GURL(S(document->documentURI())); + form->action = GURL(S(document->completeURL(element.action()))); + // If the completed URL is not valid, just use the action we get from + // WebKit. + if (!form->action.is_valid()) + form->action = GURL(S(element.action())); + + // A map from a FormField's name to the FormField itself. + std::map<string16, FormField*> name_map; + + // The extracted FormFields. We use pointers so we can store them in + // |name_map|. + ScopedVector<FormField> form_fields; + + WTF::Vector<WebCore::HTMLFormControlElement*> control_elements = element.associatedElements(); + + // A vector of bools that indicate whether each field in the form meets the + // requirements and thus will be in the resulting |form|. + std::vector<bool> fields_extracted(control_elements.size(), false); + for (size_t i = 0; i < control_elements.size(); ++i) { + const HTMLFormControlElement* control_element = control_elements[i]; + + if (requirements & REQUIRE_AUTOCOMPLETE && + control_element->type() == WTF::String("text")) { + const WebCore::HTMLInputElement* input_element = static_cast<const WebCore::HTMLInputElement*>(control_element); + if (!input_element->autoComplete()) + continue; + } + + if (requirements & REQUIRE_ELEMENTS_ENABLED && !control_element->isEnabledFormControl()) + continue; + + // Create a new FormField, fill it out and map it to the field's name. + FormField* field = new FormField; + HTMLFormControlElementToFormField(*control_element, get_values, field); + form_fields.push_back(field); + // TODO: A label element is mapped to a form control element's id. + // field->name() will contain the id only if the name does not exist. Add + // an id() method to HTMLFormControlElement and use that here. + name_map[field->name()] = field; + fields_extracted[i] = true; + } + + // Don't extract field labels if we have no fields. + if (form_fields.empty()) + return false; + + // Loop through the label elements inside the form element. For each label + // element, get the corresponding form control element, use the form control + // element's name as a key into the <name, FormField> map to find the + // previously created FormField and set the FormField's label to the + // label.firstChild().nodeValue() of the label element. + RefPtr<WebCore::NodeList> labels = element.getElementsByTagName("label"); + for (unsigned i = 0; i < labels->length(); ++i) { + WebCore::HTMLLabelElement* label = static_cast<WebCore::HTMLLabelElement*>(labels->item(i)); + HTMLFormControlElement* field_element = label->control(); + if (!field_element || field_element->type() == "hidden") + continue; + + std::map<string16, FormField*>::iterator iter = + name_map.find(S(field_element->name())); + if (iter != name_map.end()) + iter->second->set_label(FindChildText(label)); + } + + // Loop through the form control elements, extracting the label text from the + // DOM. We use the |fields_extracted| vector to make sure we assign the + // extracted label to the correct field, as it's possible |form_fields| will + // not contain all of the elements in |control_elements|. + for (size_t i = 0, field_idx = 0; + i < control_elements.size() && field_idx < form_fields.size(); ++i) { + // This field didn't meet the requirements, so don't try to find a label for + // it. + if (!fields_extracted[i]) + continue; + + const HTMLFormControlElement* control_element = control_elements[i]; + if (form_fields[field_idx]->label().empty()) + form_fields[field_idx]->set_label( + FormManager::InferLabelForElement(*control_element)); + + ++field_idx; + + } + // Copy the created FormFields into the resulting FormData object. + for (ScopedVector<FormField>::const_iterator iter = form_fields.begin(); + iter != form_fields.end(); ++iter) { + form->fields.push_back(**iter); + } + + return true; +} + +void FormManager::ExtractForms(WebCore::Document* document) { + + WTF::PassRefPtr<WebCore::HTMLCollection> collection = document->forms(); + + WebCore::HTMLFormElement* form; + WebCore::HTMLInputElement* input; + for (Node* node = collection->firstItem(); + node && !node->namespaceURI().isNull() && !node->namespaceURI().isEmpty(); + node = collection->nextItem()) { + FormElement* form_elements = new FormElement; + form = static_cast<WebCore::HTMLFormElement*>(node); + if (form->autoComplete()) { + WTF::Vector<WebCore::HTMLFormControlElement*> elements = form->associatedElements(); + size_t size = elements.size(); + for (size_t i = 0; i < size; i++) { + WebCore::HTMLFormControlElement* e = elements[i]; + form_elements->control_elements.push_back(e); + } + form_elements->form_element = form; + } + form_elements_map_[document].push_back(form_elements); + } +} + +void FormManager::GetForms(RequirementsMask requirements, + std::vector<FormData>* forms) { + ASSERT(forms); + + for (DocumentFormElementMap::iterator iter = form_elements_map_.begin(); + iter != form_elements_map_.end(); ++iter) { + for (std::vector<FormElement*>::iterator form_iter = iter->second.begin(); + form_iter != iter->second.end(); ++form_iter) { + FormData form; + if (HTMLFormElementToFormData(*(*form_iter)->form_element, + requirements, + true, + &form)) + forms->push_back(form); + } + } +} + +void FormManager::GetFormsInDocument(const WebCore::Document* document, + RequirementsMask requirements, + std::vector<FormData>* forms) { + ASSERT(document); + ASSERT(forms); + + DocumentFormElementMap::iterator iter = form_elements_map_.find(document); + if (iter == form_elements_map_.end()) + return; + + // TODO: Factor this out and use it here and in GetForms. + const std::vector<FormElement*>& form_elements = iter->second; + for (std::vector<FormElement*>::const_iterator form_iter = + form_elements.begin(); + form_iter != form_elements.end(); ++form_iter) { + FormElement* form_element = *form_iter; + + // We need at least |kRequiredAutoFillFields| fields before appending this + // form to |forms|. + if (form_element->control_elements.size() < kRequiredAutoFillFields) + continue; + + if (requirements & REQUIRE_AUTOCOMPLETE && + !form_element->form_element->autoComplete()) + continue; + + FormData form; + FormElementToFormData(document, form_element, requirements, &form); + if (form.fields.size() >= kRequiredAutoFillFields) + forms->push_back(form); + } +} + +bool FormManager::FindForm(const HTMLFormElement& element, + RequirementsMask requirements, + FormData* form) { + ASSERT(form); + + const WebCore::Document* document = element.document(); + DocumentFormElementMap::const_iterator document_iter = + form_elements_map_.find(document); + if (document_iter == form_elements_map_.end()) + return false; + + for (std::vector<FormElement*>::const_iterator iter = + document_iter->second.begin(); + iter != document_iter->second.end(); ++iter) { + if ((*iter)->form_element->name() != element.name()) + continue; + return FormElementToFormData(document, *iter, requirements, form); + } + return false; +} + +bool FormManager::FindFormWithFormControlElement( + const HTMLFormControlElement& element, + RequirementsMask requirements, + FormData* form) { + ASSERT(form); + + const WebCore::Document* document = element.document(); + if (form_elements_map_.find(document) == form_elements_map_.end()) + return false; + + const std::vector<FormElement*> forms = form_elements_map_[document]; + for (std::vector<FormElement*>::const_iterator iter = forms.begin(); + iter != forms.end(); ++iter) { + const FormElement* form_element = *iter; + + for (std::vector<HTMLFormControlElement*>::const_iterator iter = + form_element->control_elements.begin(); + iter != form_element->control_elements.end(); ++iter) { + if ((*iter)->name() == element.name()) { + HTMLFormElementToFormData( + *form_element->form_element, requirements, true, form); + return true; + } + } + } + + return false; +} + +bool FormManager::FillForm(const FormData& form) { + FormElement* form_element = NULL; + + for (DocumentFormElementMap::iterator iter = form_elements_map_.begin(); + iter != form_elements_map_.end(); ++iter) { + const WebCore::Document* document = iter->first; + + for (std::vector<FormElement*>::iterator form_iter = iter->second.begin(); + form_iter != iter->second.end(); ++form_iter) { + // TODO: matching on form name here which is not guaranteed to + // be unique for the page, nor is it guaranteed to be non-empty. Need to + // find a way to uniquely identify the form cross-process. For now we'll + // check form name and form action for identity. + // http://crbug.com/37990 test file sample8.html. + // Also note that WebString() == WebString(string16()) does not seem to + // evaluate to |true| for some reason TBD, so forcing to string16. + string16 element_name(S((*form_iter)->form_element->name())); + GURL action( + S(document->completeURL((*form_iter)->form_element->action()).string())); + if (element_name == form.name && action == form.action) { + form_element = *form_iter; + break; + } + } + } + + if (!form_element) + return false; + + // It's possible that the site has injected fields into the form after the + // page has loaded, so we can't assert that the size of the cached control + // elements is equal to the size of the fields in |form|. Fortunately, the + // one case in the wild where this happens, paypal.com signup form, the fields + // are appended to the end of the form and are not visible. + + for (size_t i = 0, j = 0; + i < form_element->control_elements.size() && j < form.fields.size(); + ++i, ++j) { + // Once again, empty WebString != empty string16, so we have to explicitly + // check for this case. + if (form_element->control_elements[i]->name().length() == 0 && + form.fields[j].name().empty()) + continue; + + // We assume that the intersection of the fields in + // |form_element->control_elements| and |form.fields| is ordered, but it's + // possible that one or the other sets may have more fields than the other, + // so loop past non-matching fields in the set with more elements. + while (S(form_element->control_elements[i]->name()) != + form.fields[j].name()) { + if (form_element->control_elements.size() > form.fields.size()) { + // We're at the end of the elements already. + if (i + 1 == form_element->control_elements.size()) + break; + ++i; + } else if (form.fields.size() > form_element->control_elements.size()) { + // We're at the end of the elements already. + if (j + 1 == form.fields.size()) + break; + ++j; + } else { + // Shouldn't get here. + ASSERT(false); + } + + continue; + } + + HTMLFormControlElement* element = form_element->control_elements[i]; + if (!form.fields[j].value().empty() && + element->type() != WTF::String("submit")) { + if (element->type() == WTF::String("text")) { + WebCore::HTMLInputElement* input_element = static_cast<WebCore::HTMLInputElement*>(element); + // If the maxlength attribute contains a negative value, maxLength() + // returns the default maxlength value. + input_element->setValue( + WTF::String(form.fields[j].value().substr(0, input_element->maxLength()).c_str())); + input_element->setAutofilled(true); + } else if (element->type() == + WTF::String("select-one")) { + WebCore::HTMLSelectElement* select_element = static_cast<WebCore::HTMLSelectElement*>(element); + select_element->setValue(WTF::String(form.fields[j].value().c_str())); + } + } + } + + return true; +} + +void FormManager::FillForms(const std::vector<webkit_glue::FormData>& forms) { + for (std::vector<webkit_glue::FormData>::const_iterator iter = forms.begin(); + iter != forms.end(); ++iter) { + FillForm(*iter); + } +} + +void FormManager::Reset() { + for (DocumentFormElementMap::iterator iter = form_elements_map_.begin(); + iter != form_elements_map_.end(); ++iter) { + STLDeleteElements(&iter->second); + } + form_elements_map_.clear(); +} + +void FormManager::ResetFrame(const WebCore::Document* document) { + + DocumentFormElementMap::iterator iter = form_elements_map_.find(document); + if (iter != form_elements_map_.end()) { + STLDeleteElements(&iter->second); + form_elements_map_.erase(iter); + } +} + +// static +bool FormManager::FormElementToFormData(const WebCore::Document* document, + const FormElement* form_element, + RequirementsMask requirements, + FormData* form) { + if (requirements & REQUIRE_AUTOCOMPLETE && + !form_element->form_element->autoComplete()) + return false; + + form->name = S(form_element->form_element->name()); + form->method = S(form_element->form_element->method()); + form->origin = GURL(S(document->documentURI())); + form->action = GURL(S(document->completeURL(form_element->form_element->action()))); + + // If the completed URL is not valid, just use the action we get from + // WebKit. + if (!form->action.is_valid()) + form->action = GURL(S(form_element->form_element->action())); + + // Form elements loop. + for (std::vector<HTMLFormControlElement*>::const_iterator element_iter = + form_element->control_elements.begin(); + element_iter != form_element->control_elements.end(); ++element_iter) { + const HTMLFormControlElement* control_element = *element_iter; + + if (requirements & REQUIRE_AUTOCOMPLETE && + control_element->type() == WTF::String("text")) { + const WebCore::HTMLInputElement* input_element = + static_cast<const WebCore::HTMLInputElement*>(control_element); + if (!input_element->autoComplete()) + continue; + } + + if (requirements & REQUIRE_ELEMENTS_ENABLED && !control_element->isEnabledFormControl()) + continue; + + FormField field; + HTMLFormControlElementToFormField(*control_element, false, &field); + form->fields.push_back(field); + } + + return true; +} + +// static +string16 FormManager::InferLabelForElement( + const HTMLFormControlElement& element) { + string16 inferred_label; + Node* previous = element.previousSibling(); + if (previous) { + if (previous->isTextNode()) { + inferred_label = S(previous->nodeValue()); + TrimWhitespace(inferred_label, TRIM_ALL, &inferred_label); + } + + // If we didn't find text, check for previous paragraph. + // Eg. <p>Some Text</p><input ...> + // Note the lack of whitespace between <p> and <input> elements. + if (inferred_label.empty()) { + if (previous->isElementNode()) { + if (previous->hasTagName(pTag)) { + inferred_label = FindChildText((HTMLElement*)previous); + } + } + } + + // If we didn't find paragraph, check for previous paragraph to this. + // Eg. <p>Some Text</p> <input ...> + // Note the whitespace between <p> and <input> elements. + if (inferred_label.empty()) { + previous = previous->previousSibling(); + if (previous) { + if (previous->hasTagName(pTag)) { + inferred_label = FindChildText((HTMLElement*)previous); + } + } + } + } + + // If we didn't find paragraph, check for table cell case. + // Eg. <tr><td>Some Text</td><td><input ...></td></tr> + // Eg. <tr><td><b>Some Text</b></td><td><b><input ...></b></td></tr> + if (inferred_label.empty()) { + Node* parent = element.parentNode(); + while (parent && + !parent->hasTagName(tdTag)) + parent = parent->parentNode(); + + if (parent) { + if (parent->hasTagName(tdTag)) { + previous = parent->previousSibling(); + + // Skip by any intervening text nodes. + while (previous && previous->isTextNode()) + previous = previous->previousSibling(); + + if (previous) { + if (previous->hasTagName(tdTag)) { + inferred_label = FindChildText((HTMLElement*)previous); + } + } + } + } + } + return inferred_label; +} + +} diff --git a/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.h b/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.h new file mode 100644 index 0000000..395e651 --- /dev/null +++ b/WebKit/android/WebCoreSupport/autofill/FormManagerAndroid.h @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2010 The Chromium Authors. All rights reserved. + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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. + */ + +#ifndef FormManagerAndroid_h +#define FormManagerAndroid_h + +#include "ChromiumIncludes.h" + +#include <map> +#include <vector> + +// TODO: This file is taken from chromium/chrome/renderer/form_manager.h and +// customised to use WebCore types rather than WebKit API types. It would be +// nice and would ease future merge pain if the two could be combined. + +namespace webkit_glue { +struct FormData; +class FormField; +} // namespace webkit_glue + +namespace WebCore { + class HTMLFormControlElement; + class HTMLFormElement; + class Document; +} + +namespace android { + +// Manages the forms in a Document. +class FormManager { + public: + // A bit field mask for form requirements. + enum RequirementsMask { + REQUIRE_NONE = 0x0, // No requirements. + REQUIRE_AUTOCOMPLETE = 0x1, // Require that autocomplete != off. + REQUIRE_ELEMENTS_ENABLED = 0x2 // Require that disabled attribute is off. + }; + + FormManager(); + virtual ~FormManager(); + + // Fills out a FormField object from a given WebFormControlElement. + // If |get_value| is true, |field| will have the value set from |element|. + static void HTMLFormControlElementToFormField( + const WebCore::HTMLFormControlElement& element, + bool get_value, + webkit_glue::FormField* field); + + // Returns the corresponding label for |element|. WARNING: This method can + // potentially be very slow. Do not use during any code paths where the page + // is loading. + static string16 LabelForElement(const WebCore::HTMLFormControlElement& element); + + // Fills out a FormData object from a given WebFormElement. If |get_values| + // is true, the fields in |form| will have the values filled out. Returns + // true if |form| is filled out; it's possible that |element| won't meet the + // requirements in |requirements|. This also returns false if there are no + // fields in |form|. + // TODO: Remove the user of this in RenderView and move this to + // private. + static bool HTMLFormElementToFormData(WebCore::HTMLFormElement& element, + RequirementsMask requirements, + bool get_values, + webkit_glue::FormData* form); + + // Scans the DOM in |document| extracting and storing forms. + void ExtractForms(WebCore::Document* document); + + // Returns a vector of forms that match |requirements|. + void GetForms(RequirementsMask requirements, + std::vector<webkit_glue::FormData>* forms); + + // Returns a vector of forms in |document| that match |requirements|. + void GetFormsInDocument(const WebCore::Document* document, + RequirementsMask requirements, + std::vector<webkit_glue::FormData>* forms); + + // Returns the cached FormData for |element|. Returns true if the form was + // found in the cache. + bool FindForm(const WebCore::HTMLFormElement& element, + RequirementsMask requirements, + webkit_glue::FormData* form); + + // Finds the form that contains |element| and returns it in |form|. Returns + // false if the form is not found. + bool FindFormWithFormControlElement( + const WebCore::HTMLFormControlElement& element, + RequirementsMask requirements, + webkit_glue::FormData* form); + + // Fills the form represented by |form|. |form| should have the name set to + // the name of the form to fill out, and the number of elements and values + // must match the number of stored elements in the form. + // TODO: Is matching on name alone good enough? It's possible to + // store multiple forms with the same names from different frames. + bool FillForm(const webkit_glue::FormData& form); + + // Fills all of the forms in the cache with form data from |forms|. + void FillForms(const std::vector<webkit_glue::FormData>& forms); + + // Resets the stored set of forms. + void Reset(); + + // Resets the forms for the specified |document|. + void ResetFrame(const WebCore::Document* document); + + private: + // Stores the HTMLFormElement and the form control elements for a form. + struct FormElement { + WebCore::HTMLFormElement* form_element; + std::vector<WebCore::HTMLFormControlElement*> control_elements; + }; + + // A map of vectors of FormElements keyed by the Document containing each + // form. + typedef std::map<const WebCore::Document*, std::vector<FormElement*> > + DocumentFormElementMap; + + // Converts a FormElement to FormData storage. Returns false if the form does + // not meet all the requirements in the requirements mask. + static bool FormElementToFormData(const WebCore::Document* document, + const FormElement* form_element, + RequirementsMask requirements, + webkit_glue::FormData* form); + + // Infers corresponding label for |element| from surrounding context in the + // DOM. Contents of preceeding <p> tag or preceeding text element found in + // the form. + static string16 InferLabelForElement( + const WebCore::HTMLFormControlElement& element); + + // The map of form elements. + DocumentFormElementMap form_elements_map_; + + DISALLOW_COPY_AND_ASSIGN(FormManager); +}; + +} // namespace android + +#endif // FormManagerAndroid_h diff --git a/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.cpp b/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.cpp new file mode 100644 index 0000000..598b9c4 --- /dev/null +++ b/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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 "MainThreadProxy.h" + +#include <wtf/MainThread.h> + +void MainThreadProxy::CallOnMainThread(CallOnMainThreadFunction f, void* c) +{ + callOnMainThread(f, c); +} diff --git a/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.h b/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.h new file mode 100644 index 0000000..d9310bb --- /dev/null +++ b/WebKit/android/WebCoreSupport/autofill/MainThreadProxy.h @@ -0,0 +1,37 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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. + */ + +#ifndef MAIN_THREAD_PROXY_H +#define MAIN_THREAD_PROXY_H + +typedef void CallOnMainThreadFunction(void*); + +class MainThreadProxy +{ +public: + static void CallOnMainThread(CallOnMainThreadFunction, void*); +}; + +#endif diff --git a/WebKit/android/WebCoreSupport/autofill/WebAutoFill.cpp b/WebKit/android/WebCoreSupport/autofill/WebAutoFill.cpp new file mode 100644 index 0000000..08d5e58 --- /dev/null +++ b/WebKit/android/WebCoreSupport/autofill/WebAutoFill.cpp @@ -0,0 +1,125 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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 "WebAutoFill.h" + +#if ENABLE(WEB_AUTOFILL) + +#include "AutoFillHostAndroid.h" +#include "Document.h" +#include "Frame.h" +#include "FormData.h" +#include "FormManagerAndroid.h" +#include "FrameLoader.h" +#include "HTMLFormControlElement.h" +#include "MainThreadProxy.h" +#include "Node.h" +#include "WebFrame.h" +#include "WebRequestContext.h" +#include "WebUrlLoaderClient.h" +#include "WebViewCore.h" + +namespace android +{ + +WebAutoFill::WebAutoFill() +{ + mFormManager = new FormManager(); + mQueryId = 1; + + AndroidURLRequestContextGetter::Get()->SetURLRequestContext(WebRequestContext::GetAndroidContext()); + AndroidURLRequestContextGetter::Get()->SetIOThread(WebUrlLoaderClient::ioThread()); + TabContents* tabContents = new TabContents(); + mAutoFillManager = new AutoFillManager(tabContents); + + // FIXME: For testing use a precanned profile. This should come from Java land! + mAutoFillProfile = new AutoFillProfile(); + mAutoFillProfile->SetInfo(AutoFillType(NAME_FULL), string16(ASCIIToUTF16("John Smith"))); + mAutoFillProfile->SetInfo(AutoFillType(EMAIL_ADDRESS), string16(ASCIIToUTF16("jsmith@gmail.com"))); + mAutoFillProfile->SetInfo(AutoFillType(ADDRESS_HOME_ZIP), string16(ASCIIToUTF16("AB12 3DE"))); + mAutoFillProfile->SetInfo(AutoFillType(PHONE_HOME_WHOLE_NUMBER), string16(ASCIIToUTF16("0123456789"))); + + std::vector<AutoFillProfile> profiles; + profiles.push_back(*mAutoFillProfile); + tabContents->profile()->GetPersonalDataManager()->SetProfiles(&profiles); + mAutoFillHost = new AutoFillHostAndroid(this); + tabContents->SetAutoFillHost(mAutoFillHost.get()); +} + +WebAutoFill::~WebAutoFill() +{ + mQueryMap.clear(); +} + +void WebAutoFill::searchDocument(WebCore::Document* document) +{ + // TODO: This method is called when the main frame finishes loading and + // scans the document for forms and tries to work out the type of the + // fields in those forms. It's currently synchronous and might be slow + // if the page has many or complex forms. Might want to make this an + // async method. + if (!mFormManager) + return; + + mQueryMap.clear(); + mQueryId = 1; + mAutoFillManager->Reset(); + mFormManager->Reset(); + mFormManager->ExtractForms(document); + mForms.clear(); + mFormManager->GetForms(FormManager::REQUIRE_AUTOCOMPLETE, &mForms); + mAutoFillManager->FormsSeen(mForms); +} + +void WebAutoFill::formFieldFocused(WebCore::HTMLFormControlElement* formFieldElement) +{ + // Get the FormField from the Node. + webkit_glue::FormField formField; + FormManager::HTMLFormControlElementToFormField(*formFieldElement, false, &formField); + formField.set_label(FormManager::LabelForElement(*formFieldElement)); + + webkit_glue::FormData* form = new webkit_glue::FormData; + mFormManager->FindFormWithFormControlElement(*formFieldElement, FormManager::REQUIRE_AUTOCOMPLETE, form); + mQueryMap[mQueryId] = form; + + mAutoFillManager->GetAutoFillSuggestions(mQueryId, false, formField); + mQueryId++; +} + +void WebAutoFill::fillFormFields(int queryId, const string16& value, const string16& label, int uniqueId) +{ + webkit_glue::FormData* form = mQueryMap[queryId]; + mAutoFillManager->FillAutoFillFormData(queryId, *form, value, label, uniqueId); +} + +void WebAutoFill::fillFormInPage(int queryId, const webkit_glue::FormData& form) +{ + mFormManager->FillForm(form); +} + +} + +#endif diff --git a/WebKit/android/WebCoreSupport/autofill/WebAutoFill.h b/WebKit/android/WebCoreSupport/autofill/WebAutoFill.h new file mode 100644 index 0000000..335f9b2 --- /dev/null +++ b/WebKit/android/WebCoreSupport/autofill/WebAutoFill.h @@ -0,0 +1,80 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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 THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT OWNER 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. + */ + +#ifndef WebAutoFill_h +#define WebAutoFill_h + +#if ENABLE(WEB_AUTOFILL) + +#include "ChromiumIncludes.h" + +#include <map> +#include <vector> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> + +class AutoFillManager; +class AutoFillProfile; +class AutoFillHost; + +namespace WebCore { +class Document; +class HTMLFormControlElement; +} + +namespace android +{ +class FormManager; +class WebViewCore; + +class WebAutoFill : public Noncopyable +{ +public: + WebAutoFill(); + virtual ~WebAutoFill(); + + void searchDocument(WebCore::Document*); + void formFieldFocused(WebCore::HTMLFormControlElement*); + void fillFormFields(int queryId, const string16& value, const string16& label, int uniqueId); + void fillFormInPage(int queryId, const webkit_glue::FormData& form); + +private: + OwnPtr<FormManager> mFormManager; + OwnPtr<AutoFillManager> mAutoFillManager; + OwnPtr<AutoFillProfile> mAutoFillProfile; + OwnPtr<AutoFillHost> mAutoFillHost; + + typedef std::vector<webkit_glue::FormData, std::allocator<webkit_glue::FormData> > FormList; + FormList mForms; + + typedef std::map<int, webkit_glue::FormData*> AutoFillQueryMap; + AutoFillQueryMap mQueryMap; + int mQueryId; +}; + +} + +#endif // ENABLE(WEB_AUTOFILL) +#endif // WebAutoFill_h diff --git a/WebKit/android/jni/WebCoreFrameBridge.cpp b/WebKit/android/jni/WebCoreFrameBridge.cpp index a717301..c1cb907 100644 --- a/WebKit/android/jni/WebCoreFrameBridge.cpp +++ b/WebKit/android/jni/WebCoreFrameBridge.cpp @@ -120,6 +120,10 @@ #include "WebArchiveAndroid.h" #endif +#if ENABLE(WEB_AUTOFILL) +#include "autofill/WebAutoFill.h" +#endif + using namespace JSC::Bindings; static String* gUploadFileLabel; @@ -639,6 +643,13 @@ WebFrame::didFinishLoad(WebCore::Frame* frame) (int)loadType, isMainFrame); checkException(env); env->DeleteLocalRef(urlStr); +#if ENABLE(WEB_AUTOFILL) + // TODO: Need to consider child frames. + if (isMainFrame) { + EditorClientAndroid* editorClient = static_cast<EditorClientAndroid*>(mPage->editorClient()); + editorClient->getAutoFill()->searchDocument(frame->document()); + } +#endif } void diff --git a/WebKit/android/jni/WebViewCore.cpp b/WebKit/android/jni/WebViewCore.cpp index e7026d7..1ae92d1 100644 --- a/WebKit/android/jni/WebViewCore.cpp +++ b/WebKit/android/jni/WebViewCore.cpp @@ -28,6 +28,7 @@ #include "config.h" #include "WebViewCore.h" +#include "AccessibilityObject.h" #include "BaseLayerAndroid.h" #include "CachedNode.h" #include "CachedRoot.h" @@ -43,6 +44,7 @@ #include "EditorClientAndroid.h" #include "EventHandler.h" #include "EventNames.h" +#include "ExceptionCode.h" #include "FocusController.h" #include "Font.h" #include "Frame.h" @@ -106,6 +108,7 @@ #include "WebFrameView.h" #include "WindowsKeyboardCodes.h" #include "android_graphics.h" +#include "markup.h" #include <JNIHelp.h> #include <JNIUtility.h> @@ -429,6 +432,7 @@ void WebViewCore::reset(bool fromConstructor) m_screenWidth = 0; m_screenHeight = 0; m_groupForVisitedLinks = NULL; + m_currentNodeDomNavigationAxis = 0; } static bool layoutIfNeededRecursive(WebCore::Frame* f) @@ -1881,50 +1885,360 @@ void WebViewCore::setSelection(int start, int end) } } -String WebViewCore::modifySelection(const String& alter, const String& direction, const String& granularity) +String WebViewCore::modifySelection(const int direction, const int axis) { DOMSelection* selection = m_mainFrame->domWindow()->getSelection(); + if (selection->rangeCount() > 1) + selection->removeAllRanges(); + switch (axis) { + case AXIS_CHARACTER: + case AXIS_WORD: + case AXIS_SENTENCE: + return modifySelectionTextNavigationAxis(selection, direction, axis); + case AXIS_HEADING: + case AXIS_SIBLING: + case AXIS_PARENT_FIRST_CHILD: + case AXIS_DOCUMENT: + return modifySelectionDomNavigationAxis(selection, direction, axis); + default: + LOGE("Invalid navigation axis: %d", axis); + return String(); + } +} - if (selection->rangeCount() == 0) { - Document* document = m_mainFrame->document(); - HTMLElement* body = document->body(); - ExceptionCode ec; - - PassRefPtr<Range> rangeRef = document->createRange(); - rangeRef->setStart(PassRefPtr<Node>(body), 0, ec); - if (ec) { - LOGE("Error setting range start. Error code: %d", ec); - return String(); +String WebViewCore::modifySelectionTextNavigationAxis(DOMSelection* selection, int direction, int axis) +{ + String directionString; + if (direction == DIRECTION_FORWARD) + directionString = "forward"; + else if (direction == DIRECTION_BACKWARD) + directionString = "backward"; + else { + LOGE("Invalid direction: %d", direction); + return String(); + } + String axisString; + if (axis == AXIS_CHARACTER) + axisString = "character"; + else if (axis == AXIS_WORD) + axisString = "word"; + else // axis == AXIS_SENTENCE + axisString = "sentence"; + + // TODO: Add support of IFrames. + HTMLElement* body = m_mainFrame->document()->body(); + + Node* focusNode = 0; + if (m_currentNodeDomNavigationAxis + && CacheBuilder::validNode(m_mainFrame, m_mainFrame, m_currentNodeDomNavigationAxis)) { + focusNode = m_currentNodeDomNavigationAxis; + m_currentNodeDomNavigationAxis = 0; + do { + focusNode = (direction == DIRECTION_FORWARD) ? + focusNode->traverseNextNode(body) : + focusNode->traversePreviousNode(body); + } while (focusNode && focusNode->isTextNode()); + } else + focusNode = (selection->focusNode()) ? selection->focusNode() : currentFocus(); + + Text* currentNode = 0; + if (!focusNode) { + // we have no selection so start from the body or its recursively last child + focusNode = (direction == DIRECTION_FORWARD) ? body : body->lastDescendant(); + if (focusNode->isTextNode()) + currentNode = static_cast<Text*>(focusNode); + else + currentNode = traverseNonEmptyNonWhitespaceTextNode(focusNode, body, direction); + if (!setSelection(selection, currentNode, direction)) + return String(); + } else if (focusNode->isElementNode()) { + // find a non-empty text node in the current direction + currentNode = traverseNonEmptyNonWhitespaceTextNode(focusNode, body, direction); + if (!setSelection(selection, currentNode, direction)) + return String(); + } else { + currentNode = static_cast<Text*>(focusNode); + if (direction == DIRECTION_FORWARD) { + // if end of non-whitespace text go to the next non-empty text node + int higherIndex = (selection->focusOffset() > selection->anchorOffset()) ? + selection->focusOffset() : selection->anchorOffset(); + String suffix = currentNode->data().substring(higherIndex, currentNode->length()); + if (suffix.isEmpty() || suffix.stripWhiteSpace().isEmpty()) { + currentNode = traverseNonEmptyNonWhitespaceTextNode(currentNode, body, direction); + if (!setSelection(selection, currentNode, direction)) + return String(); + } else { + ExceptionCode ec = 0; + selection->collapseToEnd(ec); + if (ec) + LOGE("Error while collapsing selection. Error code: %d", ec); + } + } else { + // if beginning of non-whitespace text go to the previous non-empty text node + int lowerIndex = (selection->focusOffset() > selection->anchorOffset()) ? + selection->anchorOffset() : selection->focusOffset(); + String prefix = currentNode->data().substring(0, lowerIndex); + if (prefix.isEmpty() || prefix.stripWhiteSpace().isEmpty()) { + currentNode = traverseNonEmptyNonWhitespaceTextNode(currentNode, body, direction); + if (!setSelection(selection, currentNode, direction)) + return String(); + } else { + ExceptionCode ec = 0; + selection->collapseToStart(ec); + if (ec) + LOGE("Error while collapsing selection. Error code: %d", ec); + } } + } - rangeRef->setEnd(PassRefPtr<Node>(body), 0, ec); - if (ec) { - LOGE("Error setting range end. Error code: %d", ec); - return String(); + // extend the selection - loop as an insurance it does not get stuck + currentNode = static_cast<Text*>(selection->focusNode()); + int focusOffset = selection->focusOffset(); + while (true) { + selection->modify("extend", directionString, axisString); + if (selection->focusNode() != currentNode || selection->focusOffset() != focusOffset) + break; + currentNode = traverseNonEmptyNonWhitespaceTextNode(currentNode, body, direction); + focusOffset = (direction == DIRECTION_FORWARD) ? 0 : currentNode->data().length(); + // setSelection returns false if currentNode is 0 => the loop always terminates + if (!setSelection(selection, currentNode, direction)) + return String(); + } + + if (direction == DIRECTION_FORWARD) { + // enforce the anchor node is a text node + if (selection->anchorNode()->isElementNode()) { + if (!setSelection(selection, selection->focusNode(), selection->focusNode(), 0, + selection->focusOffset())) + return String(); + } + // enforce the focus node is a text node + if (selection->focusNode()->isElementNode()) { + int endOffset = static_cast<Text*>(selection->anchorNode())->length(); // cast is safe + if (!setSelection(selection, selection->anchorNode(), selection->anchorNode(), + selection->anchorOffset(), endOffset)) + return String(); + } + } else { + // enforce the focus node is a text node + if (selection->focusNode()->isElementNode()) { + if (!setSelection(selection, selection->anchorNode(), selection->anchorNode(), 0, + selection->anchorOffset())) + return String(); + } + // enforce the anchor node is a text node + if (selection->anchorNode()->isElementNode()) { + int endOffset = static_cast<Text*>(selection->focusNode())->length(); // cast is safe + if (!setSelection(selection, selection->focusNode(), selection->focusNode(), + selection->focusOffset(), endOffset)) + return String(); } - - selection->addRange(rangeRef.get()); } - ExceptionCode ec; - if (equalIgnoringCase(direction, "forward")) { - selection->collapseToEnd(ec); - } else if (equalIgnoringCase(direction, "backward")) { - selection->collapseToStart(ec); + tryFocusInlineSelectionElement(selection); + // TODO (svetoslavganov): Draw the selected text in the WebView - a-la-Android + String markup = formatMarkup(selection).stripWhiteSpace(); + LOGD("Selection markup: %s", markup.utf8().data()); + return markup; +} + +bool WebViewCore::setSelection(DOMSelection* selection, Text* textNode, int direction) +{ + if (!textNode) + return false; + int offset = (direction == DIRECTION_FORWARD) ? 0 : textNode->length(); + if (!setSelection(selection, textNode, textNode, offset, offset)) + return false; + return true; +} + +bool WebViewCore::setSelection(DOMSelection* selection, Node* startNode, Node* endNode, int startOffset, int endOffset) +{ + if (!selection || (!startNode && !endNode)) + return false; + ExceptionCode ec = 0; + PassRefPtr<Range> rangeRef = selection->getRangeAt(0, ec); + if (ec) { + ec = 0; + rangeRef = m_mainFrame->document()->createRange(); + } + if (startNode) + rangeRef->setStart(PassRefPtr<Node>(startNode), startOffset, ec); + if (ec) + return false; + if (endNode) + rangeRef->setEnd(PassRefPtr<Node>(endNode), endOffset, ec); + if (ec) + return false; + selection->removeAllRanges(); + selection->addRange(rangeRef.get()); + return true; +} + +Text* WebViewCore::traverseNonEmptyNonWhitespaceTextNode(Node* fromNode, Node* toNode, int direction) +{ + Node* currentNode = fromNode; + do { + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->traverseNextNode(toNode); + else + currentNode = currentNode->traversePreviousNode(toNode); + } while (currentNode && (!currentNode->isTextNode() + || isEmptyOrOnlyWhitespaceTextNode(currentNode))); + return static_cast<Text*>(currentNode); +} + +bool WebViewCore::isEmptyOrOnlyWhitespaceTextNode(Node* node) +{ + return (node->isTextNode() + && (static_cast<Text*>(node)->length() == 0 + || static_cast<Text*>(node)->containsOnlyWhitespace())); +} + +String WebViewCore::modifySelectionDomNavigationAxis(DOMSelection* selection, int direction, int axis) +{ + // TODO: Add support of IFrames. + HTMLElement* body = m_mainFrame->document()->body(); + if (!m_currentNodeDomNavigationAxis && selection->focusNode()) { + m_currentNodeDomNavigationAxis = selection->focusNode(); + selection->empty(); + if (m_currentNodeDomNavigationAxis->isTextNode()) + m_currentNodeDomNavigationAxis = m_currentNodeDomNavigationAxis->parentNode(); + } + if (!m_currentNodeDomNavigationAxis) + m_currentNodeDomNavigationAxis = currentFocus(); + if (!m_currentNodeDomNavigationAxis + || !CacheBuilder::validNode(m_mainFrame, m_mainFrame, m_currentNodeDomNavigationAxis)) + m_currentNodeDomNavigationAxis = body; + Node* currentNode = m_currentNodeDomNavigationAxis; + if (axis == AXIS_HEADING) { + if (currentNode == body && direction == DIRECTION_BACKWARD) + currentNode = currentNode->lastDescendant(); + do { + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->traverseNextNode(body); + else + currentNode = currentNode->traversePreviousNode(body); + } while (currentNode && (currentNode->isTextNode() || !isVisible(currentNode) + || !isHeading(currentNode))); + } else if (axis == AXIS_PARENT_FIRST_CHILD) { + if (direction == DIRECTION_FORWARD) { + currentNode = currentNode->firstChild(); + while (currentNode && (currentNode->isTextNode() || !isVisible(currentNode))) + currentNode = currentNode->nextSibling(); + } else { + do { + if (currentNode == body) + return String(); + currentNode = currentNode->parentNode(); + } while (currentNode && (currentNode->isTextNode() || !isVisible(currentNode))); + } + } else if (axis == AXIS_SIBLING) { + do { + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->nextSibling(); + else { + if (currentNode == body) + return String(); + currentNode = currentNode->previousSibling(); + } + } while (currentNode && (currentNode->isTextNode() || !isVisible(currentNode))); + } else if (axis == AXIS_DOCUMENT) { + currentNode = body; + if (direction == DIRECTION_FORWARD) + currentNode = currentNode->lastDescendant(); } else { - LOGE("Invalid direction: %s", direction.utf8().data()); + LOGE("Invalid axis: %d", axis); return String(); } + if (currentNode) { + m_currentNodeDomNavigationAxis = currentNode; + focusIfFocusableAndNotTextInput(selection, currentNode); + // TODO (svetoslavganov): Draw the selected text in the WebView - a-la-Android + String selectionString = createMarkup(currentNode); + LOGD("Selection markup: %s", selectionString.utf8().data()); + return selectionString; + } + return String(); +} + +bool WebViewCore::isHeading(Node* node) +{ + if (node->hasTagName(WebCore::HTMLNames::h1Tag) + || node->hasTagName(WebCore::HTMLNames::h2Tag) + || node->hasTagName(WebCore::HTMLNames::h3Tag) + || node->hasTagName(WebCore::HTMLNames::h4Tag) + || node->hasTagName(WebCore::HTMLNames::h5Tag) + || node->hasTagName(WebCore::HTMLNames::h6Tag)) { + return true; + } + + if (node->isElementNode()) { + Element* element = static_cast<Element*>(node); + String roleAttribute = element->getAttribute(WebCore::HTMLNames::roleAttr).string(); + if (equalIgnoringCase(roleAttribute, "heading")) + return true; + } + + return false; +} + +bool WebViewCore::isVisible(Node* node) +{ + if (!node->isStyledElement()) + return false; + RenderStyle* style = node->computedStyle(); + return (style->display() != NONE && style->visibility() != HIDDEN); +} - // NOTE: The selection of WebKit misbehaves and I need to add some - // voodoo here to force it behave well. Rachel did something similar - // in JS and I want to make sure it is optimal before adding it here. +String WebViewCore::formatMarkup(DOMSelection* selection) +{ + ExceptionCode ec = 0; + PassRefPtr<Range> rangeRef = selection->getRangeAt(0, ec); + if (ec) { + LOGE("Error accessing the first selection range. Error code: %d", ec); + return String(); + } + // TODO: This breaks in certain cases - WebKit bug. Figure out and work around it + String markup = createMarkup(rangeRef.get()); + int fromIdx = markup.find("<span class=\"Apple-style-span\""); + while (fromIdx > -1) { + unsigned toIdx = markup.find(">"); + markup = markup.replace(fromIdx, toIdx - fromIdx + 1, ""); + markup = markup.replace("</span>", ""); + fromIdx = markup.find("<span class=\"Apple-style-span\""); + } + return markup; +} - selection->modify(alter, direction, granularity); - String selection_string = selection->toString(); - LOGD("Selection string: %s", selection_string.utf8().data()); +void WebViewCore::tryFocusInlineSelectionElement(DOMSelection* selection) +{ + Node* currentNode = selection->anchorNode(); + Node* endNode = selection->focusNode(); + while (currentNode) { + if (focusIfFocusableAndNotTextInput(selection, currentNode)) + return; + currentNode = currentNode->traverseNextNode(endNode); + } +} - return selection_string; +bool WebViewCore::focusIfFocusableAndNotTextInput(DOMSelection* selection, Node* node) +{ + // TODO (svetoslavganov): Synchronize Android and WebKit focus + if (node->isFocusable()) { + WebCore::RenderObject* renderer = node->renderer(); + if (!renderer || (!renderer->isTextField() && !renderer->isTextArea())) { + // restore the selection after focus workaround for + // the FIXME in Element.cpp#updateFocusAppearance + ExceptionCode ec = 0; + PassRefPtr<Range> rangeRef = selection->getRangeAt(0, ec); + moveFocus(m_mainFrame, node); + if (rangeRef) + selection->addRange(rangeRef.get()); + return true; + } + } + return false; } void WebViewCore::deleteSelection(int start, int end, int textGeneration) @@ -3030,21 +3344,14 @@ static void SetSelection(JNIEnv *env, jobject obj, jint start, jint end) viewImpl->setSelection(start, end); } -static jstring ModifySelection(JNIEnv *env, jobject obj, jstring alter, jstring direction, jstring granularity) +static jstring ModifySelection(JNIEnv *env, jobject obj, jint direction, jint granularity) { #ifdef ANDROID_INSTRUMENT TimeCounterAuto counter(TimeCounter::WebViewCoreTimeCounter); #endif - String alterString = to_string(env, alter); - String directionString = to_string(env, direction); - String granularityString = to_string(env, granularity); - WebViewCore* viewImpl = GET_NATIVE_VIEW(env, obj); - String selection_string = viewImpl->modifySelection(alterString, - directionString, - granularityString); - - return WebCoreStringToJString(env, selection_string); + String selectionString = viewImpl->modifySelection(direction, granularity); + return WebCoreStringToJString(env, selectionString); } static void ReplaceTextfieldText(JNIEnv *env, jobject obj, @@ -3585,7 +3892,7 @@ static JNINativeMethod gJavaWebViewCoreMethods[] = { (void*) SetGlobalBounds }, { "nativeSetSelection", "(II)V", (void*) SetSelection } , - { "nativeModifySelection", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + { "nativeModifySelection", "(II)Ljava/lang/String;", (void*) ModifySelection }, { "nativeDeleteSelection", "(III)V", (void*) DeleteSelection } , diff --git a/WebKit/android/jni/WebViewCore.h b/WebKit/android/jni/WebViewCore.h index 176e1bf..00054f3 100644 --- a/WebKit/android/jni/WebViewCore.h +++ b/WebKit/android/jni/WebViewCore.h @@ -31,6 +31,7 @@ #include "CacheBuilder.h" #include "CachedHistory.h" #include "DeviceOrientationManager.h" +#include "DOMSelection.h" #include "PictureSet.h" #include "PlatformGraphicsContext.h" #include "SkColor.h" @@ -74,6 +75,21 @@ class SkIRect; namespace android { + enum Direction { + DIRECTION_BACKWARD = 0, + DIRECTION_FORWARD = 1 + }; + + enum NavigationAxis { + AXIS_CHARACTER = 0, + AXIS_WORD = 1, + AXIS_SENTENCE = 2, + AXIS_HEADING = 3, + AXIS_SIBLING = 4, + AXIS_PARENT_FIRST_CHILD = 5, + AXIS_DOCUMENT = 6 + }; + class CachedFrame; class CachedNode; class CachedRoot; @@ -330,13 +346,14 @@ namespace android { /** * Modifies the current selection. * - * alter - Specifies how to alter the selection. * direction - The direction in which to alter the selection. * granularity - The granularity of the selection modification. * - * returns - The selection as string. + * returns - The selected HTML as a string. This is not a well formed + * HTML, rather the selection annotated with the tags of all + * intermediary elements it crosses. */ - String modifySelection(const String& alter, const String& direction, const String& granularity); + String modifySelection(const int direction, const int granularity); /** * In the currently focused textfield, replace the characters from oldStart to oldEnd @@ -568,6 +585,19 @@ namespace android { void sendNotifyProgressFinished(); bool handleMouseClick(WebCore::Frame* framePtr, WebCore::Node* nodePtr); WebCore::HTMLAnchorElement* retrieveAnchorElement(WebCore::Frame* frame, WebCore::Node* node); + // below are members responsible for accessibility support + String modifySelectionTextNavigationAxis(DOMSelection* selection, int direction, int granularity); + String modifySelectionDomNavigationAxis(DOMSelection* selection, int direction, int granularity); + Text* traverseNonEmptyNonWhitespaceTextNode(Node* fromNode, Node* toNode ,int direction); + bool isVisible(Node* node); + bool isHeading(Node* node); + bool isEmptyOrOnlyWhitespaceTextNode(Node* node); + String formatMarkup(DOMSelection* selection); + void tryFocusInlineSelectionElement(DOMSelection* selection); + bool focusIfFocusableAndNotTextInput(DOMSelection* selection, Node* node); + bool setSelection(DOMSelection* selection, Text* textNode, int direction); + bool setSelection(DOMSelection* selection, Node* startNode, Node* endNode, int startOffset, int endOffset); + Node* m_currentNodeDomNavigationAxis; #if ENABLE(TOUCH_EVENTS) bool m_forwardingTouchEvents; |