1 /** 2 * Copyright (C) 2012-2013 KO GmbH <copyright@kogmbh.com> 3 * 4 * @licstart 5 * This file is part of WebODF. 6 * 7 * WebODF is free software: you can redistribute it and/or modify it 8 * under the terms of the GNU Affero General Public License (GNU AGPL) 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * WebODF is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU Affero General Public License for more details. 16 * 17 * You should have received a copy of the GNU Affero General Public License 18 * along with WebODF. If not, see <http://www.gnu.org/licenses/>. 19 * @licend 20 * 21 * @source: http://www.webodf.org/ 22 * @source: https://github.com/kogmbh/WebODF/ 23 */ 24 25 /*jslint nomen: true, bitwise: true, emptyblock: true, unparam: true */ 26 /*global window, XMLHttpRequest, require, console, DOMParser, document, 27 process, __dirname, setTimeout, Packages, print, 28 readFile, quit, Buffer, ArrayBuffer, Uint8Array, 29 navigator, VBArray, alert, now, clearTimeout, webodf_version */ 30 31 /** 32 * Three implementations of a runtime for browser, node.js and rhino. 33 */ 34 35 /** 36 * Abstraction of the runtime environment. 37 * @class 38 * @interface 39 */ 40 function Runtime() {"use strict"; } 41 42 /** 43 * @param {!string} name 44 * @return {*} 45 */ 46 Runtime.prototype.getVariable = function (name) { "use strict"; }; 47 48 /** 49 * @param {*} anything 50 * @return {!string} 51 */ 52 Runtime.prototype.toJson = function (anything) { "use strict"; }; 53 54 /** 55 * @param {!string} jsonstr 56 * @return {*} 57 */ 58 Runtime.prototype.fromJson = function (jsonstr) { "use strict"; }; 59 60 /** 61 * @param {!string} string 62 * @param {!string} encoding 63 * @return {!Uint8Array} 64 */ 65 Runtime.prototype.byteArrayFromString = function (string, encoding) {"use strict"; }; 66 /** 67 * @param {!Uint8Array} bytearray 68 * @param {!string} encoding 69 * @return {!string} 70 */ 71 Runtime.prototype.byteArrayToString = function (bytearray, encoding) {"use strict"; }; 72 /** 73 * Read part of a binary file. 74 * @param {!string} path 75 * @param {!number} offset 76 * @param {!number} length 77 * @param {!function(?string,?Uint8Array):undefined} callback 78 * @return {undefined} 79 */ 80 Runtime.prototype.read = function (path, offset, length, callback) {"use strict"; }; 81 /** 82 * Read the contents of a file. Returns the result via a callback. If the 83 * encoding is 'binary', the result is returned as a Uint8Array, 84 * otherwise, it is returned as a string. 85 * @param {!string} path 86 * @param {!string} encoding text encoding or 'binary' 87 * @param {!function(?string,?(string|Uint8Array)):undefined} callback 88 * @return {undefined} 89 */ 90 Runtime.prototype.readFile = function (path, encoding, callback) {"use strict"; }; 91 /** 92 * Read a file completely, throw an exception if there is a problem. 93 * @param {!string} path 94 * @param {!string} encoding text encoding or 'binary' 95 * @return {!string|!Uint8Array} 96 */ 97 Runtime.prototype.readFileSync = function (path, encoding) {"use strict"; }; 98 /** 99 * @param {!string} path 100 * @param {!function(?string,?Document):undefined} callback 101 * @return {undefined} 102 */ 103 Runtime.prototype.loadXML = function (path, callback) {"use strict"; }; 104 /** 105 * @param {!string} path 106 * @param {!Uint8Array} data 107 * @param {!function(?string):undefined} callback 108 * @return {undefined} 109 */ 110 Runtime.prototype.writeFile = function (path, data, callback) {"use strict"; }; 111 /** 112 * @param {!string} path 113 * @param {!function(boolean):undefined} callback 114 * @return {undefined} 115 */ 116 Runtime.prototype.isFile = function (path, callback) {"use strict"; }; 117 /** 118 * @param {!string} path 119 * @param {!function(number):undefined} callback 120 * @return {undefined} 121 */ 122 Runtime.prototype.getFileSize = function (path, callback) {"use strict"; }; 123 /** 124 * @param {!string} path 125 * @param {!function(?string):undefined} callback 126 * @return {undefined} 127 */ 128 Runtime.prototype.deleteFile = function (path, callback) {"use strict"; }; 129 /** 130 * @param {!string} msgOrCategory 131 * @param {!string=} msg 132 * @return {undefined} 133 */ 134 Runtime.prototype.log = function (msgOrCategory, msg) {"use strict"; }; 135 /** 136 * @param {!function():undefined} callback 137 * @param {!number} milliseconds 138 * @return {!number} 139 */ 140 Runtime.prototype.setTimeout = function (callback, milliseconds) {"use strict"; }; 141 /** 142 * @param {!number} timeoutID 143 * @return {undefined} 144 */ 145 Runtime.prototype.clearTimeout = function (timeoutID) {"use strict"; }; 146 /** 147 * @return {!Array.<string>} 148 */ 149 Runtime.prototype.libraryPaths = function () {"use strict"; }; 150 /** 151 * @return {!string} 152 */ 153 Runtime.prototype.currentDirectory = function () {"use strict"; }; 154 /** 155 * @param {!string} dir 156 * @return {undefined} 157 */ 158 Runtime.prototype.setCurrentDirectory = function (dir) {"use strict"; }; 159 /** 160 * @return {string} 161 */ 162 Runtime.prototype.type = function () {"use strict"; }; 163 /** 164 * @return {?DOMImplementation} 165 */ 166 Runtime.prototype.getDOMImplementation = function () {"use strict"; }; 167 /** 168 * @param {!string} xml 169 * @return {?Document} 170 */ 171 Runtime.prototype.parseXML = function (xml) {"use strict"; }; 172 /** 173 * @param {!number} exitCode 174 */ 175 Runtime.prototype.exit = function (exitCode) {"use strict"; }; 176 /** 177 * @return {?Window} 178 */ 179 Runtime.prototype.getWindow = function () {"use strict"; }; 180 /** 181 * @param {!function():undefined} callback 182 * @return {!number} 183 */ 184 Runtime.prototype.requestAnimationFrame = function (callback) {"use strict"; }; 185 /** 186 * @param {!number} requestId 187 * @return {undefined} 188 */ 189 Runtime.prototype.cancelAnimationFrame = function (requestId) {"use strict"; }; 190 /** 191 * @param {!boolean} condition 192 * @param {!string} message 193 * @param {!function():undefined=} callback 194 * @return {undefined} 195 */ 196 Runtime.prototype.assert = function (condition, message, callback) { "use strict"; }; 197 /*jslint emptyblock: false, unparam: false */ 198 199 /** @define {boolean} */ 200 var IS_COMPILED_CODE = false; 201 202 /** 203 * @this {Runtime} 204 * @param {!Uint8Array} bytearray 205 * @param {!string} encoding 206 * @return {!string} 207 */ 208 Runtime.byteArrayToString = function (bytearray, encoding) { 209 "use strict"; 210 /** 211 * @param {!Uint8Array} bytearray 212 * @return {!string} 213 */ 214 function byteArrayToString(bytearray) { 215 var s = "", i, l = bytearray.length; 216 for (i = 0; i < l; i += 1) { 217 s += String.fromCharCode(bytearray[i] & 0xff); 218 } 219 return s; 220 } 221 /** 222 * @param {!Uint8Array} bytearray 223 * @return {!string} 224 */ 225 function utf8ByteArrayToString(bytearray) { 226 var s = "", i, l = bytearray.length, 227 chars = [], 228 c0, c1, c2, c3, codepoint; 229 230 for (i = 0; i < l; i += 1) { 231 c0 = /**@type{!number}*/(bytearray[i]); 232 if (c0 < 0x80) { 233 chars.push(c0); 234 } else { 235 i += 1; 236 c1 = /**@type{!number}*/(bytearray[i]); 237 if (c0 >= 0xc2 && c0 < 0xe0) { 238 chars.push(((c0 & 0x1f) << 6) | (c1 & 0x3f)); 239 } else { 240 i += 1; 241 c2 = /**@type{!number}*/(bytearray[i]); 242 if (c0 >= 0xe0 && c0 < 0xf0) { 243 chars.push(((c0 & 0x0f) << 12) | ((c1 & 0x3f) << 6) | (c2 & 0x3f)); 244 } else { 245 i += 1; 246 c3 = /**@type{!number}*/(bytearray[i]); 247 if (c0 >= 0xf0 && c0 < 0xf5) { 248 codepoint = ((c0 & 0x07) << 18) | ((c1 & 0x3f) << 12) | ((c2 & 0x3f) << 6) | (c3 & 0x3f); 249 codepoint -= 0x10000; 250 chars.push((codepoint >> 10) + 0xd800, (codepoint & 0x3ff) + 0xdc00); 251 } 252 } 253 } 254 } 255 if (chars.length >= 1000) { 256 // more than 2 chars can be added in the above logic, so the length might exceed 1000 257 258 // Char-to-string conversion is done using apply as it provides a roughly %30 improvement vs. 259 // converting the characters 1-by-1, and improves memory usage significantly as well. 260 261 // However, the apply function has an upper limit on the size of the arguments array. If it is exceeded, 262 // most browsers with throw a RangeError. Avoid this problem by converting no more than 1000(ish) 263 // characters per call. 264 s += String.fromCharCode.apply(null, chars); 265 chars.length = 0; 266 } 267 } 268 // Based on the above chars.length check, there is guaranteed to be less than 1000 chars left in the array 269 return s + String.fromCharCode.apply(null, chars); 270 } 271 var result; 272 if (encoding === "utf8") { 273 result = utf8ByteArrayToString(bytearray); 274 } else { 275 if (encoding !== "binary") { 276 this.log("Unsupported encoding: " + encoding); 277 } 278 result = byteArrayToString(bytearray); 279 } 280 return result; 281 }; 282 283 /** 284 * @param {!string} name 285 * @return {*} 286 */ 287 Runtime.getVariable = function (name) { 288 "use strict"; 289 /*jslint evil: true*/ 290 try { 291 return eval(name); 292 } catch (e) { 293 return undefined; 294 } 295 /*jslint evil: false*/ 296 }; 297 298 /** 299 * @param {*} anything 300 * @return {!string} 301 */ 302 Runtime.toJson = function (anything) { 303 "use strict"; 304 return JSON.stringify(anything); 305 }; 306 307 /** 308 * @param {!string} jsonstr 309 * @return {*} 310 */ 311 Runtime.fromJson = function (jsonstr) { 312 "use strict"; 313 return JSON.parse(jsonstr); 314 }; 315 316 /** 317 * @param {!Function} f 318 * @return {?string} 319 */ 320 Runtime.getFunctionName = function getFunctionName(f) { 321 "use strict"; 322 var m; 323 if (f.name === undefined) { 324 m = new RegExp("function\\s+(\\w+)").exec(f); 325 return m && m[1]; 326 } 327 return f.name; 328 }; 329 /** 330 * @class 331 * @constructor 332 * @augments Runtime 333 * @implements {Runtime} 334 * @param {Element} logoutput 335 */ 336 function BrowserRuntime(logoutput) { 337 "use strict"; 338 var self = this, 339 /**@type{!Object.<!string|!Uint8Array>}*/ 340 cache = {}; 341 342 /** 343 * Return the number of bytes a string would take up when encoded as utf-8. 344 * @param {string} string 345 * @return {number} 346 */ 347 function getUtf8LengthForString(string) { 348 var l = string.length, i, n, j = 0; 349 for (i = 0; i < l; i += 1) { 350 n = string.charCodeAt(i); 351 j += 1 + (n > 0x80) + (n > 0x800); 352 if (n > 0xd700 && n < 0xe000) { // first of a surrogate pair 353 j += 1; 354 i += 1; // skip second half of in surrogate pair 355 } 356 } 357 return j; 358 } 359 360 /** 361 * Convert UCS-2 string to UTF-8 array. 362 * @param {string} string 363 * @param {number} length the length of the resulting array 364 * @param {boolean} addBOM whether or not to start with a BOM 365 * @return {!Uint8Array} 366 */ 367 function utf8ByteArrayFromString(string, length, addBOM) { 368 var l = string.length, bytearray, i, n, 369 j; 370 // allocate a buffer and convert to a utf8 array 371 bytearray = new Uint8Array(new ArrayBuffer(length)); 372 if (addBOM) { 373 bytearray[0] = 0xef; 374 bytearray[1] = 0xbb; 375 bytearray[2] = 0xbf; 376 j = 3; 377 } else { 378 j = 0; 379 } 380 for (i = 0; i < l; i += 1) { 381 n = string.charCodeAt(i); 382 if (n < 0x80) { 383 bytearray[j] = n; 384 j += 1; 385 } else if (n < 0x800) { 386 bytearray[j] = 0xc0 | (n >>> 6); 387 bytearray[j + 1] = 0x80 | (n & 0x3f); 388 j += 2; 389 } else if (n <= 0xd700 || n >= 0xe000) { 390 bytearray[j] = 0xe0 | ((n >>> 12) & 0x0f); 391 bytearray[j + 1] = 0x80 | ((n >>> 6) & 0x3f); 392 bytearray[j + 2] = 0x80 | (n & 0x3f); 393 j += 3; 394 } else { // surrogate pair 395 i += 1; 396 n = (((n - 0xd800) << 10) | (string.charCodeAt(i) - 0xdc00)) 397 + 0x10000; 398 bytearray[j] = 0xf0 | (n >>> 18 & 0x07); 399 bytearray[j + 1] = 0x80 | (n >>> 12 & 0x3f); 400 bytearray[j + 2] = 0x80 | (n >>> 6 & 0x3f); 401 bytearray[j + 3] = 0x80 | (n & 0x3f); 402 j += 4; 403 } 404 } 405 return bytearray; 406 } 407 /** 408 * Convert UCS-2 string to UTF-8 array. 409 * wishLength is the desired length, if it is 3 bytes longer than 410 * forsee by the string data, a BOM is prepended. 411 * @param {string} string 412 * @param {(number|string)=} wishLength 413 * @return {!Uint8Array|undefined} 414 */ 415 function utf8ByteArrayFromXHRString(string, wishLength) { 416 var addBOM = false, 417 length = getUtf8LengthForString(string); 418 if (typeof wishLength === "number") { 419 if (wishLength !== length && wishLength !== length + 3) { 420 // the desired length does not match the content of the string 421 return undefined; 422 } 423 addBOM = length + 3 === wishLength; 424 length = wishLength; 425 } 426 return utf8ByteArrayFromString(string, length, addBOM); 427 } 428 /** 429 * @param {!string} string 430 * @return {!Uint8Array} 431 */ 432 function byteArrayFromString(string) { 433 // ignore encoding for now 434 var l = string.length, 435 a = new Uint8Array(new ArrayBuffer(l)), 436 i; 437 for (i = 0; i < l; i += 1) { 438 a[i] = string.charCodeAt(i) & 0xff; 439 } 440 return a; 441 } 442 /** 443 * @param {!string} string 444 * @param {!string} encoding 445 * @return {!Uint8Array} 446 */ 447 this.byteArrayFromString = function (string, encoding) { 448 var result; 449 if (encoding === "utf8") { 450 result = utf8ByteArrayFromString(string, 451 getUtf8LengthForString(string), false); 452 } else { 453 if (encoding !== "binary") { 454 self.log("unknown encoding: " + encoding); 455 } 456 result = byteArrayFromString(string); 457 } 458 return result; 459 }; 460 this.byteArrayToString = Runtime.byteArrayToString; 461 462 /** 463 * @param {!string} name 464 * @return {*} 465 */ 466 this.getVariable = Runtime.getVariable; 467 468 469 /** 470 * @param {!string} jsonstr 471 * @return {*} 472 */ 473 this.fromJson = Runtime.fromJson; 474 /** 475 * @param {*} anything 476 * @return {!string} 477 */ 478 this.toJson = Runtime.toJson; 479 480 /** 481 * @param {!string} msgOrCategory 482 * @param {string=} msg 483 * @return {undefined} 484 */ 485 function log(msgOrCategory, msg) { 486 var node, doc, category; 487 if (msg !== undefined) { 488 category = msgOrCategory; 489 } else { 490 msg = msgOrCategory; 491 } 492 if (logoutput) { 493 doc = logoutput.ownerDocument; 494 if (category) { 495 node = doc.createElement("span"); 496 node.className = category; 497 node.appendChild(doc.createTextNode(category)); 498 logoutput.appendChild(node); 499 logoutput.appendChild(doc.createTextNode(" ")); 500 } 501 node = doc.createElement("span"); 502 if (msg.length > 0 && msg[0] === "<") { 503 node.innerHTML = msg; 504 } else { 505 node.appendChild(doc.createTextNode(msg)); 506 } 507 logoutput.appendChild(node); 508 logoutput.appendChild(doc.createElement("br")); 509 } else if (console) { 510 console.log(msg); 511 } 512 if (category === "alert") { 513 alert(msg); 514 } 515 } 516 517 /** 518 * @param {!boolean} condition 519 * @param {!string} message 520 * @param {!function():undefined=} callback 521 * @return {undefined} 522 */ 523 function assert(condition, message, callback) { 524 if (!condition) { 525 log("alert", "ASSERTION FAILED:\n" + message); 526 if (callback) { 527 callback(); 528 } 529 throw message; // interrupt execution and provide a backtrace 530 } 531 } 532 /** 533 * @param {!Array.<!number>} buffer 534 * @return {!Uint8Array} 535 */ 536 function arrayToUint8Array(buffer) { 537 var l = buffer.length, i, 538 a = new Uint8Array(new ArrayBuffer(l)); 539 for (i = 0; i < l; i += 1) { 540 a[i] = buffer[i]; 541 } 542 return a; 543 } 544 /** 545 * Convert the text received by XHR to a byte array. 546 * An XHR request can send a text as a response even though binary content 547 * was requested. This text string should be converted to a byte array. 548 * If the length of the text is equal to the reported length of the content 549 * then each character becomes one byte. 550 * If the length is different, which can happen on WebKit and Blink 551 * browsers, the string should be converted while taking into account the 552 * encoding. Currently, only utf8 is supported for this procedure. 553 * @param {!XMLHttpRequest} xhr 554 * @return {!Uint8Array} 555 */ 556 function stringToBinaryWorkaround(xhr) { 557 var cl, data; 558 cl = xhr.getResponseHeader("Content-Length"); 559 if (cl) { 560 cl = parseInt(cl, 10); 561 } 562 // If Content-Length was found and is a valid number that is not equal 563 // to the length of the string, the byte array should be reconstructed 564 // from the encoding. 565 if (cl && cl !== xhr.responseText.length) { 566 // The text is not simple ascii, so we assume it is utf8 and try to 567 // reconstruct the text from that. 568 data = utf8ByteArrayFromXHRString(xhr.responseText, cl); 569 } 570 if (data === undefined) { 571 data = byteArrayFromString(xhr.responseText); 572 } 573 return data; 574 } 575 /** 576 * @param {!string} path 577 * @param {!string} encoding 578 * @param {!XMLHttpRequest} xhr 579 * @return {!{err:?string,data:(?string|?Uint8Array)}} 580 */ 581 function handleXHRResult(path, encoding, xhr) { 582 var r, d, a, 583 /**@type{!Uint8Array|!string}*/ 584 data; 585 if (xhr.status === 0 && !xhr.responseText) { 586 // for local files there is no difference between missing 587 // and empty files, so empty files are considered as errors 588 r = {err: "File " + path + " is empty.", data: null}; 589 } else if (xhr.status === 200 || xhr.status === 0) { 590 // report file 591 if (xhr.response && typeof xhr.response !== "string") { 592 // w3c compliant way http://www.w3.org/TR/XMLHttpRequest2/#the-response-attribute 593 if (encoding === "binary") { 594 d = /**@type{!ArrayBuffer}*/(xhr.response); 595 data = new Uint8Array(d); 596 } else { 597 data = String(xhr.response); 598 } 599 } else if (encoding === "binary") { 600 if (xhr.responseBody !== null 601 && String(typeof VBArray) !== "undefined") { 602 // fallback for IE <= 10 603 a = (new VBArray(xhr.responseBody)).toArray(); 604 data = arrayToUint8Array(a); 605 } else { 606 data = stringToBinaryWorkaround(xhr); 607 } 608 } else { 609 // if we just want text, it's simple 610 data = xhr.responseText; 611 } 612 cache[path] = data; 613 r = {err: null, data: data}; 614 } else { 615 // report error 616 r = {err: xhr.responseText || xhr.statusText, data: null}; 617 } 618 return r; 619 } 620 /** 621 * @param {!string} path 622 * @param {!string} encoding 623 * @param {!boolean} async 624 * @return {!XMLHttpRequest} 625 */ 626 function createXHR(path, encoding, async) { 627 var xhr = new XMLHttpRequest(); 628 xhr.open('GET', path, async); 629 if (xhr.overrideMimeType) { 630 if (encoding !== "binary") { 631 xhr.overrideMimeType("text/plain; charset=" + encoding); 632 } else { 633 xhr.overrideMimeType("text/plain; charset=x-user-defined"); 634 } 635 } 636 return xhr; 637 } 638 /** 639 * Read the contents of a file. Returns the result via a callback. If the 640 * encoding is 'binary', the result is returned as a Uint8Array, 641 * otherwise, it is returned as a string. 642 * @param {!string} path 643 * @param {!string} encoding text encoding or 'binary' 644 * @param {!function(?string,?(string|Uint8Array)):undefined} callback 645 * @return {undefined} 646 */ 647 function readFile(path, encoding, callback) { 648 if (cache.hasOwnProperty(path)) { 649 callback(null, cache[path]); 650 return; 651 } 652 var xhr = createXHR(path, encoding, true); 653 function handleResult() { 654 var r; 655 if (xhr.readyState === 4) { 656 r = handleXHRResult(path, encoding, xhr); 657 callback(r.err, r.data); 658 } 659 } 660 xhr.onreadystatechange = handleResult; 661 try { 662 xhr.send(null); 663 } catch (/**@type{!Error}*/e) { 664 callback(e.message, null); 665 } 666 } 667 /** 668 * @param {!string} path 669 * @param {!number} offset 670 * @param {!number} length 671 * @param {!function(?string,?Uint8Array):undefined} callback 672 * @return {undefined} 673 */ 674 function read(path, offset, length, callback) { 675 readFile(path, "binary", function (err, result) { 676 var r = null; 677 if (result) { 678 if (typeof result === "string") { 679 throw "This should not happen."; 680 } 681 r = /**@type{!Uint8Array}*/(result.subarray(offset, 682 offset + length)); 683 } 684 callback(err, r); 685 }); 686 } 687 /** 688 * @param {!string} path 689 * @param {!string} encoding text encoding or 'binary' 690 * @return {!string|!Uint8Array} 691 */ 692 function readFileSync(path, encoding) { 693 var xhr = createXHR(path, encoding, false), 694 r; 695 try { 696 xhr.send(null); 697 r = handleXHRResult(path, encoding, xhr); 698 if (r.err) { 699 throw r.err; 700 } 701 if (r.data === null) { 702 throw "No data read from " + path + "."; 703 } 704 } catch (/**@type{!Error}*/e) { 705 throw e; 706 } 707 return r.data; 708 } 709 /** 710 * @param {!string} path 711 * @param {!Uint8Array} data 712 * @param {!function(?string):undefined} callback 713 * @return {undefined} 714 */ 715 function writeFile(path, data, callback) { 716 cache[path] = data; 717 var xhr = new XMLHttpRequest(), 718 /**@type{!string|!ArrayBuffer}*/ 719 d; 720 function handleResult() { 721 if (xhr.readyState === 4) { 722 if (xhr.status === 0 && !xhr.responseText) { 723 // for local files there is no difference between missing 724 // and empty files, so empty files are considered as errors 725 callback("File " + path + " is empty."); 726 } else if ((xhr.status >= 200 && xhr.status < 300) || 727 xhr.status === 0) { 728 // report success 729 callback(null); 730 } else { 731 // report error 732 callback("Status " + String(xhr.status) + ": " + 733 xhr.responseText || xhr.statusText); 734 } 735 } 736 } 737 xhr.open('PUT', path, true); 738 xhr.onreadystatechange = handleResult; 739 // ArrayBufferView will have an ArrayBuffer property, in WebKit, XHR 740 // can send() an ArrayBuffer, In Firefox, one must use sendAsBinary with 741 // a string 742 if (data.buffer && !xhr.sendAsBinary) { 743 d = data.buffer; // webkit supports sending an ArrayBuffer 744 } else { 745 // encode into a string, this works in FireFox >= 3 746 d = self.byteArrayToString(data, "binary"); 747 } 748 try { 749 if (xhr.sendAsBinary) { 750 xhr.sendAsBinary(d); 751 } else { 752 xhr.send(d); 753 } 754 } catch (/**@type{!Error}*/e) { 755 self.log("HUH? " + e + " " + data); 756 callback(e.message); 757 } 758 } 759 /** 760 * @param {!string} path 761 * @param {!function(?string):undefined} callback 762 * @return {undefined} 763 */ 764 function deleteFile(path, callback) { 765 delete cache[path]; 766 var xhr = new XMLHttpRequest(); 767 xhr.open('DELETE', path, true); 768 xhr.onreadystatechange = function () { 769 if (xhr.readyState === 4) { 770 if (xhr.status < 200 && xhr.status >= 300) { 771 callback(xhr.responseText); 772 } else { 773 callback(null); 774 } 775 } 776 }; 777 xhr.send(null); 778 } 779 /** 780 * @param {!string} path 781 * @param {!function(?string,?Document):undefined} callback 782 * @return {undefined} 783 */ 784 function loadXML(path, callback) { 785 var xhr = new XMLHttpRequest(); 786 function handleResult() { 787 if (xhr.readyState === 4) { 788 if (xhr.status === 0 && !xhr.responseText) { 789 callback("File " + path + " is empty.", null); 790 } else if (xhr.status === 200 || xhr.status === 0) { 791 // report file 792 callback(null, xhr.responseXML); 793 } else { 794 // report error 795 callback(xhr.responseText, null); 796 } 797 } 798 } 799 xhr.open("GET", path, true); 800 if (xhr.overrideMimeType) { 801 xhr.overrideMimeType("text/xml"); 802 } 803 xhr.onreadystatechange = handleResult; 804 try { 805 xhr.send(null); 806 } catch (/**@type{!Error}*/e) { 807 callback(e.message, null); 808 } 809 } 810 /** 811 * @param {!string} path 812 * @param {!function(boolean):undefined} callback 813 * @return {undefined} 814 */ 815 function isFile(path, callback) { 816 self.getFileSize(path, function (size) { 817 callback(size !== -1); 818 }); 819 } 820 /** 821 * @param {!string} path 822 * @param {!function(number):undefined} callback 823 * @return {undefined} 824 */ 825 function getFileSize(path, callback) { 826 if (cache.hasOwnProperty(path) && typeof cache[path] !== "string") { 827 callback(cache[path].length); 828 return; 829 } 830 var xhr = new XMLHttpRequest(); 831 xhr.open("HEAD", path, true); 832 xhr.onreadystatechange = function () { 833 if (xhr.readyState !== 4) { 834 return; 835 } 836 var cl = xhr.getResponseHeader("Content-Length"); 837 if (cl) { 838 callback(parseInt(cl, 10)); 839 } else { 840 // Due to CORS implementation bugs, some browsers will not allow access to certain headers 841 // even if the server says it's ok. This specific bug was observed in Cocoa's WebView on OSX10.8. 842 // However, even though the browser won't pull the content-length header (coz it's a security risk!) 843 // the content can still be fetched. 844 // This data will be cached, so we'll still only ever have to fetch it once 845 readFile(path, "binary", function (err, data) { 846 if (!err) { 847 callback(data.length); 848 } else { 849 callback(-1); 850 } 851 }); 852 } 853 }; 854 xhr.send(null); 855 } 856 this.readFile = readFile; 857 this.read = read; 858 this.readFileSync = readFileSync; 859 this.writeFile = writeFile; 860 this.deleteFile = deleteFile; 861 this.loadXML = loadXML; 862 this.isFile = isFile; 863 this.getFileSize = getFileSize; 864 this.log = log; 865 this.assert = assert; 866 /** 867 * @param {!function():undefined} f 868 * @param {!number} msec 869 * @return {!number} 870 */ 871 this.setTimeout = function (f, msec) { 872 return setTimeout(function () { 873 f(); 874 }, msec); 875 }; 876 /** 877 * @param {!number} timeoutID 878 * @return {undefined} 879 */ 880 this.clearTimeout = function (timeoutID) { 881 clearTimeout(timeoutID); 882 }; 883 /** 884 * @return {!Array.<!string>} 885 */ 886 this.libraryPaths = function () { 887 return ["lib"]; // TODO: find a good solution 888 // probably let html app specify it 889 }; 890 /*jslint emptyblock: true*/ 891 this.setCurrentDirectory = function () { 892 }; 893 /*jslint emptyblock: false*/ 894 /** 895 * @return {!string} 896 */ 897 this.currentDirectory = function () { 898 return ""; 899 }; 900 this.type = function () { 901 return "BrowserRuntime"; 902 }; 903 this.getDOMImplementation = function () { 904 return window.document.implementation; 905 }; 906 /** 907 * @param {!string} xml 908 * @return {?Document} 909 */ 910 this.parseXML = function (xml) { 911 var parser = new DOMParser(); 912 return parser.parseFromString(xml, "text/xml"); 913 }; 914 /** 915 * @param {!number} exitCode 916 */ 917 this.exit = function (exitCode) { 918 log("Calling exit with code " + String(exitCode) + 919 ", but exit() is not implemented."); 920 }; 921 /** 922 * @return {!Window} 923 */ 924 this.getWindow = function () { 925 return window; 926 }; 927 /** 928 * @param {!function():undefined} callback 929 * @return {!number} 930 */ 931 this.requestAnimationFrame = function (callback) { 932 var rAF = window.requestAnimationFrame 933 || window.webkitRequestAnimationFrame 934 || window.mozRequestAnimationFrame 935 || window.msRequestAnimationFrame, 936 requestId = 0; 937 938 if (rAF) { 939 // This code is to satisfy Closure, which expects 940 // that the `this` of rAF should be window. 941 rAF.bind(window); 942 requestId = /**@type{function(!function():undefined):!number}*/(rAF)(callback); 943 } else { 944 return setTimeout(callback, 15); 945 } 946 947 return requestId; 948 }; 949 /** 950 * @param {!number} requestId 951 * @return {undefined} 952 */ 953 this.cancelAnimationFrame = function (requestId) { 954 var cAF = window.cancelAnimationFrame 955 || window.webkitCancelAnimationFrame 956 || window.mozCancelAnimationFrame 957 || window.msCancelAnimationFrame; 958 959 if (cAF) { 960 cAF.bind(window); 961 /**@type{function(!number)}*/(cAF)(requestId); 962 } else { 963 clearTimeout(requestId); 964 } 965 }; 966 } 967 968 /** 969 * @constructor 970 * @implements {Runtime} 971 */ 972 function NodeJSRuntime() { 973 "use strict"; 974 var self = this, 975 fs = require('fs'), 976 pathmod = require('path'), 977 /**@type{!string}*/ 978 currentDirectory = "", 979 /**@type{!DOMParser}*/ 980 parser, 981 domImplementation; 982 983 /** 984 * @param {!Buffer} buffer 985 * @return {!Uint8Array} 986 */ 987 function bufferToUint8Array(buffer) { 988 var l = buffer.length, i, 989 a = new Uint8Array(new ArrayBuffer(l)); 990 for (i = 0; i < l; i += 1) { 991 a[i] = buffer[i]; 992 } 993 return a; 994 } 995 /** 996 * @param {!string} string 997 * @param {!string} encoding 998 * @return {!Uint8Array} 999 */ 1000 this.byteArrayFromString = function (string, encoding) { 1001 var buf = new Buffer(string, encoding), i, l = buf.length, 1002 a = new Uint8Array(new ArrayBuffer(l)); 1003 for (i = 0; i < l; i += 1) { 1004 a[i] = buf[i]; 1005 } 1006 return a; 1007 }; 1008 1009 this.byteArrayToString = Runtime.byteArrayToString; 1010 1011 /** 1012 * @param {!string} name 1013 * @return {*} 1014 */ 1015 this.getVariable = Runtime.getVariable; 1016 1017 /** 1018 * @param {!string} jsonstr 1019 * @return {*} 1020 */ 1021 this.fromJson = Runtime.fromJson; 1022 /** 1023 * @param {*} anything 1024 * @return {!string} 1025 */ 1026 this.toJson = Runtime.toJson; 1027 1028 /** 1029 * @param {!string} path 1030 * @param {!function(boolean):undefined} callback 1031 * @return {undefined} 1032 */ 1033 function isFile(path, callback) { 1034 path = pathmod.resolve(currentDirectory, path); 1035 fs.stat(path, function (err, stats) { 1036 callback(!err && stats.isFile()); 1037 }); 1038 } 1039 /** 1040 * Read the contents of a file. Returns the result via a callback. If the 1041 * encoding is 'binary', the result is returned as a Uint8Array, 1042 * otherwise, it is returned as a string. 1043 * @param {!string} path 1044 * @param {!string} encoding text encoding or 'binary' 1045 * @param {!function(?string,?(string|Uint8Array)):undefined} callback 1046 * @return {undefined} 1047 */ 1048 function readFile(path, encoding, callback) { 1049 /** 1050 * @param {?string} err 1051 * @param {?Buffer|?string} data 1052 * @return {undefined} 1053 */ 1054 function convert(err, data) { 1055 if (err) { 1056 return callback(err, null); 1057 } 1058 if (!data) { 1059 return callback("No data for " + path + ".", null); 1060 } 1061 var d; 1062 if (typeof data === "string") { 1063 d = /**@type{!string}*/(data); 1064 return callback(err, d); 1065 } 1066 d = /**@type{!Buffer}*/(data); 1067 callback(err, bufferToUint8Array(d)); 1068 } 1069 path = pathmod.resolve(currentDirectory, path); 1070 if (encoding !== "binary") { 1071 fs.readFile(path, encoding, convert); 1072 } else { 1073 fs.readFile(path, null, convert); 1074 } 1075 } 1076 this.readFile = readFile; 1077 /** 1078 * @param {!string} path 1079 * @param {!function(?string,?Document):undefined} callback 1080 * @return {undefined} 1081 */ 1082 function loadXML(path, callback) { 1083 readFile(path, "utf-8", function (err, data) { 1084 if (err) { 1085 return callback(err, null); 1086 } 1087 if (!data) { 1088 return callback("No data for " + path + ".", null); 1089 } 1090 var d = /**@type{!string}*/(data); 1091 callback(null, self.parseXML(d)); 1092 }); 1093 } 1094 this.loadXML = loadXML; 1095 /** 1096 * @param {!string} path 1097 * @param {!Uint8Array} data 1098 * @param {!function(?string):undefined} callback 1099 * @return {undefined} 1100 */ 1101 this.writeFile = function (path, data, callback) { 1102 var buf = new Buffer(data); 1103 path = pathmod.resolve(currentDirectory, path); 1104 fs.writeFile(path, buf, "binary", function (err) { 1105 callback(err || null); 1106 }); 1107 }; 1108 /** 1109 * @param {!string} path 1110 * @param {!function(?string):undefined} callback 1111 * @return {undefined} 1112 */ 1113 this.deleteFile = function (path, callback) { 1114 path = pathmod.resolve(currentDirectory, path); 1115 fs.unlink(path, callback); 1116 }; 1117 /** 1118 * @param {!string} path 1119 * @param {!number} offset 1120 * @param {!number} length 1121 * @param {!function(?string,?Uint8Array):undefined} callback 1122 * @return {undefined} 1123 */ 1124 this.read = function (path, offset, length, callback) { 1125 path = pathmod.resolve(currentDirectory, path); 1126 fs.open(path, "r+", 666, function (err, fd) { 1127 if (err) { 1128 callback(err, null); 1129 return; 1130 } 1131 var buffer = new Buffer(length); 1132 fs.read(fd, buffer, 0, length, offset, function (err) { 1133 fs.close(fd); 1134 callback(err, bufferToUint8Array(buffer)); 1135 }); 1136 }); 1137 }; 1138 /** 1139 * @param {!string} path 1140 * @param {!string} encoding text encoding or 'binary' 1141 * @return {!string|!Uint8Array} 1142 */ 1143 this.readFileSync = function (path, encoding) { 1144 var s, 1145 enc = (encoding === "binary") ? null : encoding, 1146 r = fs.readFileSync(path, enc); 1147 if (r === null) { 1148 throw "File " + path + " could not be read."; 1149 } 1150 if (encoding === "binary") { 1151 s = /**@type{!Buffer}*/(r); 1152 s = bufferToUint8Array(s); 1153 } else { 1154 s = /**@type{!string}*/(r); 1155 } 1156 return s; 1157 }; 1158 this.isFile = isFile; 1159 /** 1160 * @param {!string} path 1161 * @param {!function(number):undefined} callback 1162 * @return {undefined} 1163 */ 1164 this.getFileSize = function (path, callback) { 1165 path = pathmod.resolve(currentDirectory, path); 1166 fs.stat(path, function (err, stats) { 1167 if (err) { 1168 callback(-1); 1169 } else { 1170 callback(stats.size); 1171 } 1172 }); 1173 }; 1174 /** 1175 * @param {!string} msgOrCategory 1176 * @param {string=} msg 1177 * @return {undefined} 1178 */ 1179 function log(msgOrCategory, msg) { 1180 var category; 1181 if (msg !== undefined) { 1182 category = msgOrCategory; 1183 } else { 1184 msg = msgOrCategory; 1185 } 1186 if (category === "alert") { 1187 process.stderr.write("\n!!!!! ALERT !!!!!" + '\n'); 1188 } 1189 process.stderr.write(msg + '\n'); 1190 if (category === "alert") { 1191 process.stderr.write("!!!!! ALERT !!!!!" + '\n'); 1192 } 1193 } 1194 this.log = log; 1195 /** 1196 * @param {!boolean} condition 1197 * @param {!string} message 1198 * @param {!function():undefined=} callback 1199 * @return {undefined} 1200 */ 1201 function assert(condition, message, callback) { 1202 if (!condition) { 1203 process.stderr.write("ASSERTION FAILED: " + message); 1204 if (callback) { 1205 callback(); 1206 } 1207 } 1208 } 1209 this.assert = assert; 1210 /** 1211 * @param {!function():undefined} f 1212 * @param {!number} msec 1213 * @return {!number} 1214 */ 1215 this.setTimeout = function (f, msec) { 1216 return setTimeout(function () { 1217 f(); 1218 }, msec); 1219 }; 1220 /** 1221 * @param {!number} timeoutID 1222 * @return {undefined} 1223 */ 1224 this.clearTimeout = function (timeoutID) { 1225 clearTimeout(timeoutID); 1226 }; 1227 /** 1228 * @return {!Array.<!string>} 1229 */ 1230 this.libraryPaths = function () { 1231 return [__dirname]; 1232 }; 1233 /** 1234 * @param {!string} dir 1235 * @return {undefined} 1236 */ 1237 this.setCurrentDirectory = function (dir) { 1238 currentDirectory = dir; 1239 }; 1240 this.currentDirectory = function () { 1241 return currentDirectory; 1242 }; 1243 this.type = function () { 1244 return "NodeJSRuntime"; 1245 }; 1246 this.getDOMImplementation = function () { 1247 return domImplementation; 1248 }; 1249 /** 1250 * @param {!string} xml 1251 * @return {?Document} 1252 */ 1253 this.parseXML = function (xml) { 1254 return parser.parseFromString(xml, "text/xml"); 1255 }; 1256 this.exit = process.exit; 1257 this.getWindow = function () { 1258 return null; 1259 }; 1260 /** 1261 * @param {!function():undefined} callback 1262 * @return {!number} 1263 */ 1264 this.requestAnimationFrame = function (callback) { 1265 return setTimeout(callback, 15); 1266 }; 1267 /** 1268 * @param {!number} requestId 1269 * @return {undefined} 1270 */ 1271 this.cancelAnimationFrame = function (requestId) { 1272 clearTimeout(requestId); 1273 }; 1274 function init() { 1275 var /**@type{function(new:DOMParser)}*/ 1276 DOMParser = require('xmldom').DOMParser; 1277 parser = new DOMParser(); 1278 domImplementation = self.parseXML("<a/>").implementation; 1279 } 1280 init(); 1281 } 1282 1283 /** 1284 * @constructor 1285 * @implements {Runtime} 1286 */ 1287 function RhinoRuntime() { 1288 "use strict"; 1289 var self = this, 1290 Packages = {}, 1291 dom = Packages.javax.xml.parsers.DocumentBuilderFactory.newInstance(), 1292 /**@type{!Packages.javax.xml.parsers.DocumentBuilder}*/ 1293 builder, 1294 entityresolver, 1295 /**@type{!string}*/ 1296 currentDirectory = ""; 1297 dom.setValidating(false); 1298 dom.setNamespaceAware(true); 1299 dom.setExpandEntityReferences(false); 1300 dom.setSchema(null); 1301 /*jslint unparam: true */ 1302 entityresolver = Packages.org.xml.sax.EntityResolver({ 1303 /** 1304 * @param {!string} publicId 1305 * @param {!string} systemId 1306 * @return {!Packages.org.xml.sax.InputSource} 1307 */ 1308 resolveEntity: function (publicId, systemId) { 1309 var file; 1310 /** 1311 * @param {!string} path 1312 * @return {!Packages.org.xml.sax.InputSource} 1313 */ 1314 function open(path) { 1315 var reader = new Packages.java.io.FileReader(path), 1316 source = new Packages.org.xml.sax.InputSource(reader); 1317 return source; 1318 } 1319 file = systemId; 1320 //file = /[^\/]*$/.exec(systemId); // what should this do? 1321 return open(file); 1322 } 1323 }); 1324 /*jslint unparam: false */ 1325 //dom.setEntityResolver(entityresolver); 1326 builder = dom.newDocumentBuilder(); 1327 builder.setEntityResolver(entityresolver); 1328 1329 /*jslint unparam: true*/ 1330 /** 1331 * @param {!string} string 1332 * @param {!string} encoding 1333 * @return {!Uint8Array} 1334 */ 1335 this.byteArrayFromString = function (string, encoding) { 1336 // ignore encoding for now 1337 var i, 1338 l = string.length, 1339 a = new Uint8Array(new ArrayBuffer(l)); 1340 for (i = 0; i < l; i += 1) { 1341 a[i] = string.charCodeAt(i) & 0xff; 1342 } 1343 return a; 1344 }; 1345 /*jslint unparam: false*/ 1346 this.byteArrayToString = Runtime.byteArrayToString; 1347 1348 /** 1349 * @param {!string} name 1350 * @return {*} 1351 */ 1352 this.getVariable = Runtime.getVariable; 1353 1354 /** 1355 * @param {!string} jsonstr 1356 * @return {*} 1357 */ 1358 this.fromJson = Runtime.fromJson; 1359 /** 1360 * @param {*} anything 1361 * @return {!string} 1362 */ 1363 this.toJson = Runtime.toJson; 1364 1365 /** 1366 * @param {!string} path 1367 * @param {!function(?string,?Document):undefined} callback 1368 * @return {undefined} 1369 */ 1370 function loadXML(path, callback) { 1371 var file = new Packages.java.io.File(path), 1372 xmlDocument = null; 1373 try { 1374 xmlDocument = builder.parse(file); 1375 } catch (/**@type{!string}*/err) { 1376 print(err); 1377 return callback(err, null); 1378 } 1379 callback(null, xmlDocument); 1380 } 1381 /** 1382 * @param {!string} path 1383 * @param {!string} encoding text encoding or 'binary' 1384 * @param {!function(?string,?(string|Uint8Array)):undefined} callback 1385 * @return {undefined} 1386 */ 1387 function runtimeReadFile(path, encoding, callback) { 1388 if (currentDirectory) { 1389 path = currentDirectory + "/" + path; 1390 } 1391 var file = new Packages.java.io.File(path), 1392 data, 1393 // read binary, seems hacky but works 1394 rhinoencoding = (encoding === "binary") ? "latin1" : encoding; 1395 if (!file.isFile()) { 1396 callback(path + " is not a file.", null); 1397 } else { 1398 data = readFile(path, rhinoencoding); 1399 if (data && encoding === "binary") { 1400 data = self.byteArrayFromString(data, "binary"); 1401 } 1402 callback(null, data); 1403 } 1404 } 1405 /** 1406 * @param {!string} path 1407 * @param {!string} encoding 1408 * @return {?string} 1409 */ 1410 function runtimeReadFileSync(path, encoding) { 1411 var file = new Packages.java.io.File(path); 1412 if (!file.isFile()) { 1413 return null; 1414 } 1415 if (encoding === "binary") { 1416 encoding = "latin1"; // read binary, seems hacky but works 1417 } 1418 return readFile(path, encoding); 1419 } 1420 /** 1421 * @param {!string} path 1422 * @param {!function(boolean):undefined} callback 1423 * @return {undefined} 1424 */ 1425 function isFile(path, callback) { 1426 if (currentDirectory) { 1427 path = currentDirectory + "/" + path; 1428 } 1429 var file = new Packages.java.io.File(path); 1430 callback(file.isFile()); 1431 } 1432 this.loadXML = loadXML; 1433 this.readFile = runtimeReadFile; 1434 /** 1435 * @param {!string} path 1436 * @param {!Uint8Array} data 1437 * @param {!function(?string):undefined} callback 1438 * @return {undefined} 1439 */ 1440 this.writeFile = function (path, data, callback) { 1441 if (currentDirectory) { 1442 path = currentDirectory + "/" + path; 1443 } 1444 var out = new Packages.java.io.FileOutputStream(path), 1445 i, 1446 l = data.length; 1447 for (i = 0; i < l; i += 1) { 1448 out.write(data[i]); 1449 } 1450 out.close(); 1451 callback(null); 1452 }; 1453 /** 1454 * @param {!string} path 1455 * @param {!function(?string):undefined} callback 1456 * @return {undefined} 1457 */ 1458 this.deleteFile = function (path, callback) { 1459 if (currentDirectory) { 1460 path = currentDirectory + "/" + path; 1461 } 1462 var file = new Packages.java.io.File(path), 1463 otherPath = path + Math.random(), 1464 other = new Packages.java.io.File(otherPath); 1465 // 'delete' cannot be used with closure compiler, so we use a workaround 1466 if (file.rename(other)) { 1467 other.deleteOnExit(); 1468 callback(null); 1469 } else { 1470 callback("Could not delete " + path); 1471 } 1472 }; 1473 /** 1474 * @param {!string} path 1475 * @param {!number} offset 1476 * @param {!number} length 1477 * @param {!function(?string,?Uint8Array):undefined} callback 1478 * @return {undefined} 1479 */ 1480 this.read = function (path, offset, length, callback) { 1481 // TODO: adapt to read only a part instead of the whole file 1482 if (currentDirectory) { 1483 path = currentDirectory + "/" + path; 1484 } 1485 var data = runtimeReadFileSync(path, "binary"); 1486 if (data) { 1487 callback(null, this.byteArrayFromString( 1488 data.substring(offset, offset + length), 1489 "binary" 1490 )); 1491 } else { 1492 callback("Cannot read " + path, null); 1493 } 1494 }; 1495 /** 1496 * @param {!string} path 1497 * @param {!string} encoding text encoding or 'binary' 1498 * @return {!string} 1499 */ 1500 this.readFileSync = function (path, encoding) { 1501 if (!encoding) { 1502 return ""; 1503 } 1504 var s = readFile(path, encoding); 1505 if (s === null) { 1506 throw "File could not be read."; 1507 } 1508 return s; 1509 }; 1510 this.isFile = isFile; 1511 /** 1512 * @param {!string} path 1513 * @param {!function(number):undefined} callback 1514 * @return {undefined} 1515 */ 1516 this.getFileSize = function (path, callback) { 1517 if (currentDirectory) { 1518 path = currentDirectory + "/" + path; 1519 } 1520 var file = new Packages.java.io.File(path); 1521 callback(file.length()); 1522 }; 1523 /** 1524 * @param {!string} msgOrCategory 1525 * @param {string=} msg 1526 * @return {undefined} 1527 */ 1528 function log(msgOrCategory, msg) { 1529 var category; 1530 if (msg !== undefined) { 1531 category = msgOrCategory; 1532 } else { 1533 msg = msgOrCategory; 1534 } 1535 if (category === "alert") { 1536 print("\n!!!!! ALERT !!!!!"); 1537 } 1538 print(msg); 1539 if (category === "alert") { 1540 print("!!!!! ALERT !!!!!"); 1541 } 1542 } 1543 this.log = log; 1544 1545 /** 1546 * @param {!boolean} condition 1547 * @param {!string} message 1548 * @param {!function():undefined=} callback 1549 * @return {undefined} 1550 */ 1551 function assert(condition, message, callback) { 1552 if (!condition) { 1553 log("alert", "ASSERTION FAILED: " + message); 1554 if (callback) { 1555 callback(); 1556 } 1557 } 1558 } 1559 this.assert = assert; 1560 /** 1561 * @param {!function():undefined} f 1562 * @return {!number} 1563 */ 1564 this.setTimeout = function (f) { 1565 f(); 1566 return 0; 1567 }; 1568 /*jslint emptyblock: true */ 1569 /** 1570 * @return {undefined} 1571 */ 1572 this.clearTimeout = function () { 1573 }; 1574 /*jslint emptyblock: false */ 1575 /** 1576 * @return {!Array.<!string>} 1577 */ 1578 this.libraryPaths = function () { 1579 return ["lib"]; 1580 }; 1581 /** 1582 * @param {!string} dir 1583 */ 1584 this.setCurrentDirectory = function (dir) { 1585 currentDirectory = dir; 1586 }; 1587 this.currentDirectory = function () { 1588 return currentDirectory; 1589 }; 1590 this.type = function () { 1591 return "RhinoRuntime"; 1592 }; 1593 this.getDOMImplementation = function () { 1594 return builder.getDOMImplementation(); 1595 }; 1596 /** 1597 * @param {!string} xml 1598 * @return {?Document} 1599 */ 1600 this.parseXML = function (xml) { 1601 var reader = new Packages.java.io.StringReader(xml), 1602 source = new Packages.org.xml.sax.InputSource(reader); 1603 return builder.parse(source); 1604 }; 1605 this.exit = quit; 1606 this.getWindow = function () { 1607 return null; 1608 }; 1609 /** 1610 * @param {!function():undefined} callback 1611 * @return {!number} 1612 */ 1613 this.requestAnimationFrame = function (callback) { 1614 callback(); 1615 return 0; 1616 }; 1617 /*jslint emptyblock: true */ 1618 /** 1619 * @return {undefined} 1620 */ 1621 this.cancelAnimationFrame = function () { 1622 }; 1623 /*jslint emptyblock: false */ 1624 } 1625 1626 /** 1627 * @return {!Runtime} 1628 */ 1629 Runtime.create = function create() { 1630 "use strict"; 1631 var /**@type{!Runtime}*/ 1632 result; 1633 if (String(typeof window) !== "undefined") { 1634 result = new BrowserRuntime(window.document.getElementById("logoutput")); 1635 } else if (String(typeof require) !== "undefined") { 1636 result = new NodeJSRuntime(); 1637 } else { 1638 result = new RhinoRuntime(); 1639 } 1640 return result; 1641 }; 1642 1643 /** 1644 * @const 1645 * @type {!Runtime} 1646 */ 1647 var runtime = Runtime.create(); 1648 1649 /** 1650 * @namespace The core package. 1651 */ 1652 var core = {}; 1653 /** 1654 * @namespace The gui package. 1655 */ 1656 var gui = {}; 1657 /** 1658 * @namespace The xmldom package. 1659 */ 1660 var xmldom = {}; 1661 /** 1662 * @namespace The ODF package. 1663 */ 1664 var odf = {}; 1665 /** 1666 * @namespace The editing operations 1667 */ 1668 var ops = {}; 1669 1670 /** 1671 * @namespace The webodf namespace 1672 */ 1673 var webodf = {}; 1674 1675 (function () { 1676 "use strict"; 1677 /** 1678 * @return {string} 1679 */ 1680 function getWebODFVersion() { 1681 var version = (String(typeof webodf_version) !== "undefined" 1682 ? webodf_version 1683 : "From Source" 1684 ); 1685 return version; 1686 } 1687 /** 1688 * @const 1689 * @type {!string} 1690 */ 1691 webodf.Version = getWebODFVersion(); 1692 }()); 1693 1694 /*jslint sloppy: true*/ 1695 (function () { 1696 /** 1697 * @param {string} dir Needs to be non-empty, use "." to denote same directory 1698 * @param {!Object.<string,!{dir:string, deps:!Array.<string>}>} dependencies 1699 * @param {!boolean=} expectFail Set to true if it is not known if there is a manifest 1700 */ 1701 function loadDependenciesFromManifest(dir, dependencies, expectFail) { 1702 "use strict"; 1703 var path = dir + "/manifest.json", 1704 content, 1705 list, 1706 manifest, 1707 /**@type{string}*/ 1708 m; 1709 runtime.log("Loading manifest: "+path); 1710 try { 1711 content = runtime.readFileSync(path, "utf-8"); 1712 } catch (/**@type{string}*/e) { 1713 if (expectFail) { 1714 runtime.log("No loadable manifest found."); 1715 } else { 1716 console.log(String(e)); 1717 throw e; 1718 } 1719 return; 1720 } 1721 list = JSON.parse(/**@type{string}*/(content)); 1722 manifest = /**@type{!Object.<!Array.<string>>}*/(list); 1723 for (m in manifest) { 1724 if (manifest.hasOwnProperty(m)) { 1725 dependencies[m] = {dir: dir, deps: manifest[m]}; 1726 } 1727 } 1728 } 1729 /** 1730 * @return {!Object.<string,!{dir:string, deps:!Array.<string>}>} 1731 */ 1732 function loadDependenciesFromManifests() { 1733 "use strict"; 1734 var /**@type{!Object.<string,!{dir:string, deps:!Array.<string>}>}*/ 1735 dependencies = [], 1736 paths = runtime.libraryPaths(), 1737 i; 1738 // Convenience: try to load any possible manifest in the current directory 1739 // but only if it not also included in the library paths 1740 if (runtime.currentDirectory() && paths.indexOf(runtime.currentDirectory()) === -1) { 1741 // there is no need to have a manifest there, so allow loading to fail 1742 loadDependenciesFromManifest(runtime.currentDirectory(), dependencies, true); 1743 } 1744 for (i = 0; i < paths.length; i += 1) { 1745 loadDependenciesFromManifest(paths[i], dependencies); 1746 } 1747 return dependencies; 1748 } 1749 /** 1750 * @param {string} dir 1751 * @param {string} className 1752 * @return {string} 1753 */ 1754 function getPath(dir, className) { 1755 "use strict"; 1756 return dir + "/" + className.replace(".", "/") + ".js"; 1757 } 1758 /** 1759 * Create a list of required classes from a list of desired classes. 1760 * A new list is created that lists all classes that still need to be loaded 1761 * to load the list of desired classes. 1762 * @param {!Array.<string>} classNames 1763 * @param {!Object.<string,!{dir:string, deps:!Array.<string>}>} dependencies 1764 * @param {function(string):boolean} isDefined 1765 * @return {!Array.<string>} 1766 */ 1767 function getLoadList(classNames, dependencies, isDefined) { 1768 "use strict"; 1769 var loadList = [], 1770 stack = {}, 1771 /**@type{!Object.<string,boolean>}*/ 1772 visited = {}; 1773 /** 1774 * @param {string} n 1775 */ 1776 function visit(n) { 1777 if (visited[n] || isDefined(n)) { 1778 return; 1779 } 1780 if (stack[n]) { 1781 throw "Circular dependency detected for " + n + "."; 1782 } 1783 stack[n] = true; 1784 if (!dependencies[n]) { 1785 throw "Missing dependency information for class " + n + "."; 1786 } 1787 var d = dependencies[n], deps = d.deps, i, l = deps.length; 1788 for (i = 0; i < l; i += 1) { 1789 visit(deps[i]); 1790 } 1791 stack[n] = false; 1792 visited[n] = true; 1793 loadList.push(getPath(d.dir, n)); 1794 } 1795 classNames.forEach(visit); 1796 return loadList; 1797 } 1798 /** 1799 * @param {string} path 1800 * @param {string} content 1801 * @return {string} 1802 */ 1803 function addContent(path, content) { 1804 "use strict"; 1805 content += "\n//# sourceURL=" + path; 1806 content += "\n//@ sourceURL=" + path; // Chrome 1807 return content; 1808 } 1809 /** 1810 * @param {!Array.<string>} paths 1811 */ 1812 function loadFiles(paths) { 1813 // this function is not strict, so eval can assign to globals 1814 var i, 1815 content; 1816 for (i = 0; i < paths.length; i += 1) { 1817 content = runtime.readFileSync(paths[i], "utf-8"); 1818 content = addContent(paths[i], /**@type{string}*/(content)); 1819 /*jslint evil: true*/ 1820 eval(content); 1821 /*jslint evil: false*/ 1822 } 1823 } 1824 /** 1825 * Load scripts by adding <script/> elements to the DOM. 1826 * The new script tags are added after the <script/> tag for runtime.js. 1827 * The scripts are added with async = false so that they are executed in the 1828 * right order. The scripts are executed when control returns to the browser 1829 * from the current stack. 1830 * If a callback is provided, it is executed after the last script has run. 1831 * @param {!Array.<string>} paths array with one or more script paths 1832 * @param {!Function=} callback 1833 */ 1834 function loadFilesInBrowser(paths, callback) { 1835 "use strict"; 1836 var e = document.currentScript || document.documentElement.lastChild, 1837 df = document.createDocumentFragment(), 1838 script, 1839 i; 1840 for (i = 0; i < paths.length; i += 1) { 1841 script = document.createElement("script"); 1842 script.type = "text/javascript"; 1843 script.charset = "utf-8"; 1844 script.async = false; // execute the scripts in order 1845 script.setAttribute("src", paths[i]); 1846 df.appendChild(script); 1847 } 1848 if (callback) { 1849 script.onload = callback; 1850 } 1851 e.parentNode.insertBefore(df, e); 1852 } 1853 var /**@type{!Object.<string,!{dir:string, deps:!Array.<string>}>}*/ 1854 dependencies, 1855 packages = { 1856 core: core, 1857 gui: gui, 1858 xmldom: xmldom, 1859 odf: odf, 1860 ops: ops 1861 }; 1862 /** 1863 * Check if a class has been defined. 1864 * For class "core.sub.Name", this checks if there is an entry 1865 * packages.core.sub.Name. 1866 * @param {string} classname 1867 * @return {boolean} 1868 */ 1869 function isDefined(classname) { 1870 "use strict"; 1871 var parts = classname.split("."), i, 1872 /**@type{Object}*/ 1873 p = packages, 1874 l = parts.length; 1875 for (i = 0; i < l; i += 1) { 1876 if (!p.hasOwnProperty(parts[i])) { 1877 return false; 1878 } 1879 p = /**@type{Object}*/(p[parts[i]]); 1880 } 1881 return true; 1882 } 1883 /** 1884 * @param {!Array.<string>} classnames 1885 * @param {function():undefined=} callback 1886 * @returns {undefined} 1887 */ 1888 runtime.loadClasses = function (classnames, callback) { 1889 "use strict"; 1890 if (IS_COMPILED_CODE || classnames.length === 0) { 1891 return callback && callback(); 1892 } 1893 dependencies = dependencies || loadDependenciesFromManifests(); 1894 classnames = getLoadList(classnames, dependencies, isDefined); 1895 if (classnames.length === 0) { 1896 return callback && callback(); 1897 } 1898 if (runtime.type() === "BrowserRuntime" && callback) { 1899 loadFilesInBrowser(classnames, callback); 1900 } else { 1901 loadFiles(classnames); 1902 if (callback) { 1903 callback(); 1904 } 1905 } 1906 }; 1907 /** 1908 * @param {string} classname 1909 * @param {function():undefined=} callback 1910 * @return {undefined} 1911 * @expose 1912 */ 1913 runtime.loadClass = function (classname, callback) { 1914 "use strict"; 1915 runtime.loadClasses([classname], callback); 1916 }; 1917 }()); 1918 /*jslint sloppy: false*/ 1919 1920 (function () { 1921 "use strict"; 1922 /** 1923 * @param {!string} string 1924 * @return {!string} 1925 */ 1926 var translator = function (string) { 1927 return string; 1928 }; 1929 /*jslint emptyblock: false*/ 1930 1931 /** 1932 * Translator function. Takes the original string 1933 * and returns the translation if it exists, else 1934 * returns the original. 1935 * @param {!string} original 1936 * @return {!string} 1937 */ 1938 function tr(original) { 1939 var result = translator(original); 1940 if (!result || (String(typeof result) !== "string")) { 1941 return original; 1942 } 1943 return result; 1944 } 1945 1946 /** 1947 * Gets the custom translator function 1948 * @return {!function(!string):!string} 1949 */ 1950 runtime.getTranslator = function () { 1951 return translator; 1952 }; 1953 /** 1954 * Set an external translator function 1955 * @param {!function(!string):!string} translatorFunction 1956 * @return {undefined} 1957 */ 1958 runtime.setTranslator = function (translatorFunction) { 1959 translator = translatorFunction; 1960 }; 1961 /** 1962 * @param {string} original 1963 * @return {string} 1964 */ 1965 runtime.tr = tr; 1966 }()); 1967 1968 /*jslint sloppy: true*/ 1969 (function (args) { 1970 if (args) { 1971 args = Array.prototype.slice.call(/**@type{{length:number}}*/(args)); 1972 } else { 1973 args = []; 1974 } 1975 1976 /*jslint unvar: true, defined: true*/ 1977 /** 1978 * @param {!Array.<!string>} argv 1979 */ 1980 function run(argv) { 1981 if (!argv.length) { 1982 return; 1983 } 1984 var script = argv[0]; 1985 runtime.readFile(script, "utf8", function (err, code) { 1986 var path = "", 1987 pathEndIndex = script.lastIndexOf("/"), 1988 codestring = /**@type{string}*/(code); 1989 1990 if (pathEndIndex !== -1) { 1991 path = script.substring(0, pathEndIndex); 1992 } else { 1993 path = "."; 1994 } 1995 runtime.setCurrentDirectory(path); 1996 function inner_run() { 1997 var script, path, args, argv, result; // hide variables 1998 // execute script and make arguments available via argv 1999 /*jslint evil: true*/ 2000 result = /**@type{!number}*/(eval(codestring)); 2001 /*jslint evil: false*/ 2002 if (result) { 2003 runtime.exit(result); 2004 } 2005 return; 2006 } 2007 if (err) { 2008 runtime.log(err); 2009 runtime.exit(1); 2010 } else if (codestring === null) { 2011 runtime.log("No code found for " + script); 2012 runtime.exit(1); 2013 } else { 2014 // run the script with arguments bound to arguments parameter 2015 inner_run.apply(null, argv); 2016 } 2017 }); 2018 } 2019 /*jslint unvar: false, defined: false*/ 2020 // if rhino or node.js, run the scripts provided as arguments 2021 if (runtime.type() === "NodeJSRuntime") { 2022 run(process.argv.slice(2)); 2023 } else if (runtime.type() === "RhinoRuntime") { 2024 run(args); 2025 } else { 2026 run(args.slice(1)); 2027 } 2028 }(String(typeof arguments) !== "undefined" && arguments)); 2029