Copied: trunk/Source/WebCore/Scripts/DumpEditingHistory.js (from rev 210317, trunk/Source/WebCore/InternalScripts/DumpEditingHistory.js) (0 => 210318)
--- trunk/Source/WebCore/Scripts/DumpEditingHistory.js (rev 0)
+++ trunk/Source/WebCore/Scripts/DumpEditingHistory.js 2017-01-05 05:09:04 UTC (rev 210318)
@@ -0,0 +1,93 @@
+(() => {
+ let initialized = false;
+ let globalNodeMap = new EditingHistory.GlobalNodeMap();
+ let topLevelUpdates = [];
+ let currentChildUpdates = [];
+ let isProcessingTopLevelUpdate = false;
+ let lastKnownSelectionState = null;
+ let mutationObserver = new MutationObserver(records => appendDOMUpdatesFromRecords(records));
+
+ function beginProcessingTopLevelUpdate() {
+ isProcessingTopLevelUpdate = true;
+ }
+
+ function endProcessingTopLevelUpdate(topLevelUpdate) {
+ topLevelUpdates.push(topLevelUpdate);
+ currentChildUpdates = [];
+ isProcessingTopLevelUpdate = false;
+ }
+
+ function appendDOMUpdatesFromRecords(records) {
+ if (!records.length)
+ return;
+
+ let newUpdates = EditingHistory.DOMUpdate.fromRecords(records, globalNodeMap);
+ if (isProcessingTopLevelUpdate)
+ currentChildUpdates = currentChildUpdates.concat(newUpdates);
+ else
+ topLevelUpdates = topLevelUpdates.concat(newUpdates);
+ }
+
+ function appendSelectionUpdateIfNecessary() {
+ let newSelectionState = EditingHistory.SelectionState.fromSelection(getSelection(), globalNodeMap);
+ if (newSelectionState.isEqual(lastKnownSelectionState))
+ return;
+
+ let update = new EditingHistory.SelectionUpdate(globalNodeMap, newSelectionState);
+ if (isProcessingTopLevelUpdate)
+ currentChildUpdates.push(update);
+ else
+ topLevelUpdates.push(update);
+ lastKnownSelectionState = newSelectionState;
+ }
+
+ document.body.setAttribute("contenteditable", true);
+ document.body.addEventListener("focus", () => {
+ if (initialized)
+ return;
+
+ initialized = true;
+
+ EditingHistory.getEditingHistoryAsJSONString = (formatted) => {
+ let record = {};
+ record.updates = topLevelUpdates.map(update => update.toObject());
+ record.globalNodeMap = globalNodeMap.toObject();
+ return formatted ? JSON.stringify(record, null, 4) : JSON.stringify(record);
+ };
+
+ document.addEventListener("selectionchange", () => {
+ appendSelectionUpdateIfNecessary();
+ });
+ document.addEventListener("beforeinput", event => {
+ appendDOMUpdatesFromRecords(mutationObserver.takeRecords());
+ beginProcessingTopLevelUpdate();
+ });
+
+ document.addEventListener("input", event => {
+ appendDOMUpdatesFromRecords(mutationObserver.takeRecords());
+ let eventData = event.dataTransfer ? event.dataTransfer.getData("text/html") : event.data;
+ lastKnownSelectionState = null;
+ endProcessingTopLevelUpdate(new EditingHistory.InputEventUpdate(globalNodeMap, currentChildUpdates, event.inputType, eventData, event.timeStamp));
+ });
+
+ document.addEventListener("keydown", event => {
+ if (event.key !== "s" || !event.metaKey)
+ return;
+
+ let fakeLink = document.createElement("a");
+ fakeLink.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(EditingHistory.getEditingHistoryAsJSONString()));
+ fakeLink.setAttribute("download", "record.json");
+ fakeLink.click();
+ event.preventDefault();
+ });
+
+ mutationObserver.observe(document, {
+ childList: true,
+ attributes: true,
+ characterData: true,
+ subtree: true,
+ attributeOldValue: true,
+ characterDataOldValue: true,
+ });
+ });
+})();
Copied: trunk/Source/WebCore/Scripts/EditingHistoryUtil.js (from rev 210317, trunk/Source/WebCore/InternalScripts/EditingHistoryUtil.js) (0 => 210318)
--- trunk/Source/WebCore/Scripts/EditingHistoryUtil.js (rev 0)
+++ trunk/Source/WebCore/Scripts/EditingHistoryUtil.js 2017-01-05 05:09:04 UTC (rev 210318)
@@ -0,0 +1,693 @@
+(() => {
+ class Obfuscator {
+ constructor() {
+ this._scrambledLowercaseLetters = this._scramble(Array(26).fill().map((_, i) => 97 + i));
+ this._scrambledUppercaseLetters = this._scramble(Array(26).fill().map((_, i) => 65 + i));
+ this._scrambledNumbers = this._scramble(Array(10).fill().map((_, i) => 48 + i));
+ this.enabled = false;
+ }
+
+ _scramble(array) {
+ for (var i = array.length - 1; i > 0; i--) {
+ let j = Math.floor(Math.random() * (i + 1));
+ let temp = array[i];
+ array[i] = array[j];
+ array[j] = temp;
+ }
+ return array;
+ }
+
+ applyToText(text) {
+ if (!this.enabled || !text)
+ return text;
+
+ let result = "";
+ for (let index = 0; index < text.length; index++) {
+ let code = text.charCodeAt(index);
+ let numberIndex = this._scrambedNumberIndexForCode(code);
+ let lowercaseIndex = this._scrambedLowercaseIndexForCode(code);
+ let uppercaseIndex = this._scrambedUppercaseIndexForCode(code);
+
+ if (numberIndex != null)
+ result += String.fromCharCode(this._scrambledNumbers[numberIndex]);
+ else if (lowercaseIndex != null)
+ result += String.fromCharCode(this._scrambledLowercaseLetters[lowercaseIndex]);
+ else if (uppercaseIndex != null)
+ result += String.fromCharCode(this._scrambledUppercaseLetters[uppercaseIndex]);
+ else
+ result += text.charAt(index);
+ }
+ return result;
+ }
+
+ applyToFilename(filename) {
+ if (!this.enabled || !filename)
+ return filename;
+
+ let components = filename.split(".");
+ return components.map((component, index) => {
+ if (index == components.length - 1)
+ return component;
+
+ return this.applyToText(component);
+ }).join(".");
+ }
+
+ _scrambedNumberIndexForCode(code) {
+ return 48 <= code && code <= 57 ? code - 48 : null;
+ }
+
+ _scrambedLowercaseIndexForCode(code) {
+ return 97 <= code && code <= 122 ? code - 97 : null;
+ }
+
+ _scrambedUppercaseIndexForCode(code) {
+ return 65 <= code && code <= 90 ? code - 65 : null;
+ }
+
+ static shared() {
+ if (!Obfuscator._sharedInstance)
+ Obfuscator._sharedInstance = new Obfuscator();
+ return Obfuscator._sharedInstance;
+ }
+ }
+
+ function elementFromMarkdown(html) {
+ let temporaryDiv = document.createElement("div");
+ temporaryDiv.innerHTML = html;
+ return temporaryDiv.children[0];
+ }
+
+ class GlobalNodeMap {
+ constructor(nodesByGUID) {
+ this._nodesByGUID = nodesByGUID ? nodesByGUID : new Map();
+ this._guidsByNode = new Map();
+ this._currentGUID = 0;
+ for (let [guid, node] of this._nodesByGUID) {
+ this._guidsByNode.set(node, guid);
+ this._currentGUID = Math.max(this._currentGUID, guid);
+ }
+ this._currentGUID++;
+ }
+
+ nodesForGUIDs(guids) {
+ if (!guids.map)
+ guids = Array.from(guids);
+ return guids.map(guid => this.nodeForGUID(guid));
+ }
+
+ guidsForNodes(nodes) {
+ if (!nodes.map)
+ nodes = Array.from(nodes);
+ return nodes.map(node => this.guidForNode(node));
+ }
+
+ nodeForGUID(guid) {
+ if (!guid)
+ return null;
+
+ return this._nodesByGUID.get(guid);
+ }
+
+ guidForNode(node) {
+ if (!node)
+ return 0;
+
+ if (this.hasGUIDForNode(node))
+ return this._guidsByNode.get(node);
+
+ const guid = this._currentGUID;
+ this._guidsByNode.set(node, guid);
+ this._nodesByGUID.set(guid, node);
+ this._currentGUID++;
+ return guid;
+ }
+
+ hasGUIDForNode(node) {
+ return !!this._guidsByNode.get(node);
+ }
+
+ nodes() {
+ return Array.from(this._nodesByGUID.values());
+ }
+
+ toObject() {
+ let nodesAndGUIDsToProcess = [], guidsToProcess = new Set();
+ let guidsByNodeIterator = this._guidsByNode.entries();
+ for (let entry = guidsByNodeIterator.next(); !entry.done; entry = guidsByNodeIterator.next()) {
+ nodesAndGUIDsToProcess.push(entry.value);
+ guidsToProcess.add(entry.value[1]);
+ }
+
+ let iterator = document.createNodeIterator(document.body, NodeFilter.SHOW_ALL);
+ for (let node = iterator.nextNode(); node; node = iterator.nextNode()) {
+ if (this.hasGUIDForNode(node))
+ continue;
+
+ let newGUID = this.guidForNode(node);
+ nodesAndGUIDsToProcess.push([node, newGUID]);
+ guidsToProcess.add(newGUID);
+ }
+
+ let nodeInfoArray = [];
+ while (nodesAndGUIDsToProcess.length) {
+ let [node, guid] = nodesAndGUIDsToProcess.pop();
+ let info = {};
+ info.guid = guid;
+ info.tagName = node.tagName;
+ info.attributes = GlobalNodeMap.nodeAttributesToObject(node);
+ info.type = node.nodeType;
+ info.data = ""
+ if (node.hasChildNodes()) {
+ info.childGUIDs = this.guidsForNodes(node.childNodes);
+ for (let childGUID of info.childGUIDs) {
+ if (!guidsToProcess.has(childGUID))
+ nodesAndGUIDsToProcess.push([this.nodeForGUID(childGUID), childGUID]);
+ }
+ }
+ nodeInfoArray.push(info);
+ }
+
+ return nodeInfoArray;
+ }
+
+ static fromObject(nodeInfoArray) {
+ let nodesByGUID = new Map();
+ for (let info of nodeInfoArray) {
+ let node = null;
+ if (info.type == Node.ELEMENT_NODE)
+ node = GlobalNodeMap.elementFromTagName(info.tagName, info.attributes, info.data);
+
+ if (info.type == Node.TEXT_NODE)
+ node = document.createTextNode(info.data);
+
+ if (info.type == Node.DOCUMENT_NODE)
+ node = document;
+
+ console.assert(node);
+ nodesByGUID.set(info.guid, node);
+ }
+
+ // Then, set child nodes for all nodes that do not appear in the DOM.
+ for (let info of nodeInfoArray.filter(info => !!info.childGUIDs)) {
+ let node = nodesByGUID.get(info.guid);
+ for (let childGUID of info.childGUIDs)
+ node.appendChild(nodesByGUID.get(childGUID));
+ }
+
+ return new GlobalNodeMap(nodesByGUID);
+ }
+
+ static dataForNode(node) {
+ if (node.nodeType === Node.TEXT_NODE)
+ return Obfuscator.shared().applyToText(node.data);
+
+ if (node.tagName && node.tagName.toLowerCase() === "attachment") {
+ return {
+ type: node.file.type,
+ name: Obfuscator.shared().applyToFilename(node.file.name),
+ lastModified: new Date().getTime()
+ };
+ }
+
+ return null;
+ }
+
+ static elementFromTagName(tagName, attributes, data) {
+ let node = document.createElement(tagName);
+ for (let attributeName in attributes)
+ node.setAttribute(attributeName, attributes[attributeName]);
+
+ if (tagName.toLowerCase() == "attachment") {
+ node.file = new File([`File named '${data.name}'`], data.name, {
+ type: data.type,
+ lastModified: data.lastModified
+ });
+ }
+
+ return node;
+ }
+
+ // Returns an Object containing attribute name => attribute value
+ static nodeAttributesToObject(node, attributesToExclude=[]) {
+ const excludeAttributesSet = new Set(attributesToExclude);
+ if (!node.attributes)
+ return null;
+
+ let attributeMap = {};
+ for (let index = 0; index < node.attributes.length; index++) {
+ const attribute = node.attributes.item(index);
+ const [localName, value] = [attribute.localName, attribute.value];
+ if (excludeAttributesSet.has(localName))
+ continue;
+
+ attributeMap[localName] = value;
+ }
+
+ return attributeMap;
+ }
+
+ descriptionHTMLForGUID(guid) {
+ return `<span eh-guid=${guid} class="eh-node">${this.nodeForGUID(guid).nodeName}</span>`;
+ }
+
+ descriptionHTMLForNode(node) {
+ if (!node)
+ return "(null)";
+ return `<span eh-guid=${this.guidForNode(node)} class="eh-node">${node.nodeName}</span>`;
+ }
+ }
+
+ class SelectionState {
+ constructor(nodeMap, startNode, startOffset, endNode, endOffset, anchorNode, anchorOffset, focusNode, focusOffset) {
+ console.assert(nodeMap);
+ this.nodeMap = nodeMap;
+ this.startGUID = nodeMap.guidForNode(startNode);
+ this.startOffset = startOffset;
+ this.endGUID = nodeMap.guidForNode(endNode);
+ this.endOffset = endOffset;
+ this.anchorGUID = nodeMap.guidForNode(anchorNode);
+ this.anchorOffset = anchorOffset;
+ this.focusGUID = nodeMap.guidForNode(focusNode);
+ this.focusOffset = focusOffset;
+ }
+
+ isEqual(otherSelectionState) {
+ return otherSelectionState
+ && this.startGUID === otherSelectionState.startGUID && this.startOffset === otherSelectionState.startOffset
+ && this.endGUID === otherSelectionState.endGUID && this.endOffset === otherSelectionState.endOffset
+ && this.anchorGUID === otherSelectionState.anchorGUID && this.anchorOffset === otherSelectionState.anchorOffset
+ && this.focusGUID === otherSelectionState.focusGUID && this.focusOffset === otherSelectionState.focusOffset;
+ }
+
+ applyToSelection(selection) {
+ selection.removeAllRanges();
+ let range = document.createRange();
+ range.setStart(this.nodeMap.nodeForGUID(this.startGUID), this.startOffset);
+ range.setEnd(this.nodeMap.nodeForGUID(this.endGUID), this.endOffset);
+ selection.addRange(range);
+ selection.setBaseAndExtent(this.nodeMap.nodeForGUID(this.anchorGUID), this.anchorOffset, this.nodeMap.nodeForGUID(this.focusGUID), this.focusOffset);
+ }
+
+ static fromSelection(selection, nodeMap) {
+ let [startNode, startOffset, endNode, endOffset] = [null, 0, null, 0];
+ if (selection.rangeCount) {
+ let selectedRange = selection.getRangeAt(0);
+ startNode = selectedRange.startContainer;
+ startOffset = selectedRange.startOffset;
+ endNode = selectedRange.endContainer;
+ endOffset = selectedRange.endOffset;
+ }
+ return new SelectionState(
+ nodeMap, startNode, startOffset, endNode, endOffset,
+ selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset
+ );
+ }
+
+ toObject() {
+ return {
+ startGUID: this.startGUID, startOffset: this.startOffset, endGUID: this.endGUID, endOffset: this.endOffset,
+ anchorGUID: this.anchorGUID, anchorOffset: this.anchorOffset, focusGUID: this.focusGUID, focusOffset: this.focusOffset
+ };
+ }
+
+ static fromObject(json, nodeMap) {
+ if (!json)
+ return null;
+
+ return new SelectionState(
+ nodeMap, nodeMap.nodeForGUID(json.startGUID), json.startOffset, nodeMap.nodeForGUID(json.endGUID), json.endOffset,
+ nodeMap.nodeForGUID(json.anchorGUID), json.anchorOffset, nodeMap.nodeForGUID(json.focusGUID), json.focusOffset
+ );
+ }
+ }
+
+ class DOMUpdate {
+ constructor(nodeMap) {
+ console.assert(nodeMap);
+ this.nodeMap = nodeMap;
+ }
+
+ apply() {
+ throw "Expected subclass implementation.";
+ }
+
+ unapply() {
+ throw "Expected subclass implementation.";
+ }
+
+ targetNode() {
+ return this.nodeMap.nodeForGUID(this.targetGUID);
+ }
+
+ detailsElement() {
+ throw "Expected subclass implementation.";
+ }
+
+ static ofType(type) {
+ if (!DOMUpdate._allTypes)
+ DOMUpdate._allTypes = { ChildListUpdate, CharacterDataUpdate, AttributeUpdate, InputEventUpdate, SelectionUpdate };
+ return DOMUpdate._allTypes[type];
+ }
+
+ static fromRecords(records, nodeMap) {
+ let updates = []
+ , characterDataUpdates = []
+ , attributeUpdates = [];
+
+ for (let record of records) {
+ let target = record.target;
+ switch (record.type) {
+ case "characterData":
+ var update = new CharacterDataUpdate(nodeMap, nodeMap.guidForNode(target), record.oldValue, target.data)
+ updates.push(update);
+ characterDataUpdates.push(update);
+ break;
+ case "childList":
+ var update = new ChildListUpdate(nodeMap, nodeMap.guidForNode(target), nodeMap.guidsForNodes(record.addedNodes), nodeMap.guidsForNodes(record.removedNodes), nodeMap.guidForNode(record.nextSibling))
+ updates.push(update);
+ break;
+ case "attributes":
+ var update = new AttributeUpdate(nodeMap, nodeMap.guidForNode(target), record.attributeName, record.oldValue, target.getAttribute(record.attributeName))
+ updates.push(update);
+ attributeUpdates.push(update);
+ break;
+ }
+ }
+
+ // Adjust all character data updates for the same target.
+ characterDataUpdates.forEach((currentUpdate, index) => {
+ if (index == characterDataUpdates.length - 1)
+ return;
+
+ for (let nextUpdateIndex = index + 1; nextUpdateIndex < characterDataUpdates.length; nextUpdateIndex++) {
+ let nextUpdate = characterDataUpdates[nextUpdateIndex];
+ if (currentUpdate.targetGUID === nextUpdate.targetGUID) {
+ currentUpdate.newData = nextUpdate.oldData;
+ break;
+ }
+ }
+ });
+
+ // Adjust all attribute updates for the same target and attribute name.
+ attributeUpdates.forEach((currentUpdate, index) => {
+ if (index == attributeUpdates.length - 1)
+ return;
+
+ for (let nextUpdateIndex = index + 1; nextUpdateIndex < attributeUpdates.length; nextUpdateIndex++) {
+ let nextUpdate = attributeUpdates[nextUpdateIndex];
+ if (currentUpdate.targetGUID === nextUpdate.targetGUID && currentUpdate.attribute === nextUpdate.attribute) {
+ currentUpdate.newData = nextUpdate.oldData;
+ break;
+ }
+ }
+ });
+
+ return updates;
+ }
+ }
+
+ class ChildListUpdate extends DOMUpdate {
+ constructor(nodeMap, targetGUID, addedGUIDs, removedGUIDs, nextSiblingGUID) {
+ super(nodeMap);
+ this.targetGUID = targetGUID;
+ this.added = addedGUIDs;
+ this.removed = removedGUIDs;
+ this.nextSiblingGUID = nextSiblingGUID == undefined ? null : nextSiblingGUID;
+ console.assert(nodeMap.nodeForGUID(targetGUID));
+ }
+
+ apply() {
+ for (let removedNode of this._removedNodes())
+ removedNode.remove();
+
+ let target = this.targetNode();
+ for (let addedNode of this._addedNodes())
+ target.insertBefore(addedNode, this._nextSibling());
+ }
+
+ unapply() {
+ for (let addedNode of this._addedNodes())
+ addedNode.remove();
+
+ let target = this.targetNode();
+ for (let removedNode of this._removedNodes())
+ target.insertBefore(removedNode, this._nextSibling());
+ }
+
+ _nextSibling() {
+ if (this.nextSiblingGUID == null)
+ return null;
+ return this.nodeMap.nodeForGUID(this.nextSiblingGUID);
+ }
+
+ _removedNodes() {
+ return this.nodeMap.nodesForGUIDs(this.removed);
+ }
+
+ _addedNodes() {
+ return this.nodeMap.nodesForGUIDs(this.added);
+ }
+
+ toObject() {
+ return {
+ type: "ChildListUpdate",
+ targetGUID: this.targetGUID,
+ addedGUIDs: this.added,
+ removedGUIDs: this.removed,
+ nextSiblingGUID: this.nextSiblingGUID
+ };
+ }
+
+ detailsElement() {
+ let nextSibling = this._nextSibling();
+ let html =
+ `<details>
+ <summary>child list changed</summary>
+ <ul>
+ <li>parent: ${this.nodeMap.descriptionHTMLForGUID(this.targetGUID)}</li>
+ <li>added: [ ${[this._addedNodes().map(node => this.nodeMap.descriptionHTMLForNode(node))]} ]</li>
+ <li>removed: [ ${[this._removedNodes().map(node => this.nodeMap.descriptionHTMLForNode(node))]} ]</li>
+ <li>before sibling: ${nextSibling ? this.nodeMap.descriptionHTMLForNode(nextSibling) : "(null)"}</li>
+ </ul>
+ </details>`;
+ return elementFromMarkdown(html);
+ }
+
+ static fromObject(json, nodeMap) {
+ return new ChildListUpdate(nodeMap, json.targetGUID, json.addedGUIDs, json.removedGUIDs, json.nextSiblingGUID);
+ }
+ }
+
+ class CharacterDataUpdate extends DOMUpdate {
+ constructor(nodeMap, targetGUID, oldData, newData) {
+ super(nodeMap);
+ this.targetGUID = targetGUID;
+ this.oldData = oldData;
+ this.newData = newData;
+ console.assert(nodeMap.nodeForGUID(targetGUID));
+ }
+
+ apply() {
+ this.targetNode().data = ""
+ }
+
+ unapply() {
+ this.targetNode().data = ""
+ }
+
+ detailsElement() {
+ let html =
+ `<details>
+ <summary>character data changed</summary>
+ <ul>
+ <li>old: ${this.oldData != null ? "'" + this.oldData + "'" : "(null)"}</li>
+ <li>new: ${this.newData != null ? "'" + this.newData + "'" : "(null)"}</li>
+ </ul>
+ </details>`;
+ return elementFromMarkdown(html);
+ }
+
+ toObject() {
+ return {
+ type: "CharacterDataUpdate",
+ targetGUID: this.targetGUID,
+ oldData: Obfuscator.shared().applyToText(this.oldData),
+ newData: Obfuscator.shared().applyToText(this.newData)
+ };
+ }
+
+ static fromObject(json, nodeMap) {
+ return new CharacterDataUpdate(nodeMap, json.targetGUID, json.oldData, json.newData);
+ }
+ }
+
+ class AttributeUpdate extends DOMUpdate {
+ constructor(nodeMap, targetGUID, attribute, oldValue, newValue) {
+ super(nodeMap);
+ this.targetGUID = targetGUID;
+ this.attribute = attribute;
+ this.oldValue = oldValue;
+ this.newValue = newValue;
+ console.assert(nodeMap.nodeForGUID(targetGUID));
+ }
+
+ apply() {
+ if (this.newValue == null)
+ this.targetNode().removeAttribute(this.attribute);
+ else
+ this.targetNode().setAttribute(this.attribute, this.newValue);
+ }
+
+ unapply() {
+ if (this.oldValue == null)
+ this.targetNode().removeAttribute(this.attribute);
+ else
+ this.targetNode().setAttribute(this.attribute, this.oldValue);
+ }
+
+ detailsElement() {
+ let html =
+ `<details>
+ <summary>attribute changed</summary>
+ <ul>
+ <li>target: ${this.nodeMap.descriptionHTMLForGUID(this.targetGUID)}</li>
+ <li>attribute: ${this.attribute}</li>
+ <li>old: ${this.oldValue != null ? "'" + this.oldValue + "'" : "(null)"}</li>
+ <li>new: ${this.newValue != null ? "'" + this.newValue + "'" : "(null)"}</li>
+ </ul>
+ </details>`;
+ return elementFromMarkdown(html);
+ }
+
+ toObject() {
+ return {
+ type: "AttributeUpdate",
+ targetGUID: this.targetGUID,
+ attribute: this.attribute,
+ oldValue: this.oldValue,
+ newValue: this.newValue
+ };
+ }
+
+ static fromObject(json, nodeMap) {
+ return new AttributeUpdate(nodeMap, json.targetGUID, json.attribute, json.oldValue, json.newValue);
+ }
+ }
+
+ class SelectionUpdate extends DOMUpdate {
+ constructor(nodeMap, state) {
+ super(nodeMap);
+ this.state = state;
+ }
+
+ // SelectionUpdates are not applied/unapplied by the normal means. The selection is applied via
+ // DOMUpdateHistoryContext.applyCurrentSelectionState instead, which considers the updates before and after the
+ // current update index.
+ apply() { }
+ unapply() { }
+
+ toObject() {
+ return {
+ type: "SelectionUpdate",
+ state: this.state ? this.state.toObject() : null
+ };
+ }
+
+ static fromObject(json, nodeMap) {
+ return new SelectionUpdate(nodeMap, SelectionState.fromObject(json.state, nodeMap));
+ }
+
+ _rangeDescriptionHTML() {
+ return `(${this.nodeMap.descriptionHTMLForGUID(this.state.startGUID)}:${this.state.startOffset},
+ ${this.nodeMap.descriptionHTMLForGUID(this.state.endGUID)}:${this.state.endOffset})`;
+ }
+
+ _anchorDescriptionHTML() {
+ return `${this.nodeMap.descriptionHTMLForGUID(this.state.anchorGUID)}:${this.state.anchorOffset}`;
+ }
+
+ _focusDescriptionHTML() {
+ return `${this.nodeMap.descriptionHTMLForGUID(this.state.focusGUID)}:${this.state.focusOffset}`;
+ }
+
+ detailsElement() {
+ let html =
+ `<details>
+ <summary>Selection changed</summary>
+ <ul>
+ <li>range: ${this._rangeDescriptionHTML()}</li>
+ <li>anchor: ${this._anchorDescriptionHTML()}</li>
+ <li>focus: ${this._focusDescriptionHTML()}</li>
+ </ul>
+ </details>`;
+ return elementFromMarkdown(html);
+ }
+ }
+
+ class InputEventUpdate extends DOMUpdate {
+ constructor(nodeMap, updates, inputType, data, timeStamp) {
+ super(nodeMap);
+ this.updates = updates;
+ this.inputType = inputType;
+ this.data = ""
+ this.timeStamp = timeStamp;
+ }
+
+ _obfuscatedData() {
+ return this.inputType.indexOf("insert") == 0 ? Obfuscator.shared().applyToText(this.data) : this.data;
+ }
+
+ apply() {
+ for (let update of this.updates)
+ update.apply();
+ }
+
+ unapply() {
+ for (let index = this.updates.length - 1; index >= 0; index--)
+ this.updates[index].unapply();
+ }
+
+ toObject() {
+ return {
+ type: "InputEventUpdate",
+ inputType: this.inputType,
+ data: this._obfuscatedData(),
+ timeStamp: this.timeStamp,
+ updates: this.updates.map(update => update.toObject())
+ };
+ }
+
+ static fromObject(json, nodeMap) {
+ let updates = json.updates.map(update => DOMUpdate.ofType(update.type).fromObject(update, nodeMap));
+ return new InputEventUpdate(nodeMap, updates, json.inputType, json.data, json.timeStamp);
+ }
+
+ detailsElement() {
+ let html =
+ `<details>
+ <summary>Input (${this.inputType})</summary>
+ <ul>
+ <li>time: ${this.timeStamp}</li>
+ <li>data: ${!this.data ? "(null)" : "'" + this.data + "'"}</li>
+ </ul>
+ </details>`;
+ let topLevelDetails = elementFromMarkdown(html);
+ for (let update of this.updates)
+ topLevelDetails.children[topLevelDetails.childElementCount - 1].appendChild(update.detailsElement());
+ return topLevelDetails;
+ }
+ }
+
+ window.EditingHistory = {
+ GlobalNodeMap,
+ SelectionState,
+ DOMUpdate,
+ ChildListUpdate,
+ CharacterDataUpdate,
+ AttributeUpdate,
+ SelectionUpdate,
+ InputEventUpdate,
+ Obfuscator
+ };
+})();
Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (210317 => 210318)
--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj 2017-01-05 04:43:13 UTC (rev 210317)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj 2017-01-05 05:09:04 UTC (rev 210318)
@@ -2213,8 +2213,6 @@
51C81B8A0C4422F70019ECE3 /* FTPDirectoryParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 51C81B880C4422F70019ECE3 /* FTPDirectoryParser.h */; };
51CBFC990D10E483002DBF51 /* CachedFramePlatformData.h in Headers */ = {isa = PBXBuildFile; fileRef = 51CBFC980D10E483002DBF51 /* CachedFramePlatformData.h */; settings = {ATTRIBUTES = (Private, ); }; };
51D0C5160DAA90B7003B3831 /* JSStorageCustom.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51D0C5150DAA90B7003B3831 /* JSStorageCustom.cpp */; };
- 51D394781DF2492200ABE875 /* DumpEditingHistory.js in Copy Internal Scripts */ = {isa = PBXBuildFile; fileRef = 51D394741DF2454000ABE875 /* DumpEditingHistory.js */; };
- 51D394791DF2492200ABE875 /* EditingHistoryUtil.js in Copy Internal Scripts */ = {isa = PBXBuildFile; fileRef = 51D394751DF2454000ABE875 /* EditingHistoryUtil.js */; };
51D7236C1BB6174900478CA3 /* IDBResultData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51D7236A1BB60BFE00478CA3 /* IDBResultData.cpp */; };
51D7236D1BB6174900478CA3 /* IDBResultData.h in Headers */ = {isa = PBXBuildFile; fileRef = 51D7236B1BB60BFE00478CA3 /* IDBResultData.h */; settings = {ATTRIBUTES = (Private, ); }; };
51D7EFEA1BDE8F8C00E93E10 /* ThreadSafeDataBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = 511FAEA91BDC989A00B4AFE4 /* ThreadSafeDataBuffer.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -6489,6 +6487,8 @@
F478755519983AFF0024A287 /* ScrollSnapAnimatorState.mm in Sources */ = {isa = PBXBuildFile; fileRef = F478755319983AFF0024A287 /* ScrollSnapAnimatorState.mm */; };
F47A5E3E195B8C8A00483100 /* StyleScrollSnapPoints.h in Headers */ = {isa = PBXBuildFile; fileRef = F47A5E3B195B8C8A00483100 /* StyleScrollSnapPoints.h */; settings = {ATTRIBUTES = (Private, ); }; };
F47A5E3F195B8E4800483100 /* StyleScrollSnapPoints.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F47A5E3A195B8C8A00483100 /* StyleScrollSnapPoints.cpp */; };
+ F4BFB9851E1DDF9B00862C24 /* DumpEditingHistory.js in Copy Scripts */ = {isa = PBXBuildFile; fileRef = F48389831E1DDF2B0076B7EA /* DumpEditingHistory.js */; };
+ F4BFB9861E1DDF9B00862C24 /* EditingHistoryUtil.js in Copy Scripts */ = {isa = PBXBuildFile; fileRef = F48389841E1DDF2B0076B7EA /* EditingHistoryUtil.js */; };
F50664F7157F52DC00AC226F /* FormController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F50664F5157F52DC00AC226F /* FormController.cpp */; };
F50664F8157F52DC00AC226F /* FormController.h in Headers */ = {isa = PBXBuildFile; fileRef = F50664F6157F52DC00AC226F /* FormController.h */; };
F513A3EA15FF4841001526DB /* ValidationMessageClient.h in Headers */ = {isa = PBXBuildFile; fileRef = F513A3E915FF4841001526DB /* ValidationMessageClient.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -6948,6 +6948,8 @@
dstPath = PrivateHeaders/Scripts;
dstSubfolderSpec = 1;
files = (
+ F4BFB9851E1DDF9B00862C24 /* DumpEditingHistory.js in Copy Scripts */,
+ F4BFB9861E1DDF9B00862C24 /* EditingHistoryUtil.js in Copy Scripts */,
3717D7E817ECC591003C276D /* extract-localizable-strings.pl in Copy Scripts */,
37D456FD1A9A50D8003330A1 /* LocalizableStrings.pm in Copy Scripts */,
);
@@ -6954,18 +6956,6 @@
name = "Copy Scripts";
runOnlyForDeploymentPostprocessing = 0;
};
- 51D394771DF2486700ABE875 /* Copy Internal Scripts */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 8;
- dstPath = "$(APPLE_INTERNAL_LIBRARY_DIR)/WebKit/InternalScripts";
- dstSubfolderSpec = 0;
- files = (
- 51D394781DF2492200ABE875 /* DumpEditingHistory.js in Copy Internal Scripts */,
- 51D394791DF2492200ABE875 /* EditingHistoryUtil.js in Copy Internal Scripts */,
- );
- name = "Copy Internal Scripts";
- runOnlyForDeploymentPostprocessing = 1;
- };
CD0DBF001422765700280263 /* Copy Audio Resources */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -9424,8 +9414,6 @@
51C81B880C4422F70019ECE3 /* FTPDirectoryParser.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FTPDirectoryParser.h; sourceTree = "<group>"; };
51CBFC980D10E483002DBF51 /* CachedFramePlatformData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CachedFramePlatformData.h; sourceTree = "<group>"; };
51D0C5150DAA90B7003B3831 /* JSStorageCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSStorageCustom.cpp; sourceTree = "<group>"; };
- 51D394741DF2454000ABE875 /* DumpEditingHistory.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode._javascript_; name = DumpEditingHistory.js; path = InternalScripts/DumpEditingHistory.js; sourceTree = "<group>"; };
- 51D394751DF2454000ABE875 /* EditingHistoryUtil.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode._javascript_; name = EditingHistoryUtil.js; path = InternalScripts/EditingHistoryUtil.js; sourceTree = "<group>"; };
51D7196C181106DF0016DC51 /* DOMWindowIndexedDatabase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DOMWindowIndexedDatabase.cpp; sourceTree = "<group>"; };
51D7196D181106DF0016DC51 /* DOMWindowIndexedDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DOMWindowIndexedDatabase.h; sourceTree = "<group>"; };
51D7196E181106DF0016DC51 /* DOMWindowIndexedDatabase.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = DOMWindowIndexedDatabase.idl; sourceTree = "<group>"; };
@@ -14516,6 +14504,8 @@
F478755319983AFF0024A287 /* ScrollSnapAnimatorState.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ScrollSnapAnimatorState.mm; sourceTree = "<group>"; };
F47A5E3A195B8C8A00483100 /* StyleScrollSnapPoints.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StyleScrollSnapPoints.cpp; sourceTree = "<group>"; };
F47A5E3B195B8C8A00483100 /* StyleScrollSnapPoints.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StyleScrollSnapPoints.h; sourceTree = "<group>"; };
+ F48389831E1DDF2B0076B7EA /* DumpEditingHistory.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode._javascript_; name = DumpEditingHistory.js; path = Scripts/DumpEditingHistory.js; sourceTree = "<group>"; };
+ F48389841E1DDF2B0076B7EA /* EditingHistoryUtil.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode._javascript_; name = EditingHistoryUtil.js; path = Scripts/EditingHistoryUtil.js; sourceTree = "<group>"; };
F50664F5157F52DC00AC226F /* FormController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FormController.cpp; sourceTree = "<group>"; };
F50664F6157F52DC00AC226F /* FormController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FormController.h; sourceTree = "<group>"; };
F513A3E915FF4841001526DB /* ValidationMessageClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ValidationMessageClient.h; sourceTree = "<group>"; };
@@ -15421,7 +15411,6 @@
2E4346310F546A6800B0F1BA /* workers */,
E1F0424309839389006694EA /* xml */,
656580EC09D12B20000E61D7 /* Derived Sources */,
- 51D394731DF244BA00ABE875 /* InternalScripts */,
089C1665FE841158C02AAC07 /* Resources */,
3717D7E417ECC36C003C276D /* Scripts */,
0867D69AFE84028FC02AAC07 /* Frameworks */,
@@ -16507,6 +16496,7 @@
3717D7E417ECC36C003C276D /* Scripts */ = {
isa = PBXGroup;
children = (
+ F48389791E1DD23A0076B7EA /* EditingHistory */,
3717D7E517ECC3A6003C276D /* extract-localizable-strings.pl */,
37D456FB1A9A50B6003330A1 /* LocalizableStrings.pm */,
);
@@ -17351,15 +17341,6 @@
path = mac;
sourceTree = "<group>";
};
- 51D394731DF244BA00ABE875 /* InternalScripts */ = {
- isa = PBXGroup;
- children = (
- 51D394741DF2454000ABE875 /* DumpEditingHistory.js */,
- 51D394751DF2454000ABE875 /* EditingHistoryUtil.js */,
- );
- name = InternalScripts;
- sourceTree = "<group>";
- };
59B5977111086556007159E8 /* jsc */ = {
isa = PBXGroup;
children = (
@@ -23347,6 +23328,15 @@
path = mac;
sourceTree = "<group>";
};
+ F48389791E1DD23A0076B7EA /* EditingHistory */ = {
+ isa = PBXGroup;
+ children = (
+ F48389831E1DDF2B0076B7EA /* DumpEditingHistory.js */,
+ F48389841E1DDF2B0076B7EA /* EditingHistoryUtil.js */,
+ );
+ name = EditingHistory;
+ sourceTree = "<group>";
+ };
F523D18402DE42E8018635CA /* css */ = {
isa = PBXGroup;
children = (
@@ -28460,7 +28450,6 @@
37A1EAA3142699BC0087F425 /* Check For Inappropriate Objective-C Class Names */,
5DF50887116F3077005202AB /* Check For Inappropriate Files In Framework */,
71D6AA381DA4E69400B23969 /* Copy modern media controls code and assets */,
- 51D394771DF2486700ABE875 /* Copy Internal Scripts */,
);
buildRules = (
);