mirror of
https://github.com/ducbao414/win32.run.git
synced 2025-12-17 01:32:50 +09:00
314 lines
12 KiB
JavaScript
314 lines
12 KiB
JavaScript
/**
|
|
* Serializer module for Rangy.
|
|
* Serializes Ranges and Selections. An example use would be to store a user's selection on a particular page in a
|
|
* cookie or local storage and restore it on the user's next visit to the same page.
|
|
*
|
|
* Part of Rangy, a cross-browser JavaScript range and selection library
|
|
* https://github.com/timdown/rangy
|
|
*
|
|
* Depends on Rangy core.
|
|
*
|
|
* Copyright 2015, Tim Down
|
|
* Licensed under the MIT license.
|
|
* Version: 1.3.0
|
|
* Build date: 10 May 2015
|
|
*/
|
|
(function(factory, root) {
|
|
if (typeof define == "function" && define.amd) {
|
|
// AMD. Register as an anonymous module with a dependency on Rangy.
|
|
define(["./rangy-core"], factory);
|
|
} else if (typeof module != "undefined" && typeof exports == "object") {
|
|
// Node/CommonJS style
|
|
module.exports = factory( require("rangy") );
|
|
} else {
|
|
// No AMD or CommonJS support so we use the rangy property of root (probably the global variable)
|
|
factory(root.rangy);
|
|
}
|
|
})(function(rangy) {
|
|
rangy.createModule("Serializer", ["WrappedSelection"], function(api, module) {
|
|
var UNDEF = "undefined";
|
|
var util = api.util;
|
|
|
|
// encodeURIComponent and decodeURIComponent are required for cookie handling
|
|
if (typeof encodeURIComponent == UNDEF || typeof decodeURIComponent == UNDEF) {
|
|
module.fail("encodeURIComponent and/or decodeURIComponent method is missing");
|
|
}
|
|
|
|
// Checksum for checking whether range can be serialized
|
|
var crc32 = (function() {
|
|
function utf8encode(str) {
|
|
var utf8CharCodes = [];
|
|
|
|
for (var i = 0, len = str.length, c; i < len; ++i) {
|
|
c = str.charCodeAt(i);
|
|
if (c < 128) {
|
|
utf8CharCodes.push(c);
|
|
} else if (c < 2048) {
|
|
utf8CharCodes.push((c >> 6) | 192, (c & 63) | 128);
|
|
} else {
|
|
utf8CharCodes.push((c >> 12) | 224, ((c >> 6) & 63) | 128, (c & 63) | 128);
|
|
}
|
|
}
|
|
return utf8CharCodes;
|
|
}
|
|
|
|
var cachedCrcTable = null;
|
|
|
|
function buildCRCTable() {
|
|
var table = [];
|
|
for (var i = 0, j, crc; i < 256; ++i) {
|
|
crc = i;
|
|
j = 8;
|
|
while (j--) {
|
|
if ((crc & 1) == 1) {
|
|
crc = (crc >>> 1) ^ 0xEDB88320;
|
|
} else {
|
|
crc >>>= 1;
|
|
}
|
|
}
|
|
table[i] = crc >>> 0;
|
|
}
|
|
return table;
|
|
}
|
|
|
|
function getCrcTable() {
|
|
if (!cachedCrcTable) {
|
|
cachedCrcTable = buildCRCTable();
|
|
}
|
|
return cachedCrcTable;
|
|
}
|
|
|
|
return function(str) {
|
|
var utf8CharCodes = utf8encode(str), crc = -1, crcTable = getCrcTable();
|
|
for (var i = 0, len = utf8CharCodes.length, y; i < len; ++i) {
|
|
y = (crc ^ utf8CharCodes[i]) & 0xFF;
|
|
crc = (crc >>> 8) ^ crcTable[y];
|
|
}
|
|
return (crc ^ -1) >>> 0;
|
|
};
|
|
})();
|
|
|
|
var dom = api.dom;
|
|
|
|
function escapeTextForHtml(str) {
|
|
return str.replace(/</g, "<").replace(/>/g, ">");
|
|
}
|
|
|
|
function nodeToInfoString(node, infoParts) {
|
|
infoParts = infoParts || [];
|
|
var nodeType = node.nodeType, children = node.childNodes, childCount = children.length;
|
|
var nodeInfo = [nodeType, node.nodeName, childCount].join(":");
|
|
var start = "", end = "";
|
|
switch (nodeType) {
|
|
case 3: // Text node
|
|
start = escapeTextForHtml(node.nodeValue);
|
|
break;
|
|
case 8: // Comment
|
|
start = "<!--" + escapeTextForHtml(node.nodeValue) + "-->";
|
|
break;
|
|
default:
|
|
start = "<" + nodeInfo + ">";
|
|
end = "</>";
|
|
break;
|
|
}
|
|
if (start) {
|
|
infoParts.push(start);
|
|
}
|
|
for (var i = 0; i < childCount; ++i) {
|
|
nodeToInfoString(children[i], infoParts);
|
|
}
|
|
if (end) {
|
|
infoParts.push(end);
|
|
}
|
|
return infoParts;
|
|
}
|
|
|
|
// Creates a string representation of the specified element's contents that is similar to innerHTML but omits all
|
|
// attributes and comments and includes child node counts. This is done instead of using innerHTML to work around
|
|
// IE <= 8's policy of including element properties in attributes, which ruins things by changing an element's
|
|
// innerHTML whenever the user changes an input within the element.
|
|
function getElementChecksum(el) {
|
|
var info = nodeToInfoString(el).join("");
|
|
return crc32(info).toString(16);
|
|
}
|
|
|
|
function serializePosition(node, offset, rootNode) {
|
|
var pathParts = [], n = node;
|
|
rootNode = rootNode || dom.getDocument(node).documentElement;
|
|
while (n && n != rootNode) {
|
|
pathParts.push(dom.getNodeIndex(n, true));
|
|
n = n.parentNode;
|
|
}
|
|
return pathParts.join("/") + ":" + offset;
|
|
}
|
|
|
|
function deserializePosition(serialized, rootNode, doc) {
|
|
if (!rootNode) {
|
|
rootNode = (doc || document).documentElement;
|
|
}
|
|
var parts = serialized.split(":");
|
|
var node = rootNode;
|
|
var nodeIndices = parts[0] ? parts[0].split("/") : [], i = nodeIndices.length, nodeIndex;
|
|
|
|
while (i--) {
|
|
nodeIndex = parseInt(nodeIndices[i], 10);
|
|
if (nodeIndex < node.childNodes.length) {
|
|
node = node.childNodes[nodeIndex];
|
|
} else {
|
|
throw module.createError("deserializePosition() failed: node " + dom.inspectNode(node) +
|
|
" has no child with index " + nodeIndex + ", " + i);
|
|
}
|
|
}
|
|
|
|
return new dom.DomPosition(node, parseInt(parts[1], 10));
|
|
}
|
|
|
|
function serializeRange(range, omitChecksum, rootNode) {
|
|
rootNode = rootNode || api.DomRange.getRangeDocument(range).documentElement;
|
|
if (!dom.isOrIsAncestorOf(rootNode, range.commonAncestorContainer)) {
|
|
throw module.createError("serializeRange(): range " + range.inspect() +
|
|
" is not wholly contained within specified root node " + dom.inspectNode(rootNode));
|
|
}
|
|
var serialized = serializePosition(range.startContainer, range.startOffset, rootNode) + "," +
|
|
serializePosition(range.endContainer, range.endOffset, rootNode);
|
|
if (!omitChecksum) {
|
|
serialized += "{" + getElementChecksum(rootNode) + "}";
|
|
}
|
|
return serialized;
|
|
}
|
|
|
|
var deserializeRegex = /^([^,]+),([^,\{]+)(\{([^}]+)\})?$/;
|
|
|
|
function deserializeRange(serialized, rootNode, doc) {
|
|
if (rootNode) {
|
|
doc = doc || dom.getDocument(rootNode);
|
|
} else {
|
|
doc = doc || document;
|
|
rootNode = doc.documentElement;
|
|
}
|
|
var result = deserializeRegex.exec(serialized);
|
|
var checksum = result[4];
|
|
if (checksum) {
|
|
var rootNodeChecksum = getElementChecksum(rootNode);
|
|
if (checksum !== rootNodeChecksum) {
|
|
throw module.createError("deserializeRange(): checksums of serialized range root node (" + checksum +
|
|
") and target root node (" + rootNodeChecksum + ") do not match");
|
|
}
|
|
}
|
|
var start = deserializePosition(result[1], rootNode, doc), end = deserializePosition(result[2], rootNode, doc);
|
|
var range = api.createRange(doc);
|
|
range.setStartAndEnd(start.node, start.offset, end.node, end.offset);
|
|
return range;
|
|
}
|
|
|
|
function canDeserializeRange(serialized, rootNode, doc) {
|
|
if (!rootNode) {
|
|
rootNode = (doc || document).documentElement;
|
|
}
|
|
var result = deserializeRegex.exec(serialized);
|
|
var checksum = result[3];
|
|
return !checksum || checksum === getElementChecksum(rootNode);
|
|
}
|
|
|
|
function serializeSelection(selection, omitChecksum, rootNode) {
|
|
selection = api.getSelection(selection);
|
|
var ranges = selection.getAllRanges(), serializedRanges = [];
|
|
for (var i = 0, len = ranges.length; i < len; ++i) {
|
|
serializedRanges[i] = serializeRange(ranges[i], omitChecksum, rootNode);
|
|
}
|
|
return serializedRanges.join("|");
|
|
}
|
|
|
|
function deserializeSelection(serialized, rootNode, win) {
|
|
if (rootNode) {
|
|
win = win || dom.getWindow(rootNode);
|
|
} else {
|
|
win = win || window;
|
|
rootNode = win.document.documentElement;
|
|
}
|
|
var serializedRanges = serialized.split("|");
|
|
var sel = api.getSelection(win);
|
|
var ranges = [];
|
|
|
|
for (var i = 0, len = serializedRanges.length; i < len; ++i) {
|
|
ranges[i] = deserializeRange(serializedRanges[i], rootNode, win.document);
|
|
}
|
|
sel.setRanges(ranges);
|
|
|
|
return sel;
|
|
}
|
|
|
|
function canDeserializeSelection(serialized, rootNode, win) {
|
|
var doc;
|
|
if (rootNode) {
|
|
doc = win ? win.document : dom.getDocument(rootNode);
|
|
} else {
|
|
win = win || window;
|
|
rootNode = win.document.documentElement;
|
|
}
|
|
var serializedRanges = serialized.split("|");
|
|
|
|
for (var i = 0, len = serializedRanges.length; i < len; ++i) {
|
|
if (!canDeserializeRange(serializedRanges[i], rootNode, doc)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
var cookieName = "rangySerializedSelection";
|
|
|
|
function getSerializedSelectionFromCookie(cookie) {
|
|
var parts = cookie.split(/[;,]/);
|
|
for (var i = 0, len = parts.length, nameVal, val; i < len; ++i) {
|
|
nameVal = parts[i].split("=");
|
|
if (nameVal[0].replace(/^\s+/, "") == cookieName) {
|
|
val = nameVal[1];
|
|
if (val) {
|
|
return decodeURIComponent(val.replace(/\s+$/, ""));
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function restoreSelectionFromCookie(win) {
|
|
win = win || window;
|
|
var serialized = getSerializedSelectionFromCookie(win.document.cookie);
|
|
if (serialized) {
|
|
deserializeSelection(serialized, win.doc);
|
|
}
|
|
}
|
|
|
|
function saveSelectionCookie(win, props) {
|
|
win = win || window;
|
|
props = (typeof props == "object") ? props : {};
|
|
var expires = props.expires ? ";expires=" + props.expires.toUTCString() : "";
|
|
var path = props.path ? ";path=" + props.path : "";
|
|
var domain = props.domain ? ";domain=" + props.domain : "";
|
|
var secure = props.secure ? ";secure" : "";
|
|
var serialized = serializeSelection(api.getSelection(win));
|
|
win.document.cookie = encodeURIComponent(cookieName) + "=" + encodeURIComponent(serialized) + expires + path + domain + secure;
|
|
}
|
|
|
|
util.extend(api, {
|
|
serializePosition: serializePosition,
|
|
deserializePosition: deserializePosition,
|
|
serializeRange: serializeRange,
|
|
deserializeRange: deserializeRange,
|
|
canDeserializeRange: canDeserializeRange,
|
|
serializeSelection: serializeSelection,
|
|
deserializeSelection: deserializeSelection,
|
|
canDeserializeSelection: canDeserializeSelection,
|
|
restoreSelectionFromCookie: restoreSelectionFromCookie,
|
|
saveSelectionCookie: saveSelectionCookie,
|
|
getElementChecksum: getElementChecksum,
|
|
nodeToInfoString: nodeToInfoString
|
|
});
|
|
|
|
util.crc32 = crc32;
|
|
});
|
|
|
|
return rangy;
|
|
}, this); |