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