Make it possible to insert editable images with a gesture
authortimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 25 Nov 2018 12:11:16 +0000 (12:11 +0000)
committertimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 25 Nov 2018 12:11:16 +0000 (12:11 +0000)
https://bugs.webkit.org/show_bug.cgi?id=191937

Reviewed by Wenson Hsieh.

Source/WebCore:

Tests:
    editing/images/redo-insert-editable-image-maintains-strokes.html,
    editing/images/undo-insert-editable-image.html,
    editing/images/basic-editable-image-from-execCommand.html

* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/editing/EditorCommand.cpp:
* Source/WebCore/en.lproj/Localizable.strings:
* editing/EditAction.h:
* editing/Editor.cpp:
(WebCore::Editor::insertEditableImage):
* editing/Editor.h:
* editing/InsertEditableImageCommand.cpp: Added.
(WebCore::InsertEditableImageCommand::InsertEditableImageCommand):
(WebCore::InsertEditableImageCommand::doApply):
* editing/InsertEditableImageCommand.h: Added.
(WebCore::InsertEditableImageCommand::create):
* editing/VisibleSelection.cpp:
Add an editor command that inserts an editable image.
It will likely get a bit more complicated, but for now it just inserts
a 100% by 300px editable image.

Source/WebKit:

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _stylusTapGestureShouldCreateEditableImage]):
* UIProcess/API/Cocoa/WKWebViewInternal.h:
Add a internal getter for a WKWebViewConfiguration property.

* UIProcess/WebEditCommandProxy.cpp:
(WebKit::WebEditCommandProxy::nameForEditAction):
Add a undo name.

* UIProcess/WebPageProxy.h:
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView setupInteraction]):
(-[WKContentView cleanupInteraction]):
(-[WKContentView _removeDefaultGestureRecognizers]):
(-[WKContentView _addDefaultGestureRecognizers]):
Add a single-stylus-tap gesture recognizer.

(-[WKContentView _stylusSingleTapRecognized:]):
If allowed, request to insert an editable image when a stylus tap occurs.

* UIProcess/ios/WebPageProxyIOS.mm:
(WebKit::WebPageProxy::handleStylusSingleTapAtPoint):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::handleStylusSingleTapAtPoint):
Do a hit test, select the hit position, insert an editable image, and
then de-assist any assisted node (to make the keyboard go away).
For now, we'll only insert if we hit non-replaced elements,
though this heuristic will need to be enhanced significantly once we
decide on a design.

Source/WebKitLegacy/mac:

* WebCoreSupport/WebEditorClient.mm:
(undoNameForEditAction):
Add a undo name.

Tools:

* WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::drawSquareInEditableImage):
If the canvas already has a drawing, draw a new stroke on top of it
instead of removing the existing stroke.

LayoutTests:

* editing/images/basic-editable-image-from-execCommand-expected.txt: Added.
* editing/images/basic-editable-image-from-execCommand.html: Added.
Add a test that ensures that editable images also work when
inserted via the editor command.

* editing/images/redo-insert-editable-image-maintains-strokes-expected.txt: Added.
* editing/images/redo-insert-editable-image-maintains-strokes.html: Added.
Add a test that ensures that strokes are maintained when re-doing an
un-done editable image insertion.

* editing/images/undo-insert-editable-image-expected.txt: Added.
* editing/images/undo-insert-editable-image.html: Added.
Add a test that ensures that the selection stays in a sensible place
when undoing and redoing editable image insertion.

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

33 files changed:
LayoutTests/ChangeLog
LayoutTests/editing/images/basic-editable-image-from-execCommand-expected.txt [new file with mode: 0644]
LayoutTests/editing/images/basic-editable-image-from-execCommand.html [new file with mode: 0644]
LayoutTests/editing/images/redo-insert-editable-image-maintains-strokes-expected.txt [new file with mode: 0644]
LayoutTests/editing/images/redo-insert-editable-image-maintains-strokes.html [new file with mode: 0644]
LayoutTests/editing/images/undo-insert-editable-image-expected.txt [new file with mode: 0644]
LayoutTests/editing/images/undo-insert-editable-image.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/Sources.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/editing/EditAction.h
Source/WebCore/editing/Editor.cpp
Source/WebCore/editing/Editor.h
Source/WebCore/editing/EditorCommand.cpp
Source/WebCore/editing/InsertEditableImageCommand.cpp [new file with mode: 0644]
Source/WebCore/editing/InsertEditableImageCommand.h [new file with mode: 0644]
Source/WebCore/editing/VisibleSelection.cpp
Source/WebCore/en.lproj/Localizable.strings
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/API/Cocoa/WKWebViewInternal.h
Source/WebKit/UIProcess/WebEditCommandProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
Source/WebKitLegacy/mac/ChangeLog
Source/WebKitLegacy/mac/WebCoreSupport/WebEditorClient.mm
Tools/ChangeLog
Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm

index f64e0b4..04cf1ed 100644 (file)
@@ -1,3 +1,25 @@
+2018-11-25  Tim Horton  <timothy_horton@apple.com>
+
+        Make it possible to insert editable images with a gesture
+        https://bugs.webkit.org/show_bug.cgi?id=191937
+
+        Reviewed by Wenson Hsieh.
+
+        * editing/images/basic-editable-image-from-execCommand-expected.txt: Added.
+        * editing/images/basic-editable-image-from-execCommand.html: Added.
+        Add a test that ensures that editable images also work when
+        inserted via the editor command.
+
+        * editing/images/redo-insert-editable-image-maintains-strokes-expected.txt: Added.
+        * editing/images/redo-insert-editable-image-maintains-strokes.html: Added.
+        Add a test that ensures that strokes are maintained when re-doing an
+        un-done editable image insertion.
+
+        * editing/images/undo-insert-editable-image-expected.txt: Added.
+        * editing/images/undo-insert-editable-image.html: Added.
+        Add a test that ensures that the selection stays in a sensible place
+        when undoing and redoing editable image insertion.
+
 2018-11-23  Ryosuke Niwa  <rniwa@webkit.org>
 
         REGRESSION (r236785): Nullptr crash in StyledMarkupAccumulator::traverseNodesForSerialization
diff --git a/LayoutTests/editing/images/basic-editable-image-from-execCommand-expected.txt b/LayoutTests/editing/images/basic-editable-image-from-execCommand-expected.txt
new file mode 100644 (file)
index 0000000..9101159
--- /dev/null
@@ -0,0 +1,3 @@
+
+Had 0 strokes in editable image before drawing.
+Had 1 stroke in editable image after drawing.
diff --git a/LayoutTests/editing/images/basic-editable-image-from-execCommand.html b/LayoutTests/editing/images/basic-editable-image-from-execCommand.html
new file mode 100644 (file)
index 0000000..40eaac8
--- /dev/null
@@ -0,0 +1,23 @@
+<!DOCTYPE html><!-- webkit-test-runner [ enableEditableImages=true ] -->
+<head>
+<script src="../../resources/ui-helper.js"></script>
+<script>
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+addEventListener("load", async () => {
+    window.getSelection().setPosition(document.body, 0);
+    document.execCommand("InsertEditableImage");
+    const initialNumberOfStrokesInEditableImage = (await UIHelper.numberOfStrokesInEditableImage());
+    await UIHelper.drawSquareInEditableImage();
+    const numberOfStrokesInEditableImageAfterDrawing = (await UIHelper.numberOfStrokesInEditableImage());
+    document.getElementById("log").innerHTML = `Had ${initialNumberOfStrokesInEditableImage} strokes in editable image before drawing.<br/>Had ${numberOfStrokesInEditableImageAfterDrawing} stroke in editable image after drawing.`;
+    testRunner.notifyDone();
+});
+</script>
+</head>
+<body contenteditable>
+<div id="log"></div>
+</body>
diff --git a/LayoutTests/editing/images/redo-insert-editable-image-maintains-strokes-expected.txt b/LayoutTests/editing/images/redo-insert-editable-image-maintains-strokes-expected.txt
new file mode 100644 (file)
index 0000000..6a73862
--- /dev/null
@@ -0,0 +1,6 @@
+
+Had 0 strokes in editable image before drawing.
+Had 1 stroke in editable image after drawing.
+Had 0 strokes in editable image after undo.
+Had 1 stroke in editable image after redo.
+Had 2 strokes in editable image after drawing again.
diff --git a/LayoutTests/editing/images/redo-insert-editable-image-maintains-strokes.html b/LayoutTests/editing/images/redo-insert-editable-image-maintains-strokes.html
new file mode 100644 (file)
index 0000000..669adc4
--- /dev/null
@@ -0,0 +1,29 @@
+<!DOCTYPE html><!-- webkit-test-runner [ enableEditableImages=true ] -->
+<head>
+<script src="../../resources/ui-helper.js"></script>
+<script>
+if (window.testRunner) {
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+}
+
+addEventListener("load", async () => {
+    window.getSelection().setPosition(document.body, 0);
+    document.execCommand("InsertEditableImage");
+    const initialNumberOfStrokesInEditableImage = (await UIHelper.numberOfStrokesInEditableImage());
+    await UIHelper.drawSquareInEditableImage();
+    const numberOfStrokesInEditableImageAfterDrawing = (await UIHelper.numberOfStrokesInEditableImage());
+    document.execCommand('undo', false, null);
+    const numberOfStrokesInEditableImageAfterUndo = (await UIHelper.numberOfStrokesInEditableImage());
+    document.execCommand('redo', false, null);
+    const numberOfStrokesInEditableImageAfterRedo = (await UIHelper.numberOfStrokesInEditableImage());
+    await UIHelper.drawSquareInEditableImage();
+    const numberOfStrokesInEditableImageAfterSecondDrawing = (await UIHelper.numberOfStrokesInEditableImage());
+    document.getElementById("log").innerHTML = `Had ${initialNumberOfStrokesInEditableImage} strokes in editable image before drawing.<br/>Had ${numberOfStrokesInEditableImageAfterDrawing} stroke in editable image after drawing.<br/>Had ${numberOfStrokesInEditableImageAfterUndo} strokes in editable image after undo.<br/>Had ${numberOfStrokesInEditableImageAfterRedo} stroke in editable image after redo.<br/>Had ${numberOfStrokesInEditableImageAfterSecondDrawing} strokes in editable image after drawing again.`;
+    testRunner.notifyDone();
+});
+</script>
+</head>
+<body contenteditable>
+<div id="log"></div>
+</body>
diff --git a/LayoutTests/editing/images/undo-insert-editable-image-expected.txt b/LayoutTests/editing/images/undo-insert-editable-image-expected.txt
new file mode 100644 (file)
index 0000000..ed2708e
--- /dev/null
@@ -0,0 +1,28 @@
+This test inserts a editable image, performs an undo, then a redo, ensuring that the selection remains after the image when re-done.
+
+before undo:
+| "Hello, world!"
+| <img>
+|   height="300px"
+|   style="display: block"
+|   width="100%"
+|   x-apple-editable-image=""
+|   <shadow:root>
+|     <attachment>
+|       style="display: none !important;"
+| <#selection-caret>
+
+after undo:
+| "Hello, world!<#selection-caret>"
+
+after redo:
+| "Hello, world!"
+| <img>
+|   height="300px"
+|   style="display: block"
+|   width="100%"
+|   x-apple-editable-image=""
+|   <shadow:root>
+|     <attachment>
+|       style="display: none !important;"
+| <#selection-caret>
diff --git a/LayoutTests/editing/images/undo-insert-editable-image.html b/LayoutTests/editing/images/undo-insert-editable-image.html
new file mode 100644 (file)
index 0000000..2bf1655
--- /dev/null
@@ -0,0 +1,20 @@
+<!DOCTYPE html><!-- webkit-test-runner [ enableEditableImages=true ] -->
+<head>
+<script src="../../resources/dump-as-markup.js" type="text/javascript"></script>
+</head>
+<body contenteditable>
+<div id="test">Hello, world!</div>
+<script>
+const div = document.getElementById("test");
+
+window.getSelection().setPosition(div, 13);
+document.execCommand("InsertEditableImage");
+
+Markup.description("This test inserts a editable image, performs an undo, then a redo, ensuring that the selection remains after the image when re-done.")
+Markup.dump(div, "before undo");
+document.execCommand('undo', false, null);
+Markup.dump(div, "after undo");
+document.execCommand('redo', false, null);
+Markup.dump(div, "after redo");
+</script>
+</body>
index 5d83c27..00e42d6 100644 (file)
@@ -1,3 +1,33 @@
+2018-11-25  Tim Horton  <timothy_horton@apple.com>
+
+        Make it possible to insert editable images with a gesture
+        https://bugs.webkit.org/show_bug.cgi?id=191937
+
+        Reviewed by Wenson Hsieh.
+
+        Tests:
+            editing/images/redo-insert-editable-image-maintains-strokes.html,
+            editing/images/undo-insert-editable-image.html,
+            editing/images/basic-editable-image-from-execCommand.html
+
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * Source/WebCore/editing/EditorCommand.cpp:
+        * Source/WebCore/en.lproj/Localizable.strings:
+        * editing/EditAction.h:
+        * editing/Editor.cpp:
+        (WebCore::Editor::insertEditableImage):
+        * editing/Editor.h:
+        * editing/InsertEditableImageCommand.cpp: Added.
+        (WebCore::InsertEditableImageCommand::InsertEditableImageCommand):
+        (WebCore::InsertEditableImageCommand::doApply):
+        * editing/InsertEditableImageCommand.h: Added.
+        (WebCore::InsertEditableImageCommand::create):
+        * editing/VisibleSelection.cpp:
+        Add an editor command that inserts an editable image.
+        It will likely get a bit more complicated, but for now it just inserts
+        a 100% by 300px editable image.
+
 2018-11-24  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [Cocoa] Fix a few localizable string descriptions in WebEditCommandProxy.cpp and WebEditorClient.mm
index a0a303c..b5a7fbb 100644 (file)
@@ -914,6 +914,7 @@ editing/FrameSelection.cpp
 editing/HTMLInterchange.cpp
 editing/InsertNestedListCommand.cpp
 editing/IndentOutdentCommand.cpp
+editing/InsertEditableImageCommand.cpp
 editing/InsertIntoTextNodeCommand.cpp
 editing/InsertLineBreakCommand.cpp
 editing/InsertListCommand.cpp
index d7929ab..13270eb 100644 (file)
                2D70BA1318074DDF0001908A /* PlatformCALayerCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D70BA1218074DDF0001908A /* PlatformCALayerCocoa.h */; settings = {ATTRIBUTES = (Private, ); }; };
                2D76BB821945632400CFD29A /* RunLoopObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D76BB801945632400CFD29A /* RunLoopObserver.h */; settings = {ATTRIBUTES = (Private, ); }; };
                2D7ED0AB1BAE99170043B3E5 /* TimerEventBasedMock.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D7ED0A91BAE99170043B3E5 /* TimerEventBasedMock.h */; };
+               2D81E1CF21A78CC200A32CF4 /* InsertEditableImageCommand.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D81E1CD21A78CC200A32CF4 /* InsertEditableImageCommand.h */; };
                2D8287F716E4A0380086BD00 /* HitTestLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 2D8287F516E4A0380086BD00 /* HitTestLocation.h */; settings = {ATTRIBUTES = (Private, ); }; };
                2D8B92CE203D13E1009C868F /* UnifiedSource481.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DE5F84AD1FA1A48B006DB63B /* UnifiedSource481.cpp */; };
                2D8B92CF203D13E1009C868F /* UnifiedSource482.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DE5F84C81FA1A4A4006DB63B /* UnifiedSource482.cpp */; };
                2D76BB801945632400CFD29A /* RunLoopObserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RunLoopObserver.h; sourceTree = "<group>"; };
                2D76BB8319456F8100CFD29A /* RunLoopObserver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RunLoopObserver.cpp; sourceTree = "<group>"; };
                2D7ED0A91BAE99170043B3E5 /* TimerEventBasedMock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimerEventBasedMock.h; sourceTree = "<group>"; };
+               2D81E1CB21A78CC100A32CF4 /* InsertEditableImageCommand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InsertEditableImageCommand.cpp; sourceTree = "<group>"; };
+               2D81E1CD21A78CC200A32CF4 /* InsertEditableImageCommand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InsertEditableImageCommand.h; sourceTree = "<group>"; };
                2D8287F416E4A0380086BD00 /* HitTestLocation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HitTestLocation.cpp; sourceTree = "<group>"; };
                2D8287F516E4A0380086BD00 /* HitTestLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HitTestLocation.h; sourceTree = "<group>"; };
                2D8FEBDA143E3EF70072502B /* CSSCrossfadeValue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CSSCrossfadeValue.cpp; sourceTree = "<group>"; };
                                93309D97099E64910056E581 /* HTMLInterchange.h */,
                                DB23C2C90A508D29002489EB /* IndentOutdentCommand.cpp */,
                                DB23C2CA0A508D29002489EB /* IndentOutdentCommand.h */,
+                               2D81E1CB21A78CC100A32CF4 /* InsertEditableImageCommand.cpp */,
+                               2D81E1CD21A78CC200A32CF4 /* InsertEditableImageCommand.h */,
                                93309D9A099E64910056E581 /* InsertIntoTextNodeCommand.cpp */,
                                93309D9B099E64910056E581 /* InsertIntoTextNodeCommand.h */,
                                93309D9C099E64910056E581 /* InsertLineBreakCommand.cpp */,
                                E52EFDF42112875A00AD282A /* InputMode.h in Headers */,
                                37E3524D12450C6600BAF5D9 /* InputType.h in Headers */,
                                C348612415FDE21E007A1CC9 /* InputTypeNames.h in Headers */,
+                               2D81E1CF21A78CC200A32CF4 /* InsertEditableImageCommand.h in Headers */,
                                93309DEA099E64920056E581 /* InsertIntoTextNodeCommand.h in Headers */,
                                93309DEC099E64920056E581 /* InsertLineBreakCommand.h in Headers */,
                                D07DEABA0A36554A00CA30F8 /* InsertListCommand.h in Headers */,
index feb7786..42464e2 100644 (file)
@@ -87,7 +87,8 @@ enum class EditAction : uint8_t {
     ConvertToOrderedList,
     ConvertToUnorderedList,
     Indent,
-    Outdent
+    Outdent,
+    InsertEditableImage
 };
 
 } // namespace WebCore
index 0b0cdaf..abad1b3 100644 (file)
@@ -68,6 +68,7 @@
 #include "HitTestResult.h"
 #include "IndentOutdentCommand.h"
 #include "InputEvent.h"
+#include "InsertEditableImageCommand.h"
 #include "InsertListCommand.h"
 #include "InsertTextCommand.h"
 #include "KeyboardEvent.h"
@@ -4250,4 +4251,9 @@ String Editor::clientReplacementURLForResource(Ref<SharedBuffer>&& resourceData,
     return { };
 }
 
+void Editor::insertEditableImage()
+{
+    InsertEditableImageCommand::create(document())->apply();
+}
+
 } // namespace WebCore
index 7a14edc..228f917 100644 (file)
@@ -532,6 +532,8 @@ public:
 #endif
 #endif
 
+    WEBCORE_EXPORT void insertEditableImage();
+
 private:
     Document& document() const;
 
index ca6df10..8cd66fe 100644 (file)
@@ -46,6 +46,7 @@
 #include "HTMLImageElement.h"
 #include "HTMLNames.h"
 #include "IndentOutdentCommand.h"
+#include "InsertEditableImageCommand.h"
 #include "InsertListCommand.h"
 #include "InsertNestedListCommand.h"
 #include "Page.h"
@@ -478,6 +479,13 @@ static bool executeInsertImage(Frame& frame, Event*, EditorCommandSource, const
     return executeInsertNode(frame, WTFMove(image));
 }
 
+static bool executeInsertEditableImage(Frame& frame, Event*, EditorCommandSource, const String&)
+{
+    ASSERT(frame.document());
+    InsertEditableImageCommand::create(*frame.document())->apply();
+    return true;
+}
+
 static bool executeInsertLineBreak(Frame& frame, Event* event, EditorCommandSource source, const String&)
 {
     switch (source) {
@@ -1371,6 +1379,13 @@ static bool enabledUndo(Frame& frame, Event*, EditorCommandSource)
     return frame.editor().canUndo();
 }
 
+static bool enabledInRichlyEditableTextWithEditableImagesEnabled(Frame& frame, Event* event, EditorCommandSource source)
+{
+    if (!frame.settings().editableImagesEnabled())
+        return false;
+    return enabledInRichlyEditableText(frame, event, source);
+}
+
 // State functions
 
 static TriState stateNone(Frame&, Event*)
@@ -1590,6 +1605,7 @@ static const CommandMap& createCommandMap()
         { "IgnoreSpelling", { executeIgnoreSpelling, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
         { "Indent", { executeIndent, supported, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
         { "InsertBacktab", { executeInsertBacktab, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, isTextInsertion, doNotAllowExecutionWhenDisabled } },
+        { "InsertEditableImage", { executeInsertEditableImage, supported, enabledInRichlyEditableTextWithEditableImagesEnabled, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
         { "InsertHTML", { executeInsertHTML, supported, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
         { "InsertHorizontalRule", { executeInsertHorizontalRule, supported, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
         { "InsertImage", { executeInsertImage, supported, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } },
diff --git a/Source/WebCore/editing/InsertEditableImageCommand.cpp b/Source/WebCore/editing/InsertEditableImageCommand.cpp
new file mode 100644 (file)
index 0000000..3df2112
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 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. ``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
+ * 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 "InsertEditableImageCommand.h"
+
+#include "Editing.h"
+#include "HTMLImageElement.h"
+
+namespace WebCore {
+
+InsertEditableImageCommand::InsertEditableImageCommand(Document& document)
+    : CompositeEditCommand(document)
+{
+}
+
+void InsertEditableImageCommand::doApply()
+{
+    if (endingSelection().isNone())
+        return;
+
+    auto imgElement = HTMLImageElement::create(document());
+    imgElement->setAttributeWithoutSynchronization(x_apple_editable_imageAttr, emptyAtom());
+    imgElement->setAttributeWithoutSynchronization(widthAttr, AtomicString("100%", AtomicString::ConstructFromLiteral));
+    imgElement->setAttributeWithoutSynchronization(heightAttr, AtomicString("300px", AtomicString::ConstructFromLiteral));
+    imgElement->setAttributeWithoutSynchronization(styleAttr, AtomicString("display: block", AtomicString::ConstructFromLiteral));
+
+    insertNodeAt(imgElement.copyRef(), endingSelection().start());
+    setEndingSelection(visiblePositionAfterNode(imgElement.get()));
+}
+
+}
diff --git a/Source/WebCore/editing/InsertEditableImageCommand.h b/Source/WebCore/editing/InsertEditableImageCommand.h
new file mode 100644 (file)
index 0000000..c4fc67d
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 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. ``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
+ * 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 "CompositeEditCommand.h"
+
+namespace WebCore {
+
+class InsertEditableImageCommand : public CompositeEditCommand {
+public:
+    static Ref<InsertEditableImageCommand> create(Document& document)
+    {
+        return adoptRef(*new InsertEditableImageCommand(document));
+    }
+
+private:
+    InsertEditableImageCommand(Document&);
+
+    void doApply() override;
+    EditAction editingAction() const final { return EditAction::InsertEditableImage; }
+};
+
+} // namespace WebCore
index 3f8ff2b..454cceb 100644 (file)
@@ -31,6 +31,7 @@
 #include "Element.h"
 #include "HTMLInputElement.h"
 #include "Settings.h"
+#include "ShadowRoot.h"
 #include "TextIterator.h"
 #include "VisibleUnits.h"
 #include <stdio.h>
index 5f1b589..1ed78ce 100644 (file)
 "Indent (Undo action name)" = "Indent";
 
 /* Undo action name */
+"Insert Drawing (Undo action name)" = "Insert Drawing";
+
+/* Undo action name */
 "Insert List (Undo action name)" = "Insert List";
 
 /* Inspect Element context menu item */
index bb76b41..dcb9fb5 100644 (file)
@@ -1,5 +1,45 @@
 2018-11-25  Tim Horton  <timothy_horton@apple.com>
 
+        Make it possible to insert editable images with a gesture
+        https://bugs.webkit.org/show_bug.cgi?id=191937
+
+        Reviewed by Wenson Hsieh.
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _stylusTapGestureShouldCreateEditableImage]):
+        * UIProcess/API/Cocoa/WKWebViewInternal.h:
+        Add a internal getter for a WKWebViewConfiguration property.
+
+        * UIProcess/WebEditCommandProxy.cpp:
+        (WebKit::WebEditCommandProxy::nameForEditAction):
+        Add a undo name.
+
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView setupInteraction]):
+        (-[WKContentView cleanupInteraction]):
+        (-[WKContentView _removeDefaultGestureRecognizers]):
+        (-[WKContentView _addDefaultGestureRecognizers]):
+        Add a single-stylus-tap gesture recognizer.
+
+        (-[WKContentView _stylusSingleTapRecognized:]):
+        If allowed, request to insert an editable image when a stylus tap occurs.
+
+        * UIProcess/ios/WebPageProxyIOS.mm:
+        (WebKit::WebPageProxy::handleStylusSingleTapAtPoint):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::handleStylusSingleTapAtPoint):
+        Do a hit test, select the hit position, insert an editable image, and
+        then de-assist any assisted node (to make the keyboard go away).
+        For now, we'll only insert if we hit non-replaced elements,
+        though this heuristic will need to be enhanced significantly once we
+        decide on a design.
+
+2018-11-25  Tim Horton  <timothy_horton@apple.com>
+
         Scrolling and drawing compete for incoming gestures
         https://bugs.webkit.org/show_bug.cgi?id=191940
 
index aed5bd0..2787e58 100644 (file)
@@ -2440,6 +2440,11 @@ static WebCore::FloatPoint constrainContentOffset(WebCore::FloatPoint contentOff
     return !areEssentiallyEqualAsFloat(contentZoomScale(self), 1);
 }
 
+- (BOOL)_stylusTapGestureShouldCreateEditableImage
+{
+    return [_configuration _editableImagesEnabled];
+}
+
 #pragma mark - UIScrollViewDelegate
 
 - (BOOL)usesStandardContentView
index c266d6e..71e291f 100644 (file)
@@ -167,6 +167,7 @@ class URL;
 @property (nonatomic, readonly) WKSelectionGranularity _selectionGranularity;
 
 @property (nonatomic, readonly) BOOL _allowsDoubleTapGestures;
+@property (nonatomic, readonly) BOOL _stylusTapGestureShouldCreateEditableImage;
 @property (nonatomic, readonly) BOOL _haveSetObscuredInsets;
 @property (nonatomic, readonly) UIEdgeInsets _computedObscuredInset;
 @property (nonatomic, readonly) UIEdgeInsets _computedUnobscuredSafeAreaInset;
index cd57c62..a526477 100644 (file)
@@ -177,6 +177,8 @@ String WebEditCommandProxy::nameForEditAction(EditAction editAction)
         return WEB_UI_STRING_KEY("Convert to Ordered List", "Convert to Ordered List (Undo action name)", "Undo action name");
     case EditAction::ConvertToUnorderedList:
         return WEB_UI_STRING_KEY("Convert to Unordered List", "Convert to Unordered List (Undo action name)", "Undo action name");
+    case EditAction::InsertEditableImage:
+        return WEB_UI_STRING_KEY("Insert Drawing", "Insert Drawing (Undo action name)", "Undo action name");
     }
     return String();
 }
index ada5c62..1951b2e 100644 (file)
@@ -652,6 +652,7 @@ public:
     void contentSizeCategoryDidChange(const String& contentSizeCategory);
     void getSelectionContext(WTF::Function<void(const String&, const String&, const String&, CallbackBase::Error)>&&);
     void handleTwoFingerTapAtPoint(const WebCore::IntPoint&, uint64_t requestID);
+    void handleStylusSingleTapAtPoint(const WebCore::IntPoint&, uint64_t requestID);
     void setForceAlwaysUserScalable(bool);
     bool forceAlwaysUserScalable() const { return m_forceAlwaysUserScalable; }
     double layoutSizeScaleFactor() const { return m_viewportConfigurationLayoutSizeScaleFactor; }
index c48c39d..a807ccd 100644 (file)
@@ -210,6 +210,7 @@ struct WKAutoCorrectionData {
     RetainPtr<UITapGestureRecognizer> _nonBlockingDoubleTapGestureRecognizer;
     RetainPtr<UITapGestureRecognizer> _twoFingerDoubleTapGestureRecognizer;
     RetainPtr<UITapGestureRecognizer> _twoFingerSingleTapGestureRecognizer;
+    RetainPtr<UITapGestureRecognizer> _stylusSingleTapGestureRecognizer;
     RetainPtr<WKInspectorNodeSearchGestureRecognizer> _inspectorNodeSearchGestureRecognizer;
 
 #if PLATFORM(IOSMAC)
index d9b478e..a8be12e 100644 (file)
@@ -691,6 +691,12 @@ static inline bool hasAssistedNode(WebKit::AssistedNodeInformation assistedNodeI
     [_twoFingerSingleTapGestureRecognizer setDelegate:self];
     [self addGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()];
 
+    _stylusSingleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_stylusSingleTapRecognized:)]);
+    [_stylusSingleTapGestureRecognizer setNumberOfTapsRequired:1];
+    [_stylusSingleTapGestureRecognizer setDelegate:self];
+    [_stylusSingleTapGestureRecognizer setAllowedTouchTypes:@[ @(UITouchTypePencil) ]];
+    [self addGestureRecognizer:_stylusSingleTapGestureRecognizer.get()];
+
 #if HAVE(LINK_PREVIEW)
     [self _registerPreview];
 #endif
@@ -778,6 +784,9 @@ static inline bool hasAssistedNode(WebKit::AssistedNodeInformation assistedNodeI
     [_twoFingerSingleTapGestureRecognizer setDelegate:nil];
     [self removeGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()];
 
+    [_stylusSingleTapGestureRecognizer setDelegate:nil];
+    [self removeGestureRecognizer:_stylusSingleTapGestureRecognizer.get()];
+
     _layerTreeTransactionIdAtLastTouchStart = 0;
 
 #if ENABLE(DATA_INTERACTION)
@@ -834,6 +843,7 @@ static inline bool hasAssistedNode(WebKit::AssistedNodeInformation assistedNodeI
     [self removeGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()];
     [self removeGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()];
     [self removeGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()];
+    [self removeGestureRecognizer:_stylusSingleTapGestureRecognizer.get()];
 #if PLATFORM(IOSMAC)
     [self removeGestureRecognizer:_hoverGestureRecognizer.get()];
 #endif
@@ -848,6 +858,7 @@ static inline bool hasAssistedNode(WebKit::AssistedNodeInformation assistedNodeI
     [self addGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()];
     [self addGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()];
     [self addGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()];
+    [self addGestureRecognizer:_stylusSingleTapGestureRecognizer.get()];
 #if PLATFORM(IOSMAC)
     [self addGestureRecognizer:_hoverGestureRecognizer.get()];
 #endif
@@ -1896,6 +1907,15 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
     _page->handleTwoFingerTapAtPoint(roundedIntPoint(gestureRecognizer.centroid), ++_latestTapID);
 }
 
+- (void)_stylusSingleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer
+{
+    if (!_webView._stylusTapGestureShouldCreateEditableImage)
+        return;
+
+    ASSERT(gestureRecognizer == _stylusSingleTapGestureRecognizer);
+    _page->handleStylusSingleTapAtPoint(roundedIntPoint(gestureRecognizer.location), ++_latestTapID);
+}
+
 - (void)_longPressRecognized:(UILongPressGestureRecognizer *)gestureRecognizer
 {
     ASSERT(gestureRecognizer == _longPressGestureRecognizer);
index efa83ca..72d8bd5 100644 (file)
@@ -597,6 +597,11 @@ void WebPageProxy::handleTwoFingerTapAtPoint(const WebCore::IntPoint& point, uin
     process().send(Messages::WebPage::HandleTwoFingerTapAtPoint(point, requestID), m_pageID);
 }
 
+void WebPageProxy::handleStylusSingleTapAtPoint(const WebCore::IntPoint& point, uint64_t requestID)
+{
+    process().send(Messages::WebPage::HandleStylusSingleTapAtPoint(point, requestID), m_pageID);
+}
+
 void WebPageProxy::selectWithTwoTouches(const WebCore::IntPoint from, const WebCore::IntPoint to, uint32_t gestureType, uint32_t gestureState, WTF::Function<void (const WebCore::IntPoint&, uint32_t, uint32_t, uint32_t, CallbackBase::Error)>&& callbackFunction)
 {
     if (!isValid()) {
index 382d1f4..d1a82d6 100644 (file)
@@ -653,6 +653,7 @@ public:
     void updateSelectionAppearance();
     void getSelectionContext(CallbackID);
     void handleTwoFingerTapAtPoint(const WebCore::IntPoint&, uint64_t requestID);
+    void handleStylusSingleTapAtPoint(const WebCore::IntPoint&, uint64_t requestID);
     void getRectsForGranularityWithSelectionOffset(uint32_t, int32_t, CallbackID);
     void getRectsAtSelectionOffsetWithText(int32_t, const String&, CallbackID);
     void storeSelectionForAccessibility(bool);
index 18b0510..8ab8437 100644 (file)
@@ -99,6 +99,7 @@ messages -> WebPage LegacyReceiver {
     GetSelectionContext(WebKit::CallbackID callbackID)
     SetAllowsMediaDocumentInlinePlayback(bool allows)
     HandleTwoFingerTapAtPoint(WebCore::IntPoint point, uint64_t requestID)
+    HandleStylusSingleTapAtPoint(WebCore::IntPoint point, uint64_t requestID)
     SetForceAlwaysUserScalable(bool userScalable)
     GetRectsForGranularityWithSelectionOffset(uint32_t granularity, int32_t offset, WebKit::CallbackID callbackID)
     GetRectsAtSelectionOffsetWithText(int32_t offset, String text, WebKit::CallbackID callbackID)
index e536d7d..fb1c0c6 100644 (file)
@@ -758,6 +758,36 @@ void WebPage::handleTwoFingerTapAtPoint(const WebCore::IntPoint& point, uint64_t
         completeSyntheticClick(nodeRespondingToClick, adjustedPoint, WebCore::TwoFingerTap);
 }
 
+void WebPage::handleStylusSingleTapAtPoint(const WebCore::IntPoint& point, uint64_t requestID)
+{
+    auto& frame = m_page->focusController().focusedOrMainFrame();
+
+    auto pointInDocument = frame.view()->rootViewToContents(point);
+    HitTestResult hitTest = frame.eventHandler().hitTestResultAtPoint(pointInDocument, HitTestRequest::ReadOnly | HitTestRequest::Active);
+
+    Node* node = hitTest.innerNonSharedNode();
+    if (!node)
+        return;
+    auto renderer = node->renderer();
+    if (!renderer)
+        return;
+
+    if (renderer->isReplaced())
+        return;
+
+    VisiblePosition position = renderer->positionForPoint(hitTest.localPoint(), nullptr);
+    if (position.isNull())
+        position = firstPositionInOrBeforeNode(node);
+
+    if (position.isNull())
+        return;
+
+    auto range = Range::create(*frame.document(), position, position);
+    frame.selection().setSelectedRange(range.ptr(), position.affinity(), WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered);
+    frame.editor().insertEditableImage();
+    resetAssistedNodeForFrame(m_mainFrame.get());
+}
+
 void WebPage::potentialTapAtPosition(uint64_t requestID, const WebCore::FloatPoint& position)
 {
     m_potentialTapNode = m_page->mainFrame().nodeRespondingToClickEvents(position, m_potentialTapLocation, m_potentialTapSecurityOrigin.get());
index 75232ff..6dac609 100644 (file)
@@ -1,3 +1,14 @@
+2018-11-25  Tim Horton  <timothy_horton@apple.com>
+
+        Make it possible to insert editable images with a gesture
+        https://bugs.webkit.org/show_bug.cgi?id=191937
+
+        Reviewed by Wenson Hsieh.
+
+        * WebCoreSupport/WebEditorClient.mm:
+        (undoNameForEditAction):
+        Add a undo name.
+
 2018-11-24  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [Cocoa] Fix a few localizable string descriptions in WebEditCommandProxy.cpp and WebEditorClient.mm
index 9fd0c12..089f039 100644 (file)
@@ -655,6 +655,7 @@ static NSString* undoNameForEditAction(EditAction editAction)
     case EditAction::Dictation: return UI_STRING_KEY_INTERNAL("Dictation", "Dictation (Undo action name)", "Undo action name");
     case EditAction::ConvertToOrderedList: return UI_STRING_KEY_INTERNAL("Convert to Ordered List", "Convert to Ordered List (Undo action name)", "Undo action name");
     case EditAction::ConvertToUnorderedList: return UI_STRING_KEY_INTERNAL("Convert to Unordered List", "Convert to Unordered List (Undo action name)", "Undo action name");
+    case EditAction::InsertEditableImage: return UI_STRING_KEY_INTERNAL("Insert Drawing", "Insert Drawing (Undo action name)", "Undo action name");
     }
     return nil;
 }
index 1b03463..4c51de0 100644 (file)
@@ -1,3 +1,15 @@
+2018-11-25  Tim Horton  <timothy_horton@apple.com>
+
+        Make it possible to insert editable images with a gesture
+        https://bugs.webkit.org/show_bug.cgi?id=191937
+
+        Reviewed by Wenson Hsieh.
+
+        * WebKitTestRunner/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptController::drawSquareInEditableImage):
+        If the canvas already has a drawing, draw a new stroke on top of it
+        instead of removing the existing stroke.
+
 2018-11-24  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [Cocoa] Add WKWebView SPI to trigger and remove data detection
index 610fa20..d5db067 100644 (file)
@@ -890,7 +890,7 @@ void UIScriptController::drawSquareInEditableImage()
     Class pkStrokeClass = NSClassFromString(@"PKStroke");
 
     PKCanvasView *canvasView = findEditableImageCanvas();
-    RetainPtr<PKDrawing> drawing = adoptNS([[pkDrawingClass alloc] init]);
+    RetainPtr<PKDrawing> drawing = canvasView.drawing ?: adoptNS([[pkDrawingClass alloc] init]);
     RetainPtr<CGPathRef> path = adoptCF(CGPathCreateWithRect(CGRectMake(0, 0, 50, 50), NULL));
     RetainPtr<PKInk> ink = [pkInkClass inkWithType:0 color:UIColor.greenColor weight:100.0];
     RetainPtr<PKStroke> stroke = adoptNS([[pkStrokeClass alloc] _initWithPath:path.get() ink:ink.get() inputScale:1]);