Add SPI to retrieve the set of text inputs in a given rect, and later focus one
authortimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 11 Mar 2019 03:32:50 +0000 (03:32 +0000)
committertimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 11 Mar 2019 03:32:50 +0000 (03:32 +0000)
https://bugs.webkit.org/show_bug.cgi?id=195499

Reviewed by Darin Adler.

Source/WebCore:

New API tests: WebKit.RequestTextInputContext and WebKit.FocusTextInputContext

* WebCore.xcodeproj/project.pbxproj:
* dom/Document.cpp:
(WebCore::Document::identifierForElement):
(WebCore::Document::elementWithIdentifier):
(WebCore::Document::identifiedElementWasRemovedFromDocument):
* dom/Document.h:
Add a mechanism where Document will vend an ObjectIdentifier for a given
element, and can (if possible) retrieve that element later.

* dom/Element.cpp:
(WebCore::Element::removedFromAncestor):
If an Element has an identifier created for it, inform Document to remove
it from the identifier map when the element is detached.

(WebCore::Element::createElementIdentifier):
* dom/Element.h:
* dom/ElementIdentifier.h: Added.
* dom/ElementRareData.cpp:
* dom/ElementRareData.h:
(WebCore::ElementRareData::hasElementIdentifier const):
(WebCore::ElementRareData::setHasElementIdentifier):
(WebCore::ElementRareData::ElementRareData):
Store a bit indicating if the Element has had a identifier created for it,
so that we can avoid a hash lookup on every Element removal.

* dom/Node.h:
* html/HTMLTextFormControlElement.h:

Source/WebKit:

* Scripts/webkit/messages.py:
* Shared/TextInputContext.cpp: Added.
(IPC::ArgumentCoder<WebKit::TextInputContext>::encode):
(IPC::ArgumentCoder<WebKit::TextInputContext>::decode):
* Shared/TextInputContext.h: Added.
(WebKit::TextInputContext::operator== const):
Add TextInputContext, which represents a minimal set of information
about a text field.

* Sources.txt:
* SourcesCocoa.txt:
* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _requestTextInputContextsInRect:completionHandler:]):
(-[WKWebView _focusTextInputContext:completionHandler:]):
* UIProcess/API/Cocoa/WKWebViewPrivate.h:
Add SPI that allows clients to asynchronously request text input
contexts for a given rect, and later focus a given context.

* UIProcess/API/Cocoa/_WKTextInputContext.h: Added.
* UIProcess/API/Cocoa/_WKTextInputContext.mm: Added.
(-[_WKTextInputContext _initWithTextInputContext:]):
(-[_WKTextInputContext boundingRect]):
(-[_WKTextInputContext _textInputContext]):
(-[_WKTextInputContext isEqual:]):
(-[_WKTextInputContext hash]):
(-[_WKTextInputContext copyWithZone:]):
* UIProcess/API/Cocoa/_WKTextInputContextInternal.h: Added.
Add an SPI object that exposes a read-only window on a TextInputContext to clients.

* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::textInputContextsInRect):
(WebKit::WebPageProxy::focusTextInputContext):
* UIProcess/WebPageProxy.h:
Plumbing from WKWebView<->WebPage.

* WebKit.xcodeproj/project.pbxproj:
* WebProcess/WebPage/WebPage.cpp:
(WebKit::elementRectInWindowCoordinates):
(WebKit::isEditableTextInputElement):
(WebKit::WebPage::textInputContextsInRect):
Search the DOM for text input contexts: <input type='text'> (or other
form fields that fall back on text field behavior), <textarea>, and
contenteditable roots. Store the WebPage, Document, and Element identifiers
so that we can find the element again later.

(WebKit::WebPage::focusTextInputContext):
Find the element for a given (web page, document, element) triple and focus it,
if it's still available.

* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm: Added.
(-[WKWebView synchronouslyRequestTextInputContextsInRect:]):
(-[WKWebView synchronouslyFocusTextInputContext:]):
(applyStyle):
(applyIframe):
(TEST):
Add some tests for this SPI.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@242696 268f45cc-cd09-0410-ab3c-d52691b4dbfc

31 files changed:
Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/dom/Element.cpp
Source/WebCore/dom/Element.h
Source/WebCore/dom/ElementIdentifier.h [new file with mode: 0644]
Source/WebCore/dom/ElementRareData.cpp
Source/WebCore/dom/ElementRareData.h
Source/WebCore/dom/Node.h
Source/WebCore/html/HTMLTextFormControlElement.h
Source/WebKit/ChangeLog
Source/WebKit/Scripts/webkit/messages.py
Source/WebKit/Shared/TextInputContext.cpp [new file with mode: 0644]
Source/WebKit/Shared/TextInputContext.h [new file with mode: 0644]
Source/WebKit/Sources.txt
Source/WebKit/SourcesCocoa.txt
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit/UIProcess/API/Cocoa/_WKTextInputContext.h [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKTextInputContext.mm [new file with mode: 0644]
Source/WebKit/UIProcess/API/Cocoa/_WKTextInputContextInternal.h [new file with mode: 0644]
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/WebKit.xcodeproj/project.pbxproj
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm [new file with mode: 0644]

index fa44d20..f61f393 100644 (file)
@@ -1,3 +1,40 @@
+2019-03-10  Tim Horton  <timothy_horton@apple.com>
+
+        Add SPI to retrieve the set of text inputs in a given rect, and later focus one
+        https://bugs.webkit.org/show_bug.cgi?id=195499
+
+        Reviewed by Darin Adler.
+
+        New API tests: WebKit.RequestTextInputContext and WebKit.FocusTextInputContext
+
+        * WebCore.xcodeproj/project.pbxproj:
+        * dom/Document.cpp:
+        (WebCore::Document::identifierForElement):
+        (WebCore::Document::elementWithIdentifier):
+        (WebCore::Document::identifiedElementWasRemovedFromDocument):
+        * dom/Document.h:
+        Add a mechanism where Document will vend an ObjectIdentifier for a given
+        element, and can (if possible) retrieve that element later.
+
+        * dom/Element.cpp:
+        (WebCore::Element::removedFromAncestor):
+        If an Element has an identifier created for it, inform Document to remove
+        it from the identifier map when the element is detached.
+
+        (WebCore::Element::createElementIdentifier):
+        * dom/Element.h:
+        * dom/ElementIdentifier.h: Added.
+        * dom/ElementRareData.cpp:
+        * dom/ElementRareData.h:
+        (WebCore::ElementRareData::hasElementIdentifier const):
+        (WebCore::ElementRareData::setHasElementIdentifier):
+        (WebCore::ElementRareData::ElementRareData):
+        Store a bit indicating if the Element has had a identifier created for it,
+        so that we can avoid a hash lookup on every Element removal.
+
+        * dom/Node.h:
+        * html/HTMLTextFormControlElement.h:
+
 2019-03-10  Zalan Bujtas  <zalan@apple.com>
 
         [ContentChangeObserver] Fix failing test cases
index 8d7d739..582c0e8 100644 (file)
                2D29ECC6192ECC8300984B78 /* DisplayRefreshMonitorClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D29ECC2192ECC8300984B78 /* DisplayRefreshMonitorClient.h */; settings = {ATTRIBUTES = (Private, ); }; };
                2D29ECC8192ECC8300984B78 /* DisplayRefreshMonitorManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D29ECC4192ECC8300984B78 /* DisplayRefreshMonitorManager.h */; settings = {ATTRIBUTES = (Private, ); }; };
                2D29ECCA192F1F1D00984B78 /* DisplayRefreshMonitorIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D29ECC9192F1F1D00984B78 /* DisplayRefreshMonitorIOS.h */; };
+               2D2BEB312234A335005544CA /* ElementIdentifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2BEB2F2234A334005544CA /* ElementIdentifier.h */; settings = {ATTRIBUTES = (Private, ); }; };
                2D2E34AC21A4E192004598B5 /* EditableImageReference.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D2E34A921A4E191004598B5 /* EditableImageReference.h */; };
                2D3A0E3613A7D76100E85AF0 /* SVGParsingError.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D3A0E3513A7D76100E85AF0 /* SVGParsingError.h */; settings = {ATTRIBUTES = (Private, ); }; };
                2D3EF4481917915C00034184 /* WebActionDisablingCALayerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D3EF4441917915C00034184 /* WebActionDisablingCALayerDelegate.h */; settings = {ATTRIBUTES = (Private, ); }; };
                2D29ECC3192ECC8300984B78 /* DisplayRefreshMonitorManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DisplayRefreshMonitorManager.cpp; sourceTree = "<group>"; };
                2D29ECC4192ECC8300984B78 /* DisplayRefreshMonitorManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DisplayRefreshMonitorManager.h; sourceTree = "<group>"; };
                2D29ECC9192F1F1D00984B78 /* DisplayRefreshMonitorIOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DisplayRefreshMonitorIOS.h; sourceTree = "<group>"; };
+               2D2BEB2F2234A334005544CA /* ElementIdentifier.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ElementIdentifier.h; sourceTree = "<group>"; };
                2D2E34A921A4E191004598B5 /* EditableImageReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditableImageReference.h; sourceTree = "<group>"; };
                2D2E34AB21A4E192004598B5 /* EditableImageReference.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EditableImageReference.cpp; sourceTree = "<group>"; };
                2D2FC0541460CD6F00263633 /* CrossfadeGeneratedImage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CrossfadeGeneratedImage.cpp; sourceTree = "<group>"; };
                                B5B7A16F17C1080600E4AA0A /* ElementData.cpp */,
                                B5B7A16E17C1048000E4AA0A /* ElementData.h */,
                                ADE11F4A18D8311B0078983B /* ElementDescendantIterator.h */,
+                               2D2BEB2F2234A334005544CA /* ElementIdentifier.h */,
                                E4AE7C1517D1BB950009FB31 /* ElementIterator.h */,
                                E401C27417CE53EC00C41A35 /* ElementIteratorAssertions.h */,
                                4FAB48641643A66D00F70C07 /* ElementRareData.cpp */,
                                E46A2B1E17CA76B1000DBCD8 /* ElementChildIterator.h in Headers */,
                                B5B7A17117C10AC000E4AA0A /* ElementData.h in Headers */,
                                93D437A11D57B3F400AB85EA /* ElementDescendantIterator.h in Headers */,
+                               2D2BEB312234A335005544CA /* ElementIdentifier.h in Headers */,
                                E4AE7C1617D1BB950009FB31 /* ElementIterator.h in Headers */,
                                E401C27517CE53EC00C41A35 /* ElementIteratorAssertions.h in Headers */,
                                63F5D4F70E8C4B7100C0BD04 /* ElementRareData.h in Headers */,
index a1c0ad3..0a1a1ec 100644 (file)
@@ -8612,6 +8612,30 @@ void Document::frameWasDisconnectedFromOwner()
     detachFromFrame();
 }
 
+ElementIdentifier Document::identifierForElement(Element& element)
+{
+    ASSERT(&element.document() == this);
+    auto result = m_identifiedElementsMap.ensure(&element, [&] {
+        return element.createElementIdentifier();
+    });
+    return result.iterator->value;
+}
+
+Element* Document::searchForElementByIdentifier(const ElementIdentifier& identifier)
+{
+    for (auto it = m_identifiedElementsMap.begin(); it != m_identifiedElementsMap.end(); ++it) {
+        if (it->value == identifier)
+            return it->key;
+    }
+
+    return nullptr;
+}
+
+void Document::identifiedElementWasRemovedFromDocument(Element& element)
+{
+    m_identifiedElementsMap.remove(&element);
+}
+
 #if ENABLE(DEVICE_ORIENTATION)
 
 DeviceOrientationAndMotionAccessController& Document::deviceOrientationAndMotionAccessController()
index 2b99d44..73fe8c8 100644 (file)
@@ -34,6 +34,7 @@
 #include "DocumentEventQueue.h"
 #include "DocumentIdentifier.h"
 #include "DocumentTiming.h"
+#include "ElementIdentifier.h"
 #include "FocusDirection.h"
 #include "FontSelectorClient.h"
 #include "FrameDestructionObserver.h"
@@ -388,6 +389,10 @@ public:
     WEBCORE_EXPORT static DocumentsMap::ValuesIteratorRange allDocuments();
     WEBCORE_EXPORT static DocumentsMap& allDocumentsMap();
 
+    WEBCORE_EXPORT ElementIdentifier identifierForElement(Element&);
+    WEBCORE_EXPORT Element* searchForElementByIdentifier(const ElementIdentifier&);
+    void identifiedElementWasRemovedFromDocument(Element&);
+
     MediaQueryMatcher& mediaQueryMatcher();
 
     using ContainerNode::ref;
@@ -2083,6 +2088,8 @@ private:
 #if PLATFORM(IOS_FAMILY)
     std::unique_ptr<ContentChangeObserver> m_contentChangeObserver;
 #endif
+
+    HashMap<Element*, ElementIdentifier> m_identifiedElementsMap;
 };
 
 Element* eventTargetElementForDocument(Document*);
index b0ea5cb..a99644a 100644 (file)
@@ -2049,6 +2049,11 @@ void Element::removedFromAncestor(RemovalType removalType, ContainerNode& oldPar
     if (frame && frame->page())
         frame->page()->removeLatchingStateForTarget(*this);
 #endif
+
+    if (hasRareData() && elementRareData()->hasElementIdentifier()) {
+        document().identifiedElementWasRemovedFromDocument(*this);
+        elementRareData()->setHasElementIdentifier(false);
+    }
 }
 
 ShadowRoot* Element::shadowRoot() const
@@ -4157,6 +4162,15 @@ Vector<RefPtr<WebAnimation>> Element::getAnimations()
     return animations;
 }
 
+ElementIdentifier Element::createElementIdentifier()
+{
+    auto& rareData = ensureElementRareData();
+    ASSERT(!rareData.hasElementIdentifier());
+
+    rareData.setHasElementIdentifier(true);
+    return ElementIdentifier::generate();
+}
+
 #if ENABLE(CSS_TYPED_OM)
 StylePropertyMap* Element::attributeStyleMap()
 {
index a9928f7..e85e0e8 100644 (file)
@@ -592,6 +592,8 @@ public:
     ExceptionOr<Ref<WebAnimation>> animate(JSC::ExecState&, JSC::Strong<JSC::JSObject>&&, Optional<Variant<double, KeyframeAnimationOptions>>&&);
     Vector<RefPtr<WebAnimation>> getAnimations();
 
+    ElementIdentifier createElementIdentifier();
+
 #if ENABLE(POINTER_EVENTS)
     OptionSet<TouchAction> computedTouchActions() const;
 #if ENABLE(ACCELERATED_OVERFLOW_SCROLLING)
diff --git a/Source/WebCore/dom/ElementIdentifier.h b/Source/WebCore/dom/ElementIdentifier.h
new file mode 100644 (file)
index 0000000..2ed3782
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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.
+ */
+
+#pragma once
+
+#include <wtf/ObjectIdentifier.h>
+
+namespace WebCore {
+
+enum ElementIdentifierType { };
+using ElementIdentifier = ObjectIdentifier<ElementIdentifierType>;
+
+}
index 4dd60eb..92657ff 100644 (file)
@@ -37,9 +37,9 @@ struct SameSizeAsElementRareData : NodeRareData {
     int tabIndex;
     unsigned short childIndex;
 #if ENABLE(FULLSCREEN_API)
-    unsigned bitfields : 11;
+    unsigned bitfields : 12;
 #else
-    unsigned bitfields : 10;
+    unsigned bitfields : 11;
 #endif
     LayoutSize sizeForResizing;
     IntPoint savedLayerScrollPosition;
index fe80e33..b6fd640 100644 (file)
@@ -123,6 +123,9 @@ public:
     bool hasCSSAnimation() const { return m_hasCSSAnimation; }
     void setHasCSSAnimation(bool value) { m_hasCSSAnimation = value; }
 
+    bool hasElementIdentifier() const { return m_hasElementIdentifier; }
+    void setHasElementIdentifier(bool value) { m_hasElementIdentifier = value; }
+
 #if ENABLE(INTERSECTION_OBSERVER)
     IntersectionObserverData* intersectionObserverData() { return m_intersectionObserverData.get(); }
     void setIntersectionObserverData(std::unique_ptr<IntersectionObserverData>&& data) { m_intersectionObserverData = WTFMove(data); }
@@ -180,6 +183,7 @@ private:
 #endif
     unsigned m_hasPendingResources : 1;
     unsigned m_hasCSSAnimation : 1;
+    unsigned m_hasElementIdentifier : 1;
     unsigned m_childrenAffectedByHover : 1;
     unsigned m_childrenAffectedByDrag : 1;
     // Bits for dynamic child matching.
@@ -228,6 +232,7 @@ inline ElementRareData::ElementRareData(RenderElement* renderer)
 #endif
     , m_hasPendingResources(false)
     , m_hasCSSAnimation(false)
+    , m_hasElementIdentifier(false)
     , m_childrenAffectedByHover(false)
     , m_childrenAffectedByDrag(false)
     , m_childrenAffectedByLastChildRules(false)
index a63c194..2369ea2 100644 (file)
@@ -283,7 +283,7 @@ public:
 
     virtual bool canContainRangeEndPoint() const { return false; }
 
-    bool isRootEditableElement() const;
+    WEBCORE_EXPORT bool isRootEditableElement() const;
     WEBCORE_EXPORT Element* rootEditableElement() const;
 
     // Called by the parser when this element's close tag is reached,
index b1e35cb..cf8c744 100644 (file)
@@ -98,6 +98,8 @@ public:
     WEBCORE_EXPORT void showPlaceholderIfNecessary();
 #endif
 
+    WEBCORE_EXPORT virtual bool isInnerTextElementEditable() const;
+
 protected:
     HTMLTextFormControlElement(const QualifiedName&, Document&, HTMLFormElement*);
     bool isPlaceholderEmpty() const;
@@ -107,7 +109,7 @@ protected:
 
     void disabledStateChanged() override;
     void readOnlyStateChanged() override;
-    virtual bool isInnerTextElementEditable() const;
+
     void updateInnerTextElementEditability();
 
     void cacheSelection(int start, int end, TextFieldSelectionDirection direction)
index a09154b..4823e42 100644 (file)
@@ -1,5 +1,64 @@
 2019-03-10  Tim Horton  <timothy_horton@apple.com>
 
+        Add SPI to retrieve the set of text inputs in a given rect, and later focus one
+        https://bugs.webkit.org/show_bug.cgi?id=195499
+
+        Reviewed by Darin Adler.
+
+        * Scripts/webkit/messages.py:
+        * Shared/TextInputContext.cpp: Added.
+        (IPC::ArgumentCoder<WebKit::TextInputContext>::encode):
+        (IPC::ArgumentCoder<WebKit::TextInputContext>::decode):
+        * Shared/TextInputContext.h: Added.
+        (WebKit::TextInputContext::operator== const):
+        Add TextInputContext, which represents a minimal set of information
+        about a text field.
+
+        * Sources.txt:
+        * SourcesCocoa.txt:
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _requestTextInputContextsInRect:completionHandler:]):
+        (-[WKWebView _focusTextInputContext:completionHandler:]):
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        Add SPI that allows clients to asynchronously request text input
+        contexts for a given rect, and later focus a given context.
+
+        * UIProcess/API/Cocoa/_WKTextInputContext.h: Added.
+        * UIProcess/API/Cocoa/_WKTextInputContext.mm: Added.
+        (-[_WKTextInputContext _initWithTextInputContext:]):
+        (-[_WKTextInputContext boundingRect]):
+        (-[_WKTextInputContext _textInputContext]):
+        (-[_WKTextInputContext isEqual:]):
+        (-[_WKTextInputContext hash]):
+        (-[_WKTextInputContext copyWithZone:]):
+        * UIProcess/API/Cocoa/_WKTextInputContextInternal.h: Added.
+        Add an SPI object that exposes a read-only window on a TextInputContext to clients.
+
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::textInputContextsInRect):
+        (WebKit::WebPageProxy::focusTextInputContext):
+        * UIProcess/WebPageProxy.h:
+        Plumbing from WKWebView<->WebPage.
+
+        * WebKit.xcodeproj/project.pbxproj:
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::elementRectInWindowCoordinates):
+        (WebKit::isEditableTextInputElement):
+        (WebKit::WebPage::textInputContextsInRect):
+        Search the DOM for text input contexts: <input type='text'> (or other
+        form fields that fall back on text field behavior), <textarea>, and
+        contenteditable roots. Store the WebPage, Document, and Element identifiers
+        so that we can find the element again later.
+
+        (WebKit::WebPage::focusTextInputContext):
+        Find the element for a given (web page, document, element) triple and focus it,
+        if it's still available.
+
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+
+2019-03-10  Tim Horton  <timothy_horton@apple.com>
+
         iOS: Using ⌥ to scroll horizontally is no different than arrow key
         https://bugs.webkit.org/show_bug.cgi?id=195268
         <rdar://problem/48326682>
index bfab03a..8def845 100644 (file)
@@ -453,6 +453,7 @@ def headers_for_type(type):
         'WebKit::WebMouseEvent': ['"WebEvent.h"'],
         'WebKit::WebTouchEvent': ['"WebEvent.h"'],
         'WebKit::WebWheelEvent': ['"WebEvent.h"'],
+        'struct WebKit::TextInputContext': ['"TextInputContext.h"'],
         'struct WebKit::WebUserScriptData': ['"WebUserContentControllerDataTypes.h"'],
         'struct WebKit::WebUserStyleSheetData': ['"WebUserContentControllerDataTypes.h"'],
         'struct WebKit::WebScriptMessageHandlerData': ['"WebUserContentControllerDataTypes.h"'],
diff --git a/Source/WebKit/Shared/TextInputContext.cpp b/Source/WebKit/Shared/TextInputContext.cpp
new file mode 100644 (file)
index 0000000..c30fafa
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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 "TextInputContext.h"
+
+#include "WebCoreArgumentCoders.h"
+#include <WebCore/Element.h>
+
+namespace WebKit {
+
+TextInputContext::~TextInputContext() = default;
+
+}
+
+namespace IPC {
+
+void ArgumentCoder<WebKit::TextInputContext>::encode(Encoder& encoder, const WebKit::TextInputContext& context)
+{
+    encoder << context.boundingRect;
+    encoder << context.webPageIdentifier;
+    encoder << context.documentIdentifier;
+    encoder << context.elementIdentifier;
+}
+
+Optional<WebKit::TextInputContext> ArgumentCoder<WebKit::TextInputContext>::decode(Decoder& decoder)
+{
+    WebKit::TextInputContext context;
+    if (!decoder.decode(context.boundingRect))
+        return WTF::nullopt;
+    if (!decoder.decode(context.webPageIdentifier))
+        return WTF::nullopt;
+    auto documentIdentifier = WebCore::DocumentIdentifier::decode(decoder);
+    if (!documentIdentifier)
+        return WTF::nullopt;
+    context.documentIdentifier = *documentIdentifier;
+    auto elementIdentifier = WebCore::ElementIdentifier::decode(decoder);
+    if (!elementIdentifier)
+        return WTF::nullopt;
+    context.elementIdentifier = *elementIdentifier;
+
+    return context;
+}
+
+}
diff --git a/Source/WebKit/Shared/TextInputContext.h b/Source/WebKit/Shared/TextInputContext.h
new file mode 100644 (file)
index 0000000..4f75fd8
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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.
+ */
+
+#pragma once
+
+#include "ArgumentCoders.h"
+#include <WebCore/DocumentIdentifier.h>
+#include <WebCore/ElementIdentifier.h>
+#include <WebCore/FloatRect.h>
+
+namespace WebKit {
+
+struct TextInputContext {
+    WebCore::FloatRect boundingRect;
+
+    uint64_t webPageIdentifier;
+    WebCore::DocumentIdentifier documentIdentifier;
+    WebCore::ElementIdentifier elementIdentifier;
+
+    ~TextInputContext();
+};
+
+inline bool operator==(const TextInputContext& a, const TextInputContext& b)
+{
+    return a.boundingRect == b.boundingRect
+        && a.webPageIdentifier == b.webPageIdentifier
+        && a.documentIdentifier == b.documentIdentifier
+        && a.elementIdentifier == b.elementIdentifier;
+}
+
+}
+
+namespace IPC {
+template<> struct ArgumentCoder<WebKit::TextInputContext> {
+    static void encode(Encoder&, const WebKit::TextInputContext&);
+    static Optional<WebKit::TextInputContext> decode(Decoder&);
+};
+}
index 4ab1fa0..1c646bd 100644 (file)
@@ -128,6 +128,7 @@ Shared/SharedStringHashStore.cpp
 Shared/SharedStringHashTableReadOnly.cpp
 Shared/SharedStringHashTable.cpp
 Shared/StatisticsData.cpp
+Shared/TextInputContext.cpp
 Shared/TouchBarMenuData.cpp
 Shared/TouchBarMenuItemData.cpp
 Shared/URLSchemeTaskParameters.cpp
index a730a55..ca8ef8f 100644 (file)
@@ -254,6 +254,7 @@ UIProcess/API/Cocoa/_WKLinkIconParameters.mm
 UIProcess/API/Cocoa/_WKProcessPoolConfiguration.mm
 UIProcess/API/Cocoa/_WKRemoteWebInspectorViewController.mm
 UIProcess/API/Cocoa/_WKSessionState.mm
+UIProcess/API/Cocoa/_WKTextInputContext.mm
 UIProcess/API/Cocoa/_WKThumbnailView.mm
 UIProcess/API/Cocoa/_WKUserContentExtensionStore.mm
 UIProcess/API/Cocoa/_WKUserContentFilter.mm
index 7965a1e..df63642 100644 (file)
@@ -49,6 +49,7 @@
 #import "RemoteObjectRegistry.h"
 #import "RemoteObjectRegistryMessages.h"
 #import "SafeBrowsingWarning.h"
+#import "TextInputContext.h"
 #import "UIDelegate.h"
 #import "UserMediaProcessManager.h"
 #import "VersionChecks.h"
@@ -96,6 +97,7 @@
 #import "_WKInspectorInternal.h"
 #import "_WKRemoteObjectRegistryInternal.h"
 #import "_WKSessionStateInternal.h"
+#import "_WKTextInputContextInternal.h"
 #import "_WKVisitedLinkStoreInternal.h"
 #import "_WKWebsitePoliciesInternal.h"
 #import <WebCore/GraphicsContextCG.h>
@@ -4826,6 +4828,31 @@ FOR_EACH_PRIVATE_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKCONTENTVIEW)
     }
 }
 
+- (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void(^)(NSArray<_WKTextInputContext *> *))completionHandler
+{
+    _page->textInputContextsInRect(rect, [capturedCompletionHandler = makeBlockPtr(completionHandler)] (const Vector<WebKit::TextInputContext>& contexts) {
+        RetainPtr<NSMutableArray> elements = adoptNS([[NSMutableArray alloc] initWithCapacity:contexts.size()]);
+
+        for (const auto& context : contexts)
+            [elements addObject:adoptNS([[_WKTextInputContext alloc] _initWithTextInputContext:context]).get()];
+
+        capturedCompletionHandler(elements.get());
+    });
+}
+
+- (void)_focusTextInputContext:(_WKTextInputContext *)textInputContext completionHandler:(void(^)(BOOL))completionHandler
+{
+    auto webContext = [textInputContext _textInputContext];
+    if (webContext.webPageIdentifier != _page->pageID())
+        [NSException raise:NSInvalidArgumentException format:@"The provided _WKTextInputContext was not created by this WKWebView."];
+
+    [self becomeFirstResponder];
+
+    _page->focusTextInputContext(webContext, [capturedCompletionHandler = makeBlockPtr(completionHandler)](bool success) {
+        capturedCompletionHandler(success);
+    });
+}
+
 #if PLATFORM(MAC)
 - (void)_setShouldSuppressFirstResponderChanges:(BOOL)shouldSuppress
 {
index ed6362d..ec691c6 100644 (file)
@@ -111,6 +111,7 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 @class _WKRemoteObjectRegistry;
 @class _WKSafeBrowsingWarning;
 @class _WKSessionState;
+@class _WKTextInputContext;
 @class _WKThumbnailView;
 @class _WKWebsitePolicies;
 @class _WKWebViewPrintFormatter;
@@ -418,6 +419,10 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 - (void)_stopAllMediaPlayback;
 - (void)_suspendAllMediaPlayback;
 - (void)_resumeAllMediaPlayback;
+
+- (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void(^)(NSArray<_WKTextInputContext *> *))completionHandler WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+- (void)_focusTextInputContext:(_WKTextInputContext *)textInputElement completionHandler:(void(^)(BOOL))completionHandler WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
 @end
 
 #if TARGET_OS_IPHONE
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextInputContext.h b/Source/WebKit/UIProcess/API/Cocoa/_WKTextInputContext.h
new file mode 100644 (file)
index 0000000..c96b3d2
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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.
+ */
+
+#import <WebKit/WKFoundation.h>
+
+WK_CLASS_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA))
+@interface _WKTextInputContext : NSObject <NSCopying>
+
+- (instancetype)init NS_UNAVAILABLE;
+
+@property (nonatomic, readonly) CGRect boundingRect;
+
+@end
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextInputContext.mm b/Source/WebKit/UIProcess/API/Cocoa/_WKTextInputContext.mm
new file mode 100644 (file)
index 0000000..c7ece2d
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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.
+ */
+
+#import "config.h"
+#import "_WKTextInputContext.h"
+
+#import "TextInputContext.h"
+#import "_WKTextInputContextInternal.h"
+
+@implementation _WKTextInputContext {
+    WebKit::TextInputContext _textInputContext;
+}
+
+- (instancetype)init
+{
+    return nil;
+}
+
+- (instancetype)_initWithTextInputContext:(const WebKit::TextInputContext&)context
+{
+    self = [super init];
+    if (!self)
+        return nil;
+
+    _textInputContext = context;
+
+    return self;
+}
+
+- (CGRect)boundingRect
+{
+    return _textInputContext.boundingRect;
+}
+
+- (const WebKit::TextInputContext&)_textInputContext
+{
+    return _textInputContext;
+}
+
+- (BOOL)isEqual:(id)otherObject
+{
+    if (self == otherObject)
+        return YES;
+
+    if (![otherObject isKindOfClass:[_WKTextInputContext class]])
+        return NO;
+
+    _WKTextInputContext *other = (_WKTextInputContext *)otherObject;
+
+    return _textInputContext == other->_textInputContext;
+}
+
+- (NSUInteger)hash
+{
+    return _textInputContext.elementIdentifier.toUInt64();
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+    return [self retain];
+}
+
+@end
diff --git a/Source/WebKit/UIProcess/API/Cocoa/_WKTextInputContextInternal.h b/Source/WebKit/UIProcess/API/Cocoa/_WKTextInputContextInternal.h
new file mode 100644 (file)
index 0000000..bf32a41
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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.
+ */
+
+#import "_WKTextInputContext.h"
+
+namespace WebKit {
+struct TextInputContext;
+}
+
+@interface _WKTextInputContext ()
+
+- (instancetype)_initWithTextInputContext:(const WebKit::TextInputContext&)context;
+
+- (const WebKit::TextInputContext&)_textInputContext;
+
+@end
index ca0d491..b369809 100644 (file)
@@ -83,6 +83,7 @@
 #include "SharedBufferDataReference.h"
 #include "TextChecker.h"
 #include "TextCheckerState.h"
+#include "TextInputContext.h"
 #include "UIMessagePortChannelProvider.h"
 #include "URLSchemeTaskParameters.h"
 #include "UndoOrRedo.h"
@@ -8817,6 +8818,26 @@ void WebPageProxy::webViewDidMoveToWindow()
     }
 }
 
+void WebPageProxy::textInputContextsInRect(WebCore::FloatRect rect, CompletionHandler<void(const Vector<WebKit::TextInputContext>&)>&& completionHandler)
+{
+    if (!isValid()) {
+        completionHandler({ });
+        return;
+    }
+
+    m_process->connection()->sendWithAsyncReply(Messages::WebPage::TextInputContextsInRect(rect), WTFMove(completionHandler), m_pageID);
+}
+
+void WebPageProxy::focusTextInputContext(const TextInputContext& context, CompletionHandler<void(bool)>&& completionHandler)
+{
+    if (!isValid()) {
+        completionHandler(false);
+        return;
+    }
+
+    m_process->connection()->sendWithAsyncReply(Messages::WebPage::FocusTextInputContext(context), WTFMove(completionHandler), m_pageID);
+}
+
 } // namespace WebKit
 
 #undef MERGE_WHEEL_EVENTS
index 34a6039..c43b6b3 100644 (file)
@@ -275,6 +275,7 @@ struct InteractionInformationRequest;
 struct LoadParameters;
 struct PlatformPopupMenuData;
 struct PrintInfo;
+struct TextInputContext;
 struct WebPopupItem;
 struct URLSchemeTaskParameters;
 
@@ -606,6 +607,9 @@ public:
     void requestFontAttributesAtSelectionStart(Function<void(const WebCore::FontAttributes&, CallbackBase::Error)>&&);
     void fontAttributesCallback(const WebCore::FontAttributes&, CallbackID);
 
+    void textInputContextsInRect(WebCore::FloatRect, CompletionHandler<void(const Vector<TextInputContext>&)>&&);
+    void focusTextInputContext(const TextInputContext&, CompletionHandler<void(bool)>&&);
+
 #if PLATFORM(IOS_FAMILY)
     double displayedContentScale() const { return m_lastVisibleContentRectUpdate.scale(); }
     const WebCore::FloatRect& exposedContentRect() const { return m_lastVisibleContentRectUpdate.exposedContentRect(); }
index f4d8468..bde9cab 100644 (file)
                2DDF731518E95060004F5A66 /* RemoteLayerBackingStoreCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DDF731318E95060004F5A66 /* RemoteLayerBackingStoreCollection.h */; };
                2DE6943D18BD2A68005C15E5 /* SmartMagnificationControllerMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2DE6943B18BD2A68005C15E5 /* SmartMagnificationControllerMessageReceiver.cpp */; };
                2DE6943E18BD2A68005C15E5 /* SmartMagnificationControllerMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DE6943C18BD2A68005C15E5 /* SmartMagnificationControllerMessages.h */; };
+               2DE9B1352231B5B2005287B7 /* TextInputContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DE9B1332231B5B2005287B7 /* TextInputContext.h */; };
+               2DE9B13A2231F61C005287B7 /* _WKTextInputContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DE9B1382231F61C005287B7 /* _WKTextInputContext.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               2DE9B13C2231F77C005287B7 /* _WKTextInputContextInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DE9B13B2231F77C005287B7 /* _WKTextInputContextInternal.h */; };
                2DEAC5CF1AC368BB00A195D8 /* _WKFindOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DEAC5CE1AC368BB00A195D8 /* _WKFindOptions.h */; settings = {ATTRIBUTES = (Private, ); }; };
                2DEB1D2E2127473600933906 /* ArgumentCodersCF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1AAF0C4912B16334008E49E2 /* ArgumentCodersCF.cpp */; };
                2DF6FE52212E110900469030 /* WebPage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BC963D6A113DD19200574BE2 /* WebPage.cpp */; };
                2DDF731418E95060004F5A66 /* RemoteLayerBackingStoreCollection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RemoteLayerBackingStoreCollection.mm; sourceTree = "<group>"; };
                2DE6943B18BD2A68005C15E5 /* SmartMagnificationControllerMessageReceiver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SmartMagnificationControllerMessageReceiver.cpp; path = DerivedSources/WebKit2/SmartMagnificationControllerMessageReceiver.cpp; sourceTree = BUILT_PRODUCTS_DIR; };
                2DE6943C18BD2A68005C15E5 /* SmartMagnificationControllerMessages.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SmartMagnificationControllerMessages.h; path = DerivedSources/WebKit2/SmartMagnificationControllerMessages.h; sourceTree = BUILT_PRODUCTS_DIR; };
+               2DE9B1332231B5B2005287B7 /* TextInputContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextInputContext.h; sourceTree = "<group>"; };
+               2DE9B1342231B5B2005287B7 /* TextInputContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextInputContext.cpp; sourceTree = "<group>"; };
+               2DE9B1372231F61C005287B7 /* _WKTextInputContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _WKTextInputContext.mm; sourceTree = "<group>"; };
+               2DE9B1382231F61C005287B7 /* _WKTextInputContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _WKTextInputContext.h; sourceTree = "<group>"; };
+               2DE9B13B2231F77C005287B7 /* _WKTextInputContextInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _WKTextInputContextInternal.h; sourceTree = "<group>"; };
                2DEAC5CE1AC368BB00A195D8 /* _WKFindOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _WKFindOptions.h; sourceTree = "<group>"; };
                2DF3962A21C8DC50008835E3 /* WKInkPickerView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = WKInkPickerView.mm; path = ios/WKInkPickerView.mm; sourceTree = "<group>"; };
                2DF3962B21C8DC50008835E3 /* WKInkPickerView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WKInkPickerView.h; path = ios/WKInkPickerView.h; sourceTree = "<group>"; };
                                5272B2881406985D0096A5D0 /* StatisticsData.cpp */,
                                5272B2891406985D0096A5D0 /* StatisticsData.h */,
                                1A5E4DA312D3BD3D0099A2BB /* TextCheckerState.h */,
+                               2DE9B1342231B5B2005287B7 /* TextInputContext.cpp */,
+                               2DE9B1332231B5B2005287B7 /* TextInputContext.h */,
                                2FD43B921FA006A30083F51C /* TouchBarMenuData.cpp */,
                                2FD43B911FA006A10083F51C /* TouchBarMenuData.h */,
                                2F809DD51FBD1BC9005FE63A /* TouchBarMenuItemData.cpp */,
                                1A002D3F196B329400B9AD44 /* _WKSessionState.h */,
                                1A002D3E196B329400B9AD44 /* _WKSessionState.mm */,
                                1A002D42196B337000B9AD44 /* _WKSessionStateInternal.h */,
+                               2DE9B1382231F61C005287B7 /* _WKTextInputContext.h */,
+                               2DE9B1372231F61C005287B7 /* _WKTextInputContext.mm */,
+                               2DE9B13B2231F77C005287B7 /* _WKTextInputContextInternal.h */,
                                2D6B371918A967AD0042AE80 /* _WKThumbnailView.h */,
                                2D6B371A18A967AD0042AE80 /* _WKThumbnailView.mm */,
                                2DACE64D18ADBFF000E4CA76 /* _WKThumbnailViewInternal.h */,
                                376311FE1A3FB600005A2E51 /* _WKSameDocumentNavigationTypeInternal.h in Headers */,
                                1A002D44196B338900B9AD44 /* _WKSessionState.h in Headers */,
                                1A002D43196B337000B9AD44 /* _WKSessionStateInternal.h in Headers */,
+                               2DE9B13A2231F61C005287B7 /* _WKTextInputContext.h in Headers */,
+                               2DE9B13C2231F77C005287B7 /* _WKTextInputContextInternal.h in Headers */,
                                2D6B371B18A967AD0042AE80 /* _WKThumbnailView.h in Headers */,
                                2DACE64E18ADBFF000E4CA76 /* _WKThumbnailViewInternal.h in Headers */,
                                99E7189C21F79D9E0055E975 /* _WKTouchEventGenerator.h in Headers */,
                                1AA417CB12C00CCA002BE67B /* TextChecker.h in Headers */,
                                53CFBBC82224D1B500266546 /* TextCheckerCompletion.h in Headers */,
                                1A5E4DA412D3BD3D0099A2BB /* TextCheckerState.h in Headers */,
+                               2DE9B1352231B5B2005287B7 /* TextInputContext.h in Headers */,
                                CE1A0BD71A48E6C60054EF74 /* TextInputSPI.h in Headers */,
                                1AAF263914687C39004A1E8A /* TiledCoreAnimationDrawingArea.h in Headers */,
                                1AF05D8714688348008B1E81 /* TiledCoreAnimationDrawingAreaProxy.h in Headers */,
index 9824b22..fdbb382 100644 (file)
@@ -6468,6 +6468,94 @@ WebCore::IntRect WebPage::rectForElementAtInteractionLocation() const
 
 #endif // !PLATFORM(IOS_FAMILY)
 
+static IntRect elementRectInWindowCoordinates(const Element& element, const Frame& frame)
+{
+    auto* view = frame.view();
+    if (!view)
+        return { };
+
+    auto* renderer = element.renderer();
+    if (!renderer)
+        return { };
+
+    return view->contentsToWindow(renderer->absoluteBoundingBoxRect());
+}
+
+static bool isEditableTextInputElement(Element& element)
+{
+    if (is<HTMLTextFormControlElement>(element)) {
+        if (!element.isTextField() && !is<HTMLTextAreaElement>(element))
+            return false;
+        return downcast<HTMLTextFormControlElement>(element).isInnerTextElementEditable();
+    }
+
+    return element.isRootEditableElement();
+}
+
+void WebPage::textInputContextsInRect(WebCore::FloatRect searchRect, CompletionHandler<void(const Vector<TextInputContext>&)>&& completionHandler)
+{
+    Vector<WebKit::TextInputContext> textInputContexts;
+
+    for (Frame* frame = &m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) {
+        Document* document = frame->document();
+        if (!document)
+            continue;
+
+        Deque<Node*> nodesToSearch;
+        nodesToSearch.append(document);
+        while (!nodesToSearch.isEmpty()) {
+            auto node = nodesToSearch.takeFirst();
+
+            // It is possible to have nested text input contexts (e.g. <input type='text'> inside contenteditable) but
+            // in this case we just take the outermost context and skip the rest.
+            if (!is<Element>(*node) || !isEditableTextInputElement(downcast<Element>(*node))) {
+                for (auto* child = node->firstChild(); child; child = child->nextSibling())
+                    nodesToSearch.append(child);
+                continue;
+            }
+
+            auto& element = downcast<Element>(*node);
+
+            IntRect elementRect = elementRectInWindowCoordinates(element, *frame);
+            if (!searchRect.intersects(elementRect))
+                continue;
+
+            WebKit::TextInputContext context;
+            context.webPageIdentifier = m_pageID;
+            context.documentIdentifier = document->identifier();
+            context.elementIdentifier = document->identifierForElement(element);
+            context.boundingRect = elementRect;
+
+            textInputContexts.append(context);
+        }
+    }
+
+    completionHandler(textInputContexts);
+}
+
+void WebPage::focusTextInputContext(const TextInputContext& textInputContext, CompletionHandler<void(bool)>&& completionHandler)
+{
+    completionHandler([&] {
+        if (textInputContext.webPageIdentifier != m_pageID)
+            return false;
+
+        auto* document = Document::allDocumentsMap().get(textInputContext.documentIdentifier);
+        if (!document)
+            return false;
+
+        if (document->page() != m_page.get())
+            return false;
+
+        auto* element = document->searchForElementByIdentifier(textInputContext.elementIdentifier);
+        if (!element)
+            return false;
+
+        element->focus();
+
+        return true;
+    }());
+}
+
 } // namespace WebKit
 
 #undef RELEASE_LOG_IF_ALLOWED
index cd954e8..b00984b 100644 (file)
@@ -251,6 +251,7 @@ struct InteractionInformationAtPosition;
 struct InteractionInformationRequest;
 struct LoadParameters;
 struct PrintInfo;
+struct TextInputContext;
 struct WebAutocorrectionContext;
 struct WebPageCreationParameters;
 struct WebPreferencesStore;
@@ -592,6 +593,9 @@ public:
     void viewportPropertiesDidChange(const WebCore::ViewportArguments&);
     void executeEditCommandWithCallback(const String&, const String& argument, CallbackID);
 
+    void textInputContextsInRect(WebCore::FloatRect, CompletionHandler<void(const Vector<WebKit::TextInputContext>&)>&&);
+    void focusTextInputContext(const TextInputContext&, CompletionHandler<void(bool)>&&);
+
 #if PLATFORM(IOS_FAMILY)
     WebCore::FloatSize screenSize() const;
     WebCore::FloatSize availableScreenSize() const;
index cce7722..912f1e4 100644 (file)
@@ -545,4 +545,7 @@ messages -> WebPage LegacyReceiver {
 
     UpdateCurrentModifierState(OptionSet<WebCore::PlatformEvent::Modifier> modifiers)
     SimulateDeviceOrientationChange(double alpha, double beta, double gamma)
+
+    TextInputContextsInRect(WebCore::FloatRect rect) -> (Vector<struct WebKit::TextInputContext> contexts) Async
+    FocusTextInputContext(struct WebKit::TextInputContext context) -> (bool success) Async
 }
index 51b1330..b68e484 100644 (file)
@@ -1,3 +1,19 @@
+2019-03-10  Tim Horton  <timothy_horton@apple.com>
+
+        Add SPI to retrieve the set of text inputs in a given rect, and later focus one
+        https://bugs.webkit.org/show_bug.cgi?id=195499
+
+        Reviewed by Darin Adler.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm: Added.
+        (-[WKWebView synchronouslyRequestTextInputContextsInRect:]):
+        (-[WKWebView synchronouslyFocusTextInputContext:]):
+        (applyStyle):
+        (applyIframe):
+        (TEST):
+        Add some tests for this SPI.
+
 2019-03-10  Yusuke Suzuki <utatane.tea@gmail.com>
 
         [WTF] Align assumption in RunLoopWin to the other platform's RunLoop
index 38c0cb2..f3a990d 100644 (file)
@@ -92,6 +92,7 @@
                2D1646E21D1862CD00015A1A /* DeferredViewInWindowStateChange.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2D1646E11D1862CD00015A1A /* DeferredViewInWindowStateChange.mm */; };
                2D1C04A71D76298B000A6816 /* TestNavigationDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2D1C04A61D76298B000A6816 /* TestNavigationDelegate.mm */; };
                2D21FE591F04642900B58E7D /* WKPDFViewStablePresentationUpdateCallback.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2D21FE581F04642800B58E7D /* WKPDFViewStablePresentationUpdateCallback.mm */; };
+               2D2BEB2D22324E5F005544CA /* RequestTextInputContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2D2BEB2C22324E5F005544CA /* RequestTextInputContext.mm */; };
                2D3CA3A8221DF4B40088E803 /* PageOverlayPlugin.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2D3CA3A4221DF2390088E803 /* PageOverlayPlugin.mm */; };
                2D4CF8BD1D8360CC0001CE8D /* WKThumbnailView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2D4CF8BC1D8360CC0001CE8D /* WKThumbnailView.mm */; };
                2D51A0C71C8BF00C00765C45 /* DOMHTMLVideoElementWrapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2D51A0C51C8BF00400765C45 /* DOMHTMLVideoElementWrapper.mm */; };
                2D1C04A61D76298B000A6816 /* TestNavigationDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = TestNavigationDelegate.mm; path = cocoa/TestNavigationDelegate.mm; sourceTree = "<group>"; };
                2D1FE0AF1AD465C1006CD9E6 /* FixedLayoutSize.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FixedLayoutSize.mm; sourceTree = "<group>"; };
                2D21FE581F04642800B58E7D /* WKPDFViewStablePresentationUpdateCallback.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WKPDFViewStablePresentationUpdateCallback.mm; sourceTree = "<group>"; };
+               2D2BEB2C22324E5F005544CA /* RequestTextInputContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RequestTextInputContext.mm; sourceTree = "<group>"; };
                2D3CA3A4221DF2390088E803 /* PageOverlayPlugin.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PageOverlayPlugin.mm; sourceTree = "<group>"; };
                2D4CF8BC1D8360CC0001CE8D /* WKThumbnailView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = WKThumbnailView.mm; path = WebKit/WKThumbnailView.mm; sourceTree = "<group>"; };
                2D51A0C51C8BF00400765C45 /* DOMHTMLVideoElementWrapper.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DOMHTMLVideoElementWrapper.mm; sourceTree = "<group>"; };
                                52D5D6BD21B9F1B20046ABA6 /* RenderingProgress.mm */,
                                52D5D6BE21B9F1B20046ABA6 /* RenderingProgressPlugIn.mm */,
                                52D5D6BF21B9F1B20046ABA6 /* RenderingProgressProtocol.h */,
+                               2D2BEB2C22324E5F005544CA /* RequestTextInputContext.mm */,
                                CD9E292B1C90A71F000BB800 /* RequiresUserActionForPlayback.mm */,
                                51C8E1A41F26AC5400BF731B /* ResourceLoadStatistics.mm */,
                                5CCB10E02134579D00AC5AF0 /* ResponsivenessTimer.mm */,
                                A12DDBFB1E836F0700CF6CAE /* RenderedImageWithOptions.mm in Sources */,
                                52D5D6C021B9F1B30046ABA6 /* RenderingProgress.mm in Sources */,
                                F464AF9220BB66EA007F9B18 /* RenderingProgressTests.mm in Sources */,
+                               2D2BEB2D22324E5F005544CA /* RequestTextInputContext.mm in Sources */,
                                7C83E0C41D0A654200FEBCF3 /* RequiresUserActionForPlayback.mm in Sources */,
                                7CCE7F0E1A411AE600447C4C /* ResizeReversePaginatedWebView.cpp in Sources */,
                                7CCE7F0F1A411AE600447C4C /* ResizeWindowAfterCrash.cpp in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm
new file mode 100644 (file)
index 0000000..bda3dd6
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2019 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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.
+ */
+
+#import "config.h"
+
+#import "PlatformUtilities.h"
+#import "Test.h"
+#import "TestNavigationDelegate.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKPreferencesRefPrivate.h>
+#import <WebKit/WKWebViewPrivate.h>
+#import <WebKit/WebKit.h>
+#import <WebKit/_WKTextInputContext.h>
+#import <wtf/RetainPtr.h>
+
+#define EXPECT_RECT_EQ(xExpected, yExpected, widthExpected, heightExpected, rect) \
+    EXPECT_DOUBLE_EQ(xExpected, rect.origin.x); \
+    EXPECT_DOUBLE_EQ(yExpected, rect.origin.y); \
+    EXPECT_DOUBLE_EQ(widthExpected, rect.size.width); \
+    EXPECT_DOUBLE_EQ(heightExpected, rect.size.height);
+
+@implementation WKWebView (SynchronousTextInputContext)
+
+- (NSArray<_WKTextInputContext *> *)synchronouslyRequestTextInputContextsInRect:(CGRect)rect
+{
+    __block bool finished = false;
+    __block RetainPtr<NSArray<_WKTextInputContext *>> result;
+    [self _requestTextInputContextsInRect:rect completionHandler:^(NSArray<_WKTextInputContext *> *contexts) {
+        result = contexts;
+        finished = true;
+    }];
+    TestWebKitAPI::Util::run(&finished);
+    return result.autorelease();
+}
+
+- (BOOL)synchronouslyFocusTextInputContext:(_WKTextInputContext *)context
+{
+    __block bool finished = false;
+    __block bool success = false;
+    [self _focusTextInputContext:context completionHandler:^(BOOL innerSuccess) {
+        success = innerSuccess;
+        finished = true;
+    }];
+    TestWebKitAPI::Util::run(&finished);
+    return success;
+}
+
+@end
+
+static NSString *applyStyle(NSString *HTMLString)
+{
+    return [@"<style>body { margin: 0; } iframe { border: none; }</style>" stringByAppendingString:HTMLString];
+}
+
+static NSString *applyIframe(NSString *HTMLString)
+{
+    return applyStyle([NSString stringWithFormat:@"<iframe src=\"data:text/html,%@\" style='position: absolute; top: 200px;'>", [applyStyle(HTMLString) stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLPathAllowedCharacterSet]]]);
+}
+
+TEST(WebKit, RequestTextInputContext)
+{
+    RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    WKPreferencesSetThreadedScrollingEnabled((WKPreferencesRef)[configuration preferences], false);
+    RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+
+    NSArray<_WKTextInputContext *> *contexts;
+
+    // Basic inputs.
+
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;'>")];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(1UL, contexts.count);
+    EXPECT_RECT_EQ(0, 0, 50, 50, contexts[0].boundingRect);
+
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<textarea style='width: 100px; height: 100px;'></textarea>")];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(1UL, contexts.count);
+    EXPECT_RECT_EQ(0, 0, 100, 100, contexts[0].boundingRect);
+
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<div contenteditable style='width: 100px; height: 100px;'></div>")];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(1UL, contexts.count);
+    EXPECT_RECT_EQ(0, 0, 100, 100, contexts[0].boundingRect);
+
+    // Basic inputs inside subframe.
+
+    [webView synchronouslyLoadHTMLString:applyIframe(@"<input type='text' style='width: 50px; height: 50px;'>")];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(1UL, contexts.count);
+    EXPECT_RECT_EQ(0, 200, 50, 50, contexts[0].boundingRect);
+
+    [webView synchronouslyLoadHTMLString:applyIframe(@"<textarea style='width: 100px; height: 100px;'></textarea>")];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(1UL, contexts.count);
+    EXPECT_RECT_EQ(0, 200, 100, 100, contexts[0].boundingRect);
+
+    [webView synchronouslyLoadHTMLString:applyIframe(@"<div contenteditable style='width: 100px; height: 100px;'></div>")];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(1UL, contexts.count);
+    EXPECT_RECT_EQ(0, 200, 100, 100, contexts[0].boundingRect);
+
+    // Read only inputs; should not be included.
+
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;' readonly>")];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(0UL, contexts.count);
+
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<textarea style='width: 100px; height: 100px;' readonly>")];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(0UL, contexts.count);
+
+    // Inputs outside the requested rect; should not be included.
+
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;'>")];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:CGRectMake(100, 100, 100, 100)];
+    EXPECT_EQ(0UL, contexts.count);
+
+    // Inputs scrolled outside the requested rect; should not be included.
+
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;'><br><div style='width: 100px; height: 5000px;'></div>")];
+    [webView objectByEvaluatingJavaScript:@"window.scrollTo(0, 5000);"];
+    [webView waitForNextPresentationUpdate];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(0UL, contexts.count);
+
+    // Inputs scrolled into the requested rect.
+
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px; position: absolute; top: 5000px;'><br><div style='width: 100px; height: 10000px;'></div>")];
+    [webView objectByEvaluatingJavaScript:@"window.scrollTo(0, 5000);"];
+    [webView waitForNextPresentationUpdate];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(1UL, contexts.count);
+    EXPECT_RECT_EQ(0, 0, 50, 50, contexts[0].boundingRect);
+
+    // Multiple inputs.
+
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' style='width: 50px; height: 50px;'><br/><input type='text' style='width: 50px; height: 50px;'><br/><input type='text' style='width: 50px; height: 50px;'>")];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(3UL, contexts.count);
+    EXPECT_RECT_EQ(0, 0, 50, 50, contexts[0].boundingRect);
+    EXPECT_RECT_EQ(0, 50, 50, 50, contexts[1].boundingRect);
+    EXPECT_RECT_EQ(0, 100, 50, 50, contexts[2].boundingRect);
+
+    // Nested <input>-inside-contenteditable. Only the contenteditable is considered.
+
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<div contenteditable style='width: 100px; height: 100px;'><input type='text' style='width: 50px; height: 50px;'></div>")];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(1UL, contexts.count);
+    EXPECT_RECT_EQ(0, 0, 100, 100, contexts[0].boundingRect);
+}
+
+TEST(WebKit, FocusTextInputContext)
+{
+    RetainPtr<WKWebViewConfiguration> configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    WKPreferencesSetThreadedScrollingEnabled((WKPreferencesRef)[configuration preferences], false);
+    RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+
+    NSArray<_WKTextInputContext *> *contexts;
+
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<input id='test' type='text' style='width: 50px; height: 50px;'>")];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(1UL, contexts.count);
+    RetainPtr<_WKTextInputContext> originalInput = contexts[0];
+    EXPECT_TRUE([webView synchronouslyFocusTextInputContext:originalInput.get()]);
+    EXPECT_WK_STREQ("test", [webView objectByEvaluatingJavaScript:@"document.activeElement.id"]);
+
+    // The _WKTextInputContext should still work even after another request.
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_TRUE([contexts[0] isEqual:originalInput.get()]);
+    EXPECT_TRUE([webView synchronouslyFocusTextInputContext:originalInput.get()]);
+
+    // Replace the <input> with a <textarea> with script; the <input> should no longer be focusable.
+    [webView objectByEvaluatingJavaScript:@"document.body.innerHTML = '<textarea id=\"area\">';"];
+    contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView frame]];
+    EXPECT_EQ(1UL, contexts.count);
+    RetainPtr<_WKTextInputContext> textArea = contexts[0];
+    EXPECT_FALSE([textArea isEqual:originalInput.get()]);
+    EXPECT_FALSE([webView synchronouslyFocusTextInputContext:originalInput.get()]);
+    EXPECT_TRUE([webView synchronouslyFocusTextInputContext:textArea.get()]);
+    EXPECT_WK_STREQ("area", [webView objectByEvaluatingJavaScript:@"document.activeElement.id"]);
+
+    // Destroy the <textarea> by navigating away; we can no longer focus it.
+    [webView synchronouslyLoadHTMLString:@""];
+    EXPECT_FALSE([webView synchronouslyFocusTextInputContext:textArea.get()]);
+}