Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80684 views
1
var core = require("./core").dom.level2.core,
2
events = require("./events").dom.level2.events,
3
applyDocumentFeatures = require('../browser/documentfeatures').applyDocumentFeatures,
4
defineGetter = require('../utils').defineGetter,
5
defineSetter = require('../utils').defineSetter,
6
inheritFrom = require("../utils").inheritFrom,
7
URL = require("url"),
8
Path = require('path'),
9
fs = require("fs"),
10
http = require('http'),
11
https = require('https');
12
13
// modify cloned instance for more info check: https://github.com/tmpvar/jsdom/issues/325
14
core = Object.create(core);
15
16
// Setup the javascript language processor
17
core.languageProcessors = {
18
javascript : require("./languages/javascript").javascript
19
};
20
21
// TODO its own package? Pull request to Node?
22
function resolveHref(baseUrl, href) {
23
// When switching protocols, the path doesn't get canonicalized (i.e. .s and ..s are still left):
24
// https://github.com/joyent/node/issues/5453
25
var intermediate = URL.resolve(baseUrl, href);
26
27
// This canonicalizes the path, at the cost of overwriting the hash.
28
var nextStep = URL.resolve(intermediate, '#');
29
30
// So, insert the hash back in, if there was one.
31
var parsed = URL.parse(intermediate);
32
var fixed = nextStep.slice(0, -1) + (parsed.hash || '');
33
34
// Finally, fix file:/// URLs on Windows, where Node removes their drive letters:
35
// https://github.com/joyent/node/issues/5452
36
if (/^file\:\/\/\/[a-z]\:\//i.test(baseUrl) && /^file\:\/\/\//.test(fixed) && !/^file\:\/\/\/[a-z]\:\//i.test(fixed)) {
37
fixed = fixed.replace(/^file\:\/\/\//, baseUrl.substring(0, 11));
38
}
39
40
return fixed;
41
}
42
43
core.resourceLoader = {
44
load: function(element, href, callback) {
45
var ownerImplementation = element._ownerDocument.implementation;
46
47
if (ownerImplementation.hasFeature('FetchExternalResources', element.tagName.toLowerCase())) {
48
var full = this.resolve(element._ownerDocument, href);
49
var url = URL.parse(full);
50
if (ownerImplementation.hasFeature('SkipExternalResources', full)) {
51
return false;
52
}
53
if (url.hostname) {
54
this.download(url, element._ownerDocument._cookie, element._ownerDocument._cookieDomain, this.baseUrl(element._ownerDocument), this.enqueue(element, callback, full));
55
}
56
else {
57
this.readFile(url.pathname, this.enqueue(element, callback, full));
58
}
59
}
60
},
61
enqueue: function(element, callback, filename) {
62
var loader = this,
63
doc = element.nodeType === core.Node.DOCUMENT_NODE ?
64
element :
65
element._ownerDocument;
66
67
if (!doc._queue) {
68
return function() {};
69
}
70
71
return doc._queue.push(function(err, data) {
72
var ev = doc.createEvent('HTMLEvents');
73
74
if (!err) {
75
try {
76
callback.call(element, data, filename || doc.URL);
77
ev.initEvent('load', false, false);
78
}
79
catch(e) {
80
err = e;
81
}
82
}
83
84
if (err) {
85
ev.initEvent('error', false, false);
86
ev.error = err;
87
}
88
89
element.dispatchEvent(ev);
90
});
91
},
92
93
baseUrl: function(document) {
94
var baseElements = document.getElementsByTagName('base');
95
var baseUrl = document.URL;
96
97
if (baseElements.length > 0) {
98
var baseHref = baseElements.item(0).href;
99
if (baseHref) {
100
baseUrl = resolveHref(baseUrl, baseHref);
101
}
102
}
103
104
return baseUrl;
105
},
106
resolve: function(document, href) {
107
// if getAttribute returns null, there is no href
108
// lets resolve to an empty string (nulls are not expected farther up)
109
if (href === null) {
110
return '';
111
}
112
113
var baseUrl = this.baseUrl(document);
114
115
return resolveHref(baseUrl, href);
116
},
117
download: function(url, cookie, cookieDomain, referrer, callback) {
118
var path = url.pathname + (url.search || ''),
119
options = {'method': 'GET', 'host': url.hostname, 'path': path},
120
request;
121
if (url.protocol === 'https:') {
122
options.port = url.port || 443;
123
request = https.request(options);
124
} else {
125
options.port = url.port || 80;
126
request = http.request(options);
127
}
128
129
// set header.
130
if (referrer) {
131
request.setHeader('Referer', referrer);
132
}
133
if (cookie) {
134
var host = url.host.split(':')[0];
135
if (host.indexOf(cookieDomain, host.length - cookieDomain.length) !== -1) {
136
request.setHeader('cookie', cookie);
137
}
138
}
139
140
request.on('response', function (response) {
141
var data = '';
142
function success () {
143
if ([301, 302, 303, 307].indexOf(response.statusCode) > -1) {
144
var redirect = URL.resolve(url, response.headers["location"]);
145
core.resourceLoader.download(URL.parse(redirect), cookie, cookieDomain, referrer, callback);
146
} else {
147
callback(null, data);
148
}
149
}
150
response.setEncoding('utf8');
151
response.on('data', function (chunk) {
152
data += chunk.toString();
153
});
154
response.on('end', function() {
155
// According to node docs, 'close' can fire after 'end', but not
156
// vice versa. Remove 'close' listener so we don't call success twice.
157
response.removeAllListeners('close');
158
success();
159
});
160
response.on('close', function (err) {
161
if (err) {
162
callback(err);
163
} else {
164
success();
165
}
166
});
167
});
168
169
request.on('error', callback);
170
request.end();
171
},
172
readFile: function(url, callback) {
173
fs.readFile(url.replace(/^file:\/\//, "").replace(/^\/([a-z]):\//i, '$1:/').replace(/%20/g, ' '), 'utf8', callback);
174
}
175
};
176
177
function define(elementClass, def) {
178
var tagName = def.tagName,
179
tagNames = def.tagNames || (tagName? [tagName] : []),
180
parentClass = def.parentClass || core.HTMLElement,
181
attrs = def.attributes || [],
182
proto = def.proto || {};
183
184
var elem = core[elementClass] = function(document, name) {
185
parentClass.call(this, document, name || tagName.toUpperCase());
186
if (elem._init) {
187
elem._init.call(this);
188
}
189
};
190
elem._init = def.init;
191
192
inheritFrom(parentClass, elem, proto);
193
194
attrs.forEach(function(n) {
195
var prop = n.prop || n,
196
attr = n.attr || prop.toLowerCase();
197
198
if (!n.prop || n.read !== false) {
199
defineGetter(elem.prototype, prop, function() {
200
var s = this.getAttribute(attr);
201
if (n.type && n.type === 'boolean') {
202
return s !== null;
203
}
204
if (n.type && n.type === 'long') {
205
return +s;
206
}
207
if (typeof n === 'object' && n.normalize) { // see GH-491
208
return n.normalize(s);
209
}
210
return s;
211
});
212
}
213
214
if (!n.prop || n.write !== false) {
215
defineSetter(elem.prototype, prop, function(val) {
216
if (!val) {
217
this.removeAttribute(attr);
218
}
219
else {
220
var s = val.toString();
221
if (typeof n === 'object' && n.normalize) {
222
s = n.normalize(s);
223
}
224
this.setAttribute(attr, s);
225
}
226
});
227
}
228
});
229
230
tagNames.forEach(function(tag) {
231
core.Document.prototype._elementBuilders[tag.toLowerCase()] = function(doc, s) {
232
var el = new elem(doc, s);
233
234
if (def.elementBuilder) {
235
return def.elementBuilder(el, doc, s);
236
}
237
238
return el;
239
};
240
});
241
}
242
243
244
245
core.HTMLCollection = function HTMLCollection(element, query) {
246
this._keys = [];
247
core.NodeList.call(this, element, query);
248
};
249
inheritFrom(core.NodeList, core.HTMLCollection, {
250
namedItem: function(name) {
251
// Try property shortcut; should work in most cases
252
if (Object.prototype.hasOwnProperty.call(this, name)) {
253
return this[name];
254
}
255
256
var results = this._toArray(),
257
l = results.length,
258
node,
259
matchingName = null;
260
261
for (var i=0; i<l; i++) {
262
node = results[i];
263
if (node.getAttribute('id') === name) {
264
return node;
265
} else if (node.getAttribute('name') === name) {
266
matchingName = node;
267
}
268
}
269
return matchingName;
270
},
271
toString: function() {
272
return '[ jsdom HTMLCollection ]: contains ' + this.length + ' items';
273
},
274
_resetTo: function(array) {
275
var i, _this = this;
276
277
for (i = 0; i < this._keys.length; ++i) {
278
delete this[this._keys[i]];
279
}
280
this._keys = [];
281
282
core.NodeList.prototype._resetTo.apply(this, arguments);
283
284
function testAttr(node, attr) {
285
var val = node.getAttribute(attr);
286
if (val && !Object.prototype.hasOwnProperty.call(_this, val)) {
287
_this[val] = node;
288
_this._keys.push(val);
289
}
290
}
291
for (i = 0; i < array.length; ++i) {
292
testAttr(array[i], 'id');
293
}
294
for (i = 0; i < array.length; ++i) {
295
testAttr(array[i], 'name');
296
}
297
}
298
});
299
Object.defineProperty(core.HTMLCollection.prototype, 'constructor', {
300
value: core.NodeList,
301
writable: true,
302
configurable: true
303
});
304
305
core.HTMLOptionsCollection = core.HTMLCollection;
306
307
function closest(e, tagName) {
308
tagName = tagName.toUpperCase();
309
while (e) {
310
if (e.nodeName.toUpperCase() === tagName ||
311
(e.tagName && e.tagName.toUpperCase() === tagName))
312
{
313
return e;
314
}
315
e = e._parentNode;
316
}
317
return null;
318
}
319
320
function descendants(e, tagName, recursive) {
321
var owner = recursive ? e._ownerDocument || e : e;
322
return new core.HTMLCollection(owner, core.mapper(e, function(n) {
323
return n.nodeName === tagName && typeof n._publicId == 'undefined';
324
}, recursive));
325
}
326
327
function firstChild(e, tagName) {
328
if (!e) {
329
return null;
330
}
331
var c = descendants(e, tagName, false);
332
return c.length > 0 ? c[0] : null;
333
}
334
335
function ResourceQueue(paused) {
336
this.paused = !!paused;
337
}
338
ResourceQueue.prototype = {
339
push: function(callback) {
340
var q = this;
341
var item = {
342
prev: q.tail,
343
check: function() {
344
if (!q.paused && !this.prev && this.fired){
345
callback(this.err, this.data);
346
if (this.next) {
347
this.next.prev = null;
348
this.next.check();
349
}else{//q.tail===this
350
q.tail = null;
351
}
352
}
353
}
354
};
355
if (q.tail) {
356
q.tail.next = item;
357
}
358
q.tail = item;
359
return function(err, data) {
360
item.fired = 1;
361
item.err = err;
362
item.data = data;
363
item.check();
364
};
365
},
366
resume: function() {
367
if(!this.paused){
368
return;
369
}
370
this.paused = false;
371
var head = this.tail;
372
while(head && head.prev){
373
head = head.prev;
374
}
375
if(head){
376
head.check();
377
}
378
}
379
};
380
381
core.HTMLDocument = function HTMLDocument(options) {
382
options = options || {};
383
if (!options.contentType) {
384
options.contentType = 'text/html';
385
}
386
core.Document.call(this, options);
387
this._referrer = options.referrer;
388
this._cookie = options.cookie;
389
this._cookieDomain = options.cookieDomain || '127.0.0.1';
390
this._URL = options.url || '/';
391
this._documentRoot = options.documentRoot || Path.dirname(this._URL);
392
this._queue = new ResourceQueue(options.deferClose);
393
this.readyState = 'loading';
394
395
// Add level2 features
396
this.implementation.addFeature('core' , '2.0');
397
this.implementation.addFeature('html' , '2.0');
398
this.implementation.addFeature('xhtml' , '2.0');
399
this.implementation.addFeature('xml' , '2.0');
400
};
401
402
inheritFrom(core.Document, core.HTMLDocument, {
403
_referrer : "",
404
get referrer() {
405
return this._referrer || '';
406
},
407
get domain() {
408
return "";
409
},
410
_URL : "",
411
get URL() {
412
return this._URL;
413
},
414
get images() {
415
return this.getElementsByTagName('IMG');
416
},
417
get applets() {
418
return new core.HTMLCollection(this, core.mapper(this, function(el) {
419
if (el && el.tagName) {
420
var upper = el.tagName.toUpperCase();
421
if (upper === "APPLET") {
422
return true;
423
} else if (upper === "OBJECT" &&
424
el.getElementsByTagName('APPLET').length > 0)
425
{
426
return true;
427
}
428
}
429
}));
430
},
431
get links() {
432
return new core.HTMLCollection(this, core.mapper(this, function(el) {
433
if (el && el.tagName) {
434
var upper = el.tagName.toUpperCase();
435
if (upper === "AREA" || (upper === "A" && el.href)) {
436
return true;
437
}
438
}
439
}));
440
},
441
get forms() {
442
return this.getElementsByTagName('FORM');
443
},
444
get anchors() {
445
return this.getElementsByTagName('A');
446
},
447
open : function() {
448
this._childNodes = new core.NodeList();
449
this._documentElement = null;
450
this._modified();
451
},
452
close : function() {
453
this._queue.resume();
454
// Set the readyState to 'complete' once all resources are loaded.
455
// As a side-effect the document's load-event will be dispatched.
456
core.resourceLoader.enqueue(this, function() {
457
this.readyState = 'complete';
458
var ev = this.createEvent('HTMLEvents');
459
ev.initEvent('DOMContentLoaded', false, false);
460
this.dispatchEvent(ev);
461
})(null, true);
462
},
463
464
write : function(text) {
465
if (this._writeAfterElement) {
466
// If called from an script element directly (during the first tick),
467
// the new elements are inserted right after that element.
468
var tempDiv = this.createElement('div');
469
tempDiv.innerHTML = text;
470
471
var child = tempDiv.firstChild;
472
var previous = this._writeAfterElement;
473
var parent = this._writeAfterElement.parentNode;
474
475
while (child) {
476
var node = child;
477
child = child.nextSibling;
478
parent.insertBefore(node, previous.nextSibling);
479
previous = node;
480
}
481
} else if (this.readyState === "loading") {
482
// During page loading, document.write appends to the current element
483
// Find the last child that has ben added to the document.
484
var node = this;
485
while (node.lastChild && node.lastChild.nodeType === this.ELEMENT_NODE) {
486
node = node.lastChild;
487
}
488
node.innerHTML = text || "<html><head></head><body></body></html>";
489
} else if (text) {
490
this.innerHTML = text;
491
}
492
},
493
494
writeln : function(text) {
495
this.write(text + '\n');
496
},
497
498
getElementsByName : function(elementName) {
499
return new core.HTMLCollection(this, core.mapper(this, function(el) {
500
return (el.getAttribute && el.getAttribute("name") === elementName);
501
}));
502
},
503
504
get title() {
505
var head = this.head,
506
title = head ? firstChild(head, 'TITLE') : null;
507
return title ? title.textContent : '';
508
},
509
510
set title(val) {
511
var title = firstChild(this.head, 'TITLE');
512
if (!title) {
513
title = this.createElement('TITLE');
514
var head = this.head;
515
if (!head) {
516
head = this.createElement('HEAD');
517
this.documentElement.insertBefore(head, this.documentElement.firstChild);
518
}
519
head.appendChild(title);
520
}
521
title.textContent = val;
522
},
523
524
get head() {
525
return firstChild(this.documentElement, 'HEAD');
526
},
527
528
set head(unused) { /* noop */ },
529
530
get body() {
531
var body = firstChild(this.documentElement, 'BODY');
532
if (!body) {
533
body = firstChild(this.documentElement, 'FRAMESET');
534
}
535
return body;
536
},
537
538
get documentElement() {
539
if (!this._documentElement) {
540
this._documentElement = firstChild(this, 'HTML');
541
}
542
return this._documentElement;
543
},
544
545
_cookie : "",
546
get cookie() {
547
var cookies = Array.isArray(this._cookie) ?
548
this._cookie :
549
(this._cookie && this._cookie.length > 0 ? [this._cookie] : []);
550
551
return cookies.map(function (x) {
552
return x.split(';')[0];
553
}).join('; ');
554
},
555
set cookie(val) {
556
var key = val.split('=')[0];
557
var cookies = Array.isArray(this._cookie) ?
558
this._cookie :
559
(this._cookie && this._cookie.length > 0 ? [this._cookie] : []);
560
for (var i = 0; i < cookies.length; i++) {
561
if (cookies[i].lastIndexOf(key + '=', 0) === 0) {
562
cookies[i] = val;
563
key = null;
564
break;
565
}
566
}
567
if (key) {
568
cookies.push(val);
569
}
570
if (cookies.length === 1) {
571
this._cookie = cookies[0];
572
} else {
573
this._cookie = cookies;
574
}
575
return val;
576
}
577
});
578
579
define('HTMLElement', {
580
parentClass: core.Element,
581
proto : {
582
// Add default event behavior (click link to navigate, click button to submit
583
// form, etc). We start by wrapping dispatchEvent so we can forward events to
584
// the element's _eventDefault function (only events that did not incur
585
// preventDefault).
586
dispatchEvent : function (event) {
587
var outcome = core.Node.prototype.dispatchEvent.call(this, event)
588
589
if (!event._preventDefault &&
590
event.target._eventDefaults[event.type] &&
591
typeof event.target._eventDefaults[event.type] === 'function')
592
{
593
event.target._eventDefaults[event.type](event)
594
}
595
return outcome;
596
},
597
getBoundingClientRect: function () {
598
return {
599
bottom: 0,
600
height: 0,
601
left: 0,
602
right: 0,
603
top: 0,
604
width: 0
605
};
606
},
607
_eventDefaults : {}
608
},
609
attributes: [
610
'id',
611
'title',
612
'lang',
613
'dir',
614
{prop: 'className', attr: 'class', normalize: function(s) { return s || ''; }}
615
]
616
});
617
618
core.Document.prototype._defaultElementBuilder = function(document, tagName) {
619
return new core.HTMLElement(document, tagName);
620
};
621
622
// http://www.whatwg.org/specs/web-apps/current-work/#category-listed
623
var listedElements = /button|fieldset|input|keygen|object|select|textarea/i;
624
625
define('HTMLFormElement', {
626
tagName: 'FORM',
627
proto: {
628
get elements() {
629
return new core.HTMLCollection(this._ownerDocument, core.mapper(this, function(e) {
630
return listedElements.test(e.nodeName) ; // TODO exclude <input type="image">
631
}));
632
},
633
get length() {
634
return this.elements.length;
635
},
636
_dispatchSubmitEvent: function() {
637
var ev = this._ownerDocument.createEvent('HTMLEvents');
638
ev.initEvent('submit', true, true);
639
if (!this.dispatchEvent(ev)) {
640
this.submit();
641
};
642
},
643
submit: function() {
644
},
645
reset: function() {
646
this.elements._toArray().forEach(function(el) {
647
el.value = el.defaultValue;
648
});
649
}
650
},
651
attributes: [
652
'name',
653
{prop: 'acceptCharset', attr: 'accept-charset'},
654
'action',
655
'enctype',
656
'method',
657
'target'
658
]
659
});
660
661
define('HTMLLinkElement', {
662
tagName: 'LINK',
663
proto: {
664
get href() {
665
return core.resourceLoader.resolve(this._ownerDocument, this.getAttribute('href'));
666
}
667
},
668
attributes: [
669
{prop: 'disabled', type: 'boolean'},
670
'charset',
671
'href',
672
'hreflang',
673
'media',
674
'rel',
675
'rev',
676
'target',
677
'type'
678
]
679
});
680
681
define('HTMLMetaElement', {
682
tagName: 'META',
683
attributes: [
684
'content',
685
{prop: 'httpEquiv', attr: 'http-equiv'},
686
'name',
687
'scheme'
688
]
689
});
690
691
define('HTMLHtmlElement', {
692
tagName: 'HTML',
693
attributes: [
694
'version'
695
]
696
});
697
698
define('HTMLHeadElement', {
699
tagName: 'HEAD',
700
attributes: [
701
'profile'
702
]
703
});
704
705
define('HTMLTitleElement', {
706
tagName: 'TITLE',
707
proto: {
708
get text() {
709
return this.innerHTML;
710
},
711
set text(s) {
712
this.innerHTML = s;
713
}
714
}
715
});
716
717
define('HTMLBaseElement', {
718
tagName: 'BASE',
719
attributes: [
720
'href',
721
'target'
722
]
723
});
724
725
726
//**Deprecated**
727
define('HTMLIsIndexElement', {
728
tagName : 'ISINDEX',
729
parentClass : core.Element,
730
proto : {
731
get form() {
732
return closest(this, 'FORM');
733
}
734
},
735
attributes : [
736
'prompt'
737
]
738
});
739
740
741
define('HTMLStyleElement', {
742
tagName: 'STYLE',
743
attributes: [
744
{prop: 'disabled', type: 'boolean'},
745
'media',
746
'type',
747
]
748
});
749
750
define('HTMLBodyElement', {
751
proto: (function() {
752
var proto = {};
753
// The body element's "traditional" event handlers are proxied to the
754
// window object.
755
// See: http://www.whatwg.org/specs/web-apps/current-work/#the-body-element
756
['onafterprint', 'onbeforeprint', 'onbeforeunload', 'onblur', 'onerror',
757
'onfocus', 'onhashchange', 'onload', 'onmessage', 'onoffline', 'ononline',
758
'onpagehide', 'onpageshow', 'onpopstate', 'onresize', 'onscroll',
759
'onstorage', 'onunload'].forEach(function (name) {
760
defineSetter(proto, name, function (handler) {
761
this._ownerDocument.parentWindow[name] = handler;
762
});
763
defineGetter(proto, name, function () {
764
return this._ownerDocument.parentWindow[name];
765
});
766
});
767
return proto;
768
})(),
769
tagName: 'BODY',
770
attributes: [
771
'aLink',
772
'background',
773
'bgColor',
774
'link',
775
'text',
776
'vLink'
777
]
778
});
779
780
define('HTMLSelectElement', {
781
tagName: 'SELECT',
782
proto: {
783
get options() {
784
return new core.HTMLOptionsCollection(this, core.mapper(this, function(n) {
785
return n.nodeName === 'OPTION';
786
}));
787
},
788
789
get length() {
790
return this.options.length;
791
},
792
793
get selectedIndex() {
794
return this.options._toArray().reduceRight(function(prev, option, i) {
795
return option.selected ? i : prev;
796
}, -1);
797
},
798
799
set selectedIndex(index) {
800
this.options._toArray().forEach(function(option, i) {
801
option.selected = i === index;
802
});
803
},
804
805
get value() {
806
var i = this.selectedIndex;
807
if (this.options.length && (i === -1)) {
808
i = 0;
809
}
810
if (i === -1) {
811
return '';
812
}
813
return this.options[i].value;
814
},
815
816
set value(val) {
817
var self = this;
818
this.options._toArray().forEach(function(option) {
819
if (option.value === val) {
820
option.selected = true;
821
} else {
822
if (!self.hasAttribute('multiple')) {
823
// Remove the selected bit from all other options in this group
824
// if the multiple attr is not present on the select
825
option.selected = false;
826
}
827
}
828
});
829
},
830
831
get form() {
832
return closest(this, 'FORM');
833
},
834
835
get type() {
836
return this.multiple ? 'select-multiple' : 'select-one';
837
},
838
839
add: function(opt, before) {
840
if (before) {
841
this.insertBefore(opt, before);
842
}
843
else {
844
this.appendChild(opt);
845
}
846
},
847
848
remove: function(index) {
849
var opts = this.options._toArray();
850
if (index >= 0 && index < opts.length) {
851
var el = opts[index];
852
el._parentNode.removeChild(el);
853
}
854
},
855
856
blur : function() {
857
this._ownerDocument.activeElement = this._ownerDocument.body;
858
},
859
focus : function() {
860
this._ownerDocument.activeElement = this;
861
}
862
863
},
864
attributes: [
865
{prop: 'disabled', type: 'boolean'},
866
{prop: 'multiple', type: 'boolean'},
867
'name',
868
{prop: 'size', type: 'long'},
869
{prop: 'tabIndex', type: 'long'},
870
]
871
});
872
873
define('HTMLOptGroupElement', {
874
tagName: 'OPTGROUP',
875
attributes: [
876
{prop: 'disabled', type: 'boolean'},
877
'label'
878
]
879
});
880
881
define('HTMLOptionElement', {
882
tagName: 'OPTION',
883
proto: {
884
_attrModified: function(name, value) {
885
if (name === 'selected') {
886
this.selected = this.defaultSelected;
887
}
888
core.HTMLElement.prototype._attrModified.call(this, arguments);
889
},
890
get form() {
891
return closest(this, 'FORM');
892
},
893
get defaultSelected() {
894
return this.getAttribute('selected') !== null;
895
},
896
set defaultSelected(s) {
897
if (s) this.setAttribute('selected', 'selected');
898
else this.removeAttribute('selected');
899
},
900
get text() {
901
return this.innerHTML;
902
},
903
get value() {
904
return (this.hasAttribute('value')) ? this.getAttribute('value') : this.innerHTML;
905
},
906
set value(val) {
907
this.setAttribute('value', val);
908
},
909
get index() {
910
return closest(this, 'SELECT').options._toArray().indexOf(this);
911
},
912
get selected() {
913
if (this._selected === undefined) {
914
this._selected = this.defaultSelected;
915
}
916
917
if (!this._selected && this.parentNode) {
918
var select = closest(this, 'SELECT');
919
920
if (select) {
921
var options = select.options;
922
923
if (options.item(0) === this && !select.hasAttribute('multiple')) {
924
var found = false, optArray = options._toArray();
925
926
for (var i = 1, l = optArray.length; i<l; i++) {
927
if (optArray[i]._selected) {
928
return false;
929
}
930
}
931
return true;
932
}
933
}
934
}
935
936
return this._selected;
937
},
938
set selected(s) {
939
// TODO: The 'selected' content attribute is the initial value of the
940
// IDL attribute, but the IDL attribute should not relfect the content
941
this._selected = !!s;
942
if (s) {
943
//Remove the selected bit from all other options in this select
944
var select = this._parentNode;
945
if (!select) return;
946
if (select.nodeName !== 'SELECT') {
947
select = select._parentNode;
948
if (!select) return;
949
if (select.nodeName !== 'SELECT') return;
950
}
951
if (!select.multiple) {
952
var o = select.options;
953
for (var i = 0; i < o.length; i++) {
954
if (o[i] !== this) {
955
o[i].selected = false;
956
}
957
}
958
}
959
}
960
}
961
},
962
attributes: [
963
{prop: 'disabled', type: 'boolean'},
964
'label'
965
]
966
});
967
968
define('HTMLInputElement', {
969
tagName: 'INPUT',
970
init: function() {
971
if (!this.type) {
972
this.type = 'text';
973
}
974
},
975
proto: {
976
_initDefaultValue: function() {
977
if (this._defaultValue === undefined) {
978
var attr = this.getAttributeNode('value');
979
this._defaultValue = attr ? attr.value : null;
980
}
981
return this._defaultValue;
982
},
983
_initDefaultChecked: function() {
984
if (this._defaultChecked === undefined) {
985
this._defaultChecked = !!this.getAttribute('checked');
986
}
987
return this._defaultChecked;
988
},
989
get form() {
990
return closest(this, 'FORM');
991
},
992
get defaultValue() {
993
return this._initDefaultValue();
994
},
995
get defaultChecked() {
996
return this._initDefaultChecked();
997
},
998
get checked() {
999
return !!this._attributes.getNamedItem('checked');
1000
},
1001
set checked(checked) {
1002
this._initDefaultChecked();
1003
if (checked) {
1004
this.setAttribute('checked', 'checked');
1005
if (this.type === 'radio') {
1006
var elements = this._ownerDocument.getElementsByName(this.name);
1007
for (var i = 0; i < elements.length; i++) {
1008
if (elements[i] !== this && elements[i].tagName === "INPUT" && elements[i].type === "radio") {
1009
elements[i].checked = false;
1010
}
1011
}
1012
}
1013
} else {
1014
this.removeAttribute('checked');
1015
}
1016
},
1017
get value() {
1018
return this.getAttribute('value');
1019
},
1020
set value(val) {
1021
this._initDefaultValue();
1022
if (val === null) {
1023
this.removeAttribute('value');
1024
}
1025
else {
1026
this.setAttribute('value', val);
1027
}
1028
},
1029
get type() {
1030
var type = this.getAttribute('type');
1031
return type ? type : 'text';
1032
},
1033
set type(type) {
1034
this.setAttribute('type', type);
1035
},
1036
blur : function() {
1037
this._ownerDocument.activeElement = this._ownerDocument.body;
1038
},
1039
focus : function() {
1040
this._ownerDocument.activeElement = this;
1041
},
1042
select: function() {
1043
},
1044
1045
_dispatchClickEvent: function() {
1046
var event = this._ownerDocument.createEvent("HTMLEvents");
1047
event.initEvent("click", true, true);
1048
this.dispatchEvent(event);
1049
},
1050
1051
click: function() {
1052
if (this.type === 'checkbox') {
1053
this.checked = !this.checked;
1054
}
1055
else if (this.type === 'radio') {
1056
this.checked = true;
1057
}
1058
else if (this.type === 'submit') {
1059
var form = this.form;
1060
if (form) {
1061
form._dispatchSubmitEvent();
1062
}
1063
}
1064
this._dispatchClickEvent();
1065
}
1066
},
1067
attributes: [
1068
'accept',
1069
'accessKey',
1070
'align',
1071
'alt',
1072
{prop: 'disabled', type: 'boolean'},
1073
{prop: 'maxLength', type: 'long'},
1074
'name',
1075
{prop: 'readOnly', type: 'boolean'},
1076
{prop: 'size', type: 'long'},
1077
'src',
1078
{prop: 'tabIndex', type: 'long'},
1079
{prop: 'type', normalize: function(val) {
1080
return val ? val.toLowerCase() : 'text';
1081
}},
1082
'useMap'
1083
]
1084
});
1085
1086
define('HTMLTextAreaElement', {
1087
tagName: 'TEXTAREA',
1088
proto: {
1089
_initDefaultValue: function() {
1090
if (this._defaultValue === undefined) {
1091
this._defaultValue = this.textContent;
1092
}
1093
return this._defaultValue;
1094
},
1095
get form() {
1096
return closest(this, 'FORM');
1097
},
1098
get defaultValue() {
1099
return this._initDefaultValue();
1100
},
1101
get value() {
1102
return this.textContent;
1103
},
1104
set value(val) {
1105
this._initDefaultValue();
1106
this.textContent = val;
1107
},
1108
get type() {
1109
return 'textarea';
1110
},
1111
blur : function() {
1112
this._ownerDocument.activeElement = this._ownerDocument.body;
1113
},
1114
focus : function() {
1115
this._ownerDocument.activeElement = this;
1116
},
1117
select: function() {
1118
}
1119
},
1120
attributes: [
1121
'accessKey',
1122
{prop: 'cols', type: 'long'},
1123
{prop: 'disabled', type: 'boolean'},
1124
{prop: 'maxLength', type: 'long'},
1125
'name',
1126
{prop: 'readOnly', type: 'boolean'},
1127
{prop: 'rows', type: 'long'},
1128
{prop: 'tabIndex', type: 'long'}
1129
]
1130
});
1131
1132
define('HTMLButtonElement', {
1133
tagName: 'BUTTON',
1134
proto: {
1135
get form() {
1136
return closest(this, 'FORM');
1137
},
1138
focus : function() {
1139
this._ownerDocument.activeElement = this;
1140
},
1141
blur : function() {
1142
this._ownerDocument.activeElement = this._ownerDocument.body;
1143
}
1144
},
1145
attributes: [
1146
'accessKey',
1147
{prop: 'disabled', type: 'boolean'},
1148
'name',
1149
{prop: 'tabIndex', type: 'long'},
1150
'type',
1151
'value'
1152
]
1153
});
1154
1155
define('HTMLLabelElement', {
1156
tagName: 'LABEL',
1157
proto: {
1158
get form() {
1159
return closest(this, 'FORM');
1160
}
1161
},
1162
attributes: [
1163
'accessKey',
1164
{prop: 'htmlFor', attr: 'for'}
1165
]
1166
});
1167
1168
define('HTMLFieldSetElement', {
1169
tagName: 'FIELDSET',
1170
proto: {
1171
get form() {
1172
return closest(this, 'FORM');
1173
}
1174
}
1175
});
1176
1177
define('HTMLLegendElement', {
1178
tagName: 'LEGEND',
1179
proto: {
1180
get form() {
1181
return closest(this, 'FORM');
1182
}
1183
},
1184
attributes: [
1185
'accessKey',
1186
'align'
1187
]
1188
});
1189
1190
define('HTMLUListElement', {
1191
tagName: 'UL',
1192
attributes: [
1193
{prop: 'compact', type: 'boolean'},
1194
'type'
1195
]
1196
});
1197
1198
define('HTMLOListElement', {
1199
tagName: 'OL',
1200
attributes: [
1201
{prop: 'compact', type: 'boolean'},
1202
{prop: 'start', type: 'long'},
1203
'type'
1204
]
1205
});
1206
1207
define('HTMLDListElement', {
1208
tagName: 'DL',
1209
attributes: [
1210
{prop: 'compact', type: 'boolean'}
1211
]
1212
});
1213
1214
define('HTMLDirectoryElement', {
1215
tagName: 'DIR',
1216
attributes: [
1217
{prop: 'compact', type: 'boolean'}
1218
]
1219
});
1220
1221
define('HTMLMenuElement', {
1222
tagName: 'MENU',
1223
attributes: [
1224
{prop: 'compact', type: 'boolean'}
1225
]
1226
});
1227
1228
define('HTMLLIElement', {
1229
tagName: 'LI',
1230
attributes: [
1231
'type',
1232
{prop: 'value', type: 'long'}
1233
]
1234
});
1235
1236
define('HTMLCanvasElement', {
1237
tagName: 'CANVAS',
1238
attributes: [
1239
'align'
1240
],
1241
elementBuilder: function (element) {
1242
// require node-canvas and catch the error if it blows up
1243
try {
1244
var canvas = new (require('canvas'))(0,0);
1245
for (var attr in element) {
1246
if (!canvas[attr]) {
1247
canvas[attr] = element[attr];
1248
}
1249
}
1250
return canvas;
1251
} catch (e) {
1252
return element;
1253
}
1254
}
1255
});
1256
1257
define('HTMLDivElement', {
1258
tagName: 'DIV',
1259
attributes: [
1260
'align'
1261
]
1262
});
1263
1264
define('HTMLParagraphElement', {
1265
tagName: 'P',
1266
attributes: [
1267
'align'
1268
]
1269
});
1270
1271
define('HTMLHeadingElement', {
1272
tagNames: ['H1','H2','H3','H4','H5','H6'],
1273
attributes: [
1274
'align'
1275
]
1276
});
1277
1278
define('HTMLQuoteElement', {
1279
tagNames: ['Q','BLOCKQUOTE'],
1280
attributes: [
1281
'cite'
1282
]
1283
});
1284
1285
define('HTMLPreElement', {
1286
tagName: 'PRE',
1287
attributes: [
1288
{prop: 'width', type: 'long'}
1289
]
1290
});
1291
1292
define('HTMLBRElement', {
1293
tagName: 'BR',
1294
attributes: [
1295
'clear'
1296
]
1297
});
1298
1299
define('HTMLBaseFontElement', {
1300
tagName: 'BASEFONT',
1301
attributes: [
1302
'color',
1303
'face',
1304
{prop: 'size', type: 'long'}
1305
]
1306
});
1307
1308
define('HTMLFontElement', {
1309
tagName: 'FONT',
1310
attributes: [
1311
'color',
1312
'face',
1313
'size'
1314
]
1315
});
1316
1317
define('HTMLHRElement', {
1318
tagName: 'HR',
1319
attributes: [
1320
'align',
1321
{prop: 'noShade', type: 'boolean'},
1322
'size',
1323
'width'
1324
]
1325
});
1326
1327
define('HTMLModElement', {
1328
tagNames: ['INS', 'DEL'],
1329
attributes: [
1330
'cite',
1331
'dateTime'
1332
]
1333
});
1334
1335
define('HTMLAnchorElement', {
1336
tagName: 'A',
1337
1338
proto: {
1339
blur : function() {
1340
this._ownerDocument.activeElement = this._ownerDocument.body;
1341
},
1342
focus : function() {
1343
this._ownerDocument.activeElement = this;
1344
},
1345
get href() {
1346
return core.resourceLoader.resolve(this._ownerDocument, this.getAttribute('href'));
1347
},
1348
get hostname() {
1349
return URL.parse(this.href).hostname || '';
1350
},
1351
get host() {
1352
return URL.parse(this.href).host || '';
1353
},
1354
get origin() {
1355
var proto = URL.parse(this.href).protocol;
1356
1357
if (proto !== undefined && proto !== null) {
1358
proto += '//';
1359
}
1360
1361
return proto + URL.parse(this.href).host || '';
1362
},
1363
get port() {
1364
return URL.parse(this.href).port || '';
1365
},
1366
get protocol() {
1367
var protocol = URL.parse(this.href).protocol;
1368
return (protocol == null) ? ':' : protocol;
1369
},
1370
get password() {
1371
var auth = URL.parse(this.href).auth;
1372
return auth.substr(auth.indexOf(':') + 1);
1373
},
1374
get pathname() {
1375
return URL.parse(this.href).pathname || '';
1376
},
1377
get username() {
1378
var auth = URL.parse(this.href).auth;
1379
return auth.substr(0, auth.indexOf(':'));
1380
},
1381
get search() {
1382
return URL.parse(this.href).search || '';
1383
},
1384
get hash() {
1385
return URL.parse(this.href).hash || '';
1386
}
1387
},
1388
attributes: [
1389
'accessKey',
1390
'charset',
1391
'coords',
1392
{prop: 'href', type: 'string', read: false},
1393
'hreflang',
1394
'name',
1395
'rel',
1396
'rev',
1397
'shape',
1398
{prop: 'tabIndex', type: 'long'},
1399
'target',
1400
'type'
1401
]
1402
});
1403
1404
define('HTMLImageElement', {
1405
tagName: 'IMG',
1406
proto: {
1407
_attrModified: function(name, value, oldVal) {
1408
if (name == 'src' && value !== oldVal) {
1409
core.resourceLoader.enqueue(this, function() {})();
1410
}
1411
},
1412
get src() {
1413
return core.resourceLoader.resolve(this._ownerDocument, this.getAttribute('src'));
1414
}
1415
},
1416
attributes: [
1417
'name',
1418
'align',
1419
'alt',
1420
'border',
1421
{prop: 'height', type: 'long'},
1422
{prop: 'hspace', type: 'long'},
1423
{prop: 'isMap', type: 'boolean'},
1424
'longDesc',
1425
{prop: 'src', type: 'string', read: false},
1426
'useMap',
1427
{prop: 'vspace', type: 'long'},
1428
{prop: 'width', type: 'long'}
1429
]
1430
});
1431
1432
define('HTMLObjectElement', {
1433
tagName: 'OBJECT',
1434
proto: {
1435
get form() {
1436
return closest(this, 'FORM');
1437
},
1438
get contentDocument() {
1439
return null;
1440
}
1441
},
1442
attributes: [
1443
'code',
1444
'align',
1445
'archive',
1446
'border',
1447
'codeBase',
1448
'codeType',
1449
'data',
1450
{prop: 'declare', type: 'boolean'},
1451
{prop: 'height', type: 'long'},
1452
{prop: 'hspace', type: 'long'},
1453
'name',
1454
'standby',
1455
{prop: 'tabIndex', type: 'long'},
1456
'type',
1457
'useMap',
1458
{prop: 'vspace', type: 'long'},
1459
{prop: 'width', type: 'long'}
1460
]
1461
});
1462
1463
define('HTMLParamElement', {
1464
tagName: 'PARAM',
1465
attributes: [
1466
'name',
1467
'type',
1468
'value',
1469
'valueType'
1470
]
1471
});
1472
1473
define('HTMLAppletElement', {
1474
tagName: 'APPLET',
1475
attributes: [
1476
'align',
1477
'alt',
1478
'archive',
1479
'code',
1480
'codeBase',
1481
'height',
1482
{prop: 'hspace', type: 'long'},
1483
'name',
1484
'object',
1485
{prop: 'vspace', type: 'long'},
1486
'width'
1487
]
1488
});
1489
1490
define('HTMLMapElement', {
1491
tagName: 'MAP',
1492
proto: {
1493
get areas() {
1494
return this.getElementsByTagName("AREA");
1495
}
1496
},
1497
attributes: [
1498
'name'
1499
]
1500
});
1501
1502
define('HTMLAreaElement', {
1503
tagName: 'AREA',
1504
attributes: [
1505
'accessKey',
1506
'alt',
1507
'coords',
1508
'href',
1509
{prop: 'noHref', type: 'boolean'},
1510
'shape',
1511
{prop: 'tabIndex', type: 'long'},
1512
'target'
1513
]
1514
});
1515
1516
define('HTMLScriptElement', {
1517
tagName: 'SCRIPT',
1518
init: function() {
1519
this.addEventListener('DOMNodeInsertedIntoDocument', function() {
1520
if (this.src) {
1521
core.resourceLoader.load(this, this.src, this._eval);
1522
}
1523
else {
1524
var src = this.sourceLocation || {},
1525
filename = src.file || this._ownerDocument.URL;
1526
1527
if (src) {
1528
filename += ':' + src.line + ':' + src.col;
1529
}
1530
filename += '<script>';
1531
1532
core.resourceLoader.enqueue(this, this._eval, filename)(null, this.text);
1533
}
1534
});
1535
},
1536
proto: {
1537
_eval: function(text, filename) {
1538
if (this._ownerDocument.implementation.hasFeature("ProcessExternalResources", "script") &&
1539
this.language &&
1540
core.languageProcessors[this.language])
1541
{
1542
this._ownerDocument._writeAfterElement = this;
1543
core.languageProcessors[this.language](this, text, filename);
1544
delete this._ownerDocument._writeAfterElement;
1545
}
1546
},
1547
get language() {
1548
var type = this.type || "text/javascript";
1549
return type.split("/").pop().toLowerCase();
1550
},
1551
get text() {
1552
var i=0, children = this.childNodes, l = children.length, ret = [];
1553
1554
for (i; i<l; i++) {
1555
ret.push(children.item(i).nodeValue);
1556
}
1557
1558
return ret.join("");
1559
},
1560
set text(text) {
1561
while (this.childNodes.length) {
1562
this.removeChild(this.childNodes[0]);
1563
}
1564
this.appendChild(this._ownerDocument.createTextNode(text));
1565
}
1566
},
1567
attributes : [
1568
{prop: 'defer', 'type': 'boolean'},
1569
'htmlFor',
1570
'event',
1571
'charset',
1572
'type',
1573
'src'
1574
]
1575
})
1576
1577
define('HTMLTableElement', {
1578
tagName: 'TABLE',
1579
proto: {
1580
get caption() {
1581
return firstChild(this, 'CAPTION');
1582
},
1583
get tHead() {
1584
return firstChild(this, 'THEAD');
1585
},
1586
get tFoot() {
1587
return firstChild(this, 'TFOOT');
1588
},
1589
get rows() {
1590
if (!this._rows) {
1591
var table = this;
1592
this._rows = new core.HTMLCollection(this._ownerDocument, function() {
1593
var sections = [table.tHead].concat(table.tBodies._toArray(), table.tFoot).filter(function(s) { return !!s });
1594
1595
if (sections.length === 0) {
1596
return core.mapDOMNodes(table, false, function(el) {
1597
return el.tagName === 'TR';
1598
});
1599
}
1600
1601
return sections.reduce(function(prev, s) {
1602
return prev.concat(s.rows._toArray());
1603
}, []);
1604
1605
});
1606
}
1607
return this._rows;
1608
},
1609
get tBodies() {
1610
if (!this._tBodies) {
1611
this._tBodies = descendants(this, 'TBODY', false);
1612
}
1613
return this._tBodies;
1614
},
1615
createTHead: function() {
1616
var el = this.tHead;
1617
if (!el) {
1618
el = this._ownerDocument.createElement('THEAD');
1619
this.appendChild(el);
1620
}
1621
return el;
1622
},
1623
deleteTHead: function() {
1624
var el = this.tHead;
1625
if (el) {
1626
el._parentNode.removeChild(el);
1627
}
1628
},
1629
createTFoot: function() {
1630
var el = this.tFoot;
1631
if (!el) {
1632
el = this._ownerDocument.createElement('TFOOT');
1633
this.appendChild(el);
1634
}
1635
return el;
1636
},
1637
deleteTFoot: function() {
1638
var el = this.tFoot;
1639
if (el) {
1640
el._parentNode.removeChild(el);
1641
}
1642
},
1643
createCaption: function() {
1644
var el = this.caption;
1645
if (!el) {
1646
el = this._ownerDocument.createElement('CAPTION');
1647
this.appendChild(el);
1648
}
1649
return el;
1650
},
1651
deleteCaption: function() {
1652
var c = this.caption;
1653
if (c) {
1654
c._parentNode.removeChild(c);
1655
}
1656
},
1657
insertRow: function(index) {
1658
var tr = this._ownerDocument.createElement('TR');
1659
if (this.childNodes.length === 0) {
1660
this.appendChild(this._ownerDocument.createElement('TBODY'));
1661
}
1662
var rows = this.rows._toArray();
1663
if (index < -1 || index > rows.length) {
1664
throw new core.DOMException(core.INDEX_SIZE_ERR);
1665
}
1666
if (index === -1 || (index === 0 && rows.length === 0)) {
1667
this.tBodies.item(0).appendChild(tr);
1668
}
1669
else if (index === rows.length) {
1670
var ref = rows[index-1];
1671
ref._parentNode.appendChild(tr);
1672
}
1673
else {
1674
var ref = rows[index];
1675
ref._parentNode.insertBefore(tr, ref);
1676
}
1677
return tr;
1678
},
1679
deleteRow: function(index) {
1680
var rows = this.rows._toArray(), l = rows.length;
1681
if (index === -1) {
1682
index = l-1;
1683
}
1684
if (index < 0 || index >= l) {
1685
throw new core.DOMException(core.INDEX_SIZE_ERR);
1686
}
1687
var tr = rows[index];
1688
tr._parentNode.removeChild(tr);
1689
}
1690
},
1691
attributes: [
1692
'align',
1693
'bgColor',
1694
'border',
1695
'cellPadding',
1696
'cellSpacing',
1697
'frame',
1698
'rules',
1699
'summary',
1700
'width'
1701
]
1702
});
1703
1704
define('HTMLTableCaptionElement', {
1705
tagName: 'CAPTION',
1706
attributes: [
1707
'align'
1708
]
1709
});
1710
1711
define('HTMLTableColElement', {
1712
tagNames: ['COL','COLGROUP'],
1713
attributes: [
1714
'align',
1715
{prop: 'ch', attr: 'char'},
1716
{prop: 'chOff', attr: 'charoff'},
1717
{prop: 'span', type: 'long'},
1718
'vAlign',
1719
'width',
1720
]
1721
});
1722
1723
define('HTMLTableSectionElement', {
1724
tagNames: ['THEAD','TBODY','TFOOT'],
1725
proto: {
1726
get rows() {
1727
if (!this._rows) {
1728
this._rows = descendants(this, 'TR', false);
1729
}
1730
return this._rows;
1731
},
1732
insertRow: function(index) {
1733
var tr = this._ownerDocument.createElement('TR');
1734
var rows = this.rows._toArray();
1735
if (index < -1 || index > rows.length) {
1736
throw new core.DOMException(core.INDEX_SIZE_ERR);
1737
}
1738
if (index === -1 || index === rows.length) {
1739
this.appendChild(tr);
1740
}
1741
else {
1742
var ref = rows[index];
1743
this.insertBefore(tr, ref);
1744
}
1745
return tr;
1746
},
1747
deleteRow: function(index) {
1748
var rows = this.rows._toArray();
1749
if (index === -1) {
1750
index = rows.length-1;
1751
}
1752
if (index < 0 || index >= rows.length) {
1753
throw new core.DOMException(core.INDEX_SIZE_ERR);
1754
}
1755
var tr = this.rows[index];
1756
this.removeChild(tr);
1757
}
1758
},
1759
attributes: [
1760
'align',
1761
{prop: 'ch', attr: 'char'},
1762
{prop: 'chOff', attr: 'charoff'},
1763
{prop: 'span', type: 'long'},
1764
'vAlign',
1765
'width',
1766
]
1767
});
1768
1769
define('HTMLTableRowElement', {
1770
tagName: 'TR',
1771
proto: {
1772
get cells() {
1773
if (!this._cells) {
1774
this._cells = new core.HTMLCollection(this, core.mapper(this, function(n) {
1775
return n.nodeName === 'TD' || n.nodeName === 'TH';
1776
}, false));
1777
}
1778
return this._cells;
1779
},
1780
get rowIndex() {
1781
var table = closest(this, 'TABLE');
1782
return table ? table.rows._toArray().indexOf(this) : -1;
1783
},
1784
1785
get sectionRowIndex() {
1786
return this._parentNode.rows._toArray().indexOf(this);
1787
},
1788
insertCell: function(index) {
1789
var td = this._ownerDocument.createElement('TD');
1790
var cells = this.cells._toArray();
1791
if (index < -1 || index > cells.length) {
1792
throw new core.DOMException(core.INDEX_SIZE_ERR);
1793
}
1794
if (index === -1 || index === cells.length) {
1795
this.appendChild(td);
1796
}
1797
else {
1798
var ref = cells[index];
1799
this.insertBefore(td, ref);
1800
}
1801
return td;
1802
},
1803
deleteCell: function(index) {
1804
var cells = this.cells._toArray();
1805
if (index === -1) {
1806
index = cells.length-1;
1807
}
1808
if (index < 0 || index >= cells.length) {
1809
throw new core.DOMException(core.INDEX_SIZE_ERR);
1810
}
1811
var td = this.cells[index];
1812
this.removeChild(td);
1813
}
1814
},
1815
attributes: [
1816
'align',
1817
'bgColor',
1818
{prop: 'ch', attr: 'char'},
1819
{prop: 'chOff', attr: 'charoff'},
1820
'vAlign'
1821
]
1822
});
1823
1824
define('HTMLTableCellElement', {
1825
tagNames: ['TH','TD'],
1826
proto: {
1827
_headers: null,
1828
set headers(h) {
1829
if (h === '') {
1830
//Handle resetting headers so the dynamic getter returns a query
1831
this._headers = null;
1832
return;
1833
}
1834
if (!(h instanceof Array)) {
1835
h = [h];
1836
}
1837
this._headers = h;
1838
},
1839
get headers() {
1840
if (this._headers) {
1841
return this._headers.join(' ');
1842
}
1843
var cellIndex = this.cellIndex,
1844
headings = [],
1845
siblings = this._parentNode.getElementsByTagName(this.tagName);
1846
1847
for (var i=0; i<siblings.length; i++) {
1848
if (siblings.item(i).cellIndex >= cellIndex) {
1849
break;
1850
}
1851
headings.push(siblings.item(i).id);
1852
}
1853
this._headers = headings;
1854
return headings.join(' ');
1855
},
1856
get cellIndex() {
1857
return closest(this, 'TR').cells._toArray().indexOf(this);
1858
}
1859
},
1860
attributes: [
1861
'abbr',
1862
'align',
1863
'axis',
1864
'bgColor',
1865
{prop: 'ch', attr: 'char'},
1866
{prop: 'chOff', attr: 'charoff'},
1867
{prop: 'colSpan', type: 'long'},
1868
'height',
1869
{prop: 'noWrap', type: 'boolean'},
1870
{prop: 'rowSpan', type: 'long'},
1871
'scope',
1872
'vAlign',
1873
'width'
1874
]
1875
});
1876
1877
define('HTMLFrameSetElement', {
1878
tagName: 'FRAMESET',
1879
attributes: [
1880
'cols',
1881
'rows'
1882
]
1883
});
1884
1885
function loadFrame (frame) {
1886
if (frame._contentDocument) {
1887
// We don't want to access document.parentWindow, since the getter will
1888
// cause a new window to be allocated if it doesn't exist. Probe the
1889
// private variable instead.
1890
if (frame._contentDocument._parentWindow) {
1891
// close calls delete on its document.
1892
frame._contentDocument.parentWindow.close();
1893
} else {
1894
delete frame._contentDocument;
1895
}
1896
}
1897
1898
var src = frame.src;
1899
var parentDoc = frame._ownerDocument;
1900
1901
// If the URL can't be resolved or the src attribute is missing / blank,
1902
// then url should be set to the string "about:blank".
1903
// (http://www.whatwg.org/specs/web-apps/current-work/#the-iframe-element)
1904
var url = core.resourceLoader.resolve(parentDoc, src) || 'about:blank';
1905
var contentDoc = frame._contentDocument = new core.HTMLDocument({
1906
url: url,
1907
documentRoot: Path.dirname(url)
1908
});
1909
applyDocumentFeatures(contentDoc, parentDoc.implementation._features);
1910
1911
var parent = parentDoc.parentWindow;
1912
var contentWindow = contentDoc.parentWindow;
1913
contentWindow.parent = parent;
1914
contentWindow.top = parent.top;
1915
1916
// Handle about:blank with a simulated load of an empty document.
1917
if(url === 'about:blank') {
1918
core.resourceLoader.enqueue(frame, function() {
1919
contentDoc.write();
1920
contentDoc.close();
1921
})();
1922
} else {
1923
core.resourceLoader.load(frame, url, function(html, filename) {
1924
contentDoc.write(html);
1925
contentDoc.close();
1926
});
1927
}
1928
}
1929
1930
define('HTMLFrameElement', {
1931
tagName: 'FRAME',
1932
init : function () {
1933
// Set up the frames array. window.frames really just returns a reference
1934
// to the window object, so the frames array is just implemented as indexes
1935
// on the window.
1936
var parent = this._ownerDocument.parentWindow;
1937
var frameID = parent._length++;
1938
var self = this;
1939
defineGetter(parent, frameID, function () {
1940
return self.contentWindow;
1941
});
1942
1943
// The contentDocument/contentWindow shouldn't be created until the frame
1944
// is inserted:
1945
// "When an iframe element is first inserted into a document, the user
1946
// agent must create a nested browsing context, and then process the
1947
// iframe attributes for the first time."
1948
// (http://www.whatwg.org/specs/web-apps/current-work/#the-iframe-element)
1949
this._initInsertListener = function () {
1950
loadFrame(self);
1951
};
1952
this.addEventListener('DOMNodeInsertedIntoDocument', this._initInsertListener, false);
1953
},
1954
proto: {
1955
_attrModified: function(name, value, oldVal) {
1956
core.HTMLElement.prototype._attrModified.call(this, name, value, oldVal);
1957
var self = this;
1958
if (name === 'name') {
1959
// Remove named frame access.
1960
if (oldVal) {
1961
this._ownerDocument.parentWindow._frame(oldVal);
1962
}
1963
// Set up named frame access.
1964
if (value) {
1965
this._ownerDocument.parentWindow._frame(value, this);
1966
}
1967
} else if (name === 'src') {
1968
// Page we don't fetch the page until the node is inserted. This at
1969
// least seems to be the way Chrome does it.
1970
if (!this._attachedToDocument) {
1971
if (!this._waitingOnInsert) {
1972
// First, remove the listener added in 'init'.
1973
this.removeEventListener('DOMNodeInsertedIntoDocument',
1974
this._initInsertListener, false)
1975
1976
// If we aren't already waiting on an insert, add a listener.
1977
// This guards against src being set multiple times before the frame
1978
// is inserted into the document - we don't want to register multiple
1979
// callbacks.
1980
this.addEventListener('DOMNodeInsertedIntoDocument', function loader () {
1981
self.removeEventListener('DOMNodeInsertedIntoDocument', loader, false);
1982
this._waitingOnInsert = false;
1983
loadFrame(self);
1984
}, false);
1985
this._waitingOnInsert = true;
1986
}
1987
} else {
1988
loadFrame(self);
1989
}
1990
}
1991
},
1992
_contentDocument : null,
1993
get contentDocument() {
1994
if (this._contentDocument == null) {
1995
this._contentDocument = new core.HTMLDocument();
1996
}
1997
return this._contentDocument;
1998
},
1999
get contentWindow() {
2000
return this.contentDocument.parentWindow;
2001
}
2002
},
2003
attributes: [
2004
'frameBorder',
2005
'longDesc',
2006
'marginHeight',
2007
'marginWidth',
2008
'name',
2009
{prop: 'noResize', type: 'boolean'},
2010
'scrolling',
2011
{prop: 'src', type: 'string', write: false}
2012
]
2013
});
2014
2015
define('HTMLIFrameElement', {
2016
tagName: 'IFRAME',
2017
parentClass: core.HTMLFrameElement,
2018
attributes: [
2019
'align',
2020
'frameBorder',
2021
'height',
2022
'longDesc',
2023
'marginHeight',
2024
'marginWidth',
2025
'name',
2026
'scrolling',
2027
'src',
2028
'width'
2029
]
2030
});
2031
2032
exports.define = define;
2033
exports.dom = {
2034
level2 : {
2035
html : core
2036
}
2037
}
2038
2039
2040