summaryrefslogtreecommitdiff
path: root/Source/WebCore/Scripts
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/Scripts
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebCore/Scripts')
-rw-r--r--Source/WebCore/Scripts/DumpEditingHistory.js82
-rw-r--r--Source/WebCore/Scripts/EditingHistoryUtil.js693
-rwxr-xr-xSource/WebCore/Scripts/make-js-file-arrays.py91
3 files changed, 866 insertions, 0 deletions
diff --git a/Source/WebCore/Scripts/DumpEditingHistory.js b/Source/WebCore/Scripts/DumpEditingHistory.js
new file mode 100644
index 000000000..16b1d81b2
--- /dev/null
+++ b/Source/WebCore/Scripts/DumpEditingHistory.js
@@ -0,0 +1,82 @@
+(() => {
+ 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.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));
+ });
+
+ mutationObserver.observe(document, {
+ childList: true,
+ attributes: true,
+ characterData: true,
+ subtree: true,
+ attributeOldValue: true,
+ characterDataOldValue: true,
+ });
+ });
+})();
diff --git a/Source/WebCore/Scripts/EditingHistoryUtil.js b/Source/WebCore/Scripts/EditingHistoryUtil.js
new file mode 100644
index 000000000..645870b3d
--- /dev/null
+++ b/Source/WebCore/Scripts/EditingHistoryUtil.js
@@ -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 = GlobalNodeMap.dataForNode(node);
+ 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 = this.newData;
+ }
+
+ unapply() {
+ this.targetNode().data = this.oldData;
+ }
+
+ 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 = 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
+ };
+})();
diff --git a/Source/WebCore/Scripts/make-js-file-arrays.py b/Source/WebCore/Scripts/make-js-file-arrays.py
new file mode 100755
index 000000000..3116a11bb
--- /dev/null
+++ b/Source/WebCore/Scripts/make-js-file-arrays.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python
+# Copyright (C) 2014 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. AND ITS CONTRIBUTORS ``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 ITS 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.
+
+import io
+import os
+from optparse import OptionParser
+from StringIO import StringIO
+from jsmin import JavascriptMinify
+
+
+def stringifyCodepoint(code):
+ if code < 128:
+ return '{0:d}'.format(code)
+ else:
+ return "'\\x{0:02x}'".format(code)
+
+
+def chunk(list, chunkSize):
+ for i in xrange(0, len(list), chunkSize):
+ yield list[i:i + chunkSize]
+
+
+def main():
+ parser = OptionParser(usage="usage: %prog [--no-minify] header source [input [input...]]")
+ parser.add_option('--no-minify', action='store_true', help='Do not run the input files through jsmin')
+ (options, arguments) = parser.parse_args()
+ if len(arguments) < 3:
+ print 'Error: must provide at least 3 arguments'
+ parser.print_usage()
+ exit(-1)
+
+ headerPath = arguments[0]
+ sourcePath = arguments[1]
+ inputPaths = arguments[2:]
+
+ headerFile = open(headerPath, 'w')
+ print >> headerFile, 'namespace WebCore {'
+
+ sourceFile = open(sourcePath, 'w')
+ print >> sourceFile, '#include "{0:s}"'.format(os.path.basename(headerPath))
+ print >> sourceFile, 'namespace WebCore {'
+
+ jsm = JavascriptMinify()
+
+ for inputFileName in inputPaths:
+ inputStream = io.FileIO(inputFileName)
+ outputStream = StringIO()
+
+ if not options.no_minify:
+ jsm.minify(inputStream, outputStream)
+ characters = outputStream.getvalue()
+ else:
+ characters = inputStream.read()
+
+ size = len(characters)
+ variableName = os.path.splitext(os.path.basename(inputFileName))[0]
+
+ print >> headerFile, 'extern const char {0:s}JavaScript[{1:d}];'.format(variableName, size)
+ print >> sourceFile, 'const char {0:s}JavaScript[{1:d}] = {{'.format(variableName, size)
+
+ codepoints = map(ord, characters)
+ for codepointChunk in chunk(codepoints, 16):
+ print >> sourceFile, ' {0:s},'.format(','.join(map(stringifyCodepoint, codepointChunk)))
+
+ print >> sourceFile, '};'
+
+ print >> headerFile, '}'
+ print >> sourceFile, '}'
+
+if __name__ == '__main__':
+ main()