Title: [224512] trunk
Revision
224512
Author
[email protected]
Date
2017-11-06 13:46:54 -0800 (Mon, 06 Nov 2017)

Log Message

[Attachment Support] Implement delegate hooks for attachment element insertion and removal
https://bugs.webkit.org/show_bug.cgi?id=179016
<rdar://problem/35250890>

Reviewed by Tim Horton.

Source/WebCore:

Implements a mechanism for notifying WebKit2 clients when attachment elements are inserted into or removed from
the document. See per-change comments below for more details.

API tests: WKAttachmentTests.AttachmentElementInsertion
           WKAttachmentTests.AttachmentUpdatesWhenInsertingAndDeletingNewline
           WKAttachmentTests.AttachmentUpdatesWhenUndoingAndRedoing
           WKAttachmentTests.AttachmentUpdatesWhenChangingFontStyles
           WKAttachmentTests.AttachmentUpdatesWhenInsertingLists
           WKAttachmentTests.AttachmentUpdatesWhenInsertingRichMarkup

* editing/Editor.cpp:
(WebCore::Editor::respondToChangedSelection):
(WebCore::Editor::editorUIUpdateTimerFired):

Additionally notify the client of any attachment updates.

(WebCore::Editor::scheduleEditorUIUpdate):

Add a new helper that starts the editor UI update timer with 0 delay, and use it everywhere we schedule an
editor UI update.

(WebCore::Editor::didInsertAttachmentElement):
(WebCore::Editor::didRemoveAttachmentElement):

Maintain two sets of attachment element identifiers -- the first one tracking insertions, and the second one
tracking removals. When an attachment element is inserted, we first check to see if that attachment element has
just been removed; if so, we don't add it to the inserted identifiers set, but instead remove it from the set of
removed identifiers. We perform a similar check in the opposite case. This prevents us from notifying the client
of extraneous insertions and removals during certain editing commands which may reparent and move attachment
elements around. In both cases, we schedule an editor UI update afterwards, where we will notify the client of
attachment updates.

(WebCore::Editor::notifyClientOfAttachmentUpdates):
(WebCore::Editor::insertAttachmentFromFile):
* editing/Editor.h:
* html/HTMLAttachmentElement.cpp:
(WebCore::HTMLAttachmentElement::HTMLAttachmentElement):

Remove the version of HTMLAttachmentElement's constructor that takes a unique identifier.

(WebCore::HTMLAttachmentElement::insertedIntoAncestor):
(WebCore::HTMLAttachmentElement::removedFromAncestor):

Implement these hooks to observe insertion into and removal from the DOM. If the element was attached to or
removed from an ancestor that was connected to the document, call out to the document's frame's editor. This
"document-connected" rule prevents us from calling out to the client in cases where (for instance) we append an
attachment element to a newly created DocumentFragment in preparation for executing a ReplaceSelectionCommand.

(WebCore::HTMLAttachmentElement::uniqueIdentifier const):
(WebCore::HTMLAttachmentElement::setUniqueIdentifier):

Refactor unique identifier to refer to the new attachment identifier attribute instead of a member variable.

* html/HTMLAttachmentElement.h:
* html/HTMLAttributeNames.in:

Add a new attribute representing an attachment element's identifier. This enables us to keep track of particular
attachments as they are destroyed and recreated as different objects, as a result of some editing commands.

* page/EditorClient.h:
(WebCore::EditorClient::didInsertAttachment):
(WebCore::EditorClient::didRemoveAttachment):

Add boilerplate editor client hooks for attachment insertion and removal.

Source/WebKit:

Adds boilerplate plumbing to WebEditorClient, WebPage, and the usual machinery in the UI process to notify
WebKit2 clients when attachment elements have been inserted or removed from the document. See the WebCore
ChangeLog for more details about the implementation, or the Tools ChangeLog for more information about new API
tests.

* UIProcess/API/Cocoa/WKUIDelegatePrivate.h:
* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _didInsertAttachment:]):
(-[WKWebView _didRemoveAttachment:]):
* UIProcess/API/Cocoa/WKWebViewInternal.h:
* UIProcess/Cocoa/PageClientImplCocoa.h:
* UIProcess/Cocoa/PageClientImplCocoa.mm:
(WebKit::PageClientImplCocoa::didInsertAttachment):
(WebKit::PageClientImplCocoa::didRemoveAttachment):
* UIProcess/PageClient.h:
(WebKit::PageClient::didInsertAttachment):
(WebKit::PageClient::didRemoveAttachment):
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::didInsertAttachment):
(WebKit::WebPageProxy::didRemoveAttachment):
* UIProcess/WebPageProxy.h:
* UIProcess/WebPageProxy.messages.in:
* WebProcess/WebCoreSupport/WebEditorClient.cpp:
(WebKit::WebEditorClient::didInsertAttachment):
(WebKit::WebEditorClient::didRemoveAttachment):
* WebProcess/WebCoreSupport/WebEditorClient.h:

Tools:

Introduces new API tests to check that various editing operations will or won't result in the new attachment
insertion and removal delegate hooks being fired. Additionally refactors an existing test to verify that
attachments insertion and removal is observable by the UI delegate.

* TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm:
(-[AttachmentUpdateObserver init]):
(-[AttachmentUpdateObserver inserted]):
(-[AttachmentUpdateObserver removed]):
(-[AttachmentUpdateObserver _webView:didInsertAttachment:]):
(-[AttachmentUpdateObserver _webView:didRemoveAttachment:]):
(TestWebKitAPI::ObserveAttachmentUpdatesForScope::ObserveAttachmentUpdatesForScope):
(TestWebKitAPI::ObserveAttachmentUpdatesForScope::~ObserveAttachmentUpdatesForScope):
(TestWebKitAPI::ObserveAttachmentUpdatesForScope::expectAttachmentUpdates):

Implement a testing mechanism to temporarily bind a UI delegate to a given WKWebView and listen for inserted or
removed attachments over the course of a particular scope. The API tests use this mechanism to check that the UI
delegate hooks added in this patch are invoked with the right attachments when performing edit commands.

(-[TestWKWebView _synchronouslyExecuteEditCommand:argument:]):
(-[TestWKWebView expectUpdatesAfterCommand:withArgument:expectedRemovals:expectedInsertions:]):
(TestWebKitAPI::TEST):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (224511 => 224512)


--- trunk/Source/WebCore/ChangeLog	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebCore/ChangeLog	2017-11-06 21:46:54 UTC (rev 224512)
@@ -1,3 +1,76 @@
+2017-11-06  Wenson Hsieh  <[email protected]>
+
+        [Attachment Support] Implement delegate hooks for attachment element insertion and removal
+        https://bugs.webkit.org/show_bug.cgi?id=179016
+        <rdar://problem/35250890>
+
+        Reviewed by Tim Horton.
+
+        Implements a mechanism for notifying WebKit2 clients when attachment elements are inserted into or removed from
+        the document. See per-change comments below for more details.
+
+        API tests: WKAttachmentTests.AttachmentElementInsertion
+                   WKAttachmentTests.AttachmentUpdatesWhenInsertingAndDeletingNewline
+                   WKAttachmentTests.AttachmentUpdatesWhenUndoingAndRedoing
+                   WKAttachmentTests.AttachmentUpdatesWhenChangingFontStyles
+                   WKAttachmentTests.AttachmentUpdatesWhenInsertingLists
+                   WKAttachmentTests.AttachmentUpdatesWhenInsertingRichMarkup
+
+        * editing/Editor.cpp:
+        (WebCore::Editor::respondToChangedSelection):
+        (WebCore::Editor::editorUIUpdateTimerFired):
+
+        Additionally notify the client of any attachment updates.
+
+        (WebCore::Editor::scheduleEditorUIUpdate):
+
+        Add a new helper that starts the editor UI update timer with 0 delay, and use it everywhere we schedule an
+        editor UI update.
+
+        (WebCore::Editor::didInsertAttachmentElement):
+        (WebCore::Editor::didRemoveAttachmentElement):
+
+        Maintain two sets of attachment element identifiers -- the first one tracking insertions, and the second one
+        tracking removals. When an attachment element is inserted, we first check to see if that attachment element has
+        just been removed; if so, we don't add it to the inserted identifiers set, but instead remove it from the set of
+        removed identifiers. We perform a similar check in the opposite case. This prevents us from notifying the client
+        of extraneous insertions and removals during certain editing commands which may reparent and move attachment
+        elements around. In both cases, we schedule an editor UI update afterwards, where we will notify the client of
+        attachment updates.
+
+        (WebCore::Editor::notifyClientOfAttachmentUpdates):
+        (WebCore::Editor::insertAttachmentFromFile):
+        * editing/Editor.h:
+        * html/HTMLAttachmentElement.cpp:
+        (WebCore::HTMLAttachmentElement::HTMLAttachmentElement):
+
+        Remove the version of HTMLAttachmentElement's constructor that takes a unique identifier.
+
+        (WebCore::HTMLAttachmentElement::insertedIntoAncestor):
+        (WebCore::HTMLAttachmentElement::removedFromAncestor):
+
+        Implement these hooks to observe insertion into and removal from the DOM. If the element was attached to or
+        removed from an ancestor that was connected to the document, call out to the document's frame's editor. This
+        "document-connected" rule prevents us from calling out to the client in cases where (for instance) we append an
+        attachment element to a newly created DocumentFragment in preparation for executing a ReplaceSelectionCommand.
+
+        (WebCore::HTMLAttachmentElement::uniqueIdentifier const):
+        (WebCore::HTMLAttachmentElement::setUniqueIdentifier):
+
+        Refactor unique identifier to refer to the new attachment identifier attribute instead of a member variable.
+
+        * html/HTMLAttachmentElement.h:
+        * html/HTMLAttributeNames.in:
+
+        Add a new attribute representing an attachment element's identifier. This enables us to keep track of particular
+        attachments as they are destroyed and recreated as different objects, as a result of some editing commands.
+
+        * page/EditorClient.h:
+        (WebCore::EditorClient::didInsertAttachment):
+        (WebCore::EditorClient::didRemoveAttachment):
+
+        Add boilerplate editor client hooks for attachment insertion and removal.
+
 2017-11-06  Ryan Haddad  <[email protected]>
 
         Unreviewed, rolling out r224494.

Modified: trunk/Source/WebCore/editing/Editor.cpp (224511 => 224512)


--- trunk/Source/WebCore/editing/Editor.cpp	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebCore/editing/Editor.cpp	2017-11-06 21:46:54 UTC (rev 224512)
@@ -3428,7 +3428,7 @@
     m_editorUIUpdateTimerShouldCheckSpellingAndGrammar = options & FrameSelection::CloseTyping
         && !(options & FrameSelection::SpellCorrectionTriggered);
     m_editorUIUpdateTimerWasTriggeredByDictation = options & FrameSelection::DictationTriggered;
-    m_editorUIUpdateTimer.startOneShot(0_s);
+    scheduleEditorUIUpdate();
 }
 
 #if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS)
@@ -3601,6 +3601,10 @@
         m_alternativeTextController->respondToChangedSelection(oldSelection);
 
     m_oldSelectionForEditorUIUpdate = m_frame.selection().selection();
+
+#if ENABLE(ATTACHMENT_ELEMENT)
+    notifyClientOfAttachmentUpdates();
+#endif
 }
 
 static Node* findFirstMarkable(Node* node)
@@ -3733,8 +3737,48 @@
     return TextIterator::subrange(*contextRange, result.location, result.length);
 }
 
+void Editor::scheduleEditorUIUpdate()
+{
+    m_editorUIUpdateTimer.startOneShot(0_s);
+}
+
 #if ENABLE(ATTACHMENT_ELEMENT)
 
+void Editor::didInsertAttachmentElement(HTMLAttachmentElement& attachment)
+{
+    auto identifier = attachment.uniqueIdentifier();
+    if (identifier.isEmpty())
+        return;
+
+    if (!m_removedAttachmentIdentifiers.take(identifier))
+        m_insertedAttachmentIdentifiers.add(identifier);
+    scheduleEditorUIUpdate();
+}
+
+void Editor::didRemoveAttachmentElement(HTMLAttachmentElement& attachment)
+{
+    auto identifier = attachment.uniqueIdentifier();
+    if (identifier.isEmpty())
+        return;
+
+    if (!m_insertedAttachmentIdentifiers.take(identifier))
+        m_removedAttachmentIdentifiers.add(identifier);
+    scheduleEditorUIUpdate();
+}
+
+void Editor::notifyClientOfAttachmentUpdates()
+{
+    if (auto* editorClient = client()) {
+        for (auto& identifier : m_removedAttachmentIdentifiers)
+            editorClient->didRemoveAttachment(identifier);
+        for (auto& identifier : m_insertedAttachmentIdentifiers)
+            editorClient->didInsertAttachment(identifier);
+    }
+
+    m_removedAttachmentIdentifiers.clear();
+    m_insertedAttachmentIdentifiers.clear();
+}
+
 void Editor::insertAttachment(const String& identifier, const String& filename, const String& filepath, std::optional<String> contentType)
 {
     if (!contentType)
@@ -3751,7 +3795,7 @@
 
 void Editor::insertAttachmentFromFile(const String& identifier, const String& filename, const String& contentType, Ref<File>&& file)
 {
-    auto attachment = HTMLAttachmentElement::create(HTMLNames::attachmentTag, document(), identifier);
+    auto attachment = HTMLAttachmentElement::create(HTMLNames::attachmentTag, document());
     attachment->setAttribute(HTMLNames::titleAttr, filename);
     attachment->setAttribute(HTMLNames::subtitleAttr, fileSizeDescription(file->size()));
     attachment->setAttribute(HTMLNames::typeAttr, contentType);

Modified: trunk/Source/WebCore/editing/Editor.h (224511 => 224512)


--- trunk/Source/WebCore/editing/Editor.h	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebCore/editing/Editor.h	2017-11-06 21:46:54 UTC (rev 224512)
@@ -503,6 +503,8 @@
 #if ENABLE(ATTACHMENT_ELEMENT)
     WEBCORE_EXPORT void insertAttachment(const String& identifier, const String& filename, const String& filepath, std::optional<String> contentType = std::nullopt);
     WEBCORE_EXPORT void insertAttachment(const String& identifier, const String& filename, Ref<SharedBuffer>&& data, std::optional<String> contentType = std::nullopt);
+    void didInsertAttachmentElement(HTMLAttachmentElement&);
+    void didRemoveAttachmentElement(HTMLAttachmentElement&);
 #endif
 
 private:
@@ -550,6 +552,12 @@
     static RefPtr<SharedBuffer> dataInRTFFormat(NSAttributedString *);
 #endif
 
+    void scheduleEditorUIUpdate();
+
+#if ENABLE(ATTACHMENT_ELEMENT)
+    void notifyClientOfAttachmentUpdates();
+#endif
+
     void postTextStateChangeNotificationForCut(const String&, const VisibleSelection&);
 
     Frame& m_frame;
@@ -569,6 +577,11 @@
     EditorParagraphSeparator m_defaultParagraphSeparator { EditorParagraphSeparatorIsDiv };
     bool m_overwriteModeEnabled { false };
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+    HashSet<String> m_insertedAttachmentIdentifiers;
+    HashSet<String> m_removedAttachmentIdentifiers;
+#endif
+
     VisibleSelection m_oldSelectionForEditorUIUpdate;
     Timer m_editorUIUpdateTimer;
     bool m_editorUIUpdateTimerShouldCheckSpellingAndGrammar { false };

Modified: trunk/Source/WebCore/html/HTMLAttachmentElement.cpp (224511 => 224512)


--- trunk/Source/WebCore/html/HTMLAttachmentElement.cpp	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebCore/html/HTMLAttachmentElement.cpp	2017-11-06 21:46:54 UTC (rev 224512)
@@ -33,24 +33,17 @@
 #include "Frame.h"
 #include "HTMLNames.h"
 #include "RenderAttachment.h"
-#include <wtf/UUID.h>
 
 namespace WebCore {
 
 using namespace HTMLNames;
 
-HTMLAttachmentElement::HTMLAttachmentElement(const QualifiedName& tagName, Document& document, const String& identifier)
+HTMLAttachmentElement::HTMLAttachmentElement(const QualifiedName& tagName, Document& document)
     : HTMLElement(tagName, document)
-    , m_uniqueIdentifier(identifier)
 {
     ASSERT(hasTagName(attachmentTag));
 }
 
-HTMLAttachmentElement::HTMLAttachmentElement(const QualifiedName& tagName, Document& document)
-    : HTMLAttachmentElement(tagName, document, createCanonicalUUIDString())
-{
-}
-
 HTMLAttachmentElement::~HTMLAttachmentElement() = default;
 
 Ref<HTMLAttachmentElement> HTMLAttachmentElement::create(const QualifiedName& tagName, Document& document)
@@ -58,11 +51,6 @@
     return adoptRef(*new HTMLAttachmentElement(tagName, document));
 }
 
-Ref<HTMLAttachmentElement> HTMLAttachmentElement::create(const QualifiedName& tagName, Document& document, const String& identifier)
-{
-    return adoptRef(*new HTMLAttachmentElement(tagName, document, identifier));
-}
-
 RenderPtr<RenderElement> HTMLAttachmentElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
 {
     return createRenderer<RenderAttachment>(*this, WTFMove(style));
@@ -81,6 +69,25 @@
         renderer->invalidate();
 }
 
+Node::InsertedIntoAncestorResult HTMLAttachmentElement::insertedIntoAncestor(InsertionType type, ContainerNode& ancestor)
+{
+    auto result = HTMLElement::insertedIntoAncestor(type, ancestor);
+    if (auto* frame = document().frame()) {
+        if (type.connectedToDocument)
+            frame->editor().didInsertAttachmentElement(*this);
+    }
+    return result;
+}
+
+void HTMLAttachmentElement::removedFromAncestor(RemovalType type, ContainerNode& ancestor)
+{
+    HTMLElement::removedFromAncestor(type, ancestor);
+    if (auto* frame = document().frame()) {
+        if (type.disconnectedFromDocument)
+            frame->editor().didRemoveAttachmentElement(*this);
+    }
+}
+
 void HTMLAttachmentElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
 {
     if (name == progressAttr || name == subtitleAttr || name == titleAttr || name == typeAttr) {
@@ -91,6 +98,16 @@
     HTMLElement::parseAttribute(name, value);
 }
 
+String HTMLAttachmentElement::uniqueIdentifier() const
+{
+    return attributeWithoutSynchronization(HTMLNames::webkitattachmentidAttr);
+}
+
+void HTMLAttachmentElement::setUniqueIdentifier(const String& identifier)
+{
+    setAttributeWithoutSynchronization(HTMLNames::webkitattachmentidAttr, identifier);
+}
+
 String HTMLAttachmentElement::attachmentTitle() const
 {
     auto& title = attributeWithoutSynchronization(titleAttr);

Modified: trunk/Source/WebCore/html/HTMLAttachmentElement.h (224511 => 224512)


--- trunk/Source/WebCore/html/HTMLAttachmentElement.h	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebCore/html/HTMLAttachmentElement.h	2017-11-06 21:46:54 UTC (rev 224512)
@@ -37,14 +37,16 @@
 class HTMLAttachmentElement final : public HTMLElement {
 public:
     static Ref<HTMLAttachmentElement> create(const QualifiedName&, Document&);
-    static Ref<HTMLAttachmentElement> create(const QualifiedName&, Document&, const String& identifier);
 
     WEBCORE_EXPORT File* file() const;
     void setFile(File*);
 
-    WEBCORE_EXPORT String uniqueIdentifier() const { return m_uniqueIdentifier; }
-    void setUniqueIdentifier(const String& identifier) { m_uniqueIdentifier = identifier; }
+    WEBCORE_EXPORT String uniqueIdentifier() const;
+    void setUniqueIdentifier(const String&);
 
+    InsertedIntoAncestorResult insertedIntoAncestor(InsertionType, ContainerNode&) final;
+    void removedFromAncestor(RemovalType, ContainerNode&) final;
+
     WEBCORE_EXPORT String attachmentTitle() const;
     String attachmentType() const;
 
@@ -52,7 +54,6 @@
 
 private:
     HTMLAttachmentElement(const QualifiedName&, Document&);
-    HTMLAttachmentElement(const QualifiedName&, Document&, const String& identifier);
     virtual ~HTMLAttachmentElement();
 
     RenderPtr<RenderElement> createElementRenderer(RenderStyle&&, const RenderTreePosition&) final;
@@ -68,7 +69,6 @@
     void parseAttribute(const QualifiedName&, const AtomicString&) final;
     
     RefPtr<File> m_file;
-    String m_uniqueIdentifier;
 };
 
 } // namespace WebCore

Modified: trunk/Source/WebCore/html/HTMLAttributeNames.in (224511 => 224512)


--- trunk/Source/WebCore/html/HTMLAttributeNames.in	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebCore/html/HTMLAttributeNames.in	2017-11-06 21:46:54 UTC (rev 224512)
@@ -381,6 +381,7 @@
 vlink
 vspace
 webkitallowfullscreen
+webkitattachmentid
 webkitattachmentpath
 webkitdirectory
 width

Modified: trunk/Source/WebCore/page/EditorClient.h (224511 => 224512)


--- trunk/Source/WebCore/page/EditorClient.h	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebCore/page/EditorClient.h	2017-11-06 21:46:54 UTC (rev 224512)
@@ -72,6 +72,11 @@
     virtual void didApplyStyle() = 0;
     virtual bool shouldMoveRangeAfterDelete(Range*, Range*) = 0;
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+    virtual void didInsertAttachment(const String&) { }
+    virtual void didRemoveAttachment(const String&) { }
+#endif
+
     virtual void didBeginEditing() = 0;
     virtual void respondToChangedContents() = 0;
     virtual void respondToChangedSelection(Frame*) = 0;

Modified: trunk/Source/WebKit/ChangeLog (224511 => 224512)


--- trunk/Source/WebKit/ChangeLog	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebKit/ChangeLog	2017-11-06 21:46:54 UTC (rev 224512)
@@ -1,3 +1,38 @@
+2017-11-06  Wenson Hsieh  <[email protected]>
+
+        [Attachment Support] Implement delegate hooks for attachment element insertion and removal
+        https://bugs.webkit.org/show_bug.cgi?id=179016
+        <rdar://problem/35250890>
+
+        Reviewed by Tim Horton.
+
+        Adds boilerplate plumbing to WebEditorClient, WebPage, and the usual machinery in the UI process to notify
+        WebKit2 clients when attachment elements have been inserted or removed from the document. See the WebCore
+        ChangeLog for more details about the implementation, or the Tools ChangeLog for more information about new API
+        tests.
+
+        * UIProcess/API/Cocoa/WKUIDelegatePrivate.h:
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _didInsertAttachment:]):
+        (-[WKWebView _didRemoveAttachment:]):
+        * UIProcess/API/Cocoa/WKWebViewInternal.h:
+        * UIProcess/Cocoa/PageClientImplCocoa.h:
+        * UIProcess/Cocoa/PageClientImplCocoa.mm:
+        (WebKit::PageClientImplCocoa::didInsertAttachment):
+        (WebKit::PageClientImplCocoa::didRemoveAttachment):
+        * UIProcess/PageClient.h:
+        (WebKit::PageClient::didInsertAttachment):
+        (WebKit::PageClient::didRemoveAttachment):
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::didInsertAttachment):
+        (WebKit::WebPageProxy::didRemoveAttachment):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebPageProxy.messages.in:
+        * WebProcess/WebCoreSupport/WebEditorClient.cpp:
+        (WebKit::WebEditorClient::didInsertAttachment):
+        (WebKit::WebEditorClient::didRemoveAttachment):
+        * WebProcess/WebCoreSupport/WebEditorClient.h:
+
 2017-11-06  Jeremy Jones  <[email protected]>
 
         iOS element fullscreen should use a UIGestureRecognizer to detect user interaction.

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKUIDelegatePrivate.h (224511 => 224512)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKUIDelegatePrivate.h	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKUIDelegatePrivate.h	2017-11-06 21:46:54 UTC (rev 224512)
@@ -110,6 +110,9 @@
 - (void)_webView:(WKWebView *)webView runBeforeUnloadConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler WK_API_AVAILABLE(macosx(10.13), ios(11.0));
 - (void)_webView:(WKWebView *)webView editorStateDidChange:(NSDictionary *)editorState WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
 
+- (void)_webView:(WKWebView *)webView didRemoveAttachment:(_WKAttachment *)attachment WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+- (void)_webView:(WKWebView *)webView didInsertAttachment:(_WKAttachment *)attachment WK_API_AVAILABLE(macosx(WK_MAC_TBA), ios(WK_IOS_TBA));
+
 #if TARGET_OS_IPHONE
 - (BOOL)_webView:(WKWebView *)webView shouldIncludeAppLinkActionsForElement:(_WKActivatedElementInfo *)element WK_API_AVAILABLE(ios(9.0));
 - (NSArray *)_webView:(WKWebView *)webView actionsForElement:(_WKActivatedElementInfo *)element defaultActions:(NSArray<_WKElementAction *> *)defaultActions;

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm (224511 => 224512)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2017-11-06 21:46:54 UTC (rev 224512)
@@ -1197,6 +1197,24 @@
         [uiDelegate _webView:self editorStateDidChange:dictionaryRepresentationForEditorState(_page->editorState())];
 }
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+
+- (void)_didInsertAttachment:(NSString *)identifier
+{
+    id <WKUIDelegatePrivate> uiDelegate = (id <WKUIDelegatePrivate>)self.UIDelegate;
+    if ([uiDelegate respondsToSelector:@selector(_webView:didInsertAttachment:)])
+        [uiDelegate _webView:self didInsertAttachment:[wrapper(API::Attachment::create(identifier, *_page).leakRef()) autorelease]];
+}
+
+- (void)_didRemoveAttachment:(NSString *)identifier
+{
+    id <WKUIDelegatePrivate> uiDelegate = (id <WKUIDelegatePrivate>)self.UIDelegate;
+    if ([uiDelegate respondsToSelector:@selector(_webView:didRemoveAttachment:)])
+        [uiDelegate _webView:self didRemoveAttachment:[wrapper(API::Attachment::create(identifier, *_page).leakRef()) autorelease]];
+}
+
+#endif // ENABLE(ATTACHMENT_ELEMENT)
+
 #pragma mark iOS-specific methods
 
 #if PLATFORM(IOS)

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewInternal.h (224511 => 224512)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewInternal.h	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewInternal.h	2017-11-06 21:46:54 UTC (rev 224512)
@@ -148,6 +148,11 @@
 @property (nonatomic, readonly) UIEdgeInsets _computedUnobscuredSafeAreaInset;
 #endif
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+- (void)_didRemoveAttachment:(NSString *)identifier;
+- (void)_didInsertAttachment:(NSString *)identifier;
+#endif
+
 - (WKPageRef)_pageForTesting;
 - (WebKit::WebPageProxy*)_page;
 

Modified: trunk/Source/WebKit/UIProcess/Cocoa/PageClientImplCocoa.h (224511 => 224512)


--- trunk/Source/WebKit/UIProcess/Cocoa/PageClientImplCocoa.h	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebKit/UIProcess/Cocoa/PageClientImplCocoa.h	2017-11-06 21:46:54 UTC (rev 224512)
@@ -37,6 +37,12 @@
         : m_webView(webView) { }
     void isPlayingAudioWillChange() final;
     void isPlayingAudioDidChange() final;
+
+#if ENABLE(ATTACHMENT_ELEMENT)
+    void didInsertAttachment(const String& identifier) final;
+    void didRemoveAttachment(const String& identifier) final;
+#endif
+
 protected:
     WKWebView *m_webView;
 };

Modified: trunk/Source/WebKit/UIProcess/Cocoa/PageClientImplCocoa.mm (224511 => 224512)


--- trunk/Source/WebKit/UIProcess/Cocoa/PageClientImplCocoa.mm	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebKit/UIProcess/Cocoa/PageClientImplCocoa.mm	2017-11-06 21:46:54 UTC (rev 224512)
@@ -26,7 +26,7 @@
 #import "config.h"
 #import "PageClientImplCocoa.h"
 
-#import "WKWebViewPrivate.h"
+#import "WKWebViewInternal.h"
 
 namespace WebKit {
 
@@ -43,5 +43,27 @@
     [m_webView didChangeValueForKey:NSStringFromSelector(@selector(_isPlayingAudio))];
 #endif
 }
+
+#if ENABLE(ATTACHMENT_ELEMENT)
+
+void PageClientImplCocoa::didInsertAttachment(const String& identifier)
+{
+#if WK_API_ENABLED
+    [m_webView _didInsertAttachment:identifier];
+#else
+    UNUSED_PARAM(identifier);
+#endif
+}
+
+void PageClientImplCocoa::didRemoveAttachment(const String& identifier)
+{
+#if WK_API_ENABLED
+    [m_webView _didRemoveAttachment:identifier];
+#else
+    UNUSED_PARAM(identifier);
+#endif
+}
+
+#endif
     
 }

Modified: trunk/Source/WebKit/UIProcess/PageClient.h (224511 => 224512)


--- trunk/Source/WebKit/UIProcess/PageClient.h	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebKit/UIProcess/PageClient.h	2017-11-06 21:46:54 UTC (rev 224512)
@@ -384,6 +384,11 @@
     virtual void didChangeDataInteractionCaretRect(const WebCore::IntRect& previousCaretRect, const WebCore::IntRect& caretRect) = 0;
 #endif
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+    virtual void didInsertAttachment(const String& identifier) { }
+    virtual void didRemoveAttachment(const String& identifier) { }
+#endif
+
 #if PLATFORM(GTK) || PLATFORM(WPE)
     virtual JSGlobalContextRef _javascript_GlobalContext() { return nullptr; }
 #endif

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.cpp (224511 => 224512)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.cpp	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.cpp	2017-11-06 21:46:54 UTC (rev 224512)
@@ -7113,6 +7113,16 @@
     m_process->send(Messages::WebPage::InsertAttachment(identifier, filename, contentType, IPC::SharedBufferDataReference { &data }, callbackID), m_pageID);
 }
 
+void WebPageProxy::didInsertAttachment(const String& identifier)
+{
+    m_pageClient.didInsertAttachment(identifier);
+}
+
+void WebPageProxy::didRemoveAttachment(const String& identifier)
+{
+    m_pageClient.didRemoveAttachment(identifier);
+}
+
 #endif // ENABLE(ATTACHMENT_ELEMENT)
 
 } // namespace WebKit

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.h (224511 => 224512)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.h	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.h	2017-11-06 21:46:54 UTC (rev 224512)
@@ -1637,6 +1637,11 @@
 
     void stopAllURLSchemeTasks();
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+    void didInsertAttachment(const String& identifier);
+    void didRemoveAttachment(const String& identifier);
+#endif
+
     PageClient& m_pageClient;
     Ref<API::PageConfiguration> m_configuration;
 

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.messages.in (224511 => 224512)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.messages.in	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.messages.in	2017-11-06 21:46:54 UTC (rev 224512)
@@ -498,4 +498,9 @@
     StopURLSchemeTask(uint64_t handlerIdentifier, uint64_t taskIdentifier)
 
     RequestStorageAccess(String subFrameHost, String topFrameHost, uint64_t contextID)
+
+#if ENABLE(ATTACHMENT_ELEMENT)
+    DidInsertAttachment(String identifier)
+    DidRemoveAttachment(String identifier)
+#endif
 }

Modified: trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp (224511 => 224512)


--- trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.cpp	2017-11-06 21:46:54 UTC (rev 224512)
@@ -157,6 +157,20 @@
     return result;
 }
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+
+void WebEditorClient::didInsertAttachment(const String& identifier)
+{
+    m_page->send(Messages::WebPageProxy::DidInsertAttachment(identifier));
+}
+
+void WebEditorClient::didRemoveAttachment(const String& identifier)
+{
+    m_page->send(Messages::WebPageProxy::DidRemoveAttachment(identifier));
+}
+
+#endif
+
 void WebEditorClient::didApplyStyle()
 {
     m_page->didApplyStyle();

Modified: trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h (224511 => 224512)


--- trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Source/WebKit/WebProcess/WebCoreSupport/WebEditorClient.h	2017-11-06 21:46:54 UTC (rev 224512)
@@ -59,6 +59,11 @@
     void didApplyStyle() final;
     bool shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*) final;
 
+#if ENABLE(ATTACHMENT_ELEMENT)
+    void didInsertAttachment(const String& identifier) final;
+    void didRemoveAttachment(const String& identifier) final;
+#endif
+
     void didBeginEditing() final;
     void respondToChangedContents() final;
     void respondToChangedSelection(WebCore::Frame*) final;

Modified: trunk/Tools/ChangeLog (224511 => 224512)


--- trunk/Tools/ChangeLog	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Tools/ChangeLog	2017-11-06 21:46:54 UTC (rev 224512)
@@ -1,3 +1,33 @@
+2017-11-06  Wenson Hsieh  <[email protected]>
+
+        [Attachment Support] Implement delegate hooks for attachment element insertion and removal
+        https://bugs.webkit.org/show_bug.cgi?id=179016
+        <rdar://problem/35250890>
+
+        Reviewed by Tim Horton.
+
+        Introduces new API tests to check that various editing operations will or won't result in the new attachment
+        insertion and removal delegate hooks being fired. Additionally refactors an existing test to verify that
+        attachments insertion and removal is observable by the UI delegate.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm:
+        (-[AttachmentUpdateObserver init]):
+        (-[AttachmentUpdateObserver inserted]):
+        (-[AttachmentUpdateObserver removed]):
+        (-[AttachmentUpdateObserver _webView:didInsertAttachment:]):
+        (-[AttachmentUpdateObserver _webView:didRemoveAttachment:]):
+        (TestWebKitAPI::ObserveAttachmentUpdatesForScope::ObserveAttachmentUpdatesForScope):
+        (TestWebKitAPI::ObserveAttachmentUpdatesForScope::~ObserveAttachmentUpdatesForScope):
+        (TestWebKitAPI::ObserveAttachmentUpdatesForScope::expectAttachmentUpdates):
+
+        Implement a testing mechanism to temporarily bind a UI delegate to a given WKWebView and listen for inserted or
+        removed attachments over the course of a particular scope. The API tests use this mechanism to check that the UI
+        delegate hooks added in this patch are invoked with the right attachments when performing edit commands.
+
+        (-[TestWKWebView _synchronouslyExecuteEditCommand:argument:]):
+        (-[TestWKWebView expectUpdatesAfterCommand:withArgument:expectedRemovals:expectedInsertions:]):
+        (TestWebKitAPI::TEST):
+
 2017-11-06  Christopher Reid  <[email protected]>
 
         Use enum classes within FileSystem

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm (224511 => 224512)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm	2017-11-06 21:44:34 UTC (rev 224511)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm	2017-11-06 21:46:54 UTC (rev 224512)
@@ -33,6 +33,90 @@
 
 #if WK_API_ENABLED
 
+@interface AttachmentUpdateObserver : NSObject <WKUIDelegatePrivate>
+@property (nonatomic, readonly) NSArray *inserted;
+@property (nonatomic, readonly) NSArray *removed;
+@end
+
+@implementation AttachmentUpdateObserver {
+    RetainPtr<NSMutableArray<_WKAttachment *>> _inserted;
+    RetainPtr<NSMutableArray<_WKAttachment *>> _removed;
+}
+
+- (instancetype)init
+{
+    if (self = [super init]) {
+        _inserted = adoptNS([[NSMutableArray alloc] init]);
+        _removed = adoptNS([[NSMutableArray alloc] init]);
+    }
+    return self;
+}
+
+- (NSArray<_WKAttachment *> *)inserted
+{
+    return _inserted.get();
+}
+
+- (NSArray<_WKAttachment *> *)removed
+{
+    return _removed.get();
+}
+
+- (void)_webView:(WKWebView *)webView didInsertAttachment:(_WKAttachment *)attachment
+{
+    [_inserted addObject:attachment];
+}
+
+- (void)_webView:(WKWebView *)webView didRemoveAttachment:(_WKAttachment *)attachment
+{
+    [_removed addObject:attachment];
+}
+
+@end
+
+namespace TestWebKitAPI {
+
+class ObserveAttachmentUpdatesForScope {
+public:
+    ObserveAttachmentUpdatesForScope(TestWKWebView *webView)
+        : m_webView(webView)
+    {
+        m_previousDelegate = retainPtr(webView.UIDelegate);
+        m_observer = adoptNS([[AttachmentUpdateObserver alloc] init]);
+        webView.UIDelegate = m_observer.get();
+    }
+
+    ~ObserveAttachmentUpdatesForScope()
+    {
+        [m_webView setUIDelegate:m_previousDelegate.get()];
+    }
+
+    AttachmentUpdateObserver *observer() const { return m_observer.get(); }
+
+    void expectAttachmentUpdates(NSArray<_WKAttachment *> *removed, NSArray<_WKAttachment *> *inserted)
+    {
+        BOOL removedAttachmentsMatch = [observer().removed isEqual:removed];
+        if (!removedAttachmentsMatch)
+            NSLog(@"Expected removed attachments: %@ to match %@.", observer().removed, removed);
+        EXPECT_TRUE(removedAttachmentsMatch);
+
+        BOOL insertedAttachmentsMatch = [observer().inserted isEqual:inserted];
+        if (!insertedAttachmentsMatch)
+            NSLog(@"Expected inserted attachments: %@ to match %@.", observer().inserted, inserted);
+        EXPECT_TRUE(insertedAttachmentsMatch);
+    }
+
+private:
+    RetainPtr<AttachmentUpdateObserver> m_observer;
+    RetainPtr<TestWKWebView> m_webView;
+    RetainPtr<id> m_previousDelegate;
+};
+
+}
+
+@interface TestWKWebView (AttachmentTesting)
+@end
+
 static RetainPtr<TestWKWebView> webViewForTestingAttachments()
 {
     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
@@ -57,6 +141,18 @@
 
 @implementation TestWKWebView (AttachmentTesting)
 
+- (BOOL)_synchronouslyExecuteEditCommand:(NSString *)command argument:(NSString *)argument
+{
+    __block bool done = false;
+    __block bool success;
+    [self _executeEditCommand:command argument:argument completion:^(BOOL completionSuccess) {
+        done = true;
+        success = completionSuccess;
+    }];
+    TestWebKitAPI::Util::run(&done);
+    return success;
+}
+
 - (_WKAttachment *)synchronouslyInsertAttachmentWithFilename:(NSString *)filename contentType:(NSString *)contentType data:(NSData *)data options:(_WKAttachmentDisplayOptions *)options
 {
     __block bool done = false;
@@ -72,6 +168,13 @@
     return [self stringByEvaluatingJavaScript:[NSString stringWithFormat:@"document.querySelector('%@').getAttribute('%@')", querySelector, attributeName]];
 }
 
+- (void)expectUpdatesAfterCommand:(NSString *)command withArgument:(NSString *)argument expectedRemovals:(NSArray<_WKAttachment *> *)removed expectedInsertions:(NSArray<_WKAttachment *> *)inserted
+{
+    TestWebKitAPI::ObserveAttachmentUpdatesForScope observer(self);
+    EXPECT_TRUE([self _synchronouslyExecuteEditCommand:command argument:argument]);
+    observer.expectAttachmentUpdates(removed, inserted);
+}
+
 @end
 
 namespace TestWebKitAPI {
@@ -79,21 +182,116 @@
 TEST(WKAttachmentTests, AttachmentElementInsertion)
 {
     auto webView = webViewForTestingAttachments();
+    RetainPtr<_WKAttachment> firstAttachment;
+    RetainPtr<_WKAttachment> secondAttachment;
+    {
+        ObserveAttachmentUpdatesForScope observer(webView.get());
+        // Use the given content type for the attachment element's type.
+        firstAttachment = retainPtr([webView synchronouslyInsertAttachmentWithFilename:@"foo" contentType:@"text/html" data:testHTMLData() options:nil]);
+        EXPECT_WK_STREQ(@"foo", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
+        EXPECT_WK_STREQ(@"text/html", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
+        EXPECT_WK_STREQ(@"38 bytes", [webView valueOfAttribute:@"subtitle" forQuerySelector:@"attachment"]);
+        observer.expectAttachmentUpdates(@[ ], @[ firstAttachment.get() ]);
+    }
+    {
+        ObserveAttachmentUpdatesForScope scope(webView.get());
+        // Since no content type is explicitly specified, compute it from the file extension.
+        [webView _executeEditCommand:@"DeleteBackward" argument:nil completion:nil];
+        secondAttachment = retainPtr([webView synchronouslyInsertAttachmentWithFilename:@"bar.png" contentType:nil data:testImageData() options:nil]);
+        EXPECT_WK_STREQ(@"bar.png", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
+        EXPECT_WK_STREQ(@"image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
+        EXPECT_WK_STREQ(@"37 KB", [webView valueOfAttribute:@"subtitle" forQuerySelector:@"attachment"]);
+        scope.expectAttachmentUpdates(@[ firstAttachment.get() ], @[ secondAttachment.get() ]);
+    }
+}
 
-    // Use the given content type for the attachment element's type.
-    [webView synchronouslyInsertAttachmentWithFilename:@"foo" contentType:@"text/html" data:testHTMLData() options:nil];
-    EXPECT_WK_STREQ(@"foo", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
-    EXPECT_WK_STREQ(@"text/html", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
-    EXPECT_WK_STREQ(@"38 bytes", [webView valueOfAttribute:@"subtitle" forQuerySelector:@"attachment"]);
+TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingAndDeletingNewline)
+{
+    auto webView = webViewForTestingAttachments();
+    RetainPtr<_WKAttachment> attachment;
+    {
+        ObserveAttachmentUpdatesForScope observer(webView.get());
+        attachment = retainPtr([webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil]);
+        observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
+    }
+    [webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
+    [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
+    [webView stringByEvaluatingJavaScript:@"getSelection().collapse(document.body)"];
+    [webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
+    [webView expectUpdatesAfterCommand:@"DeleteForward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
+}
 
-    // Since no content type is explicitly specified, compute it from the file extension.
-    [webView _executeEditCommand:@"DeleteBackward" argument:nil completion:nil];
-    [webView synchronouslyInsertAttachmentWithFilename:@"bar.png" contentType:nil data:testImageData() options:nil];
-    EXPECT_WK_STREQ(@"bar.png", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
-    EXPECT_WK_STREQ(@"image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
-    EXPECT_WK_STREQ(@"37 KB", [webView valueOfAttribute:@"subtitle" forQuerySelector:@"attachment"]);
+TEST(WKAttachmentTests, AttachmentUpdatesWhenUndoingAndRedoing)
+{
+    auto webView = webViewForTestingAttachments();
+    RetainPtr<_WKAttachment> attachment;
+    {
+        ObserveAttachmentUpdatesForScope observer(webView.get());
+        attachment = retainPtr([webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil]);
+        observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
+    }
+    [webView expectUpdatesAfterCommand:@"Undo" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
+    [webView expectUpdatesAfterCommand:@"Redo" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
+    [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
+    [webView expectUpdatesAfterCommand:@"Undo" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
+    [webView expectUpdatesAfterCommand:@"Redo" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
 }
 
+TEST(WKAttachmentTests, AttachmentUpdatesWhenChangingFontStyles)
+{
+    auto webView = webViewForTestingAttachments();
+    RetainPtr<_WKAttachment> attachment;
+    [webView _synchronouslyExecuteEditCommand:@"InsertText" argument:@"Hello"];
+    {
+        ObserveAttachmentUpdatesForScope observer(webView.get());
+        attachment = retainPtr([webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil]);
+        observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
+    }
+    [webView expectUpdatesAfterCommand:@"InsertText" withArgument:@"World" expectedRemovals:@[] expectedInsertions:@[]];
+    [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
+    [webView expectUpdatesAfterCommand:@"ToggleBold" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
+    [webView expectUpdatesAfterCommand:@"ToggleItalic" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
+    [webView expectUpdatesAfterCommand:@"ToggleUnderline" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
+
+    // Inserting text should delete the current selection, removing the attachment in the process.
+    [webView expectUpdatesAfterCommand:@"InsertText" withArgument:@"foo" expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
+}
+
+TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingLists)
+{
+    auto webView = webViewForTestingAttachments();
+    RetainPtr<_WKAttachment> attachment;
+    {
+        ObserveAttachmentUpdatesForScope observer(webView.get());
+        attachment = retainPtr([webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil]);
+        observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
+        NSLog(@"The markup is now %@", [webView stringByEvaluatingJavaScript:@"document.body.innerHTML"]);
+    }
+    [webView expectUpdatesAfterCommand:@"InsertOrderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
+    // This edit command behaves more like a "toggle", and will actually break us out of the list we just inserted.
+    [webView expectUpdatesAfterCommand:@"InsertOrderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
+    [webView expectUpdatesAfterCommand:@"InsertUnorderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
+    [webView expectUpdatesAfterCommand:@"InsertUnorderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
+}
+
+TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingRichMarkup)
+{
+    auto webView = webViewForTestingAttachments();
+    RetainPtr<_WKAttachment> attachment;
+    {
+        ObserveAttachmentUpdatesForScope observer(webView.get());
+        [webView _synchronouslyExecuteEditCommand:@"InsertHTML" argument:@"<div><strong><attachment title='a' webkitattachmentid='a06fec41-9aa0-4c2c-ba3a-0149b54aad99'></attachment></strong></div>"];
+        attachment = observer.observer().inserted[0];
+        observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
+    }
+    {
+        ObserveAttachmentUpdatesForScope observer(webView.get());
+        [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').remove()"];
+        [webView waitForNextPresentationUpdate];
+        observer.expectAttachmentUpdates(@[attachment.get()], @[ ]);
+    }
+}
+
 } // namespace TestWebKitAPI
 
 #endif // WK_API_ENABLED
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to