| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 |
- var sys = require('sys'),
- http = require('http'),
- URL = require('url'),
- HtmlToDom = require('./htmltodom').HtmlToDom,
- domToHtml = require('./domtohtml').domToHtml,
- htmlencoding = require('./htmlencoding'),
- HTMLEncode = htmlencoding.HTMLEncode,
- HTMLDecode = htmlencoding.HTMLDecode,
- jsdom = require('../../jsdom'),
- vm = require('vm');
- function NOT_IMPLEMENTED(target) {
- return function() {
- if (!jsdom.debugMode) {
- var trigger = target ? target.trigger : this.trigger;
- trigger.call(this, 'error', 'NOT IMPLEMENTED');
- }
- };
- }
- /**
- * Creates a window having a document. The document can be passed as option,
- * if omitted, a new document will be created.
- */
- exports.windowAugmentation = function(dom, options) {
- options = options || {};
- var window = exports.createWindow(dom, options);
- if (!options.document) {
- var browser = browserAugmentation(dom, options);
- if (options.features && options.features.QuerySelector) {
- require(__dirname + "/../selectors/index").applyQuerySelectorPrototype(browser);
- }
- options.document = (browser.HTMLDocument) ?
- new browser.HTMLDocument(options) :
- new browser.Document(options);
- options.document.write('<html><head></head><body></body></html>');
- }
- var doc = window.document = options.document;
- if (doc.addEventListener) {
- if (doc.readyState == 'complete') {
- var ev = doc.createEvent('HTMLEvents');
- ev.initEvent('load', false, false);
- window.dispatchEvent(ev);
- }
- else {
- doc.addEventListener('load', function(ev) {
- window.dispatchEvent(ev);
- });
- }
- }
- return window;
- };
- /**
- * Creates a document-less window.
- */
- exports.createWindow = function(dom, options) {
- var timers = [];
- function startTimer(startFn, stopFn, callback, ms) {
- var res = startFn(callback, ms);
- timers.push( [ res, stopFn ] );
- return res;
- }
- function stopTimer(id) {
- if (typeof id === 'undefined') {
- return;
- }
- for (var i in timers) {
- if (timers[i][0] === id) {
- timers[i][1].call(this, id);
- timers.splice(i, 1);
- break;
- }
- }
- }
- function stopAllTimers() {
- timers.forEach(function (t) {
- t[1].call(this, t[0]);
- });
- timers = [];
- }
- function DOMWindow(options) {
- this.frames = [this];
- this.contentWindow = this;
- this.window = this;
- this.self = this;
- var href = (options || {}).url || 'file://' + __filename;
- this.location = URL.parse(href);
- this.location.reload = NOT_IMPLEMENTED(this);
- this.location.replace = NOT_IMPLEMENTED(this);
- this.location.toString = function() {
- return href;
- };
- var window = this.console._window = this;
- if (options && options.document) {
- options.document.location = this.location;
- }
- this.addEventListener = function() {
- dom.Node.prototype.addEventListener.apply(window, arguments);
- };
- this.removeEventListener = function() {
- dom.Node.prototype.removeEventListener.apply(window, arguments);
- };
- this.dispatchEvent = function() {
- dom.Node.prototype.dispatchEvent.apply(window, arguments);
- };
- this.trigger = function(){
- dom.Node.prototype.trigger.apply(window.document, arguments);
- };
- this.setTimeout = function (fn, ms) { return startTimer(setTimeout, clearTimeout, fn, ms); };
- this.setInterval = function (fn, ms) { return startTimer(setInterval, clearInterval, fn, ms); };
- this.clearInterval = stopTimer;
- this.clearTimeout = stopTimer;
- this.__stopAllTimers = stopAllTimers;
- }
- DOMWindow.prototype = {
- __proto__: dom,
- get document() {
- return this._document;
- },
- set document(value) {
- this._document = value;
- if (value) {
- value.parentWindow = this;
- }
- },
- close : function() {
- if (this._document) {
- if (this._document.body) {
- this._document.body.innerHTML = "";
- }
- if (this._document.close) {
- this._document.close();
- }
- delete this._document;
- }
- stopAllTimers();
- if (this.__scriptContext) {
- delete this.__scriptContext;
- }
- },
- getComputedStyle: function(node) {
- var s = node.style,
- cs = {};
- for (var n in s) {
- cs[n] = s[n];
- }
- cs.__proto__ = {
- getPropertyValue: function(name) {
- return node.style[name];
- }
- };
- return cs;
- },
- console: {
- log: function(message) { this._window.trigger('log', message) },
- info: function(message) { this._window.trigger('info', message) },
- warn: function(message) { this._window.trigger('warn', message) },
- error: function(message) { this._window.trigger('error', message) }
- },
- navigator: {
- userAgent: 'Node.js (' + process.platform + '; U; rv:' + process.version + ')',
- appName: 'Node.js jsDom',
- platform: process.platform,
- appVersion: process.version
- },
- XMLHttpRequest: function XMLHttpRequest() {},
- name: 'nodejs',
- innerWidth: 1024,
- innerHeight: 768,
- length: 1,
- outerWidth: 1024,
- outerHeight: 768,
- pageXOffset: 0,
- pageYOffset: 0,
- screenX: 0,
- screenY: 0,
- screenLeft: 0,
- screenTop: 0,
- scrollX: 0,
- scrollY: 0,
- scrollTop: 0,
- scrollLeft: 0,
- alert: NOT_IMPLEMENTED(),
- blur: NOT_IMPLEMENTED(),
- confirm: NOT_IMPLEMENTED(),
- createPopup: NOT_IMPLEMENTED(),
- focus: NOT_IMPLEMENTED(),
- moveBy: NOT_IMPLEMENTED(),
- moveTo: NOT_IMPLEMENTED(),
- open: NOT_IMPLEMENTED(),
- print: NOT_IMPLEMENTED(),
- prompt: NOT_IMPLEMENTED(),
- resizeBy: NOT_IMPLEMENTED(),
- resizeTo: NOT_IMPLEMENTED(),
- scroll: NOT_IMPLEMENTED(),
- scrollBy: NOT_IMPLEMENTED(),
- scrollTo: NOT_IMPLEMENTED(),
- screen : {
- width : 0,
- height : 0
- },
- Image : NOT_IMPLEMENTED()
- };
- var window = new DOMWindow(options);
- // This is the last chance we have to properly update the window
- // so turn it into a context now.
- window = vm.createContext(window);
- return window;
- };
- //Caching for HTMLParser require. HUGE performace boost.
- /**
- * 5000 iterations
- * Without cache: ~1800+ms
- * With cache: ~80ms
- */
- var defaultParser = null;
- function getDefaultParser() {
- if (defaultParser === null) {
- try {
- defaultParser = require('htmlparser');
- }
- catch (e) {
- try {
- defaultParser = require('node-htmlparser/lib/node-htmlparser');
- }
- catch (e2) {
- defaultParser = undefined;
- }
- }
- }
- return defaultParser;
- }
- /**
- * Augments the given DOM by adding browser-specific properties and methods (BOM).
- * Returns the augmented DOM.
- */
- var browserAugmentation = exports.browserAugmentation = function(dom, options) {
- if (dom._augmented) {
- return dom;
- }
- if(!options) {
- options = {};
- }
- // set up html parser - use a provided one or try and load from library
- var htmltodom = new HtmlToDom(options.parser || getDefaultParser());
- if (!dom.HTMLDocument) {
- dom.HTMLDocument = dom.Document;
- }
- if (!dom.HTMLDocument.prototype.write) {
- dom.HTMLDocument.prototype.write = function(html) {
- this.innerHTML = html;
- };
- }
- dom.Element.prototype.getElementsByClassName = function(className) {
- function filterByClassName(child) {
- if (!child) {
- return false;
- }
- if (child.nodeType &&
- child.nodeType === dom.Node.ENTITY_REFERENCE_NODE)
- {
- child = child._entity;
- }
- var classString = child.className;
- if (classString) {
- var s = classString.split(" ");
- for (var i=0; i<s.length; i++) {
- if (s[i] === className) {
- return true;
- }
- }
- }
- return false;
- }
- return new dom.NodeList(this.ownerDocument || this, dom.mapper(this, filterByClassName));
- };
- dom.Element.prototype.__defineGetter__('sourceIndex', function() {
- /*
- * According to QuirksMode:
- * Get the sourceIndex of element x. This is also the index number for
- * the element in the document.getElementsByTagName('*') array.
- * http://www.quirksmode.org/dom/w3c_core.html#t77
- */
- var items = this.ownerDocument.getElementsByTagName('*'),
- len = items.length;
- for (var i = 0; i < len; i++) {
- if (items[i] === this) {
- return i;
- }
- }
- });
- dom.Document.prototype.__defineGetter__('outerHTML', function() {
- return domToHtml(this);
- });
- dom.Element.prototype.__defineGetter__('outerHTML', function() {
- return domToHtml(this);
- });
- dom.Element.prototype.__defineGetter__('innerHTML', function() {
- return domToHtml(this._childNodes, true);
- });
- dom.Element.prototype.__defineSetter__('doctype', function() {
- throw new core.DOMException(NO_MODIFICATION_ALLOWED_ERR);
- });
- dom.Element.prototype.__defineGetter__('doctype', function() {
- var r = null;
- if (this.nodeName == '#document') {
- if (this._doctype) {
- r = this._doctype;
- }
- }
- return r;
- });
- dom.Element.prototype.__defineSetter__('innerHTML', function(html) {
- //Check for lib first
- if (html === null) {
- return null;
- }
- //Clear the children first:
- var child;
- while ((child = this._childNodes[0])) {
- this.removeChild(child);
- }
- if (this.nodeName === '#document') {
- parseDocType(this, html);
- }
- var nodes = htmltodom.appendHtmlToElement(html, this);
- return html;
- });
- dom.Document.prototype.__defineGetter__('innerHTML', function() {
- return domToHtml(this._childNodes, true);
- });
- dom.Document.prototype.__defineSetter__('innerHTML', function(html) {
- //Check for lib first
- if (html === null) {
- return null;
- }
- //Clear the children first:
- var child;
- while ((child = this._childNodes[0])) {
- this.removeChild(child);
- }
- if (this.nodeName === '#document') {
- parseDocType(this, html);
- }
- var nodes = htmltodom.appendHtmlToElement(html, this);
- return html;
- });
- var DOC_HTML5 = /<!doctype html>/i,
- DOC_TYPE = /<!DOCTYPE (\w(.|\n)*)">/i;
- DOC_TYPE_START = '<!DOCTYPE ',
- DOC_TYPE_END = '">';
- function parseDocType(doc, html) {
- var publicID = '',
- systemID = '',
- fullDT = '',
- name = 'HTML',
- set = true,
- doctype = html.match(DOC_HTML5);
- //Default, No doctype === null
- doc._doctype = null;
- if (doctype && doctype[0]) { //Handle the HTML shorty doctype
- fullDT = doctype[0];
- } else { //Parse the doctype
- // find the start
- var start = html.indexOf(DOC_TYPE_START),
- end = html.indexOf(DOC_TYPE_END),
- docString;
- if (start < 0 || end < 0) {
- return;
- }
- docString = html.substr(start, (end-start)+DOC_TYPE_END.length);
- doctype = docString.replace(/[\n\r]/g,'').match(DOC_TYPE);
- if (!doctype) {
- return;
- }
- fullDT = doctype[0];
- doctype = doctype[1].split(' "');
- var _id1 = doctype.pop().replace(/"/g, ''),
- _id2 = doctype.pop().replace(/"/g, '');
- if (_id1.indexOf('-//') !== -1) {
- publicID = _id1;
- }
- if (_id2.indexOf('-//') !== -1) {
- publicID = _id2;
- }
- if (_id1.indexOf('://') !== -1) {
- systemID = _id1;
- }
- if (_id2.indexOf('://') !== -1) {
- systemID = _id2;
- }
- if (doctype.length) {
- doctype = doctype[0].split(' ');
- name = doctype[0].toUpperCase();
- }
- }
- doc._doctype = new dom.DOMImplementation().createDocumentType(name, publicID, systemID);
- doc._doctype._ownerDocument = doc;
- doc._doctype._fullDT = fullDT;
- doc._doctype.toString = function() {
- return this._fullDT;
- };
- }
- dom.Document.prototype.getElementsByClassName = function(className) {
- function filterByClassName(child) {
- if (!child) {
- return false;
- }
- if (child.nodeType &&
- child.nodeType === dom.Node.ENTITY_REFERENCE_NODE)
- {
- child = child._entity;
- }
- var classString = child.className;
- if (classString) {
- var s = classString.split(" ");
- for (var i=0; i<s.length; i++) {
- if (s[i] === className) {
- return true;
- }
- }
- }
- return false;
- }
- return new dom.NodeList(this.ownerDocument || this, dom.mapper(this, filterByClassName));
- };
- dom.Element.prototype.__defineGetter__('nodeName', function(val) {
- return this._nodeName.toUpperCase();
- });
- dom.Element.prototype.__defineGetter__('tagName', function(val) {
- var t = this._tagName.toUpperCase();
- //Document should not return a tagName
- if (this.nodeName === '#document') {
- t = null;
- }
- return t;
- });
- dom.Element.prototype.scrollTop = 0;
- dom.Element.prototype.scrollLeft = 0;
- dom.Document.prototype.__defineGetter__('parentWindow', function() {
- if (!this._parentWindow) {
- this._parentWindow = exports.windowAugmentation(dom, {document: this, url: this.URL});
- }
- return this._parentWindow;
- });
- dom.Document.prototype.__defineSetter__('parentWindow', function(window) {
- this._parentWindow = window;
- });
- dom.Document.prototype.__defineGetter__('defaultView', function() {
- return this.parentWindow;
- });
- dom._augmented = true;
- return dom;
- };
|