[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /** 2 * Serializer module for Rangy. 3 * Serializes Ranges and Selections. An example use would be to store a user's selection on a particular page in a 4 * cookie or local storage and restore it on the user's next visit to the same page. 5 * 6 * Part of Rangy, a cross-browser JavaScript range and selection library 7 * https://github.com/timdown/rangy 8 * 9 * Depends on Rangy core. 10 * 11 * Copyright 2015, Tim Down 12 * Licensed under the MIT license. 13 * Version: 1.3.0 14 * Build date: 10 May 2015 15 */ 16 (function(factory, root) { 17 // No AMD or CommonJS support so we use the rangy property of root (probably the global variable) 18 factory(root.rangy); 19 })(function(rangy) { 20 rangy.createModule("Serializer", ["WrappedSelection"], function(api, module) { 21 var UNDEF = "undefined"; 22 var util = api.util; 23 24 // encodeURIComponent and decodeURIComponent are required for cookie handling 25 if (typeof encodeURIComponent == UNDEF || typeof decodeURIComponent == UNDEF) { 26 module.fail("encodeURIComponent and/or decodeURIComponent method is missing"); 27 } 28 29 // Checksum for checking whether range can be serialized 30 var crc32 = (function() { 31 function utf8encode(str) { 32 var utf8CharCodes = []; 33 34 for (var i = 0, len = str.length, c; i < len; ++i) { 35 c = str.charCodeAt(i); 36 if (c < 128) { 37 utf8CharCodes.push(c); 38 } else if (c < 2048) { 39 utf8CharCodes.push((c >> 6) | 192, (c & 63) | 128); 40 } else { 41 utf8CharCodes.push((c >> 12) | 224, ((c >> 6) & 63) | 128, (c & 63) | 128); 42 } 43 } 44 return utf8CharCodes; 45 } 46 47 var cachedCrcTable = null; 48 49 function buildCRCTable() { 50 var table = []; 51 for (var i = 0, j, crc; i < 256; ++i) { 52 crc = i; 53 j = 8; 54 while (j--) { 55 if ((crc & 1) == 1) { 56 crc = (crc >>> 1) ^ 0xEDB88320; 57 } else { 58 crc >>>= 1; 59 } 60 } 61 table[i] = crc >>> 0; 62 } 63 return table; 64 } 65 66 function getCrcTable() { 67 if (!cachedCrcTable) { 68 cachedCrcTable = buildCRCTable(); 69 } 70 return cachedCrcTable; 71 } 72 73 return function(str) { 74 var utf8CharCodes = utf8encode(str), crc = -1, crcTable = getCrcTable(); 75 for (var i = 0, len = utf8CharCodes.length, y; i < len; ++i) { 76 y = (crc ^ utf8CharCodes[i]) & 0xFF; 77 crc = (crc >>> 8) ^ crcTable[y]; 78 } 79 return (crc ^ -1) >>> 0; 80 }; 81 })(); 82 83 var dom = api.dom; 84 85 function escapeTextForHtml(str) { 86 return str.replace(/</g, "<").replace(/>/g, ">"); 87 } 88 89 function nodeToInfoString(node, infoParts) { 90 infoParts = infoParts || []; 91 var nodeType = node.nodeType, children = node.childNodes, childCount = children.length; 92 var nodeInfo = [nodeType, node.nodeName, childCount].join(":"); 93 var start = "", end = ""; 94 switch (nodeType) { 95 case 3: // Text node 96 start = escapeTextForHtml(node.nodeValue); 97 break; 98 case 8: // Comment 99 start = "<!--" + escapeTextForHtml(node.nodeValue) + "-->"; 100 break; 101 default: 102 start = "<" + nodeInfo + ">"; 103 end = "</>"; 104 break; 105 } 106 if (start) { 107 infoParts.push(start); 108 } 109 for (var i = 0; i < childCount; ++i) { 110 nodeToInfoString(children[i], infoParts); 111 } 112 if (end) { 113 infoParts.push(end); 114 } 115 return infoParts; 116 } 117 118 // Creates a string representation of the specified element's contents that is similar to innerHTML but omits all 119 // attributes and comments and includes child node counts. This is done instead of using innerHTML to work around 120 // IE <= 8's policy of including element properties in attributes, which ruins things by changing an element's 121 // innerHTML whenever the user changes an input within the element. 122 function getElementChecksum(el) { 123 var info = nodeToInfoString(el).join(""); 124 return crc32(info).toString(16); 125 } 126 127 function serializePosition(node, offset, rootNode) { 128 var pathParts = [], n = node; 129 rootNode = rootNode || dom.getDocument(node).documentElement; 130 while (n && n != rootNode) { 131 pathParts.push(dom.getNodeIndex(n, true)); 132 n = n.parentNode; 133 } 134 return pathParts.join("/") + ":" + offset; 135 } 136 137 function deserializePosition(serialized, rootNode, doc) { 138 if (!rootNode) { 139 rootNode = (doc || document).documentElement; 140 } 141 var parts = serialized.split(":"); 142 var node = rootNode; 143 var nodeIndices = parts[0] ? parts[0].split("/") : [], i = nodeIndices.length, nodeIndex; 144 145 while (i--) { 146 nodeIndex = parseInt(nodeIndices[i], 10); 147 if (nodeIndex < node.childNodes.length) { 148 node = node.childNodes[nodeIndex]; 149 } else { 150 throw module.createError("deserializePosition() failed: node " + dom.inspectNode(node) + 151 " has no child with index " + nodeIndex + ", " + i); 152 } 153 } 154 155 return new dom.DomPosition(node, parseInt(parts[1], 10)); 156 } 157 158 function serializeRange(range, omitChecksum, rootNode) { 159 rootNode = rootNode || api.DomRange.getRangeDocument(range).documentElement; 160 if (!dom.isOrIsAncestorOf(rootNode, range.commonAncestorContainer)) { 161 throw module.createError("serializeRange(): range " + range.inspect() + 162 " is not wholly contained within specified root node " + dom.inspectNode(rootNode)); 163 } 164 var serialized = serializePosition(range.startContainer, range.startOffset, rootNode) + "," + 165 serializePosition(range.endContainer, range.endOffset, rootNode); 166 if (!omitChecksum) { 167 serialized += "{" + getElementChecksum(rootNode) + "}"; 168 } 169 return serialized; 170 } 171 172 var deserializeRegex = /^([^,]+),([^,\{]+)(\{([^}]+)\})?$/; 173 174 function deserializeRange(serialized, rootNode, doc) { 175 if (rootNode) { 176 doc = doc || dom.getDocument(rootNode); 177 } else { 178 doc = doc || document; 179 rootNode = doc.documentElement; 180 } 181 var result = deserializeRegex.exec(serialized); 182 var checksum = result[4]; 183 if (checksum) { 184 var rootNodeChecksum = getElementChecksum(rootNode); 185 if (checksum !== rootNodeChecksum) { 186 throw module.createError("deserializeRange(): checksums of serialized range root node (" + checksum + 187 ") and target root node (" + rootNodeChecksum + ") do not match"); 188 } 189 } 190 var start = deserializePosition(result[1], rootNode, doc), end = deserializePosition(result[2], rootNode, doc); 191 var range = api.createRange(doc); 192 range.setStartAndEnd(start.node, start.offset, end.node, end.offset); 193 return range; 194 } 195 196 function canDeserializeRange(serialized, rootNode, doc) { 197 if (!rootNode) { 198 rootNode = (doc || document).documentElement; 199 } 200 var result = deserializeRegex.exec(serialized); 201 var checksum = result[3]; 202 return !checksum || checksum === getElementChecksum(rootNode); 203 } 204 205 function serializeSelection(selection, omitChecksum, rootNode) { 206 selection = api.getSelection(selection); 207 var ranges = selection.getAllRanges(), serializedRanges = []; 208 for (var i = 0, len = ranges.length; i < len; ++i) { 209 serializedRanges[i] = serializeRange(ranges[i], omitChecksum, rootNode); 210 } 211 return serializedRanges.join("|"); 212 } 213 214 function deserializeSelection(serialized, rootNode, win) { 215 if (rootNode) { 216 win = win || dom.getWindow(rootNode); 217 } else { 218 win = win || window; 219 rootNode = win.document.documentElement; 220 } 221 var serializedRanges = serialized.split("|"); 222 var sel = api.getSelection(win); 223 var ranges = []; 224 225 for (var i = 0, len = serializedRanges.length; i < len; ++i) { 226 ranges[i] = deserializeRange(serializedRanges[i], rootNode, win.document); 227 } 228 sel.setRanges(ranges); 229 230 return sel; 231 } 232 233 function canDeserializeSelection(serialized, rootNode, win) { 234 var doc; 235 if (rootNode) { 236 doc = win ? win.document : dom.getDocument(rootNode); 237 } else { 238 win = win || window; 239 rootNode = win.document.documentElement; 240 } 241 var serializedRanges = serialized.split("|"); 242 243 for (var i = 0, len = serializedRanges.length; i < len; ++i) { 244 if (!canDeserializeRange(serializedRanges[i], rootNode, doc)) { 245 return false; 246 } 247 } 248 249 return true; 250 } 251 252 var cookieName = "rangySerializedSelection"; 253 254 function getSerializedSelectionFromCookie(cookie) { 255 var parts = cookie.split(/[;,]/); 256 for (var i = 0, len = parts.length, nameVal, val; i < len; ++i) { 257 nameVal = parts[i].split("="); 258 if (nameVal[0].replace(/^\s+/, "") == cookieName) { 259 val = nameVal[1]; 260 if (val) { 261 return decodeURIComponent(val.replace(/\s+$/, "")); 262 } 263 } 264 } 265 return null; 266 } 267 268 function restoreSelectionFromCookie(win) { 269 win = win || window; 270 var serialized = getSerializedSelectionFromCookie(win.document.cookie); 271 if (serialized) { 272 deserializeSelection(serialized, win.doc); 273 } 274 } 275 276 function saveSelectionCookie(win, props) { 277 win = win || window; 278 props = (typeof props == "object") ? props : {}; 279 var expires = props.expires ? ";expires=" + props.expires.toUTCString() : ""; 280 var path = props.path ? ";path=" + props.path : ""; 281 var domain = props.domain ? ";domain=" + props.domain : ""; 282 var secure = props.secure ? ";secure" : ""; 283 var serialized = serializeSelection(api.getSelection(win)); 284 win.document.cookie = encodeURIComponent(cookieName) + "=" + encodeURIComponent(serialized) + expires + path + domain + secure; 285 } 286 287 util.extend(api, { 288 serializePosition: serializePosition, 289 deserializePosition: deserializePosition, 290 serializeRange: serializeRange, 291 deserializeRange: deserializeRange, 292 canDeserializeRange: canDeserializeRange, 293 serializeSelection: serializeSelection, 294 deserializeSelection: deserializeSelection, 295 canDeserializeSelection: canDeserializeSelection, 296 restoreSelectionFromCookie: restoreSelectionFromCookie, 297 saveSelectionCookie: saveSelectionCookie, 298 getElementChecksum: getElementChecksum, 299 nodeToInfoString: nodeToInfoString 300 }); 301 302 util.crc32 = crc32; 303 }); 304 305 return rangy; 306 }, this);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |