Add a new project for recording and playing back editing commands in editable web...
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 7 Dec 2016 20:40:43 +0000 (20:40 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 7 Dec 2016 20:40:43 +0000 (20:40 +0000)
commite64ce51796d57fb8d828dc841094240e76b86fa6
treee8cb1c1201275a74435f49571f1207b28b9eb4ab
parent0e341f32f5639ac27641dbe61e6c6fb4ba76e416
Add a new project for recording and playing back editing commands in editable web content
https://bugs.webkit.org/show_bug.cgi?id=165114
<rdar://problem/29408135>

Reviewed by Beth Dakin.

Source/WebCore:

Adds new scripts used to record and play back editing, as well as a new Xcode Copy files phase that pushes these
scripts to the internal system directory when installing. See the Tools ChangeLog and individual comments below
for more details. Covered by 3 new unit tests in the EditingHistory project.

* InternalScripts/DumpEditingHistory.js: Added.
(beginProcessingTopLevelUpdate):
(endProcessingTopLevelUpdate):
(appendDOMUpdatesFromRecords):
(appendSelectionUpdateIfNecessary):

Adds new entries into the top-level list of DOM updates captured when editing. Respectively, these are input
events and selection changes.

(EditingHistory.getEditingHistoryAsJSONString):
* InternalScripts/EditingHistoryUtil.js: Added.
(prototype._scramble):
(prototype.applyToText):
(prototype.applyToFilename):
(prototype._scrambedNumberIndexForCode):
(prototype._scrambedLowercaseIndexForCode):
(prototype._scrambedUppercaseIndexForCode):

Naive implementation of an obfuscator. Currently, this only affects alphanumeric characters. Obfuscation is off
by default, but can be toggled on in JavaScript.

(elementFromMarkdown):
(GlobalNodeMap):
(GlobalNodeMap.prototype.nodesForGUIDs):
(GlobalNodeMap.prototype.guidsForTNodes):
(GlobalNodeMap.prototype.nodeForGUID):
(GlobalNodeMap.prototype.guidForNode):
(GlobalNodeMap.prototype.hasGUIDForNode):
(GlobalNodeMap.prototype.nodes):
(GlobalNodeMap.prototype.toObject):
(GlobalNodeMap.fromObject):
(GlobalNodeMap.dataForNode):
(GlobalNodeMap.elementFromTagName):
(GlobalNodeMap.nodeAttributesToObject):
(GlobalNodeMap.prototype.descriptionHTMLForGUID):
(GlobalNodeMap.prototype.descriptionHTMLForNode):

The GlobalNodeMap keeps track of every node that has appeared in the DOM, assigning each node a globally unique
identifier (GUID). This GUID is used when reconstructing the DOM, as well as unapplying or applying editing.

(SelectionState):
(SelectionState.prototype.isEqual):
(SelectionState.prototype.applyToSelection):
(SelectionState.fromSelection):
(SelectionState.prototype.toObject):
(SelectionState.fromObject):

Represents a snapshot of the Selection state (determined by getSelection()).

(DOMUpdate):
(DOMUpdate.prototype.apply):
(DOMUpdate.prototype.unapply):
(DOMUpdate.prototype.targetNode):
(DOMUpdate.prototype.detailsElement):
(DOMUpdate.ofType):
(DOMUpdate.fromRecords):

A DOMUpdate is an abstract object representing a change in the DOM that may be applied and unapplied. These are
also serializable as hashes, which may then be converted to JSON when generating editing history data.

(ChildListUpdate):
(ChildListUpdate.prototype.apply):
(ChildListUpdate.prototype.unapply):
(ChildListUpdate.prototype._nextSibling):
(ChildListUpdate.prototype._removedNodes):
(ChildListUpdate.prototype._addedNodes):
(ChildListUpdate.prototype.toObject):
(ChildListUpdate.prototype.detailsElement):
(ChildListUpdate.fromObject):

These three update types correspond to the three types of DOM mutations. These may appear as top-level updates
if they are not captured during an input event, but for the majority of user-input-driven changes, they will be
children of an input event.

(CharacterDataUpdate):
(CharacterDataUpdate.prototype.apply):
(CharacterDataUpdate.prototype.unapply):
(CharacterDataUpdate.prototype.detailsElement):
(CharacterDataUpdate.prototype.toObject):
(CharacterDataUpdate.fromObject):
(AttributeUpdate):
(AttributeUpdate.prototype.apply):
(AttributeUpdate.prototype.unapply):
(AttributeUpdate.prototype.detailsElement):
(AttributeUpdate.prototype.toObject):
(AttributeUpdate.fromObject):
(SelectionUpdate):
(SelectionUpdate.prototype.apply):
(SelectionUpdate.prototype.unapply):
(SelectionUpdate.prototype.toObject):
(SelectionUpdate.fromObject):
(SelectionUpdate.prototype._rangeDescriptionHTML):
(SelectionUpdate.prototype._anchorDescriptionHTML):
(SelectionUpdate.prototype._focusDescriptionHTML):
(SelectionUpdate.prototype.detailsElement):

Represents a change in the Selection. While no changes to the DOM structure occur as a result of a
SelectionUpdate, the information contained in these updates is used to determine where the selection should be
when rewinding or playing back the editing history.

(InputEventUpdate):
(InputEventUpdate.prototype._obfuscatedData):
(InputEventUpdate.prototype.apply):
(InputEventUpdate.prototype.unapply):
(InputEventUpdate.prototype.toObject):
(InputEventUpdate.fromObject):
(InputEventUpdate.prototype.detailsElement):

Represents an update due to user input, which consists of some number of child DOM mutation updates.

* WebCore.xcodeproj/project.pbxproj:

Tools:

Adds a new Xcode project containing work towards rewinding and playing back editing commands. This work is
wrapped in an Xcode project to take advantage of the XCTest framework. To manually test recording, open the
capture test harness, edit the contenteditable body, and then hit cmd-S. This downloads a .json file which may
then be dragged into the playback test harness.

Also adds 3 new unit tests in EditingHistoryTests/RewindAndPlaybackTests.m. These tests carry out the following
steps:

1. Load the capture harness and perform test-specific editing on the web view.
2. Let originalState be a dump of the DOM at this point in time.
3. Extract the JSON-serialized editing history data and load the playback harness with this data.
4. Rewind all editing to the beginning.
5. Playback all editing to the end.
6. Dump the state of the DOM. This should be identical to originalState.

* EditingHistory/EditingHistory.xcodeproj/project.pbxproj: Added.
* EditingHistory/EditingHistory/Info.plist: Added.
* EditingHistory/EditingHistory/Resources/CaptureHarness.html: Added.
* EditingHistory/EditingHistory/Resources/DOMTestingUtil.js: Added.
* EditingHistory/EditingHistory/Resources/PlaybackHarness.html: Added.
* EditingHistory/EditingHistory/TestRunner.h: Added.
* EditingHistory/EditingHistory/TestRunner.m: Added.
(injectedMessageEventHandlerScript):
(-[TestRunner init]):
(-[TestRunner deleteBackwards:]):
(-[TestRunner typeString:]):
(-[TestRunner bodyElementSubtree]):
(-[TestRunner bodyTextContent]):
(-[TestRunner editingHistoryJSON]):
(-[TestRunner loadPlaybackTestHarnessWithJSON:]):
(-[TestRunner numberOfUpdates]):
(-[TestRunner jumpToUpdateIndex:]):
(-[TestRunner expectEvents:afterPerforming:]):
(-[TestRunner loadCaptureTestHarness]):
(-[TestRunner setTextObfuscationEnabled:]):
(-[TestRunner isDoneWaitingForPendingEvents]):
(-[TestRunner userContentController:didReceiveScriptMessage:]):

The TestRunner provides utilities that a unit test should use to drive the test forward (e.g. loading harnesses)
or inspect the state of the loaded page (e.g. extracting JSON editing history data from the capture harness).

* EditingHistory/EditingHistory/TestUtil.h: Added.
* EditingHistory/EditingHistory/TestUtil.m: Added.
(waitUntilWithTimeout):
(waitUntil):

Provides utilities for running tests. For now, this is just spinning the runloop on a given condition.

* EditingHistory/EditingHistory/WKWebViewAdditions.h: Added.
* EditingHistory/EditingHistory/WKWebViewAdditions.m: Added.
(-[WKWebView loadPageFromBundleNamed:]):
(-[WKWebView typeCharacter:]):
(-[WKWebView keyPressWithCharacters:keyCode:]):
(-[WKWebView stringByEvaluatingJavaScriptFromString:]):

Provides utilities for simulating interaction in a web view.

* EditingHistory/EditingHistory/main.m: Added.
(main):
* EditingHistory/EditingHistoryTests/Info.plist: Added.
* EditingHistory/EditingHistoryTests/RewindAndPlaybackTests.m: Added.
(-[RewindAndPlaybackTests setUp]):
(-[RewindAndPlaybackTests tearDown]):
(-[RewindAndPlaybackTests testTypingSingleLineOfText]):
(-[RewindAndPlaybackTests testTypingMultipleLinesOfText]):
(-[RewindAndPlaybackTests testTypingAndDeletingText]):
(-[RewindAndPlaybackTests rewindAndPlaybackEditingInPlaybackTestHarness]):
(-[RewindAndPlaybackTests originalBodySubtree:isEqualToFinalSubtree:]):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@209470 268f45cc-cd09-0410-ab3c-d52691b4dbfc
19 files changed:
Source/WebCore/ChangeLog
Source/WebCore/InternalScripts/DumpEditingHistory.js [new file with mode: 0644]
Source/WebCore/InternalScripts/EditingHistoryUtil.js [new file with mode: 0644]
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Tools/ChangeLog
Tools/EditingHistory/EditingHistory.xcodeproj/project.pbxproj [new file with mode: 0644]
Tools/EditingHistory/EditingHistory/Info.plist [new file with mode: 0644]
Tools/EditingHistory/EditingHistory/Resources/CaptureHarness.html [new file with mode: 0644]
Tools/EditingHistory/EditingHistory/Resources/DOMTestingUtil.js [new file with mode: 0644]
Tools/EditingHistory/EditingHistory/Resources/PlaybackHarness.html [new file with mode: 0644]
Tools/EditingHistory/EditingHistory/TestRunner.h [new file with mode: 0644]
Tools/EditingHistory/EditingHistory/TestRunner.m [new file with mode: 0644]
Tools/EditingHistory/EditingHistory/TestUtil.h [new file with mode: 0644]
Tools/EditingHistory/EditingHistory/TestUtil.m [new file with mode: 0644]
Tools/EditingHistory/EditingHistory/WKWebViewAdditions.h [new file with mode: 0644]
Tools/EditingHistory/EditingHistory/WKWebViewAdditions.m [new file with mode: 0644]
Tools/EditingHistory/EditingHistory/main.m [new file with mode: 0644]
Tools/EditingHistory/EditingHistoryTests/Info.plist [new file with mode: 0644]
Tools/EditingHistory/EditingHistoryTests/RewindAndPlaybackTests.m [new file with mode: 0644]