UNPKG

node-dom

Version:

Javascript fast W3C DOM generation.

1,711 lines (1,541 loc) 64.5 kB
/* Copyright (c) 2011 jCore - Aymeric Vitte - MIT license Some parts inspired from tmpvar/jsdom (MIT license) */ var domToHtml = require('./domtohtml.js').domToHtml, HTMLDecode = require('./htmlencoding').HTMLDecode, URL = require('url'), im = require('imagemagick'), http = require('http'), https = require('https'), default_style = require('./style.js').default_style; var nbloop=0; Function.prototype.De=function(obj,_name,_node) { var f=this; if (typeof(_name)=="undefined") { return function() { return f.apply(obj,arguments); } } else { //wrap example return function() { if (_name=='example') { var args = Array.prototype.slice.call(arguments); args.unshift(_node); return f.apply(obj,args); } else { return f.apply(obj,arguments); } } } }; //Some sites do forget to encode the url //Ex: http://www.carrefour.fr //TODO: nodejs modification request //Probably to remove var fixURI=function(url) { //return url; var re=/%253B|%252C|%252F%253F|%253A|%2540|%2526|%253D|%252B|%2524|%2523/g; if (url) { url=encodeURI(decodeURI(url)); /*Example : http://www.google.com/jsapi?autoload=%7B%22modules%22%3A%5B%7B%22name%22%3A%22search%22%2C%22version%22%3A%221%22%2C%22callback%22%3A%22getTradTags%22%2C%22language%22%3A%22"+mylang+"%22%2C%22nocss%22%3A%22true%22%7D%5D%7D decodeURIComponent --> http://www.google.com/jsapi?autoload={"modules":[{"name":"search","version":"1","callback":"getTradTags","language":""+mylang+"","nocss":"true"}]} decodeURI --> http://www.google.com/jsapi?autoload={"modules"%3A[{"name"%3A"search"%2C"version"%3A"1"%2C"callback"%3A"getTradTags"%2C"language"%3A""+mylang+""%2C"nocss"%3A"true"}]} ==> does not decode what would become a reserved/score character ==> but the reverse does not apply encodeURI will encode % ==> http://www.google.com/jsapi?autoload=%22modules%22%3A%22%3Fsearch%22 ==> decodeURIComponent --> http://www.google.com/jsapi?autoload="modules":"?search" ==> encodeURI --> http://www.google.com/jsapi?autoload=%2522modules%2522%253A%2522%253Fsearch%252 --> NOK ==> use encodeURI(decodeURI and replace %253A by %3A, etc reserved -> ; , / ? : @ & = + $ unescape : alphabetic, decimal digits, - _ . ! ~ * ' ( ) score -> # */ url=url.replace(re,function(val) {return val.replace(/25/,'')}); } return url; }; var pixel=function(size,SIZE) { //simplification var re=/%|em/; if (!re.test(size)) { return parseInt(size); } else { return parseInt(size)*SIZE/100; } }; var uncanon = function(str, letter) { return '-' + letter.toLowerCase(); }; //Taken from jQuery camelCase var canon=function(attr){ //border-color --> borderColor return attr.replace(/-\D/g,function(match){return match.charAt(1).toUpperCase();}); }; var __cc=function(node,regexp) { var str = node.__data.toString().replace(/^\s+/g,'').replace(/\s+$/g,'') ; if (regexp.test(str)){ return true; }; return false; }; var NOT_IMPLEMENTED=function(val) {return function() {return val}}; var NOSTYLE={script:'',style:'',head:'',meta:'',title:'',link:'',base:'',noscript:''}; var NOCONT={script:'',style:'',br:'',head:'',meta:'',title:'',html:'',link:'',base:'',noscript:'',object:'',embed:'',frame:'',iframe:'',applet:'',select:'',textarea:'',button:'',option:''}; var core={}; core.prototype={}; core.findParent = function(element, parent) { while (element._parentNode) { if (parent==element._parentNode) { return true; } element=element._parentNode; } return false; }; core.HtmlToDom = function(page,elt) { var doc=elt._document; //if (doc._ew!=='') { // return doc._parser.ParseHtml(page,doc._ew, doc._ewList); //} else { return doc._parser.ParseHtml(page); //}; }; core.HtmlToStyle = function(data) {//data= border-width:3px !important var priority=data.split('!'); // !important var dat=priority[0].toLowerCase().trim(); if (priority.length>1) { priority=true; } else { priority=false; }; dat=dat.split(':'); var attr=dat[0]; var attr2=canon(attr); dat.shift(); var val=dat.join(':').toLowerCase().trim(); return {attr:attr,attr2:attr2,priority:priority,val:val}; //{attr:'border-width',attr2:'borderWidth',priority:true,val:3px} }; core.loadedImages=function() { this.length=0; }; core.loadedImages.prototype={ push: function(val) { var i=this.loaded(val[0]); if (!i.val[0]) { this[this.length]=val; this.length++; } else { if (val[1]!=0) { this[i.index][1]=val[1]; this[i.index][2]=val[2]; }; }; }, loaded: function(xvalue) { var l=this.length; for (var i=0; i < l; i++) { if (this[i][0] === xvalue){ return {val:this[i],index:i}; } } return {val:[false,0,0],index:0}; } }; core.onload=function(fn) { //handle onload event //try {console.log(this.__name+' '+fn.toString());} catch(ee) {this.__name+' '+console.log(fn);}; if (fn) { if (typeof(fn)=='string') { var code=fn; //if we do fn=new Function(code) then it will execute in the global context, not window var win=this.__name=='window'?this:this._document._parentNode; win._onl++; win['__elem'+win._onl]=this; //define __elem+number global var in window context this._code=code; this.___onload=code;//used for outer/innerhtml core.languageProcessors['javascript'](this,'__elem'+win._onl+'.onload=new Function(__elem'+win._onl+'._code)',''); return; //this will then refer to element }; //fn is defined in the window context on script evaluation //fn does execute in the window context and this refers to the element if ((this.___data!='nothing')&&(this._document.readyState=='complete')) { //if already loaded and document complete fn.apply(this,arguments); } else { if (this._document.readyState=='complete') { //fn.apply(this,arguments); too early, wait for resources loading if ((this.__name=='script')||(this.__name=='img')||(this.__name=='link')||(this._isimage)) { if (this.__name in this._document._features['FetchExternalResources']) { //load resources var self=this; this.__onload=function() {return fn.apply(self,arguments);}; } } else { //nothing to load - execute onload fn.apply(this,arguments); } } else { var self=this; this._document._onload.push({fn:function() {return fn.apply(self,arguments);},element:self}); } } } }; core.languageProcessors = { javascript : require("../language/javascript").javascript }; core.resourceLoader = { base: function(document) { var base = document._base; var baseURI = document.URL; if (base.length > 0) { //baseURI = base.item(0).__href; baseURI=base; }; return baseURI; }, load: function(element) { var src=''; switch(element.__name) { case 'script': src=element.src?this.resolve(element._document,element.src):'';break; case 'link': src=element.href?this.resolve(element._document,element.href):'';break; case 'img': src=element.src?this.resolve(element._document,element.src):'';break; case 'input'&&(element.type=='image'): src=element.src?this.resolve(element._document,element.src):'';break; case 'style': src='';break; default : return; }; if (src) { element._href=src; src = URL.parse(src); element.___data='nothing';//empty data element.____data='nothing'; this.download(src,element); } else { if ((element.__name=='script'||element.__name=='style')) { element._href='inline'; element.___data='inline';//fill data later element.____data='inline'; this.enqueue(element); } } }, resolve: function(document, path) { return path ? URL.resolve(this.base(document), fixURI(path)):''; //some sites do forget to encode URI }, download: function(url,element) { var doc = element._document; var name = element.__name; var self=this; //console.log('download '+name+' '+url.href); if ((name=='script')||(name=='link')) { var path = url.pathname + (url.search || ''), //options = {'method': 'GET', 'host': url.hostname, 'path': url.pathname, 'headers': { // 'Connection':'keep-alive' //}}, options = {'method': 'GET', 'host': url.hostname, 'path': path}, request; this.enqueue(element); if (url.protocol == 'https:') { options.port = url.port || 443; request = https.request(options); } else { options.port = url.port || 80; request = http.request(options); }; request.on('response', function (response) { //console.log('response'); response.setEncoding('utf8'); var data = ''; response.on('data', function (chunk) { data += chunk.toString(); }); response.on('end', function() { //console.log(response.statusCode+' '+url.href); if ([301, 302, 303, 307].indexOf(response.statusCode) > -1) { var redirect = URL.resolve(url, response.headers["location"]) core.resourceLoader.download(URL.parse(redirect), element); } else if ((response.statusCode==400)||(response.statusCode==408)) { element.___data='error'; //fill data if (doc.readyState=='complete') {//Execute right away once queue empty self.execute(element); } } else { element.___data=data; //fill data if (doc.readyState=='complete') {//Execute right away once queue empty self.execute(element); } } }); }); request.on('error', function(err) {element.___data='error';}); request.end(); }; if ((name=='img')||((name=='input')&&(element.type=='image'))||(element._isimage)) { element._isimage=true; var win=element._document._parentNode; if ((element.offsetWidth!=win.screen.width)||(element.offsetHeight!=win.screen.height)) { //Size known do not load element.___data='data'; //to fire onload if defined later if ((element._document.readyState=='complete')&&(element.__onload)) { //fire onload element.__onload(); }; return; }; //this.enqueue(element); //do not queue images //console.log(url.href); var tmp=doc._downloadedImages.loaded(url.href); //console.log(doc._downloadedImages); //console.log(tmp.val[1]); if (!tmp.val[1]) { //If not already in downloadedImages or in downloadedImages but size not known if (!tmp.val[0]) {//if url not in loadedimages load it doc._downloadedImages.push([url.href,0,0,[]]); im.identify(['-format', '%wx%h', url.href], function(err, output){ if (!err) { var res=output.replace('\n',''); doc._downloadedImages.push([url.href,res.split('x')[0],res.split('x')[1]]); element.___data=res; //if (doc.readyState=='complete') {//Execute right away once queue empty self.execute(element); //} } }); } else { //queue img //console.log('queue '+tmp.index); doc._downloadedImages[tmp.index][3].unshift(element); }; } else { //Size known element.___data=tmp.val[1]+'x'+tmp.val[2]; //if (doc.readyState=='complete') {//Execute right away once queue empty self.execute(element); //} } } }, enqueue: function(element) { var doc = element._document; if (((doc._queue.length==0)&&(element.___data=='end'))||(doc.readyState=='complete')) { if (element.___data!='nothing') { if (element.__name!='script') { //do not execute inline scripts after readyState complete //example : innerHTML this.action(element); } } else { //readyState complete, do not queue //var self=this; //var wait=function() {self.action(element);}; //setTimeout(wait,100); //not nice, to see later } } else { //readyState not complete, queue and serialize resources doc._queue.push(element); } }, action: function(element) { var name=element.__name; //console.log('action '+name+' '+element._href+' '+element.___data.substr(0,100)); //console.log('action '+name+' '+element._href); if (name=='document') { element.readyState = 'complete'; console.log('Document complete'); var onload=element._onload; for (var i=0;i<onload.length;i++) { //onload can be queued before readyState complete while element is not queued yet //do not fire onload if resources not executed if (onload[i].element.___data!='nothing') { try {onload[i].fn();} catch(ee) {console.log('onload error');};//if resource loaded fire onload } else { onload[i].element.__onload=onload[i].fn;//if not wait for resource loaded } }; element._onload=[]; //Not used //Fire timers var tt=element._parentNode._ttimers; var l=tt.length; //console.log('timer length '+l); for (var i=0;i<l;i++) {; setTimeout(tt[i][0],tt[i][1]); }; var tt=element._parentNode._itimers; var l=tt.length; for (var i=0;i<l;i++) { //setInterval(tt[i][0],tt[i][1]); }; }; if (name=='script') { this.write(element); if (element.___data=='inline') { element.___data=this.code(element); }; //console.log('action2 '+name+' '+element._href+' '+element.outerHTML.substr(0,500)); core.languageProcessors['javascript'](element,element.___data,''); //fire onload if readystate complete if ((element._document.readyState=='complete')&&(element.__onload)) { element.__onload(); }; }; if (name=='link') { //TODO Stylesheets }; if ((name=='img')||((name=='input')&&(element.type=='image'))||(element._isimage)) { var doc=element._document, h=core.resourceLoader.resolve(element._document,element.src), size=element.___data.split('x'), win=doc._parentNode, width=win.screen.width,height=win.screen.height, tmp=doc._downloadedImages.loaded(h), queue=doc._downloadedImages[tmp.index][3]; queue.unshift(element); var l=queue.length; while (queue.length) { //resume queue for same image var elem=queue[0]; if (elem.style.getPropertyValue('width')==width) { elem.style['width']=size[0]+'px'; }; if (elem.style.getPropertyValue('height')==height) { elem.style['height']=size[1]+'px'; }; if ((elem._document.readyState=='complete')&&(elem.__onload)) { try {elem.__onload()} catch(ee) {console.log('onload error')}; }; queue.shift(); } }; //console.log('end action'); }, execute: function(element) { //console.log('execute'); if (((!(Object.getOwnPropertyDescriptor(element,'___data').set))||(element._pass))||(element._isimage)) { //if not in queue or image var doc=element._document; var d=doc._queue; if (!element._isimage) { for (var n in d) { if (d[n]==element) {return;} //rare but can happen do not execute twice }; } if ((doc._queue.length!=0)&&(!element._isimage)) { var self=this; var wait=function() {self.execute(element);}; setTimeout(wait,100); //wait for queue empty - TODO : do this in a nicer way but should be rarely called } else { if ((element.__name in element._document._features['ProcessExternalResources'])||(element.___data=='end')||(('img' in element._document._features['ProcessExternalResources'])&&(element._isimage))) { this.action(element);//execute action }; } } }, resume : function(doc) { //console.log('resume'); var d=doc._queue; if (d.length>0) { var el=d[0]; if (el.___data=='error') { //console.log('error'); d.shift(); this.resume(doc); } else if (el.___data=='nothing') { var self=this; //console.log("noth "+el._href); this.wait(el); var pass=function() { el._pass=true; d.shift(); self.resume(doc); }; el._tt=setTimeout(pass,1000); //if http request takes more than 1s, do not wait, continue //and execute after complete when data available //if not it blocks the queue //TODO : see how to put a timeout on http request } else { if ((el.__name in el._document._features['ProcessExternalResources'])||(el.___data=='end')||(('img' in el._document._features['ProcessExternalResources'])&&(el._isimage))) { this.action(el);//execute action }; //console.log("shift"+d.length); d.shift(); //clear executed element this.resume(doc);//execute next } } else { //console.log("Retry scripts "+doc._queue_err.length); /*When queue empty, retry to execute failed scripts Example : script insertion with document.write immediately followed by code related to the script which is not yet loaded, so script execution fails */ var self=this; var f=function() { var d=doc._queue_err; var l=d.length; for (var i=0;i<l;i++) { //console.log('retry '+d[i]._href+' '+(d[i].___data||'').substr(0,100)); self.action(d[i]); }; } setTimeout(f,5000); //Not nice - TODO : ameliorate } }, wait : function(el) { var self=this; var wait=function(val) { if (el._tt) { clearTimeout(el._tt); }; this.____data=val; //console.log('wait '+el.__name+' '+el._href); if (!this._pass) { if (el._isimage) { //if image, execute self.execute(el); } else { self.resume(el._document); } } else { //when passed and data becomes available self.execute(el); } }; var fn=function() {return this.____data;}; Object.defineProperty(el,'___data', {set:wait,get:fn}); //wait for data availability }, write : function(element) { //overwrite document.write //before complete execute write in a dummy div attached to the lastChild //approximation but covers most of cases element._document.write=function(text) { if (this.readyState === "loading") { var dummydiv=this.createElement('div'); element.parentNode.appendChild(dummydiv); dummydiv.innerHTML=text; } else { this.innerHTML=text; }; }; }, code : function(element) { var code=[]; var children=element.__children; if (children) { var l=children.length; for (var i=0;i<l;i++) { code.push(children[i].data); }; return code.join(''); //fill data for inline scripts }; return ''; } }; core.NodeList=function() { //NodeList is an array - make item not enumerable var d=Object.getPrototypeOf(this); Object.defineProperty(d,'item', {value : function(i) {return this[i];},writable : true,enumerable : false,configurable : true}); }; core.NodeList.prototype=Array.prototype; core.Attr=function() { //dummy Attr interface this.name=''; this.value=''; }; core.EventTarget=function() {}; //dummy eventtarget interface core.EventTarget.prototype={ addEventListener : function() {}, removeEventListener : function() {}, dispatchEvent : function() {} }; core.Event=function() {}; //dummy event interface core.Event.prototype={ CAPTURING_PHASE : 1, AT_TARGET : 2, BUBBLING_PHASE : 3, get type() {return 'HTMLEvents'}, get target() {return new core.EventTarget()}, get currentTarget() {return new core.EventTarget()}, get eventPhase() {return this.CAPTURING_PHASE}, get bubbles() {return true}, get cancelable() {return false;}, get timeStamp() {return 0}, stopPropagation : function() {}, preventDefault : function() {}, initEvent : function() {}, stopImmediatePropagation : function() {}, get defaultPrevented() {return false}, get isTrusted() {return true} }; core.Node={}; core.Node.prototype={ ELEMENT_NODE : 1, ATTRIBUTE_NODE : 2, //not used TEXT_NODE : 3, CDATA_SECTION_NODE : 4, //not used ENTITY_REFERENCE_NODE : 5, //not used ENTITY_NODE : 6, //not used PROCESSING_INSTRUCTION_NODE : 7 , //not used COMMENT_NODE : 8, DOCUMENT_NODE : 9, DOCUMENT_TYPE_NODE : 10, //not used DOCUMENT_FRAGMENT_NODE : 11, NOTATION_NODE : 12, //not used get document() {return this._document}, get nodeValue() {return HTMLDecode(this.__data)}, get parentNode() {return this._parentNode}, get parentElement() {return this._parentNode}, get nodeName() {return this.__name.indexOf('#')==-1?this.__name.toUpperCase():this.__name}, get firstChild() { var l=this.__children.length; return l>0?this.__children[0]:null; }, get lastChild() { var l=this.__children.length; return l>0?this.__children[l-1]:null; }, get childNodes () {return this.__children}, get _childNodes() {return this.__children}, get nextSibling() { var children=this._parentNode.__children; var l=children.length; for (var i=0;i<l;i++) { if (children[i]==this) { return children[i+1] || null; } }; return null; }, get previousSibling() { var children=this._parentNode.__children; var l=children.length; for (var i=0;i<l;i++) { if (children[i]==this) { return children[i-1] || null; } }; return null; }, attributes:NOT_IMPLEMENTED(new core.Attr()), get ownerDocument() { if (this.nodeName=='#document') {return null;}; return this._document; }, insertBefore : function(node,before) { var children=this.__children; if (before!=null) { var ind=children.indexOf(before); if (ind>=0) { children.splice(ind,0,node); } } else { children.push(node); }; node._parentNode=this; if (node.__name in node._document._features['FetchExternalResources']) { //load resources core.resourceLoader.load(node); }; if (this.__dom) { //if parent in Dom Tree if (node.__type=='tag') { //if tag, not document/text/document-fragment this.addToDom(node); } }; if ((node.__type=='text')&&(this._document.readyState!='complete')) { if ((!(this.__name in NOCONT))&&(node._document._regexp)) { if (__cc(node,node._document._regexp)) { this._document._parentNode._ewList.push(node); }; }; }; return node; }, replaceChild : function(node,before) { this.insertBefore(node,before); this.removeChild(before); return node; }, removeChild : function(node) { var children=this.__children; var ind=children.indexOf(node); if (ind>=0) { children.splice(ind,1); }; //if ((node.__type=='tag')&&(node.__name!='body')) { //delete from tag list // try { // delete this._document['_'+node.__name][node._elid]; // } catch(ee) {} //}; //if ((node.__type=='tag')&&(node.__name!='body')) { //delete from name list // try { // delete this._document._names['__'+node.__name][node._elna]; // } catch(ee) {} //}; //if (node.id) { // try { // delete this._document._ids[node.id]; //delete from id list // } catch(ee) {}; //}; if (node.__type=='tag') { this.removeFromDom(node); //mark node and children not in dom tree }; node._parentNode=null; return node; }, appendChild : function(node) { if (node._parentNode!=null) { node._parentNode.removeChild(node); }; if (node.__type=='document-fragment') { var children=node.__children; var l=children.length; for (var i=0;i<l;i++) { this.insertBefore(children[i],null) } return node; } else { return this.insertBefore(node,null); } }, hasChildNodes : function() { if (this._children.length>0) { return true; } else { } return false }, cloneNode : function(bool) { if (this.__type=='document-fragment') { var fragment=this._document.createDocumentFragment(); if (bool) { var children=this.__children; var l=children.length; for (var i=0;i<l;i++) { var tmp=this._document.createElement('div'); //dummy div fragment.appendChild(tmp); tmp.outerHTML=children[i].outerHTML; }; } return fragment; } else { if (!bool) { //console.log('cloneNode false'); var clone=this._document.createElement(this.__name); var prop=Object.keys(this); var l=prop.length; for (var i=0;i<l;i++) { var p=prop[i]; clone.setAttribute(p,this[p]); }; if (this.style) { prop=Object.keys(this.style); l=prop.length; for (var i=0;i<l;i++) { p=prop[i]; clone.style[p]=this.style[p]; } } return clone; } else { //TODO : optimization //console.log('cloneNode true'); var div=this._document.createElement('div'); //dummy div div.innerHTML=this.outerHTML; div=div.firstChild; return div; //var clone=this.cloneNode(false); //var recurse=function(node,parent) { // var children=node.__children; // var l=children.length; // for (var i=0;i<l;i++) { // var child=children[i]; // var clone_=child.cloneNode(false); // if (parent) { // parent.appendChild(clone_); // }; // recurse(child,clone_) // } //}; //recurse(this,clone); //return clone; } } }, cloneStyle : function(style) { //not w3c var prop=Object.keys(style); var l=prop.length; for (var i=0;i<l;i++) { var st=prop[i]; this.style[st]=style[st]; } }, //normalize : NOT_IMPLEMENTED(), //isSupported : NOT_IMPLEMENTED(true), //namespaceURI : '', //prefix : '', //localName: '', //hasAttributes : function() { // return this.attributes; //}, get textContent() { if (this.nodeType === this.TEXT_NODE || this.nodeType === this.COMMENT_NODE || this.nodeType === this.ATTRIBUTE_NODE || this.nodeType === this.CDATA_SECTION_NODE) { return this.nodeValue; } else if (this.nodeType === this.ELEMENT_NODE || this.nodeType === this.DOCUMENT_FRAGMENT_NODE) { var out = ''; for (var i = 0 ; i < this.childNodes.length ; i += 1) { out += this.childNodes[i].textContent || ''; } return out; } else { return null; }; }, get nodeType() { switch(this.__type) { case "text": return this.TEXT_NODE; //case "directive": return 0; //case "comment": return this.COMMENT_NODE; //case "script": return this.ELEMENT_NODE; //case "style": return this.ELEMENT_NODE; case "tag": return this.ELEMENT_NODE; case "document": return this.DOCUMENT_NODE; case "document-fragment": return this.DOCUMENT_FRAGMENT_NODE; }; return 0; }, get baseURI() { return core.resourceLoader.base(this._document); }, DOCUMENT_POSITION_DISCONNECTED : 0x01, DOCUMENT_POSITION_PRECEDING : 0x02, DOCUMENT_POSITION_FOLLOWING : 0x04, DOCUMENT_POSITION_CONTAINS : 0x08, DOCUMENT_POSITION_CONTAINED_BY : 0x10, DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC : 0x20, compareDocumentPosition: function(node) { //TODO ?? return this.DOCUMENT_POSITION_DISCONNECTED; }, contains: NOT_IMPLEMENTED(false), isSameNode: function(node) { return this===node?true:false; }, lookupPrefix:NOT_IMPLEMENTED(''), //isDefaultNamespace:NOT_IMPLEMENTED(true), lookupNamespaceURI:NOT_IMPLEMENTED(''), isEqualNode:NOT_IMPLEMENTED(false), //getFeature:NOT_IMPLEMENTED({}), //setUserData:NOT_IMPLEMENTED(''), //getUserData:NOT_IMPLEMENTED(null), isDefaultNamespace: NOT_IMPLEMENTED(false), set onload(fn) { core.onload.call(this,fn); }, addEventListener: function(type, listener, useCapture) { this['on'+type.toLowerCase()]=listener; //simplification }, removeEventListener: function(type, listener, useCapture) { delete this['on'+type.toLowerCase()]; //simplification }, dispatchEvent : NOT_IMPLEMENTED(false), _Enum : function(prop,bool,def) { Object.defineProperty(this,prop,{value:this[prop]||(typeof(def)=="undefined"?'':def),enumerable:bool,configurable:true,writable:true}); }, addToDom : function(node) { node.__dom=true; if (node._id) { node.id=node._id; //set id in document._ids list for getElementById when adding the node to DOM tree }; this.updateNode(node); //update Nodelists var children=node.__children; var l=children.length; for (var i=0;i<l;i++) { this.addToDom(children[i]); } }, removeFromDom : function(node) { node.__dom=false; this.updateNode(node); //update Nodelists var children=node.__children; var l=children.length; for (var i=0;i<l;i++) { this.removeFromDom(children[i]); } }, updateNode : function(node) { if ('_'+node.__name in this._document._Tnodelists) { this._document.updateNL(this._document['_'+node.__name],'_'+node.__name,this._document._Tnodelists); }; if (node._name) { if (node._name in this._document._Nnodelists) { this._document.updateNL(this._document._names[node._name],node._name,this._document._Nnodelists); } }; if (node._class) { var classes=node._class.split(' '); var l=classes.length; for (var i=0;i<l;i++) { if (classes[i]) { if (classes[i] in this._document._Cnodelists) { this._document.updateNL(this._document._class[classes[i]],classes[i],this._document._Cnodelists); } } } } } }; core.Text=function(doc) { this.__name='#text'; this.__children=[]; this._document=doc; this.__dom=false; //node not in dom tree at creation }; core.Text.prototype={ __proto__ : core.Node.prototype, get data() {return this.__data;}, get length() {return this.__data.length;}, appendData : NOT_IMPLEMENTED(null), insertData : NOT_IMPLEMENTED(null), deleteData : NOT_IMPLEMENTED(null), replaceData : NOT_IMPLEMENTED(null), substringData : NOT_IMPLEMENTED('') }; core.DocumentFragment=function(doc) { this.__name='#document-fragment'; this.__children=[]; this._document=doc; this.__dom=false; }; core.DocumentFragment.prototype.__proto__ = core.Node.prototype; core.BarProp = function() { this.visible=true; }; core.Element={}; core.Element.prototype = { //not w3c //Handle tag element, text and document fragment __proto__ : core.Node.prototype, get tagName() { switch(this.__type) { case 'tag' : return this.__name.toUpperCase(); //case 'text' : return 'TEXT'; //not w3c //case 'document-fragment' : return 'FRAGMENT'; //not w3c //case 'comment' : return ''; //case 'script' : return 'SCRIPT'; //case 'style' : return 'STYLE'; //case 'directive' : return ''; } return ''; }, getAttribute : function(attr) { if (attr=='href') { return this.href || ''; }; if (attr=='src') { return this.src || ''; } if (attr!='style') { return this[attr]; }; return ''; }, setAttribute : function(attr,val) { if (attr!='style') { this[attr]=val; } }, removeAttribute : function(attr) { delete this[attr]; }, //getAttributeNode : NOT_IMPLEMENTED(new core.Attr()), //setAttributeNode : NOT_IMPLEMENTED(new core.Attr()), //removeAttributeNode : NOT_IMPLEMENTED(new core.Attr()), getElementsByTagName : function(Name) { //normally in HTMLElement, keep here for now //Unlike document's nodeLists this one is not updated dynamically //Because node may not be in dom tree, so let's not put some efforts on useless things //Anyway, not important, rarely used //Just scan the node to retrieve the result var name=Name.toLowerCase(); var list=new core.NodeList(); var recurse=function() { if ((this.__name==name)||(name=='*')) { list.push(this); }; var children=this.__children; var l=children.length; for (var i=0;i<l;i++) { if (children[i].__type=='tag') { recurse.call(children[i]); } } }; if (this.__type=='tag') { recurse.call(this); }; //console.log('tagname '+Name+' '+list.length); return list; }, getElementsByClassName : function(name) { var names=name.trim().split(' ').sort(); var list=new core.NodeList(); var recurse=function() { if (this._hasclasses(names)) { list.push(this); }; var children=this.__children; var l=children.length; for (var i=0;i<l;i++) { if (children[i].__type=='tag') { recurse.call(children[i]); } } }; if (this.__type=='tag') { recurse.call(this); }; return list; }, getAttributeNS : NOT_IMPLEMENTED(''), setAttributeNS : NOT_IMPLEMENTED(null), removeAttributeNS : NOT_IMPLEMENTED(null), getAttributeNodeNS : NOT_IMPLEMENTED(new core.Attr()), setAttributeNodeNS : NOT_IMPLEMENTED(new core.Attr()), getElementsByTagNameNS : NOT_IMPLEMENTED(new core.NodeList()), hasAttribute : function(val) { return this.getAttribute(val)?true:false; }, hasAttributeNS : NOT_IMPLEMENTED(false), //setIdAttribute: NOT_IMPLEMENTED(''), //setIdAttributeNS: NOT_IMPLEMENTED(''), //setIdAttributeNode: NOT_IMPLEMENTED('') //get children() {return this.__children;}, //do not use, conflict with parser children firstElementChild : null, lastElementChild : null, previousElementSibling : null, nextElementSibling : null, childElementCount : 0 }; core.Document={}; core.Document.prototype = { __proto__: core.Node.prototype, doctype:'', implementation: {hasFeature:function() {return false;}}, get documentElement() { if (this.__documentElement) { return this.__documentElement; } else { var children = this.__children; var l=children.length; for (var i=0;i<l;i++) { if (children[i].nodeType === this.ELEMENT_NODE) { this.__documentElement = children[i]; return children[i]; } } return null; } }, nodeName: '#document', compatMode : 'CSS1Compat', charset : 'UTF-8', characterSet : 'UTF-8', defaultCharset : 'UTF-8', contentType : 'application/xml', lastModified : '', //tagName: null, //nodeValue: null, //ownerDocument: null, //readonly:NOT_IMPLEMENTED(), createElement: function(tag) { //Nodelist are created even if element not appended in dom tree //__dom property is set when element is appended //Once getElementsByTagName is called for example, an object nodelist is created and then dynamically updated //see addToDom, removeFromDom and upadteNL/Node //console.log(tag); var tagp=tag.toLowerCase(); var node=new NODE(tagp,this); node.__type='tag'; if (!this['_'+tagp]) {this['_'+tagp]={};}; //prepare lists for tag names if (!this['_*']) {this['_*']={};}; this._elid++; node._elid=this._elid; // assign element id to avoid nodelist manipulations this['_'+tagp][this._elid]=node; this['_*'][this._elid]=node; /* Some sites do define several html, body, head tags Ex: http://www.ooshop.com */ if (tagp=='html') { if (!this.html) { this.html=node; } }; if (tagp=='head') { if (!this.head) { this.head=node; } }; if (tagp=='body') { if (!this.body) { this.body=node; } }; var notattrib={__name:'',__children:[],_document:{},__type:'',_elid:0,__data:'',_parentNode:null,_href:'',___data:'',__onload:'',_elna:0,_elca:0,children:[],__dom:false,__form:{},__tHead:{},__tFoot:{},__tBody:{},__caption:{},__options:{},__elements:{},__tr:{},__cells:{},__td:{},____data:'',_pass:false,_tt:'',_isimage:false,_code:'',_parsed:false,__loaded:false}; for (var n in notattrib) { node._Enum(n,false,notattrib[n]); }; return node; }, createDocumentFragment: function() { var node = new FRAGMENT(this); node.__type='document-fragment'; return node; }, createTextNode: function(text) { var node = new TEXT(this); node.__data=text; node.__type='text'; return node; }, createComment: function(text) { //creates fake comment return this.createTextNode(''); }, //createCDATASection: function() {this.createComment();}, //fake CDATA createProcessingInstruction: function() {this.createComment();}, //fake PI //createAttribute: NOT_IMPLEMENTED(new core.Attr()), //createEntityReference: function() {this.createComment();}, //fake //createEntityNode : function() {this.createComment();}, //fake //createNotationNode : function() {this.createComment();}, //fake getElementsByTagName : function(Name) { var name=Name.toLowerCase(); if ('_'+name in this._Tnodelists) { return this._Tnodelists['_'+name]; } else { return this.updateNL(this['_'+name],'_'+name,this._Tnodelists); } }, getElementsByName : function(name) { //normally in HTMLDocument - keep here if (name in this._Nnodelists) { return this._Nnodelists[name]; } else { return this.updateNL(this._names[name],name,this._Nnodelists); } }, getElementsByClassName : function(name) { //normally in HTMLDocument and HTMLElement keep here too //this one is not updated dynamically for more than one class var names=name.trim().split(' ').sort(); var l=names.length; var res=new core.NodeList(); for (var i=0;i<l;i++) { if (names[i]) { if (names[i] in this._Cnodelists) { var tmp=this._Cnodelists[names[i]]; var l2=tmp.length; if (l==1) { return tmp; //dynamically updated } else { for (var j=0;j<l2;j++) { if (tmp[j]._hasclasses(names)) { //if object has all requested classes if (res.indexOf(tmp[j])==-1) { //if node not already in res res.push(tmp[j]); } } } } } else { var tmp=this.updateNL(this._class[names[i]],names[i],this._Cnodelists); var l2=tmp.length; if (l==1) { return tmp; //dynamically updated } else { for (var j=0;j<l2;j++) { if (tmp[j]._hasclasses(names)) { //if object has all requested classes if (res.indexOf(tmp[j])==-1) { //if node not already in res res.push(tmp[j]); } } } } } } } return res; }, importNode : function(node,pr) {return node;}, //not implemented createElementNS : function(ns,tag) {return this.createElement(tag);}, //not implemented //createAttributeNS : function(ns,attr) {return this.createAttribute(attr);}, //not implemented getElementByTagNameNS : function(ns,name) {return this.getElementsByTagName(name);}, //not implemented getElementById:function(id) { if (this._ids[id]) { if (this._ids[id].__dom) { //if in dom tree return this._ids[id]; } }; return null; }, //inputEncoding :'utf-8', //xmlEncoding :'utf-8', //xmlStandalone : false, //xmlVersion : null, //strictErrorChecking : true, get documentURI() {return this.baseURI;}, adoptNode : function(node) {return node;}, //not implemented //domConfig : NOT_IMPLEMENTED({}), //normalizeDocument : NOT_IMPLEMENTED(null), //renameNode : function(node,ns,name) { //not implemented, simplification // node.__name=name; // return node; //} createEvent : function() {return new core.Event()}, createNodeIterator : NOT_IMPLEMENTED({}), createTreeWalker : NOT_IMPLEMENTED({}) }; core.HTMLElement = function() {}; core.HTMLElement.prototype = { __proto__: core.Element.prototype, get innerHTML() { return domToHtml(this.__children,this._document._features.removeScript); }, get outerHTML() { var res=domToHtml(this,this._document._features.removeScript); return res; }, set innerHTML(val) { //See performance issue with CSSStyleDeclaration var _node=core.HtmlToDom(val,this); var tmpdom=_node.dom; var l=tmpdom.length; this.children=[]; for (var i=0;i<l;i++) { this.children.push(tmpdom[i]); }; this._document.setTree.call(this,null); }, set outerHTML(val) { var _node=core.HtmlToDom(val,this); var tmpdom=_node.dom; var dummy=new NODE('div',this._document); dummy.children=[]; dummy.__dom=this.__dom; for (var n in tmpdom) { dummy.children.push(tmpdom[n]); }; this._document.setTree.call(dummy,null); if (this._parentNode) { this._parentNode.insertBefore(dummy.firstChild,this); this._parentNode.removeChild(this); } else { //rare case //do nothing for now } }, get offsetWidth() { if (this.__type=='tag') { return this._document._parentNode.getComputedStyle(this).getPropertyValue('width'); }; return 0; }, get offsetHeight() { if (this.__type=='tag') { return this._document._parentNode.getComputedStyle(this).getPropertyValue('height'); }; return 0; }, set id(val) { for (var n in this._document._ids) { if (this._document._ids[n]==this) { delete this._document._ids[n]; } }; this._id=val; this._document._addid(this,val); }, get id(val) { return this._id; }, set name(val) { if (this._name) { if (this._name.trim()) { //delete from global list delete this._document._names[this._name][this._elna]; }; //delete from nodelist if (this._name in this._document._Nnodelists) { var list=this._document._Nnodelists[this._name]; var ind=list.indexOf(this); if (ind>=0) { list.splice(ind,1); }; }; }; this._name=val;//do not mix with __name/nodeName/tagName of node this._document._addname(this,val); }, get name() { return this._name; }, set className(val) { if (this._class) { var classes=this._class.trim().split(' '); //split classes var l=classes.length; for (var i=0;i<l;i++) { if (classes[i].trim()) { //delete from global list delete this._document._class[classes[i]][this._elca]; //delete from nodelist if (classes[i] in this._document._Cnodelists) { var list=this._document._Cnodelists[classes[i]]; var ind=list.indexOf(this); if (ind>=0) { list.splice(ind,1); }; }; } }; }; this._class=val; //class='test try' --> _class='test try' this._document._addclass(this,val); //add node to classes 'test' and 'try' }, get className() { return this._class; }, _hasclasses : function(name) { if (this._class) { var val=this._class.trim().split(' '); var l=name.length; for (var i=0;i<l;i++) { if (val.indexOf(name[i])==-1) { return false; } }; return true; }; return false; }, classList : [], //simplification clientHeight : 0, clientLeft : 0, clientTop : 0, clientWidth : 0, contentEditable : 'false', dataset : '', dir : '', lang : '', title : '', isContentEditable : false, offsetLeft : 0, get offsetParent() {return this._parentNode}, //not implemented offsetTop : 0, scrollHeight : 0, scrollLeft : 0, scrollTop : 0, scrollWidth : 0, click : NOT_IMPLEMENTED(null), focus : NOT_IMPLEMENTED(null), blur : NOT_IMPLEMENTED(null), //scrollIntoView : NOT_IMPLEMENTED(null), //setCapture : NOT_IMPLEMENTED(null) itemScope : false, itemType : '', itemId : '', itemref : {}, itemProp : {}, properties : {}, itemValue : {}, hidden : false, tabIndex : 0, accessKey : '', accessKeyLabel : '', draggable : false, dropzone : {}, contextMenu : {}, spellcheck : false, commandType : '', commandLabel : '', commandIcon : '', commandHidden : false, commandDisabled : false, commandChecked : false }; core.CSSStyleDeclaration=function(element) { //Our own CSSOM CSSStyleDeclaration //Unlike CSSStyleDeclaration, pass element to retrieve attributes width height and modifications //TODO : huge performance issue while calling getPropertyValue, this[style] suspected to be too long - to investigate //TODO (??) : use harmony proxy in v8 so element.style.test does not return undefined if test is not set Object.defineProperty(this,'node',{value:element,enumerable:false,configurable:true,writable:true}); Object.defineProperty(this,'_importants',{value:{},enumerable:false,configurable:true,writable:true}); Object.defineProperty(this,'length',{value:0,enumerable:false,configurable:true,writable:true}); //Set default styles - might be involved in performance issue //Appears to be faster to do it here rather than in prototype for (var n in default_style) { this[n]=''; }; //TODO (??) : does not handle length now }; core.CSSStyleDeclaration.prototype={ get cssText() { var css=''; var prop=Object.keys(this); var l=prop.length; for (var i=0;i<l;i++) { var style=prop[i]; if (this[style]!='') { //if not default style if (style=='_backgroundImage') {style='backgroundImage'}; var sep=';'; if (this._importants[style]) { sep=' !important;'; }; css +=style.replace(/([A-Z])/g, uncanon)+':'+this[style]+sep; } }; return css; }, set cssText(text) { if (text) { var data=text.split(';'); if (this.node._parsed) { //do not clear style on tree creation (_parsed undefined) var prop=Object.keys(this); var l=prop.length; for (var i=0;i<l;i++) { //remove all styles except width/height if (style=='_backgroundImage') {style='backgroundImage'}; if ((style!='width')&&(style!='height')) { this[style]=''; } }; }; for (var i=0;i<data.length;i++) { try { //catch errors in case of strange declaration var tmp=core.HtmlToStyle(data[i]); var name=tmp.attr2; var priority=tmp.priority;//true if !important, false if not var value=tmp.val; this.setProperty(name,value,priority); } catch(ee) {}; } } }, getPropertyValue : function(style) { //Ex: style = 'border-right' //Simplification for now //Mainly interested by width/height nbloop++; var win=this.node._document._parentNode; var camel=canon(style); var prop=Object.keys(this); var self=this; if (prop.indexOf(camel)!=-1) { //ex: border-right --> borderRight switch(style) { case 'width' : return pixel(this[style],win.screen.width); case 'height' : return pixel(this[style],win.screen.height); } return this[camel];//this['borderRight'] }; prop=Object.keys(this.node); if (prop.indexOf(camel)!=-1) { switch (style) { case 'width' : return pixel(this.node[style],win.screen.width); case 'height' : return pixel(this.node[style],win.screen.height); return this.node[style]; } }; switch(style) { //default values case 'width' : return win.screen.width; case 'height' : return win.screen.height; case 'background-image' : return 'none'; case 'background-color' : return ''; case 'visibility' : return 'visible'; case 'display' : return ''; case 'font-size' : return '12px'; case 'text-decoration' : return 'none'; return ''; } }, setProperty: function(name, value, priority) { //Unlike normal CSSStyleDeclaration //Handle properties in "borderRight" format //So if modified in js it will work //Does not set the "border-right" property this[name]=value; //ex: borderRight this._importants[name]=priority; }, set backgroundImage(bg) { if ((!this['backgroundRepeat'])&&(bg!='none')&&(bg)) { if ('img' in this.node._document._features['FetchExternalResources']) { var win=this.node._document._parentNode; var z_b = new RegExp("url\\((.[^)]*?)\\)","gi"); var href = z_b.exec(bg)[1].replace(/'|"/g,''); //console.log(href); if (!((this.node.offsetWidth!=win.screen.width)||(this.node.offsetHeight!=win.screen.height))) { //Size not known var src=core.resourceLoader.resolve(this.node._document,href); if (src) { this.node._isimage=true; this.node._href=src; //console.log('href-----------'+this.node._href+' '+bg); this.node.___data='nothing'; this.node.____data='nothing'; //console.log('bgimage----------'); core.resourceLoader.download(URL.parse(this.node._href),this.node); } } } }; this['_backgroundImage']=bg; }, get backgroundImage() { return this['_backgroundImage']; } }; core.NamedHTMLElement=function(name,doc) { this.__name=name; this.__children=[]; this.__loaded=false; this._document=doc; var self=this; if (!(this.__name in NOSTYLE)) { var g=function() { //if (!this.__style) { // this.__style=new core.CSSStyleDeclaration(this); //}; //return this.__style; delete self.style; self.style=new core.CSSStyleDeclaration(self); return self.style; }; var s=function() {}; //Object.defineProperty(this,'style',{get : g, set : s, enumerable:true, configurable:true}); //lazy creation of style //removed, this is slower than direct creation this.style=new core.CSSStyleDeclaration(this); //define element.style }; this._class=''; this.__dom=false; //node not in dom tree at creation //Extend //TODO : check for performances if it is better to extend prototype instead var html=HTML(name.toUpperCase()); //var proto=Object.getPrototypeOf(this); for (var n in html) { var tmp=Object.getOwnPropertyDescriptor(html,n); tmp.enumerable=false; Object.defineProperty(this,n,tmp); }; }; core.NamedHTMLElement.prototype = core.HTMLElement.prototype; try { var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; } catch(ee) { //dummy XMLHttpRequest var XMLHttpRequest = function() {}; XMLHttpRequest.prototype = { open : function() {}, send : function() {}, abort : function() {}, setRequestHeader : function() {}, get responseText() {return '';} } }; var DOMWindow=function(document) { //nodejs bug #1674 var ttimers=[]; var itimers=[]; // this._parentNode=false; this.__name='window'; this._document=document; this._ttimers=ttimers; //queue settimeout before readystate complete - not used this._itimers=itimers; //queue setinterval before readystate complete - not used //this.window=this; this._onl=0; this.location = document._options.url; if (!this.location.port) { if (this.location.protocol=='https') { this.location.port=443; }; this.location.port=80; }; if (!this.location.hash) { this.location.hash=''; } if (!this.location.search) { this.location.search=''; }; this.location.assign = NOT_IMPLEMENTED(); this.location.reload = NOT_IMPLEMENTED();