WebKit Bugzilla
Attachment 360117 Details for
Bug 190009
: Need a way for JavaScript (or bundle) code to participate in undo
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch for EWS
bug-190009-20190125081258.patch (text/plain), 42.23 KB, created by
Wenson Hsieh
on 2019-01-25 08:12:59 PST
(
hide
)
Description:
Patch for EWS
Filename:
MIME Type:
Creator:
Wenson Hsieh
Created:
2019-01-25 08:12:59 PST
Size:
42.23 KB
patch
obsolete
>Subversion Revision: 240452 >diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index 973512b7fc4743df9e929babe6958015160db896..c8ccdb5898a7a54b0cb6f1092d4ca24491d8fc82 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,3 +1,46 @@ >+2019-01-25 Wenson Hsieh <wenson_hsieh@apple.com> >+ >+ Need a way for JavaScript (or bundle) code to participate in undo >+ https://bugs.webkit.org/show_bug.cgi?id=190009 >+ <rdar://problem/44807048> >+ >+ Reviewed by Ryosuke Niwa. >+ >+ Finish hooking up `UndoManager::addItems()` to CustomUndoStep. >+ >+ Tests: editing/undo-manager/undo-manager-add-item-exceptions.html >+ editing/undo-manager/undo-manager-add-item.html >+ editing/undo-manager/undo-manager-delete-stale-undo-items.html >+ editing/undo-manager/undo-manager-item-labels.html >+ editing/undo-manager/undo-manager-undo-redo-after-garbage-collection.html >+ >+ * editing/CompositeEditCommand.h: >+ * editing/CustomUndoStep.cpp: >+ (WebCore::CustomUndoStep::didRemoveFromUndoManager): >+ >+ Add a method to invalidate CustomUndoStep. This clears out the pointer to the undo item, and also invalidates >+ the UndoItem, removing it from its UndoManager. >+ >+ * editing/CustomUndoStep.h: >+ * editing/Editor.cpp: >+ (WebCore::Editor::registerCustomUndoStep): >+ >+ Add a helper method to register a CustomUndoStep as a platform undoable step. >+ >+ * editing/Editor.h: >+ * editing/UndoStep.h: >+ * page/UndoItem.h: >+ (WebCore::UndoItem::undoManager const): >+ * page/UndoManager.cpp: >+ (WebCore::UndoManager::addItem): >+ >+ Create a CustomUndoStep with the given UndoItem, and register it with the platform undo manager. >+ >+ * page/UndoManager.h: >+ * page/UndoManager.idl: >+ >+ Mark addItem() as capable of throwing exceptions. >+ > 2019-01-24 Wenson Hsieh <wenson_hsieh@apple.com> > > [iOS] Unable to make a selection in jsfiddle.net using arrow keys when requesting desktop site >diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog >index 04c5c0497750fe84b67bef504bb5c0de3e23e12e..e1fa84b27665f3ee27f257f1769a97fc8cd18521 100644 >--- a/Source/WebKit/ChangeLog >+++ b/Source/WebKit/ChangeLog >@@ -1,3 +1,21 @@ >+2019-01-25 Wenson Hsieh <wenson_hsieh@apple.com> >+ >+ Need a way for JavaScript (or bundle) code to participate in undo >+ https://bugs.webkit.org/show_bug.cgi?id=190009 >+ <rdar://problem/44807048> >+ >+ Reviewed by Ryosuke Niwa. >+ >+ Invalidate undo steps when removing them from WebPage. Invalidation is a no-op for editing actions that come >+ from the UA, but for custom undo steps backed by an UndoItem, we clear out the custom undo step's pointer to its >+ UndoItem and additionally disconnect the UndoItem from its UndoManager. >+ >+ * WebProcess/WebPage/WebPage.cpp: >+ (WebKit::WebPage::addWebUndoStep): >+ (WebKit::WebPage::removeWebEditCommand): >+ * WebProcess/WebPage/WebUndoStep.h: >+ (WebKit::WebUndoStep::invalidate): >+ > 2019-01-24 Brent Fulgham <bfulgham@apple.com> > > Activate the WebResourceLoadStatisticsStore in the NetworkProcess and deactivate it in the UIProcess. >diff --git a/Source/WebCore/editing/CompositeEditCommand.h b/Source/WebCore/editing/CompositeEditCommand.h >index ff1e61c6ee4597f0ad5eba29b9bd48ac60c94fbb..04c21ff341c48963934c5de2d0e71c3cc9f38715 100644 >--- a/Source/WebCore/editing/CompositeEditCommand.h >+++ b/Source/WebCore/editing/CompositeEditCommand.h >@@ -89,6 +89,7 @@ private: > EditCommandComposition(Document&, const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction); > > String label() const final; >+ void didRemoveFromUndoManager() final { } > > RefPtr<Document> m_document; > VisibleSelection m_startingSelection; >diff --git a/Source/WebCore/editing/CustomUndoStep.cpp b/Source/WebCore/editing/CustomUndoStep.cpp >index 06807522642228727686cbcddde7e272fd00d20a..f289271ec10a0114d56884c8084663b1b2139693 100644 >--- a/Source/WebCore/editing/CustomUndoStep.cpp >+++ b/Source/WebCore/editing/CustomUndoStep.cpp >@@ -74,4 +74,10 @@ String CustomUndoStep::label() const > return m_undoItem->label(); > } > >+void CustomUndoStep::didRemoveFromUndoManager() >+{ >+ if (auto undoItem = std::exchange(m_undoItem, nullptr)) >+ undoItem->invalidate(); >+} >+ > } // namespace WebCore >diff --git a/Source/WebCore/editing/CustomUndoStep.h b/Source/WebCore/editing/CustomUndoStep.h >index d9ead13a4a727cbfbbb80b731fb44cd010bf4cde..557b7f41942838ac366534a91236cdfaac9ea1cb 100644 >--- a/Source/WebCore/editing/CustomUndoStep.h >+++ b/Source/WebCore/editing/CustomUndoStep.h >@@ -49,6 +49,7 @@ private: > EditAction editingAction() const final { return EditAction::Unspecified; } > String label() const final; > >+ void didRemoveFromUndoManager() final; > bool isValid() const; > > WeakPtr<UndoItem> m_undoItem; >diff --git a/Source/WebCore/editing/Editor.cpp b/Source/WebCore/editing/Editor.cpp >index 37556426af1ee0c4688fe32d18c8ab0cbc358445..8fd53ba98acb8a2e4791153fc84440640bc357a5 100644 >--- a/Source/WebCore/editing/Editor.cpp >+++ b/Source/WebCore/editing/Editor.cpp >@@ -39,6 +39,7 @@ > #include "ClipboardEvent.h" > #include "CompositionEvent.h" > #include "CreateLinkCommand.h" >+#include "CustomUndoStep.h" > #include "DataTransfer.h" > #include "DeleteSelectionCommand.h" > #include "DictationAlternative.h" >@@ -1740,6 +1741,13 @@ void Editor::redo() > client()->redo(); > } > >+void Editor::registerCustomUndoStep(Ref<CustomUndoStep>&& undoStep) >+{ >+ ASSERT(RuntimeEnabledFeatures::sharedFeatures().undoManagerAPIEnabled()); >+ if (auto* client = this->client()) >+ client->registerUndoStep(WTFMove(undoStep)); >+} >+ > void Editor::didBeginEditing() > { > if (client()) >diff --git a/Source/WebCore/editing/Editor.h b/Source/WebCore/editing/Editor.h >index 05cc1b82aeac140b40930df6f37ea54955c63f46..4547d4715f9833a867ce7f6a833378c5363d03bb 100644 >--- a/Source/WebCore/editing/Editor.h >+++ b/Source/WebCore/editing/Editor.h >@@ -58,6 +58,7 @@ class AlternativeTextController; > class ArchiveResource; > class DataTransfer; > class CompositeEditCommand; >+class CustomUndoStep; > class DeleteButtonController; > class EditCommand; > class EditCommandComposition; >@@ -324,6 +325,8 @@ public: > bool canRedo() const; > void redo(); > >+ void registerCustomUndoStep(Ref<CustomUndoStep>&&); >+ > void didBeginEditing(); > void didEndEditing(); > void willWriteSelectionToPasteboard(Range*); >diff --git a/Source/WebCore/editing/UndoStep.h b/Source/WebCore/editing/UndoStep.h >index 05009cb575501b5ec2d5e82645f81eb8366a8442..70e30de7de8279e117f07c3a9e96d9226950eef2 100644 >--- a/Source/WebCore/editing/UndoStep.h >+++ b/Source/WebCore/editing/UndoStep.h >@@ -44,6 +44,7 @@ public: > virtual void reapply() = 0; > virtual EditAction editingAction() const = 0; > virtual String label() const = 0; >+ virtual void didRemoveFromUndoManager() = 0; > }; > > } // namespace WebCore >diff --git a/Source/WebCore/page/UndoItem.h b/Source/WebCore/page/UndoItem.h >index 326284a59c7b5f21f0a0806eaea0000262e76bcc..6f0b5a18cb0216a7b67f7796d99cc699d3a57874 100644 >--- a/Source/WebCore/page/UndoItem.h >+++ b/Source/WebCore/page/UndoItem.h >@@ -55,6 +55,7 @@ public: > > Document* document() const; > >+ UndoManager* undoManager() const { return m_undoManager.get(); } > void setUndoManager(UndoManager*); > > const String& label() const { return m_label; } >diff --git a/Source/WebCore/page/UndoManager.cpp b/Source/WebCore/page/UndoManager.cpp >index c168acaaadfb96b847140329b66e73a55123bd61..35ed09295fe17aeea452c676ee5a19c0645de89d 100644 >--- a/Source/WebCore/page/UndoManager.cpp >+++ b/Source/WebCore/page/UndoManager.cpp >@@ -26,6 +26,8 @@ > #include "config.h" > #include "UndoManager.h" > >+#include "CustomUndoStep.h" >+#include "Frame.h" > #include "UndoItem.h" > #include <wtf/IsoMallocInlines.h> > >@@ -40,12 +42,19 @@ UndoManager::UndoManager(Document& document) > > UndoManager::~UndoManager() = default; > >-void UndoManager::addItem(Ref<UndoItem>&& item) >+ExceptionOr<void> UndoManager::addItem(Ref<UndoItem>&& item) > { >- UNUSED_PARAM(m_document); >+ if (item->undoManager()) >+ return Exception { InvalidModificationError, "This item has already been added to an UndoManager"_s }; >+ >+ auto frame = makeRefPtr(m_document.frame()); >+ if (!frame) >+ return Exception { SecurityError, "A browsing context is required to add an UndoItem"_s }; > > item->setUndoManager(this); >+ frame->editor().registerCustomUndoStep(CustomUndoStep::create(item)); > m_items.add(WTFMove(item)); >+ return { }; > } > > void UndoManager::removeItem(UndoItem& item) >diff --git a/Source/WebCore/page/UndoManager.h b/Source/WebCore/page/UndoManager.h >index 78fa720dc731080064b70d8303a2fbbdb4d3c5dd..fb99ac3ab6ca44f4f0e7e43081ff469c9e7cc529 100644 >--- a/Source/WebCore/page/UndoManager.h >+++ b/Source/WebCore/page/UndoManager.h >@@ -25,6 +25,7 @@ > > #pragma once > >+#include "ExceptionOr.h" > #include <wtf/IsoMalloc.h> > #include <wtf/Ref.h> > #include <wtf/RefCounted.h> >@@ -48,7 +49,7 @@ public: > > void removeItem(UndoItem&); > void removeAllItems(); >- void addItem(Ref<UndoItem>&&); >+ ExceptionOr<void> addItem(Ref<UndoItem>&&); > Document& document() { return m_document; } > > private: >diff --git a/Source/WebCore/page/UndoManager.idl b/Source/WebCore/page/UndoManager.idl >index d361276dc43d1145e688d0570175674d563250c0..6dd0d27d79cee7d0476de64ec77f9b6d45e2991f 100644 >--- a/Source/WebCore/page/UndoManager.idl >+++ b/Source/WebCore/page/UndoManager.idl >@@ -28,5 +28,5 @@ > ImplementationLacksVTable, > GenerateIsReachable=ImplDocument, > ] interface UndoManager { >- void addItem(UndoItem item); >+ [MayThrowException] void addItem(UndoItem item); > }; >diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.cpp b/Source/WebKit/WebProcess/WebPage/WebPage.cpp >index 3b0b5098823515d5a45bcff4632ec7a06cc95d5a..7fa11341d58a8fa31f0742d431d311534f2de2ed 100644 >--- a/Source/WebKit/WebProcess/WebPage/WebPage.cpp >+++ b/Source/WebKit/WebProcess/WebPage/WebPage.cpp >@@ -3791,12 +3791,14 @@ WebUndoStep* WebPage::webUndoStep(WebUndoStepID stepID) > > void WebPage::addWebUndoStep(WebUndoStepID stepID, Ref<WebUndoStep>&& entry) > { >- m_undoStepMap.set(stepID, WTFMove(entry)); >+ auto addResult = m_undoStepMap.set(stepID, WTFMove(entry)); >+ ASSERT_UNUSED(addResult, addResult.isNewEntry); > } > > void WebPage::removeWebEditCommand(WebUndoStepID stepID) > { >- m_undoStepMap.remove(stepID); >+ if (auto undoStep = m_undoStepMap.take(stepID)) >+ undoStep->didRemoveFromUndoManager(); > } > > bool WebPage::isAlwaysOnLoggingAllowed() const >diff --git a/Source/WebKit/WebProcess/WebPage/WebUndoStep.h b/Source/WebKit/WebProcess/WebPage/WebUndoStep.h >index 211fd99e5e6c696fa7c399f7f7edd038ab8a8d8a..665a105eaf04e4442763d56bcc1e14524044fb04 100644 >--- a/Source/WebKit/WebProcess/WebPage/WebUndoStep.h >+++ b/Source/WebKit/WebProcess/WebPage/WebUndoStep.h >@@ -39,6 +39,8 @@ public: > WebCore::UndoStep& step() const { return m_step.get(); } > WebUndoStepID stepID() const { return m_stepID; } > >+ void didRemoveFromUndoManager() { m_step->didRemoveFromUndoManager(); } >+ > private: > WebUndoStep(Ref<WebCore::UndoStep>&& step, WebUndoStepID stepID) > : m_step(WTFMove(step)) >diff --git a/Tools/ChangeLog b/Tools/ChangeLog >index a6c3d7a3aa377fa3bc9ee4f87706f07ebc216cf0..2543d5f9a9071da299b24578eaec84f0e8dfa3e0 100644 >--- a/Tools/ChangeLog >+++ b/Tools/ChangeLog >@@ -1,3 +1,35 @@ >+2019-01-25 Wenson Hsieh <wenson_hsieh@apple.com> >+ >+ Need a way for JavaScript (or bundle) code to participate in undo >+ https://bugs.webkit.org/show_bug.cgi?id=190009 >+ <rdar://problem/44807048> >+ >+ Reviewed by Ryosuke Niwa. >+ >+ Add UIScriptController helpers to grab the platform undo and redo action labels. Currently only implemented for >+ Cocoa platforms in WebKit2. See other ChangeLogs for more detail. >+ >+ * DumpRenderTree/ios/UIScriptControllerIOS.mm: >+ (WTR::UIScriptController::lastUndoLabel const): >+ (WTR::UIScriptController::firstRedoLabel const): >+ (WTR::UIScriptController::platformUndoManager const): >+ * DumpRenderTree/mac/UIScriptControllerMac.mm: >+ (WTR::UIScriptController::lastUndoLabel const): >+ (WTR::UIScriptController::firstRedoLabel const): >+ (WTR::UIScriptController::platformUndoManager const): >+ * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl: >+ * TestRunnerShared/UIScriptContext/UIScriptController.cpp: >+ (WTR::UIScriptController::lastUndoLabel const): >+ (WTR::UIScriptController::firstRedoLabel const): >+ * TestRunnerShared/UIScriptContext/UIScriptController.h: >+ * WebKitTestRunner/UIScriptControllerCocoa.mm: >+ (WTR::UIScriptController::lastUndoLabel const): >+ (WTR::UIScriptController::firstRedoLabel const): >+ * WebKitTestRunner/ios/UIScriptControllerIOS.mm: >+ (WTR::UIScriptController::platformUndoManager const): >+ * WebKitTestRunner/mac/UIScriptControllerMac.mm: >+ (WTR::UIScriptController::platformUndoManager const): >+ > 2019-01-24 Zalan Bujtas <zalan@apple.com> > > DidFirstVisuallyNonEmptyLayout milestone should always fire at some point. >diff --git a/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm b/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm >index 8a1125f4ef9198452669e0419c589a8902e8a670..db11ad641562d1d3a3164294b65446df8f53a39c 100644 >--- a/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm >+++ b/Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm >@@ -450,6 +450,21 @@ void UIScriptController::setKeyboardInputModeIdentifier(JSStringRef) > { > } > >+JSRetainPtr<JSStringRef> UIScriptController::lastUndoLabel() const >+{ >+ return nullptr; >+} >+ >+JSRetainPtr<JSStringRef> UIScriptController::firstRedoLabel() const >+{ >+ return nullptr; >+} >+ >+NSUndoManager *UIScriptController::platformUndoManager() const >+{ >+ return nil; >+} >+ > } > > #endif // PLATFORM(IOS_FAMILY) >diff --git a/Tools/DumpRenderTree/mac/UIScriptControllerMac.mm b/Tools/DumpRenderTree/mac/UIScriptControllerMac.mm >index c445145f33bef0cc1949e3d5aae0f533bee142d7..023c20723cad82f9899b1ca438ad8a9c4bd34a51 100644 >--- a/Tools/DumpRenderTree/mac/UIScriptControllerMac.mm >+++ b/Tools/DumpRenderTree/mac/UIScriptControllerMac.mm >@@ -215,6 +215,21 @@ void UIScriptController::toggleCapsLock(JSValueRef callback) > doAsyncTask(callback); > } > >+JSRetainPtr<JSStringRef> UIScriptController::lastUndoLabel() const >+{ >+ return nullptr; >+} >+ >+JSRetainPtr<JSStringRef> UIScriptController::firstRedoLabel() const >+{ >+ return nullptr; >+} >+ >+NSUndoManager *UIScriptController::platformUndoManager() const >+{ >+ return nil; >+} >+ > } > > #endif // PLATFORM(MAC) >diff --git a/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl b/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl >index 5424ba8a8cfbe4ad814c0dabe6117337eedf967c..e54467ff448a0a7c37070e9b7d0f18825c943143 100644 >--- a/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl >+++ b/Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl >@@ -290,8 +290,11 @@ interface UIScriptController { > void makeWindowContentViewFirstResponder(); > readonly attribute boolean isWindowContentViewFirstResponder; > >+ object attachmentInfo(DOMString attachmentIdentifier); >+ >+ // Editing > void drawSquareInEditableImage(); > readonly attribute long numberOfStrokesInEditableImage; >- >- object attachmentInfo(DOMString attachmentIdentifier); >+ readonly attribute DOMString lastUndoLabel; >+ readonly attribute DOMString firstRedoLabel; > }; >diff --git a/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp b/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp >index a5baa06cd533200599f3af56893e375f6e0233c1..4b45d56fdc2cb2ca4dae0f66dd1f527eefe7c39e 100644 >--- a/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp >+++ b/Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp >@@ -569,6 +569,16 @@ void UIScriptController::overridePreference(JSStringRef, JSStringRef) > { > } > >+JSRetainPtr<JSStringRef> UIScriptController::lastUndoLabel() const >+{ >+ return nullptr; >+} >+ >+JSRetainPtr<JSStringRef> UIScriptController::firstRedoLabel() const >+{ >+ return nullptr; >+} >+ > #endif // !PLATFORM(COCOA) > > #if !PLATFORM(MAC) >diff --git a/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h b/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h >index d3fb1cbb09250a8279dfcb28bbf8c9027f7021cb..956c345c32f61351cfede1143f4a61c00e66c740 100644 >--- a/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h >+++ b/Tools/TestRunnerShared/UIScriptContext/UIScriptController.h >@@ -31,6 +31,8 @@ > #include <wtf/Optional.h> > #include <wtf/Ref.h> > >+OBJC_CLASS NSUndoManager; >+ > namespace WebCore { > class FloatRect; > } >@@ -206,6 +208,9 @@ public: > void drawSquareInEditableImage(); > long numberOfStrokesInEditableImage(); > >+ JSRetainPtr<JSStringRef> lastUndoLabel() const; >+ JSRetainPtr<JSStringRef> firstRedoLabel() const; >+ > JSObjectRef attachmentInfo(JSStringRef attachmentIdentifier); > > private: >@@ -225,6 +230,10 @@ private: > void platformClearAllCallbacks(); > void platformPlayBackEventStream(JSStringRef, JSValueRef); > >+#if PLATFORM(COCOA) >+ NSUndoManager *platformUndoManager() const; >+#endif >+ > JSClassRef wrapperClass() final; > > JSObjectRef objectFromRect(const WebCore::FloatRect&) const; >diff --git a/Tools/WebKitTestRunner/UIScriptControllerCocoa.mm b/Tools/WebKitTestRunner/UIScriptControllerCocoa.mm >index a7b98e404b83102294937f646f64ecf8925712f6..702e5185523314be8d975edcea194b841f0fa48e 100644 >--- a/Tools/WebKitTestRunner/UIScriptControllerCocoa.mm >+++ b/Tools/WebKitTestRunner/UIScriptControllerCocoa.mm >@@ -192,5 +192,15 @@ JSObjectRef UIScriptController::calendarType() const > return nullptr; > #endif > } >- >+ >+JSRetainPtr<JSStringRef> UIScriptController::lastUndoLabel() const >+{ >+ return JSStringCreateWithCFString((__bridge CFStringRef)platformUndoManager().undoActionName); >+} >+ >+JSRetainPtr<JSStringRef> UIScriptController::firstRedoLabel() const >+{ >+ return JSStringCreateWithCFString((__bridge CFStringRef)platformUndoManager().redoActionName); >+} >+ > } // namespace WTR >diff --git a/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm b/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm >index 4aa94290578748cf81bc5355076dfa27a8a81c5d..e654039454ea01978c8ce4505596781bba875fbe 100644 >--- a/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm >+++ b/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm >@@ -971,6 +971,11 @@ JSObjectRef UIScriptController::attachmentInfo(JSStringRef jsAttachmentIdentifie > return JSValueToObject(m_context->jsContext(), [JSValue valueWithObject:attachmentInfoDictionary inContext:[JSContext contextWithJSGlobalContextRef:m_context->jsContext()]].JSValueRef, nullptr); > } > >+NSUndoManager *UIScriptController::platformUndoManager() const >+{ >+ return [(UIView *)[TestController::singleton().mainWebView()->platformView() valueForKeyPath:@"_currentContentView"] undoManager]; >+} >+ > } > > #endif // PLATFORM(IOS_FAMILY) >diff --git a/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm b/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm >index 218881152a895cddb6a7f40716f1900a5d77ac3b..757fa06bd78f1833f13b38cbc9b030f93419763f 100644 >--- a/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm >+++ b/Tools/WebKitTestRunner/mac/UIScriptControllerMac.mm >@@ -209,4 +209,9 @@ void UIScriptController::toggleCapsLock(JSValueRef callback) > doAsyncTask(callback); > } > >+NSUndoManager *UIScriptController::platformUndoManager() const >+{ >+ return TestController::singleton().mainWebView()->platformView().undoManager; >+} >+ > } // namespace WTR >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index a8c8f6ea2e70b277ad6c1fef8f12e7833337d5c9..f7ebe8656e39a98c14916a2c4c1fc8ed963ef621 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,48 @@ >+2019-01-25 Wenson Hsieh <wenson_hsieh@apple.com> >+ >+ Need a way for JavaScript (or bundle) code to participate in undo >+ https://bugs.webkit.org/show_bug.cgi?id=190009 >+ <rdar://problem/44807048> >+ >+ Reviewed by Ryosuke Niwa. >+ >+ Add a few new layout tests covering `UndoManager.addItem()`. >+ >+ * editing/undo-manager/undo-manager-add-item-exceptions-expected.txt: Added. >+ * editing/undo-manager/undo-manager-add-item-exceptions.html: Added. >+ >+ Add a test to verify that we throw exceptions when calling addItem() in a couple of circumstances. >+ >+ * editing/undo-manager/undo-manager-add-item-expected.txt: Added. >+ * editing/undo-manager/undo-manager-add-item.html: Added. >+ >+ Add a test that exercises the new API in both the top-level context and a child frame. >+ >+ * editing/undo-manager/undo-manager-delete-stale-undo-items-expected.txt: Added. >+ * editing/undo-manager/undo-manager-delete-stale-undo-items.html: Added. >+ >+ Add a test to verify that after adding undo items, undoing, and then performing other edit actions, garbage >+ collection will destroy JS wrappers for the previously added UndoItems, since these undo items' handlers can no >+ longer be invoked. >+ >+ * editing/undo-manager/undo-manager-item-labels-expected.txt: Added. >+ * editing/undo-manager/undo-manager-item-labels.html: Added. >+ >+ Add a test verifying that the undo and redo action labels are updated correctly when undoing and redoing. >+ >+ * editing/undo-manager/undo-manager-undo-redo-after-garbage-collection-expected.txt: Added. >+ * editing/undo-manager/undo-manager-undo-redo-after-garbage-collection.html: Added. >+ >+ Add a test to verify that triggering garbage collection after adding an undo item without keeping references to >+ the item (or its undo/redo handlers) doesn't break the API. >+ >+ * resources/ui-helper.js: >+ (window.UIHelper.undoAndRedoLabels): >+ >+ Add a helper method to grab the platform's current undo and redo action names. >+ >+ (window.UIHelper): >+ > 2019-01-24 Wenson Hsieh <wenson_hsieh@apple.com> > > [iOS] Unable to make a selection in jsfiddle.net using arrow keys when requesting desktop site >diff --git a/LayoutTests/editing/undo-manager/undo-manager-add-item-exceptions-expected.txt b/LayoutTests/editing/undo-manager/undo-manager-add-item-exceptions-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..f06856ced744d2ca7ef049f90a149d98ff7e710e >--- /dev/null >+++ b/LayoutTests/editing/undo-manager/undo-manager-add-item-exceptions-expected.txt >@@ -0,0 +1,14 @@ >+Verifies that UndoManager.addItem() throws JavaScript exceptions. This test requires WebKitTestRunner. >+ >+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". >+ >+ >+PASS firstFrameDocument.undoManager.addItem(item) threw exception SecurityError: A browsing context is required to add an UndoItem. >+PASS document.undoManager.addItem(item) did not throw exception. >+PASS secondFrameDocument.undoManager.addItem(item) threw exception InvalidModificationError: This item has already been added to an UndoManager. >+Performed undo. >+Performed redo. >+PASS successfullyParsed is true >+ >+TEST COMPLETE >+ >diff --git a/LayoutTests/editing/undo-manager/undo-manager-add-item-exceptions.html b/LayoutTests/editing/undo-manager/undo-manager-add-item-exceptions.html >new file mode 100644 >index 0000000000000000000000000000000000000000..6e653e130850e3b5dadbf2983681d7bef4e50000 >--- /dev/null >+++ b/LayoutTests/editing/undo-manager/undo-manager-add-item-exceptions.html >@@ -0,0 +1,31 @@ >+<!DOCTYPE html> <!-- webkit-test-runner [ enableUndoManagerAPI=true ] --> >+<html> >+ <meta charset="utf8"> >+ <head> >+ <script src="../../resources/js-test.js"></script> >+ <script> >+ addEventListener("load", () => { >+ description("Verifies that UndoManager.addItem() throws JavaScript exceptions. This test requires WebKitTestRunner."); >+ >+ item = new UndoItem({ label: '', undo: () => debug("Performed undo."), redo: () => debug("Performed redo.") }); >+ >+ const firstFrame = document.getElementById("one"); >+ firstFrameDocument = firstFrame.contentDocument; >+ firstFrame.remove(); >+ >+ secondFrameDocument = document.getElementById("two").contentDocument; >+ >+ shouldThrow("firstFrameDocument.undoManager.addItem(item)"); >+ shouldNotThrow("document.undoManager.addItem(item)"); >+ shouldThrow("secondFrameDocument.undoManager.addItem(item)"); >+ >+ document.execCommand("Undo"); >+ document.execCommand("Redo"); >+ }); >+ </script> >+ </head> >+ <body> >+ <iframe id="one" srcdoc="<body>One</body>"></iframe> >+ <iframe id="two" srcdoc="<body>Two</body>"></iframe> >+ </body> >+</html> >diff --git a/LayoutTests/editing/undo-manager/undo-manager-add-item-expected.txt b/LayoutTests/editing/undo-manager/undo-manager-add-item-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..956da89916c7bf440816a551efeacd7340d8136d >--- /dev/null >+++ b/LayoutTests/editing/undo-manager/undo-manager-add-item-expected.txt >@@ -0,0 +1,32 @@ >+This test verifies that UndoManager.addItem() can be used to add undo items to the platform undo stack. >+ >+ >+After adding an undo item (mainframe): >+PASS undoName is "mainframe" >+PASS redoName is "" >+After performing undo (mainframe): >+** UNDO ** >+PASS undoName is "" >+PASS redoName is "mainframe" >+After performing redo (mainframe): >+** REDO ** >+PASS undoName is "mainframe" >+PASS redoName is "" >+After adding an undo item (subframe): >+PASS undoName is "subframe" >+PASS redoName is "" >+After performing undo (subframe): >+PASS undoName is "mainframe" >+PASS redoName is "subframe" >+After performing redo (subframe): >+PASS undoName is "subframe" >+PASS redoName is "" >+PASS successfullyParsed is true >+ >+TEST COMPLETE >+ >+ >+-------- >+Frame: '<!--frame1-->' >+-------- >+UNDO, REDO >diff --git a/LayoutTests/editing/undo-manager/undo-manager-add-item.html b/LayoutTests/editing/undo-manager/undo-manager-add-item.html >new file mode 100644 >index 0000000000000000000000000000000000000000..a326c8e6de8e2e10ce614374ad3e9c0ab4f15d88 >--- /dev/null >+++ b/LayoutTests/editing/undo-manager/undo-manager-add-item.html >@@ -0,0 +1,93 @@ >+<!DOCTYPE html> <!-- webkit-test-runner [ enableUndoManagerAPI=true ] --> >+<html> >+ <head> >+ <script src="../../resources/js-test.js"></script> >+ <script src="../../resources/ui-helper.js"></script> >+ <script> >+ function addUndoItem() >+ { >+ document.undoManager.addItem(new UndoItem({ >+ label: "mainframe", >+ undo: () => debug("** UNDO **"), >+ redo: () => debug("** REDO **") >+ })); >+ } >+ </script> >+ </head> >+ <body> >+ <p>This test verifies that <code>UndoManager.addItem()</code> can be used to add undo items to the platform undo stack.</p> >+ <iframe id="frame" srcdoc=" >+ <body> >+ <pre id='output'></pre> >+ </body> >+ <script> >+ function appendOutput(string) >+ { >+ if (output.textContent.length) >+ output.textContent += ', '; >+ output.textContent += string; >+ } >+ >+ function addUndoItem() >+ { >+ document.undoManager.addItem(new UndoItem({ >+ label: 'subframe', >+ undo: () => appendOutput('UNDO'), >+ redo: () => appendOutput('REDO') >+ })); >+ } >+ </script> >+ "></iframe> >+ <pre id='console'></pre> >+ </body> >+ <script> >+ jsTestIsAsync = true; >+ undoName = null; >+ redoName = null; >+ >+ if (window.testRunner) >+ testRunner.dumpChildFramesAsText(); >+ >+ addEventListener("load", async () => { >+ debug("After adding an undo item (mainframe):"); >+ addUndoItem(); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", "mainframe"); >+ shouldBeEqualToString("redoName", ""); >+ >+ debug("After performing undo (mainframe):"); >+ document.execCommand("undo"); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", ""); >+ shouldBeEqualToString("redoName", "mainframe"); >+ >+ debug("After performing redo (mainframe):"); >+ document.execCommand("redo"); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", "mainframe"); >+ shouldBeEqualToString("redoName", ""); >+ >+ const frameWindow = frame.contentWindow; >+ >+ debug("After adding an undo item (subframe):"); >+ frameWindow.addUndoItem(); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", "subframe"); >+ shouldBeEqualToString("redoName", ""); >+ >+ debug("After performing undo (subframe):"); >+ document.execCommand("undo"); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", "mainframe"); >+ shouldBeEqualToString("redoName", "subframe"); >+ >+ debug("After performing redo (subframe):"); >+ document.execCommand("redo"); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", "subframe"); >+ shouldBeEqualToString("redoName", ""); >+ >+ finishJSTest(); >+ }); >+ </script> >+</html> >diff --git a/LayoutTests/editing/undo-manager/undo-manager-delete-stale-undo-items-expected.txt b/LayoutTests/editing/undo-manager/undo-manager-delete-stale-undo-items-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..77e1cc2da5633ee71a9912a1ce0ebf61acf5ecb8 >--- /dev/null >+++ b/LayoutTests/editing/undo-manager/undo-manager-delete-stale-undo-items-expected.txt >@@ -0,0 +1,10 @@ >+E >+Verifies that JSUndoItems are deleted when they are no longer needed by the platform clipboard. This test requires WebKitTestRunner. >+ >+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". >+ >+PASS objectCountBeforeClearingUndoStack - objectCountAfterClearingUndoStack is >= 580 >+PASS successfullyParsed is true >+ >+TEST COMPLETE >+ >diff --git a/LayoutTests/editing/undo-manager/undo-manager-delete-stale-undo-items.html b/LayoutTests/editing/undo-manager/undo-manager-delete-stale-undo-items.html >new file mode 100644 >index 0000000000000000000000000000000000000000..e52890056f593656c1d7954e98c80275ae934ca6 >--- /dev/null >+++ b/LayoutTests/editing/undo-manager/undo-manager-delete-stale-undo-items.html >@@ -0,0 +1,66 @@ >+<!DOCTYPE html> <!-- webkit-test-runner [ enableUndoManagerAPI=true ] --> >+<html> >+ <meta charset="utf8"> >+ <head> >+ <script src="../../resources/js-test.js"></script> >+ <script src="../editing.js"></script> >+ <script> >+ jsTestIsAsync = true; >+ >+ function objectCountAfterSimulatingMemoryPressureAndGarbageCollection() { >+ internals.beginSimulatedMemoryPressure(); >+ internals.endSimulatedMemoryPressure(); >+ GCController.collect(); >+ return GCController.getJSObjectCount(); >+ } >+ >+ async function runTest() { >+ description("Verifies that JSUndoItems are deleted when they are no longer needed by the platform clipboard. This test requires WebKitTestRunner."); >+ >+ if (!window.GCController) >+ return; >+ >+ editor = document.getElementById("editor"); >+ editor.focus(); >+ >+ const undoItemCount = 200; >+ >+ for (let i = 0; i < undoItemCount; ++i) { >+ document.undoManager.addItem(new UndoItem({ >+ label: "", >+ undo: () => { }, >+ redo: () => { } >+ })); >+ } >+ >+ for (let i = 0; i < undoItemCount; ++i) >+ document.execCommand("Undo"); >+ >+ objectCountBeforeClearingUndoStack = objectCountAfterSimulatingMemoryPressureAndGarbageCollection(); >+ >+ selectAllCommand(); >+ typeCharacterCommand("E"); >+ >+ // Wait until almost all of the wrappers are collected. For each UndoItem, we expect a total of 3 wrappers: >+ // one each for the undo and redo handlers, and one for the UndoItem itself. However, we also give ourselves >+ // some wiggle room for additional wrappers created when calling some testing helper functions. This is >+ // still a useful test, since it will time out in the case where either our undo items or their undo/redo >+ // handlers are not properly relinquished once they're no longer needed. >+ const minimumNumberOfWrappersToCollectBeforePassing = 3 * undoItemCount - 20; >+ objectCountAfterClearingUndoStack = objectCountBeforeClearingUndoStack; >+ while (objectCountBeforeClearingUndoStack - objectCountAfterClearingUndoStack < minimumNumberOfWrappersToCollectBeforePassing) { >+ await new Promise(resolve => setTimeout(resolve, 100)); >+ objectCountAfterClearingUndoStack = objectCountAfterSimulatingMemoryPressureAndGarbageCollection(); >+ } >+ >+ shouldBeGreaterThanOrEqual("objectCountBeforeClearingUndoStack - objectCountAfterClearingUndoStack", `${minimumNumberOfWrappersToCollectBeforePassing}`); >+ finishJSTest(); >+ } >+ </script> >+ </head> >+ <body onload="runTest()"> >+ <div contenteditable id="editor"></div> >+ <pre id="description"></pre> >+ <pre id="console"></pre> >+ </body> >+</html> >diff --git a/LayoutTests/editing/undo-manager/undo-manager-item-labels-expected.txt b/LayoutTests/editing/undo-manager/undo-manager-item-labels-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..d6d75c163a74464f687931bf0aa2cc8cbdd3ce30 >--- /dev/null >+++ b/LayoutTests/editing/undo-manager/undo-manager-item-labels-expected.txt >@@ -0,0 +1,36 @@ >+ >+Verifies that setting the label attribute of UndoItem affects the undo and redo action names in the platform undo manager. >+ >+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". >+ >+After typing: >+PASS undoName is "Typing" >+PASS redoName is "" >+After adding the first UndoItem: >+PASS undoName is "First ð¥" >+PASS redoName is "" >+After adding the second UndoItem: >+PASS undoName is "Second ð¥" >+PASS redoName is "" >+After undoing: >+PASS undoName is "First ð¥" >+PASS redoName is "Second ð¥" >+After undoing again: >+PASS undoName is "Typing" >+PASS redoName is "First ð¥" >+After redoing: >+PASS undoName is "First ð¥" >+PASS redoName is "Second ð¥" >+After redoing again: >+PASS undoName is "Second ð¥" >+PASS redoName is "" >+After undoing and then pasting: >+PASS undoName is "Paste" >+PASS redoName is "" >+After undoing the paste command: >+PASS undoName is "First ð¥" >+PASS redoName is "Paste" >+PASS successfullyParsed is true >+ >+TEST COMPLETE >+ >diff --git a/LayoutTests/editing/undo-manager/undo-manager-item-labels.html b/LayoutTests/editing/undo-manager/undo-manager-item-labels.html >new file mode 100644 >index 0000000000000000000000000000000000000000..84a7f17a26474006806f4de942499bd1b2744ded >--- /dev/null >+++ b/LayoutTests/editing/undo-manager/undo-manager-item-labels.html >@@ -0,0 +1,95 @@ >+<!DOCTYPE html> <!-- webkit-test-runner [ enableUndoManagerAPI=true ] --> >+<html> >+ <meta charset="utf8"> >+ <head> >+ <script src="../../resources/js-test.js"></script> >+ <script src="../../resources/ui-helper.js"></script> >+ <script src="../editing.js"></script> >+ <script> >+ jsTestIsAsync = true; >+ undoName = null; >+ redoName = null; >+ >+ async function runTest() { >+ description("Verifies that setting the label attribute of UndoItem affects the undo and redo action names " >+ + "in the platform undo manager."); >+ >+ field.focus(); >+ >+ debug("After typing:"); >+ document.execCommand("InsertText", true, "hello"); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", "Typing"); >+ shouldBeEqualToString("redoName", ""); >+ >+ function createUndoItemWithLabel(labelString) { >+ return new UndoItem({ >+ label: labelString, >+ undo: () => { }, >+ redo: () => { } >+ }); >+ } >+ >+ debug("After adding the first UndoItem:"); >+ const firstItem = createUndoItemWithLabel("First ð¥"); >+ document.undoManager.addItem(firstItem); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", firstItem.label); >+ shouldBeEqualToString("redoName", ""); >+ >+ debug("After adding the second UndoItem:"); >+ const secondItem = createUndoItemWithLabel("Second ð¥"); >+ document.undoManager.addItem(secondItem); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", secondItem.label); >+ shouldBeEqualToString("redoName", ""); >+ >+ debug("After undoing:"); >+ document.execCommand("undo"); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", firstItem.label); >+ shouldBeEqualToString("redoName", secondItem.label); >+ >+ debug("After undoing again:"); >+ document.execCommand("undo"); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", "Typing"); >+ shouldBeEqualToString("redoName", firstItem.label); >+ >+ debug("After redoing:"); >+ document.execCommand("redo"); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", firstItem.label); >+ shouldBeEqualToString("redoName", secondItem.label); >+ >+ debug("After redoing again:"); >+ document.execCommand("redo"); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", secondItem.label); >+ shouldBeEqualToString("redoName", ""); >+ >+ debug("After undoing and then pasting:"); >+ document.execCommand("undo"); >+ selectAllCommand(); >+ copyCommand(); >+ pasteCommand(); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", "Paste"); >+ shouldBeEqualToString("redoName", ""); >+ >+ debug("After undoing the paste command:"); >+ document.execCommand("undo"); >+ [undoName, redoName] = await UIHelper.undoAndRedoLabels(); >+ shouldBeEqualToString("undoName", firstItem.label); >+ shouldBeEqualToString("redoName", "Paste"); >+ >+ finishJSTest(); >+ } >+ </script> >+ </head> >+ <body onload="runTest()"> >+ <input id="field"></input> >+ <pre id="description"></pre> >+ <pre id="console"></pre> >+ </body> >+</html> >diff --git a/LayoutTests/editing/undo-manager/undo-manager-undo-redo-after-garbage-collection-expected.txt b/LayoutTests/editing/undo-manager/undo-manager-undo-redo-after-garbage-collection-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..7c9392adc4d3d9059907c7ecb58e15ba2845ca3b >--- /dev/null >+++ b/LayoutTests/editing/undo-manager/undo-manager-undo-redo-after-garbage-collection-expected.txt >@@ -0,0 +1,11 @@ >+After redo >+Verifies that undo and redo callback handlers survive garbage collection. This test requires WebKitTestRunner. >+ >+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". >+ >+PASS text.textContent is "After undo" >+PASS text.textContent is "After redo" >+PASS successfullyParsed is true >+ >+TEST COMPLETE >+ >diff --git a/LayoutTests/editing/undo-manager/undo-manager-undo-redo-after-garbage-collection.html b/LayoutTests/editing/undo-manager/undo-manager-undo-redo-after-garbage-collection.html >new file mode 100644 >index 0000000000000000000000000000000000000000..cad0ced4cee833cda19cee03cca14b42a4aa4821 >--- /dev/null >+++ b/LayoutTests/editing/undo-manager/undo-manager-undo-redo-after-garbage-collection.html >@@ -0,0 +1,35 @@ >+<!DOCTYPE html> <!-- webkit-test-runner [ enableUndoManagerAPI=true ] --> >+<html> >+ <meta charset="utf8"> >+ <head> >+ <script src="../../resources/js-test.js"></script> >+ <script src="../editing.js"></script> >+ <script> >+ function runTest() { >+ description("Verifies that undo and redo callback handlers survive garbage collection. This test requires WebKitTestRunner."); >+ >+ text = document.getElementById("text"); >+ >+ document.undoManager.addItem(new UndoItem({ >+ label: "Test action", >+ undo: () => text.textContent = "After undo", >+ redo: () => text.textContent = "After redo" >+ })); >+ >+ if (window.GCController) >+ GCController.collect(); >+ >+ document.execCommand("Undo"); >+ shouldBeEqualToString("text.textContent", "After undo"); >+ >+ document.execCommand("Redo"); >+ shouldBeEqualToString("text.textContent", "After redo"); >+ } >+ </script> >+ </head> >+ <body onload="runTest()"> >+ <div id="text">Initial state</div> >+ <pre id="description"></pre> >+ <pre id="console"></pre> >+ </body> >+</html> >diff --git a/LayoutTests/resources/ui-helper.js b/LayoutTests/resources/ui-helper.js >index 1884db7659650572ddcaaafc295b50202be6b564..5b320bc4d4541d781eacca63bc8fe409def6078a 100644 >--- a/LayoutTests/resources/ui-helper.js >+++ b/LayoutTests/resources/ui-helper.js >@@ -566,4 +566,13 @@ window.UIHelper = class UIHelper { > resolve({ x: offsetX, y: offsetY }); > })); > } >+ >+ static undoAndRedoLabels() >+ { >+ if (!this.isWebKit2()) >+ return Promise.resolve(); >+ >+ const script = "JSON.stringify([uiController.lastUndoLabel, uiController.firstRedoLabel])"; >+ return new Promise(resolve => testRunner.runUIScript(script, result => resolve(JSON.parse(result)))); >+ } > }
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 190009
:
359897
|
359905
|
359919
|
360113
| 360117