diff options
Diffstat (limited to 'Source/WebCore/Scripts')
-rw-r--r-- | Source/WebCore/Scripts/DumpEditingHistory.js | 82 | ||||
-rw-r--r-- | Source/WebCore/Scripts/EditingHistoryUtil.js | 693 | ||||
-rwxr-xr-x | Source/WebCore/Scripts/make-js-file-arrays.py | 91 |
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() |