Left: | ||
Right: |
LEFT | RIGHT |
---|---|
1 // Copyright (C) 2008-2011 Google Inc. | 1 // Copyright (C) 2008-2012 Google Inc. |
2 // | 2 // |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
6 // | 6 // |
7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
8 // | 8 // |
9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
13 // limitations under the License. | 13 // limitations under the License. |
14 | 14 |
15 /** | 15 /** |
16 * @fileoverview | 16 * @fileoverview |
17 * A partially tamed browser object model based on | 17 * A partially tamed browser object model based on |
18 * <a href="http://www.w3.org/TR/DOM-Level-2-HTML/Overview.html" | 18 * <a href="http://www.w3.org/TR/DOM-Level-2-HTML/Overview.html" |
19 * >DOM-Level-2-HTML</a> and specifically, the | 19 * >DOM-Level-2-HTML</a> and specifically, the |
20 * <a href="http://www.w3.org/TR/DOM-Level-2-HTML/ecma-script-binding.html" | 20 * <a href="http://www.w3.org/TR/DOM-Level-2-HTML/ecma-script-binding.html" |
21 * >ECMAScript Language Bindings</a>. | 21 * >ECMAScript Language Bindings</a>. |
22 * | 22 * |
23 * Caveats:<ul> | 23 * Caveats:<ul> |
24 * <li>This is not a full implementation. | |
25 * <li>Security Review is pending. | 24 * <li>Security Review is pending. |
26 * <li><code>===</code> and <code>!==</code> on node lists will not | 25 * <li><code>===</code> and <code>!==</code> on node lists will not |
27 * behave the same as with untamed node lists. Specifically, it is | 26 * behave the same as with untamed node lists. Specifically, it is |
28 * not always true that {@code nodeA.childNodes === nodeA.childNodes}. | 27 * not always true that {@code nodeA.childNodes === nodeA.childNodes}. |
29 * <li>Node lists are not "live" -- do not reflect changes in the DOM. | |
30 * </ul> | 28 * </ul> |
31 * | 29 * |
32 * <p> | 30 * <p> |
33 * TODO(ihab.awad): Our implementation of getAttribute (and friends) | 31 * TODO(ihab.awad): Our implementation of getAttribute (and friends) |
34 * is such that standard DOM attributes which we disallow for security | 32 * is such that standard DOM attributes which we disallow for security |
35 * reasons (like 'form:enctype') are placed in the "virtual" attributes | 33 * reasons (like 'form:enctype') are placed in the "virtual" attributes |
36 * map (the data-caja-* namespace). They appear to be settable and gettable, | 34 * map (the data-caja-* namespace). They appear to be settable and gettable, |
37 * but their values are ignored and do not have the expected semantics | 35 * but their values are ignored and do not have the expected semantics |
38 * per the DOM API. This is because we do not have a column in | 36 * per the DOM API. This is because we do not have a column in |
39 * html4-defs.js stating that an attribute is valid but explicitly | 37 * html4-defs.js stating that an attribute is valid but explicitly |
40 * blacklisted. Alternatives would be to always throw upon access to | 38 * blacklisted. Alternatives would be to always throw upon access to |
41 * these attributes; to make them always appear to be null; etc. Revisit | 39 * these attributes; to make them always appear to be null; etc. Revisit |
42 * this decision if needed. | 40 * this decision if needed. |
43 * | 41 * |
44 * @author mikesamuel@gmail.com (original Domita) | 42 * @author mikesamuel@gmail.com (original Domita) |
45 * @author kpreid@switchb.org (port to ES5) | 43 * @author kpreid@switchb.org (port to ES5) |
46 * @requires console | 44 * @requires console |
47 * @requires bridalMaker, cajaVM, cssSchema, lexCss, URI | 45 * @requires bridalMaker, cajaVM, cssSchema, lexCss, URI |
48 * @requires parseCssDeclarations, sanitizeCssProperty, unicode | 46 * @requires parseCssDeclarations, sanitizeCssProperty, unicode |
49 * @requires html, html4, htmlSchema | 47 * @requires html, html4, htmlSchema |
50 * @requires WeakMap, Proxy | 48 * @requires WeakMap, Proxy |
51 * @requires CSS_PROP_BIT_HISTORY_INSENSITIVE | 49 * @requires CSS_PROP_BIT_HISTORY_INSENSITIVE |
52 * @provides Domado | 50 * @provides Domado |
53 * @overrides domitaModules, window | 51 * @overrides window |
54 */ | 52 */ |
55 | 53 |
56 // The Turkish i seems to be a non-issue, but abort in case it is. | 54 // The Turkish i seems to be a non-issue, but abort in case it is. |
57 if ('I'.toLowerCase() !== 'i') { throw 'I/i problem'; } | 55 if ('I'.toLowerCase() !== 'i') { throw 'I/i problem'; } |
58 | 56 |
59 // TODO(kpreid): Review whether multiple uses of np() should be coalesced for | 57 // TODO(kpreid): Review whether multiple uses of np() should be coalesced for |
60 // efficiency. | 58 // efficiency. |
61 | 59 |
62 // TODO(kpreid): Move this from the global scope into the function(){}(); | |
63 // eliminate the domitaModules object (or possibly move more stuff into it). | |
64 var domitaModules; | |
65 var Domado = (function() { | 60 var Domado = (function() { |
66 'use strict'; | 61 'use strict'; |
67 | 62 |
68 var isVirtualizedElementName = htmlSchema.isVirtualizedElementName; | 63 var isVirtualizedElementName = htmlSchema.isVirtualizedElementName; |
69 var realToVirtualElementName = htmlSchema.realToVirtualElementName; | 64 var realToVirtualElementName = htmlSchema.realToVirtualElementName; |
70 var virtualToRealElementName = htmlSchema.virtualToRealElementName; | 65 var virtualToRealElementName = htmlSchema.virtualToRealElementName; |
71 | 66 |
72 var cajaPrefix = 'data-caja-'; | 67 var cajaPrefix = 'data-caja-'; |
73 var cajaPrefRe = new RegExp('^' + cajaPrefix); | 68 var cajaPrefRe = new RegExp('^' + cajaPrefix); |
74 | 69 |
(...skipping 30 matching lines...) Expand all Loading... | |
105 | 100 |
106 function uriRewrite(naiveUriPolicy, uri, effects, ltype, hints) { | 101 function uriRewrite(naiveUriPolicy, uri, effects, ltype, hints) { |
107 if (!naiveUriPolicy) { return null; } | 102 if (!naiveUriPolicy) { return null; } |
108 return allowedUriScheme(uri) ? | 103 return allowedUriScheme(uri) ? |
109 'function' === typeof naiveUriPolicy.rewrite ? | 104 'function' === typeof naiveUriPolicy.rewrite ? |
110 naiveUriPolicy.rewrite(uri, effects, ltype, hints) | 105 naiveUriPolicy.rewrite(uri, effects, ltype, hints) |
111 : null | 106 : null |
112 : null; | 107 : null; |
113 } | 108 } |
114 | 109 |
115 if (!domitaModules) { domitaModules = {}; } | 110 // TODO(kpreid): If not used for the upcoming modularity-of-element-tamings |
111 // refactoring, eliminate the domitaModules object. | |
112 var domitaModules = {}; | |
116 | 113 |
117 domitaModules.proxiesAvailable = typeof Proxy !== 'undefined'; | 114 domitaModules.proxiesAvailable = typeof Proxy !== 'undefined'; |
118 | 115 |
119 // The proxy facilities provided by Firefox and ES5/3 differ in whether the | 116 // The proxy facilities provided by Firefox and ES5/3 differ in whether the |
120 // proxy itself (or rather 'receiver') is the first argument to the 'get' | 117 // proxy itself (or rather 'receiver') is the first argument to the 'get' |
121 // traps. This autoswitches as needed, removing the first argument. | 118 // traps. This autoswitches as needed, removing the first argument. |
122 domitaModules.permuteProxyGetSet = (function () { | 119 domitaModules.permuteProxyGetSet = (function () { |
123 var needToSwap = false; | 120 var needToSwap = false; |
124 | 121 |
125 if (domitaModules.proxiesAvailable) { | 122 if (domitaModules.proxiesAvailable) { |
(...skipping 378 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
504 }; | 501 }; |
505 ExpandoProxyHandler.prototype.defineProperty = function (name, descriptor) { | 502 ExpandoProxyHandler.prototype.defineProperty = function (name, descriptor) { |
506 if (name === "ident___") { | 503 if (name === "ident___") { |
507 if (descriptor.set === null) descriptor.set = undefined; | 504 if (descriptor.set === null) descriptor.set = undefined; |
508 Object.defineProperty(this, "ident", descriptor); | 505 Object.defineProperty(this, "ident", descriptor); |
509 } else if (name in this.target) { | 506 } else if (name in this.target) { |
510 // Forwards everything already defined (not expando). | 507 // Forwards everything already defined (not expando). |
511 return ProxyHandler.prototype.defineProperty.call(this, name, | 508 return ProxyHandler.prototype.defineProperty.call(this, name, |
512 descriptor); | 509 descriptor); |
513 } else { | 510 } else { |
514 if (!this.editable) { throw new Error("Not editable"); } | 511 if (!this.editable) { throw new Error(NOT_EDITABLE); } |
515 Object.defineProperty(this.storage, name, descriptor); | 512 Object.defineProperty(this.storage, name, descriptor); |
516 return true; | 513 return true; |
517 } | 514 } |
518 return false; | 515 return false; |
519 }; | 516 }; |
520 ExpandoProxyHandler.prototype['delete'] = function (name) { | 517 ExpandoProxyHandler.prototype['delete'] = function (name) { |
521 if (name === "ident___") { | 518 if (name === "ident___") { |
522 return false; | 519 return false; |
523 } else if (name in this.target) { | 520 } else if (name in this.target) { |
524 // Forwards everything already defined (not expando). | 521 // Forwards everything already defined (not expando). |
525 return ProxyHandler.prototype['delete'].call(this, name); | 522 return ProxyHandler.prototype['delete'].call(this, name); |
526 } else { | 523 } else { |
527 if (!this.editable) { throw new Error("Not editable"); } | 524 if (!this.editable) { throw new Error(NOT_EDITABLE); } |
528 return delete this.storage[name]; | 525 return delete this.storage[name]; |
529 } | 526 } |
530 return false; | 527 return false; |
531 }; | 528 }; |
532 ExpandoProxyHandler.prototype.fix = function () { | 529 ExpandoProxyHandler.prototype.fix = function () { |
533 // TODO(kpreid): Implement fixing, because it is possible to freeze a | 530 // TODO(kpreid): Implement fixing, because it is possible to freeze a |
534 // host DOM node and so ours should support it too. | 531 // host DOM node and so ours should support it too. |
535 return undefined; | 532 return undefined; |
536 }; | 533 }; |
537 | 534 |
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
667 }; | 664 }; |
668 } else { | 665 } else { |
669 throw new Error('ActiveXObject not available'); | 666 throw new Error('ActiveXObject not available'); |
670 } | 667 } |
671 }; | 668 }; |
672 | 669 |
673 domitaModules.TameXMLHttpRequest = function( | 670 domitaModules.TameXMLHttpRequest = function( |
674 taming, | 671 taming, |
675 rulebreaker, | 672 rulebreaker, |
676 xmlHttpRequestMaker, | 673 xmlHttpRequestMaker, |
677 naiveUriPolicy) { | 674 naiveUriPolicy, |
675 getBaseURL) { | |
678 var Confidence = domitaModules.Confidence; | 676 var Confidence = domitaModules.Confidence; |
679 var setOwn = domitaModules.setOwn; | 677 var setOwn = domitaModules.setOwn; |
680 var canHaveEnumerableAccessors = domitaModules.canHaveEnumerableAccessors; | 678 var canHaveEnumerableAccessors = domitaModules.canHaveEnumerableAccessors; |
681 // See http://www.w3.org/TR/XMLHttpRequest/ | 679 // See http://www.w3.org/TR/XMLHttpRequest/ |
682 | 680 |
683 // TODO(ihab.awad): Improve implementation (interleaving, memory leaks) | 681 // TODO(ihab.awad): Improve implementation (interleaving, memory leaks) |
684 // per http://www.ilinsky.com/articles/XMLHttpRequest/ | 682 // per http://www.ilinsky.com/articles/XMLHttpRequest/ |
685 | 683 |
686 var TameXHRConf = new Confidence('TameXMLHttpRequest'); | 684 var TameXHRConf = new Confidence('TameXMLHttpRequest'); |
687 var p = TameXHRConf.p.bind(TameXHRConf); | 685 var p = TameXHRConf.p.bind(TameXHRConf); |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
762 get: method(function () { | 760 get: method(function () { |
763 var result = p(this).feral.statusText; | 761 var result = p(this).feral.statusText; |
764 return (result === undefined || result === null) ? | 762 return (result === undefined || result === null) ? |
765 result : String(result); | 763 result : String(result); |
766 }) | 764 }) |
767 } | 765 } |
768 }); | 766 }); |
769 TameXMLHttpRequest.prototype.open = method(function ( | 767 TameXMLHttpRequest.prototype.open = method(function ( |
770 method, URL, opt_async, opt_userName, opt_password) { | 768 method, URL, opt_async, opt_userName, opt_password) { |
771 method = String(method); | 769 method = String(method); |
770 URL = URI.utils.resolve(getBaseURL(), String(URL)); | |
772 // The XHR interface does not tell us the MIME type in advance, so we | 771 // The XHR interface does not tell us the MIME type in advance, so we |
773 // must assume the broadest possible. | 772 // must assume the broadest possible. |
774 var safeUri = uriRewrite( | 773 var safeUri = uriRewrite( |
775 naiveUriPolicy, | 774 naiveUriPolicy, |
776 String(URL), html4.ueffects.SAME_DOCUMENT, html4.ltypes.DATA, | 775 URL, html4.ueffects.SAME_DOCUMENT, html4.ltypes.DATA, |
777 { | 776 { |
778 "TYPE": "XHR", | 777 "TYPE": "XHR", |
779 "XHR_METHOD": method, | 778 "XHR_METHOD": method, |
780 "XHR": true // Note: this hint is deprecated | 779 "XHR": true // Note: this hint is deprecated |
781 }); | 780 }); |
782 // If the uriPolicy rejects the URL, we throw an exception, but we do | 781 // If the uriPolicy rejects the URL, we throw an exception, but we do |
783 // not put the URI in the exception so as not to put the caller at risk | 782 // not put the URI in the exception so as not to put the caller at risk |
784 // of some code in its stack sniffing the URI. | 783 // of some code in its stack sniffing the URI. |
785 if ("string" !== typeof safeUri) { throw 'URI violates security policy'; } | 784 if ("string" !== typeof safeUri) { throw 'URI violates security policy'; } |
786 switch (arguments.length) { | 785 switch (arguments.length) { |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
903 if (cssPropertyNames.hasOwnProperty(p)) { | 902 if (cssPropertyNames.hasOwnProperty(p)) { |
904 f(p); | 903 f(p); |
905 } | 904 } |
906 } | 905 } |
907 } | 906 } |
908 }; | 907 }; |
909 }; | 908 }; |
910 | 909 |
911 cajaVM.def(domitaModules); | 910 cajaVM.def(domitaModules); |
912 | 911 |
912 var NOT_EDITABLE = "Node not editable."; | |
913 var UNSAFE_TAGNAME = "Unsafe tag name."; | |
914 var UNKNOWN_TAGNAME = "Unknown tag name."; | |
915 var INDEX_SIZE_ERROR = "Index size error."; | |
916 | |
913 /** | 917 /** |
914 * Authorize the Domado library. | 918 * Authorize the Domado library. |
915 * | 919 * |
916 * The result of this constructor is almost stateless. The exceptions are | 920 * The result of this constructor is almost stateless. The exceptions are |
917 * that each instance has unique trademarks for certain types of tamed | 921 * that each instance has unique trademarks for certain types of tamed |
918 * objects, and a shared map allowing separate virtual documents to dispatch | 922 * objects, and a shared map allowing separate virtual documents to dispatch |
919 * events across them. (TODO(kpreid): Confirm this explanation is good.) | 923 * events across them. (TODO(kpreid): Confirm this explanation is good.) |
920 * | 924 * |
921 * @param {Object} taming. An interface to a taming membrane. | 925 * @param {Object} taming. An interface to a taming membrane. |
922 * @param {Object} opt_rulebreaker. If necessary, authorities to break the | 926 * @param {Object} opt_rulebreaker. If necessary, authorities to break the |
(...skipping 440 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1363 // a dimension negative. | 1367 // a dimension negative. |
1364 if (dh && element.clientHeight !== goalHeight) { | 1368 if (dh && element.clientHeight !== goalHeight) { |
1365 var hError = element.clientHeight - goalHeight; | 1369 var hError = element.clientHeight - goalHeight; |
1366 element.style.height = Math.max(0, h - hError) + 'px'; | 1370 element.style.height = Math.max(0, h - hError) + 'px'; |
1367 } | 1371 } |
1368 if (dw && element.clientWidth !== goalWidth) { | 1372 if (dw && element.clientWidth !== goalWidth) { |
1369 var wError = element.clientWidth - goalWidth; | 1373 var wError = element.clientWidth - goalWidth; |
1370 element.style.width = Math.max(0, w - wError) + 'px'; | 1374 element.style.width = Math.max(0, w - wError) + 'px'; |
1371 } | 1375 } |
1372 } | 1376 } |
1377 | |
1378 /** | |
1379 * Access policies | |
1380 * | |
1381 * Each of these objects is a policy for what type of access (read/write, | |
1382 * read-only, or none) is permitted to a Node or NodeList. Each policy | |
1383 * object determines the access for the associated node and its children. | |
1384 * The childPolicy may be overridden if the node is an opaque or foreign | |
1385 * node. | |
1386 * | |
1387 * Definitions: | |
1388 * childrenVisible: | |
1389 * This node appears to have the children it actually does; otherwise, | |
1390 * appears to have no children. | |
1391 * attributesVisible: | |
1392 * This node appears to have the attributes it actually does; | |
1393 * otherwise, appears to have no attributes. | |
1394 * editable: | |
1395 * This node's attributes and properties (other than children) may be | |
1396 * modified. | |
1397 * childrenEditable: | |
1398 * This node's childNodes list may be modified, and its children are | |
1399 * both editable and childrenEditable. | |
1400 * | |
1401 * These flags can express several meaningless cases; in particular, the | |
1402 * 'editable but not visible' cases do not occur. | |
1403 */ | |
1404 var protoNodePolicy = { | |
1405 requireEditable: function () { | |
1406 if (!this.editable) { | |
1407 throw new Error(NOT_EDITABLE); | |
1408 } | |
1409 }, | |
1410 requireChildrenEditable: function () { | |
1411 if (!this.childrenEditable) { | |
1412 throw new Error(NOT_EDITABLE); | |
1413 } | |
1414 }, | |
1415 requireUnrestricted: function () { | |
1416 if (!this.unrestricted) { | |
1417 throw new Error("Node is restricted"); | |
1418 } | |
1419 }, | |
1420 assertRestrictedBy: function (policy) { | |
1421 if (!this.childrenVisible && policy.childrenVisible || | |
1422 !this.attributesVisible && policy.attributesVisible || | |
1423 !this.editable && policy.editable || | |
1424 !this.childrenEditable && policy.childrenEditable || | |
1425 !this.upwardNavigation && policy.upwardNavigation || | |
1426 !this.unrestricted && policy.unrestricted) { | |
1427 throw new Error("Domado internal error: non-monotonic node policy"); | |
1428 } | |
1429 } | |
1430 }; | |
1431 // We eagerly await ES6 offering some kind of literal-with-prototype... | |
1432 var nodePolicyEditable = Object.create(protoNodePolicy); | |
1433 nodePolicyEditable.toString = function () { return "nodePolicyEditable"; }; | |
1434 nodePolicyEditable.childrenVisible = true; | |
1435 nodePolicyEditable.attributesVisible = true; | |
1436 nodePolicyEditable.editable = true; | |
1437 nodePolicyEditable.childrenEditable = true; | |
1438 nodePolicyEditable.upwardNavigation = true; | |
1439 nodePolicyEditable.unrestricted = true; | |
1440 nodePolicyEditable.childPolicy = nodePolicyEditable; | |
1441 | |
1442 var nodePolicyReadOnly = Object.create(protoNodePolicy); | |
1443 nodePolicyReadOnly.toString = function () { return "nodePolicyReadOnly"; }; | |
1444 nodePolicyReadOnly.childrenVisible = true; | |
1445 nodePolicyReadOnly.attributesVisible = true; | |
1446 nodePolicyReadOnly.editable = false; | |
1447 nodePolicyReadOnly.childrenEditable = false; | |
1448 nodePolicyReadOnly.upwardNavigation = true; | |
1449 nodePolicyReadOnly.unrestricted = true; | |
1450 nodePolicyReadOnly.childPolicy = nodePolicyReadOnly; | |
1451 | |
1452 var nodePolicyReadOnlyChildren = Object.create(protoNodePolicy); | |
1453 nodePolicyReadOnlyChildren.toString = | |
1454 function () { return "nodePolicyReadOnlyChildren"; }; | |
1455 nodePolicyReadOnlyChildren.childrenVisible = true; | |
1456 nodePolicyReadOnlyChildren.attributesVisible = true; | |
1457 nodePolicyReadOnlyChildren.editable = true; | |
1458 nodePolicyReadOnlyChildren.childrenEditable = false; | |
1459 nodePolicyReadOnlyChildren.upwardNavigation = true; | |
1460 nodePolicyReadOnlyChildren.unrestricted = true; | |
1461 nodePolicyReadOnlyChildren.childPolicy = nodePolicyReadOnly; | |
1462 | |
1463 var nodePolicyOpaque = Object.create(protoNodePolicy); | |
1464 nodePolicyOpaque.toString = function () { return "nodePolicyOpaque"; }; | |
1465 nodePolicyOpaque.childrenVisible = true; | |
1466 nodePolicyOpaque.attributesVisible = false; | |
1467 nodePolicyOpaque.editable = false; | |
1468 nodePolicyOpaque.childrenEditable = false; | |
1469 nodePolicyOpaque.upwardNavigation = true; | |
1470 nodePolicyOpaque.unrestricted = false; | |
1471 nodePolicyOpaque.childPolicy = nodePolicyReadOnly; | |
1472 | |
1473 var nodePolicyForeign = Object.create(protoNodePolicy); | |
1474 nodePolicyForeign.toString = function () { return "nodePolicyForeign"; }; | |
1475 nodePolicyForeign.childrenVisible = false; | |
1476 nodePolicyForeign.attributesVisible = false; | |
1477 nodePolicyForeign.editable = false; | |
1478 nodePolicyForeign.childrenEditable = false; | |
1479 nodePolicyForeign.upwardNavigation = false; | |
1480 nodePolicyForeign.unrestricted = false; | |
1481 Object.defineProperty(nodePolicyForeign, "childPolicy", { | |
1482 get: function () { | |
1483 throw new Error("Foreign node childPolicy should never be consulted"); | |
1484 } | |
1485 }); | |
1486 cajaVM.def([ | |
1487 nodePolicyEditable, | |
1488 nodePolicyReadOnly, | |
1489 nodePolicyReadOnlyChildren, | |
1490 nodePolicyOpaque, | |
1491 nodePolicyForeign | |
1492 ]); | |
1373 | 1493 |
1374 /** | 1494 /** |
1375 * Add a tamed document implementation to a Gadget's global scope. | 1495 * Add a tamed document implementation to a Gadget's global scope. |
1376 * | 1496 * |
1377 * Has the side effect of adding the classes "vdoc-container___" and | 1497 * Has the side effect of adding the classes "vdoc-container___" and |
1378 * idSuffix.substring(1) to the containerNode. | 1498 * idSuffix.substring(1) to the containerNode. |
1379 * | 1499 * |
1380 * @param {string} idSuffix a string suffix appended to all node IDs. | 1500 * @param {string} idSuffix a string suffix appended to all node IDs. |
1381 * It should begin with "-" and end with "___". | 1501 * It should begin with "-" and end with "___". |
1382 * @param {Object} uriPolicy an object like <pre>{ | 1502 * @param {Object} uriPolicy an object like <pre>{ |
(...skipping 29 matching lines...) Expand all Loading... | |
1412 'attachDocument arity mismatch: ' + arguments.length); | 1532 'attachDocument arity mismatch: ' + arguments.length); |
1413 } | 1533 } |
1414 if (!optTargetAttributePresets) { | 1534 if (!optTargetAttributePresets) { |
1415 optTargetAttributePresets = { | 1535 optTargetAttributePresets = { |
1416 'default': '_blank', | 1536 'default': '_blank', |
1417 whitelist: [ '_blank', '_self' ] | 1537 whitelist: [ '_blank', '_self' ] |
1418 }; | 1538 }; |
1419 } | 1539 } |
1420 | 1540 |
1421 var domicile = { | 1541 var domicile = { |
1422 isProcessingEvent: false | 1542 // True when we're executing a handler for events like click |
1543 handlingUserAction: false | |
1423 }; | 1544 }; |
1424 var pluginId; | 1545 var pluginId; |
1546 | |
1547 var vdocContainsForeignNodes = false; | |
1425 | 1548 |
1426 containerNode = makeDOMAccessible(containerNode); | 1549 containerNode = makeDOMAccessible(containerNode); |
1427 var document = containerNode.ownerDocument; | 1550 var document = containerNode.ownerDocument; |
1428 document = makeDOMAccessible(document); | 1551 document = makeDOMAccessible(document); |
1429 var docEl = makeDOMAccessible(document.documentElement); | 1552 var docEl = makeDOMAccessible(document.documentElement); |
1430 var bridal = bridalMaker(makeDOMAccessible, document); | 1553 var bridal = bridalMaker(makeDOMAccessible, document); |
1431 | 1554 |
1432 var window = bridalMaker.getWindow(containerNode, makeDOMAccessible); | 1555 var window = bridalMaker.getWindow(containerNode, makeDOMAccessible); |
1433 window = makeDOMAccessible(window); | 1556 window = makeDOMAccessible(window); |
1434 | 1557 |
(...skipping 28 matching lines...) Expand all Loading... | |
1463 } | 1586 } |
1464 | 1587 |
1465 // TODO(kpreid): should elementPolicies be exported in domicile? | 1588 // TODO(kpreid): should elementPolicies be exported in domicile? |
1466 | 1589 |
1467 // On IE, turn <canvas> tags into canvas elements that explorercanvas | 1590 // On IE, turn <canvas> tags into canvas elements that explorercanvas |
1468 // will recognize | 1591 // will recognize |
1469 bridal.initCanvasElements(containerNode); | 1592 bridal.initCanvasElements(containerNode); |
1470 | 1593 |
1471 // The private properties used in TameNodeConf are: | 1594 // The private properties used in TameNodeConf are: |
1472 // feral (feral node) | 1595 // feral (feral node) |
1473 // editable (this node editable) | 1596 // policy (access policy) |
1474 // childrenEditable (this node editable) | |
1475 // Several specifically for TameHTMLDocument. | 1597 // Several specifically for TameHTMLDocument. |
1476 // Furthermore, by virtual of being scoped inside attachDocument, | 1598 // Furthermore, by virtual of being scoped inside attachDocument, |
1477 // TameNodeT also indicates that the object is a node from the *same* | 1599 // TameNodeT also indicates that the object is a node from the *same* |
1478 // virtual document. | 1600 // virtual document. |
1479 // TODO(kpreid): Review how necessary it is to scope this inside | 1601 // TODO(kpreid): Review how necessary it is to scope this inside |
1480 // attachDocument. The issues are: | 1602 // attachDocument. The issues are: |
1481 // * Using authority or types from a different virtual document (check | 1603 // * Using authority or types from a different virtual document (check |
1482 // the things that used to be TameHTMLDocument.doc___ in particular) | 1604 // the things that used to be TameHTMLDocument.doc___ in particular) |
1483 // * Using nodes from a different real document (Domita would seem to | 1605 // * Using nodes from a different real document (Domita would seem to |
1484 // be vulnerable to this?) | 1606 // be vulnerable to this?) |
(...skipping 29 matching lines...) Expand all Loading... | |
1514 delete np(node).proxyHandler; // no longer needed | 1636 delete np(node).proxyHandler; // no longer needed |
1515 | 1637 |
1516 ExpandoProxyHandler.register(proxiedNode, node); | 1638 ExpandoProxyHandler.register(proxiedNode, node); |
1517 TameNodeConf.confide(proxiedNode, node); | 1639 TameNodeConf.confide(proxiedNode, node); |
1518 tamingProxies.set(node, proxiedNode); | 1640 tamingProxies.set(node, proxiedNode); |
1519 | 1641 |
1520 node = proxiedNode; | 1642 node = proxiedNode; |
1521 } | 1643 } |
1522 | 1644 |
1523 if (feral) { | 1645 if (feral) { |
1524 if (node.nodeType === 1) { | 1646 if (feral.nodeType === 1) { |
1525 // Elements must only be tamed once; to do otherwise would be | 1647 // Elements must only be tamed once; to do otherwise would be |
1526 // a bug in Domado. | 1648 // a bug in Domado. |
1527 taming.tamesTo(feral, node); | 1649 taming.tamesTo(feral, node); |
1528 } else { | 1650 } else { |
1529 // Other node types are tamed every time they are encountered; | 1651 // Other node types are tamed every time they are encountered; |
1530 // we simply remember the latest taming here. | 1652 // we simply remember the latest taming here. |
1531 taming.reTamesTo(feral, node); | 1653 taming.reTamesTo(feral, node); |
1532 } | 1654 } |
1533 } else { | 1655 } else { |
1534 // If guest code passes a node of its own with no feral counterpart to | 1656 // If guest code passes a node of its own with no feral counterpart to |
(...skipping 170 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1705 case html4.atype.URI_FRAGMENT: | 1827 case html4.atype.URI_FRAGMENT: |
1706 if (realValue && '#' === realValue.charAt(0)) { | 1828 if (realValue && '#' === realValue.charAt(0)) { |
1707 return unsuffix(realValue, idSuffix, null); | 1829 return unsuffix(realValue, idSuffix, null); |
1708 } else { | 1830 } else { |
1709 return null; | 1831 return null; |
1710 } | 1832 } |
1711 default: | 1833 default: |
1712 return realValue; | 1834 return realValue; |
1713 } | 1835 } |
1714 } | 1836 } |
1715 | |
1716 /** | |
1717 * Undoes some of the changes made by sanitizeHtml, e.g. stripping ID | |
1718 * prefixes. | |
1719 */ | |
1720 function tameInnerHtml(htmlText) { | |
1721 var out = []; | |
1722 innerHtmlTamer(htmlText, out); | |
1723 return out.join(''); | |
1724 } | |
1725 var innerHtmlTamer = html.makeSaxParser({ | |
1726 startTag: function (tagName, attribs, out) { | |
1727 tagName = realToVirtualElementName(tagName); | |
1728 out.push('<', tagName); | |
1729 for (var i = 0; i < attribs.length; i += 2) { | |
1730 var aname = '' + attribs[+i]; | |
1731 var atype = htmlSchema.attribute(tagName, aname).type; | |
1732 var value = attribs[i + 1]; | |
1733 if (aname !== 'target' && atype !== void 0) { | |
1734 value = virtualizeAttributeValue(atype, value); | |
1735 if (typeof value === 'string') { | |
1736 out.push(' ', aname, '="', html.escapeAttrib(value), '"'); | |
1737 } | |
1738 } else if (cajaPrefRe.test(aname)) { | |
1739 out.push(' ', aname.substring(cajaPrefix.length), '="', | |
1740 html.escapeAttrib(value), '"'); | |
1741 } | |
1742 } | |
1743 out.push('>'); | |
1744 }, | |
1745 endTag: function (tagName, out) { | |
1746 var rempty = htmlSchema.element(tagName).empty; | |
1747 tagName = realToVirtualElementName(tagName); | |
1748 var vempty = htmlSchema.element(tagName).empty; | |
1749 if (vempty && !rempty) { | |
1750 // omit end tag because the browser doesn't see the virtualized | |
1751 // element as empty | |
1752 return; | |
1753 } | |
1754 out.push('</', tagName, '>'); | |
1755 }, | |
1756 pcdata: function (text, out) { out.push(text); }, | |
1757 rcdata: function (text, out) { out.push(text); }, | |
1758 cdata: function (text, out) { out.push(text); } | |
1759 }); | |
1760 | 1837 |
1761 function getSafeTargetAttribute(tagName, attribName, value) { | 1838 function getSafeTargetAttribute(tagName, attribName, value) { |
1762 if (value !== null) { | 1839 if (value !== null) { |
1763 value = String(value); | 1840 value = String(value); |
1764 for (var i = 0; i < optTargetAttributePresets.whitelist.length; ++i) { | 1841 for (var i = 0; i < optTargetAttributePresets.whitelist.length; ++i) { |
1765 if (optTargetAttributePresets.whitelist[i] === value) { | 1842 if (optTargetAttributePresets.whitelist[i] === value) { |
1766 return value; | 1843 return value; |
1767 } | 1844 } |
1768 } | 1845 } |
1769 } | 1846 } |
(...skipping 132 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1902 css.push(propName + ' : ' + propValue); | 1979 css.push(propName + ' : ' + propValue); |
1903 } | 1980 } |
1904 return css.join(' ; '); | 1981 return css.join(' ; '); |
1905 // Frames are ambient, so disallow reference. | 1982 // Frames are ambient, so disallow reference. |
1906 case html4.atype.FRAME_TARGET: | 1983 case html4.atype.FRAME_TARGET: |
1907 return getSafeTargetAttribute(tagName, attribName, value); | 1984 return getSafeTargetAttribute(tagName, attribName, value); |
1908 default: | 1985 default: |
1909 return null; | 1986 return null; |
1910 } | 1987 } |
1911 } | 1988 } |
1989 ······ | |
1990 // Implementation of HTML5 "HTML fragment serialization algorithm" | |
1991 // per http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end .html#html-fragment-serialization-algorithm | |
1992 // as of 2012-09-11. | |
1993 // | |
1994 // Per HTML5: "Warning! It is possible that the output of this algorithm, | |
1995 // if parsed with an HTML parser, will not return the original tree | |
1996 // structure." Therefore, an innerHTML round-trip on a safe (from Caja's | |
1997 // perspective) but malicious DOM may be able to attack guest code. | |
1998 // TODO(kpreid): Evaluate desirability of prohibiting the worst cases of | |
1999 // this in our DOM mutators. | |
2000 function htmlFragmentSerialization(tameRoot) { | |
2001 tameRoot = TameNodeT.coerce(tameRoot); | |
2002 var sl = []; | |
2003 | |
2004 // Note: This algorithm is implemented in terms of tame nodes, not | |
2005 // feral nodes; therefore, it requires no access checks as it yields | |
2006 // only information which clients can obtain by object access. | |
2007 function recur(tameParent) { | |
2008 var nodes = tameParent.childNodes; | |
2009 var nNodes = nodes.length; | |
2010 for (var i = 0; i < nNodes; i++) { | |
2011 var tameCurrent = nodes.item(i); | |
2012 switch (tameCurrent.nodeType) { | |
2013 case 1: // Element | |
2014 // TODO(kpreid): namespace issues | |
2015 var tagName = tameCurrent.tagName; | |
2016 if (tagName === undefined) { | |
2017 // foreign node case | |
2018 continue; | |
2019 } | |
2020 tagName = tagName.toLowerCase(); | |
2021 // TODO(kpreid): not conformant | |
2022 sl.push('<', tagName); | |
2023 var attrs = tameCurrent.attributes; | |
2024 var nAttrs = attrs.length; | |
2025 for (var j = 0; j < nAttrs; j++) { | |
2026 var attr = attrs.item(j); | |
2027 var aName = attr.name; | |
2028 if (aName === 'target') { | |
2029 // hide Caja-added link target attributes | |
2030 // TODO(kpreid): Shouldn't these be hidden in the attributes | |
2031 // list? This special case (and the one below) is emulating | |
2032 // tested-for behavior in a previous .innerHTML | |
2033 // implementation, not written from first principles. | |
2034 continue; | |
2035 } | |
2036 var aValue = attr.value; | |
2037 if (aValue === null) { | |
2038 // rejected by virtualizeAttributeValue | |
2039 // TODO(kpreid): Shouldn't these be hidden in the attributes | |
2040 // list? | |
2041 continue; | |
2042 } | |
2043 // TODO(kpreid): check escapeAttrib conformance | |
2044 sl.push(' ', attr.name, '="', html.escapeAttrib(aValue), '"'); | |
2045 } | |
2046 sl.push('>'); | |
2047 switch (tagName) { | |
2048 case 'area': | |
2049 case 'base': | |
2050 case 'basefont': | |
2051 case 'bgsound': | |
2052 case 'br': | |
2053 case 'col': | |
2054 case 'command': | |
2055 case 'embed': | |
2056 case 'frame': | |
2057 case 'hr': | |
2058 case 'img': | |
2059 case 'input': | |
2060 case 'keygen': | |
2061 case 'link': | |
2062 case 'meta': | |
2063 case 'param': | |
2064 case 'source': | |
2065 case 'track': | |
2066 case 'wbr': | |
2067 // do nothing | |
2068 break; | |
2069 case 'pre': | |
2070 case 'textarea': | |
2071 case 'listing': | |
2072 if (tameCurrent.firstChild && | |
2073 tameCurrent.firstChild.nodeType === 3 && | |
2074 tameCurrent.firstChild.data[0] === '\n') { | |
2075 sl.push('\n'); | |
2076 } | |
2077 // fallthrough | |
2078 default: | |
2079 recur(tameCurrent); | |
2080 sl.push('</', tagName, '>'); | |
2081 } | |
2082 break; | |
2083 case 3: // Text | |
2084 switch (tameCurrent.parentNode.tagName.toLowerCase()) { | |
2085 // TODO(kpreid): namespace | |
2086 case 'style': | |
2087 case 'script': | |
2088 case 'xmp': | |
2089 case 'iframe': | |
2090 case 'noembed': | |
2091 case 'noframes': | |
2092 case 'plaintext': | |
2093 case 'noscript': | |
2094 sl.push(tameCurrent.data); | |
2095 break; | |
2096 default: | |
2097 sl.push(html.escapeAttrib(tameCurrent.data)); | |
2098 break; | |
2099 } | |
2100 break; | |
2101 case 8: // Comment | |
2102 sl.push('<', '!--', tameCurrent.data, '-->'); | |
2103 break; | |
2104 case 7: // ProcessingInstruction | |
2105 sl.push('<?', tameCurrent.target, ' ', tameCurrent.data, '>'); | |
2106 break; | |
2107 case 10: // DocumentType | |
2108 sl.push('<', '!DOCTYPE ', tameCurrent.name, '>'); | |
2109 break; | |
2110 default: | |
2111 if (typeof console !== 'undefined') { | |
2112 console.error('Domado internal: HTML fragment serialization ' | |
2113 + 'algorithm met unexpected node type ' | |
2114 + tameCurrent.nodeType); | |
2115 } | |
2116 break; | |
2117 } | |
2118 } | |
2119 } | |
2120 recur(tameRoot); | |
2121 | |
2122 return sl.join(''); | |
2123 } | |
1912 | 2124 |
1913 // Property descriptors which are independent of any feral object. | 2125 // Property descriptors which are independent of any feral object. |
1914 /** | 2126 /** |
1915 * Property descriptor which throws on access. | 2127 * Property descriptor which throws on access. |
1916 */ | 2128 */ |
1917 var P_UNIMPLEMENTED = { | 2129 var P_UNIMPLEMENTED = { |
1918 enumerable: true, | 2130 enumerable: true, |
1919 get: cajaVM.def(function () { | 2131 get: cajaVM.def(function () { |
1920 throw new Error('Not implemented'); | 2132 throw new Error('Not implemented'); |
1921 }) | 2133 }) |
1922 }; | 2134 }; |
1923 /** | 2135 /** |
1924 * Property descriptor for an unsettable constant attribute (like DOM | 2136 * Property descriptor for an unsettable constant attribute (like DOM |
1925 * attributes such as nodeType). | 2137 * attributes such as nodeType). |
1926 */ | 2138 */ |
1927 function P_constant(value) { | 2139 function P_constant(value) { |
1928 return { enumerable: true, value: value }; | 2140 return { enumerable: true, value: value }; |
1929 } | 2141 } |
1930 | 2142 |
1931 /** | 2143 /** |
1932 * Construct property descriptors suitable for taming objects which use | 2144 * Construct property descriptors suitable for taming objects which use |
1933 * the specified confidence, such that confidence.p(obj).feral is the | 2145 * the specified confidence, such that confidence.p(obj).feral is the |
1934 * feral object to forward to and confidence.p(obj).editable is an | 2146 * feral object to forward to and confidence.p(obj).policy is a node |
1935 * editable/readonly flag. | 2147 * policy object for writability decisions. |
1936 * | 2148 * |
1937 * Lowercase properties are property descriptors; uppercase ones are | 2149 * Lowercase properties are property descriptors; uppercase ones are |
1938 * constructors for parameterized property descriptors. | 2150 * constructors for parameterized property descriptors. |
1939 */ | 2151 */ |
1940 function PropertyTaming(confidence) { | 2152 function PropertyTaming(confidence) { |
1941 var p = confidence.p; | 2153 var p = confidence.p; |
1942 var method = confidence.protectMethod; | 2154 var method = confidence.protectMethod; |
1943 | 2155 |
1944 return cajaVM.def({ | 2156 return cajaVM.def({ |
1945 /** | 2157 /** |
(...skipping 12 matching lines...) Expand all Loading... | |
1958 * Property descriptor for properties which have the value the feral | 2170 * Property descriptor for properties which have the value the feral |
1959 * object does, and are assignable if the wrapper is editable. | 2171 * object does, and are assignable if the wrapper is editable. |
1960 */ | 2172 */ |
1961 rw: { | 2173 rw: { |
1962 enumerable: true, | 2174 enumerable: true, |
1963 extendedAccessors: true, | 2175 extendedAccessors: true, |
1964 get: method(function (prop) { | 2176 get: method(function (prop) { |
1965 return p(this).feral[prop]; | 2177 return p(this).feral[prop]; |
1966 }), | 2178 }), |
1967 set: method(function (value, prop) { | 2179 set: method(function (value, prop) { |
1968 if (!p(this).editable) { throw new Error(NOT_EDITABLE); } | 2180 var privates = p(this); |
1969 p(this).feral[prop] = value; | 2181 privates.policy.requireEditable(); |
2182 privates.feral[prop] = value; | |
1970 }) | 2183 }) |
1971 }, | 2184 }, |
1972 | 2185 |
1973 /** | 2186 /** |
1974 * Property descriptor for properties which have the value the feral | 2187 * Property descriptor for properties which have the value the feral |
1975 * object does, and are assignable (with a predicate restricting the | 2188 * object does, and are assignable (with a predicate restricting the |
1976 * values which may be assigned) if the wrapper is editable. | 2189 * values which may be assigned) if the wrapper is editable. |
1977 * TODO(kpreid): Use guards instead of predicates. | 2190 * TODO(kpreid): Use guards instead of predicates. |
1978 */ | 2191 */ |
1979 RWCond: function (predicate) { | 2192 RWCond: function (predicate) { |
1980 return { | 2193 return { |
1981 enumerable: true, | 2194 enumerable: true, |
1982 extendedAccessors: true, | 2195 extendedAccessors: true, |
1983 get: method(function (prop) { | 2196 get: method(function (prop) { |
1984 return p(this).feral[prop]; | 2197 return p(this).feral[prop]; |
1985 }), | 2198 }), |
1986 set: method(function (value, prop) { | 2199 set: method(function (value, prop) { |
1987 var privates = p(this); | 2200 var privates = p(this); |
1988 if (!privates.editable) { throw new Error(NOT_EDITABLE); } | 2201 privates.policy.requireEditable(); |
1989 if (predicate(value)) { | 2202 if (predicate(value)) { |
1990 privates.feral[prop] = value; | 2203 privates.feral[prop] = value; |
1991 } | 2204 } |
1992 }) | 2205 }) |
1993 }; | 2206 }; |
1994 }, | 2207 }, |
1995 | 2208 |
1996 /** | 2209 /** |
1997 * Property descriptor for properties which have a different name | 2210 * Property descriptor for properties which have a different name |
1998 * than what they map to (e.g. labelElement.htmlFor vs. | 2211 * than what they map to (e.g. labelElement.htmlFor vs. |
(...skipping 15 matching lines...) Expand all Loading... | |
2014 }, | 2227 }, |
2015 | 2228 |
2016 /** | 2229 /** |
2017 * Property descriptor for forwarded properties which have node values | 2230 * Property descriptor for forwarded properties which have node values |
2018 * which may be nodes that might be outside of the virtual document. | 2231 * which may be nodes that might be outside of the virtual document. |
2019 */ | 2232 */ |
2020 related: { | 2233 related: { |
2021 enumerable: true, | 2234 enumerable: true, |
2022 extendedAccessors: true, | 2235 extendedAccessors: true, |
2023 get: method(function (prop) { | 2236 get: method(function (prop) { |
2024 if (!('editable' in p(this))) { | 2237 var privates = p(this); |
2025 throw new Error( | 2238 if (privates.policy.upwardNavigation) { |
2026 "Internal error: related property tamer can only" | 2239 // TODO(kpreid): Can we move this check *into* tameRelatedNode? |
2027 + " be applied to objects with an editable flag"); | 2240 return tameRelatedNode(privates.feral[prop], defaultTameNode); |
2241 } else { | |
2242 return null; | |
2028 } | 2243 } |
2029 return tameRelatedNode(p(this).feral[prop], | |
2030 p(this).editable, | |
2031 defaultTameNode); | |
2032 }) | 2244 }) |
2033 }, | 2245 }, |
2034 | 2246 |
2035 /** | 2247 /** |
2036 * Property descriptor which maps to an attribute or property, is | 2248 * Property descriptor which maps to an attribute or property, is |
2037 * assignable, and has the value transformed in some way. | 2249 * assignable, and has the value transformed in some way. |
2038 * @param {boolean} useAttrGetter true if the getter should delegate | 2250 * @param {boolean} useAttrGetter true if the getter should delegate |
2039 * to {@code this.getAttribute}. That method is assumed to | 2251 * to {@code this.getAttribute}. That method is assumed to |
2040 * already be trusted though {@code toValue} is still called on | 2252 * already be trusted though {@code toValue} is still called on |
2041 * the result. | 2253 * the result. |
(...skipping 25 matching lines...) Expand all Loading... | |
2067 : method(function (name) { | 2279 : method(function (name) { |
2068 return toValue.call(this, p(this).feral[name]); | 2280 return toValue.call(this, p(this).feral[name]); |
2069 }) | 2281 }) |
2070 }; | 2282 }; |
2071 if (fromValue) { | 2283 if (fromValue) { |
2072 desc.set = useAttrSetter | 2284 desc.set = useAttrSetter |
2073 ? method(function (value, name) { | 2285 ? method(function (value, name) { |
2074 this.setAttribute(name, fromValue.call(this, value)); | 2286 this.setAttribute(name, fromValue.call(this, value)); |
2075 }) | 2287 }) |
2076 : method(function (value, name) { | 2288 : method(function (value, name) { |
2077 if (!p(this).editable) { throw new Error(NOT_EDITABLE); } | 2289 var privates = p(this); |
2078 p(this).feral[name] = fromValue.call(this, value); | 2290 privates.policy.requireEditable(); |
2291 privates.feral[name] = fromValue.call(this, value); | |
2079 }); | 2292 }); |
2080 } | 2293 } |
2081 return desc; | 2294 return desc; |
2082 }, | 2295 }, |
2083 filterAttr: function(toValue, fromValue) { | 2296 filterAttr: function(toValue, fromValue) { |
2084 return NP.filter(true, toValue, true, fromValue); | 2297 return NP.filter(true, toValue, true, fromValue); |
2085 }, | 2298 }, |
2086 filterProp: function(toValue, fromValue) { | 2299 filterProp: function(toValue, fromValue) { |
2087 return NP.filter(false, toValue, false, fromValue); | 2300 return NP.filter(false, toValue, false, fromValue); |
2088 } | 2301 } |
2089 }); | 2302 }); |
2090 } | 2303 } |
2091 cajaVM.def(PropertyTaming); // and its prototype | 2304 cajaVM.def(PropertyTaming); // and its prototype |
2092 | 2305 |
2093 // TODO(kpreid): We have completely unrelated things called 'np' and 'NP'. | 2306 // TODO(kpreid): We have completely unrelated things called 'np' and 'NP'. |
2094 var NP = new PropertyTaming(TameNodeConf); | 2307 var NP = new PropertyTaming(TameNodeConf); |
2095 | 2308 |
2096 // Node-specific property accessors: | 2309 // Node-specific property accessors: |
2097 /** | 2310 /** |
2098 * Property descriptor for forwarded properties which have node values | 2311 * Property descriptor for forwarded properties which have node values |
2099 * and are descendants of this node. | 2312 * and are descendants of this node. |
2100 */ | 2313 */ |
2101 var NP_tameDescendant = { | 2314 var NP_tameDescendant = { |
2102 enumerable: true, | 2315 enumerable: true, |
2103 extendedAccessors: true, | 2316 extendedAccessors: true, |
2104 get: nodeMethod(function (prop) { | 2317 get: nodeMethod(function (prop) { |
2105 return defaultTameNode(np(this).feral[prop], | 2318 var privates = np(this); |
2106 np(this).childrenEditable); | 2319 if (privates.policy.childrenVisible) { |
2320 return defaultTameNode(np(this).feral[prop]); | |
2321 } else { | |
2322 return null; | |
2323 } | |
2107 }) | 2324 }) |
2108 }; | 2325 }; |
2109 | 2326 |
2110 var nodeExpandos = new WeakMap(true); | 2327 var nodeExpandos = new WeakMap(true); |
2111 /** | 2328 /** |
2112 * Return the object which stores expando properties for a given | 2329 * Return the object which stores expando properties for a given |
2113 * host DOM node. | 2330 * host DOM node. |
2114 */ | 2331 */ |
2115 function getNodeExpandoStorage(node) { | 2332 function getNodeExpandoStorage(node) { |
2116 var s = nodeExpandos.get(node); | 2333 var s = nodeExpandos.get(node); |
2117 if (s === undefined) { | 2334 if (s === undefined) { |
2118 nodeExpandos.set(node, s = {}); | 2335 nodeExpandos.set(node, s = {}); |
2119 } | 2336 } |
2120 return s; | 2337 return s; |
2121 } | 2338 } |
2122 ······ | 2339 ······ |
2123 var nodeClassNoImplWarnings = {}; | 2340 var nodeClassNoImplWarnings = {}; |
2124 | 2341 |
2125 function makeTameNodeByType(node, editable) { | 2342 function makeTameNodeByType(node) { |
2126 switch (node.nodeType) { | 2343 switch (node.nodeType) { |
2127 case 1: // Element | 2344 case 1: // Element |
2128 var tagName = node.tagName.toLowerCase(); | 2345 var tagName = node.tagName.toLowerCase(); |
2129 var schemaElem = htmlSchema.element(tagName); | 2346 var schemaElem = htmlSchema.element(tagName); |
2130 if (schemaElem.allowed || tagName === 'script') { | 2347 if (schemaElem.allowed || tagName === 'script') { |
2131 // <script> is specifically allowed because we make provisions | 2348 // <script> is specifically allowed because we make provisions |
2132 // for controlling its content and src. | 2349 // for controlling its content and src. |
2133 var domInterfaceName = schemaElem.domInterface; | 2350 var domInterfaceName = schemaElem.domInterface; |
2134 if (nodeTamers.hasOwnProperty(domInterfaceName)) { | 2351 if (nodeTamers.hasOwnProperty(domInterfaceName)) { |
2135 return new nodeTamers[domInterfaceName]( | 2352 return new nodeTamers[domInterfaceName](node); |
2136 node, editable, editable); | |
2137 } else { | 2353 } else { |
2138 if (!nodeClassNoImplWarnings[domInterfaceName]) { | 2354 if (!nodeClassNoImplWarnings[domInterfaceName]) { |
2139 nodeClassNoImplWarnings[domInterfaceName] = true; | 2355 nodeClassNoImplWarnings[domInterfaceName] = true; |
2140 if (typeof console !== 'undefined') { | 2356 if (typeof console !== 'undefined') { |
2141 console.warn("Domado: " + domInterfaceName + " is not " + | 2357 console.warn("Domado: " + domInterfaceName + " is not " + |
2142 "tamed; its specific properties/methods will not be " + | 2358 "tamed; its specific properties/methods will not be " + |
2143 "available on <" + | 2359 "available on <" + |
2144 htmlSchema.realToVirtualElementName(tagName) + ">."); | 2360 htmlSchema.realToVirtualElementName(tagName) + ">."); |
2145 } | 2361 } |
2146 } | 2362 } |
2147 return new TameElement(node, editable, editable); | 2363 return new TameElement(node); |
2148 } | 2364 } |
2149 } else { | 2365 } else { |
2150 // If an unrecognized or unsafe node, return a | 2366 // If an unrecognized or unsafe node, return a |
2151 // placeholder that doesn't prevent tree navigation, | 2367 // placeholder that doesn't prevent tree navigation, |
2152 // but that doesn't allow mutation or leak attribute | 2368 // but that doesn't allow mutation or leak attribute |
2153 // information. | 2369 // information. |
2154 return new TameOpaqueNode(node, editable); | 2370 return new TameOpaqueNode(node); |
2155 } | 2371 } |
2156 case 2: // Attr | 2372 case 2: // Attr |
2157 // Cannot generically wrap since we must have access to the | 2373 // Cannot generically wrap since we must have access to the |
2158 // owner element | 2374 // owner element |
2159 throw 'Internal: Attr nodes cannot be generically wrapped'; | 2375 throw 'Internal: Attr nodes cannot be generically wrapped'; |
2160 case 3: // Text | 2376 case 3: // Text |
2161 case 4: // CDATA Section Node | 2377 case 4: // CDATA Section Node |
2162 return new TameTextNode(node, editable); | 2378 return new TameTextNode(node); |
2163 case 8: // Comment | 2379 case 8: // Comment |
2164 return new TameCommentNode(node, editable); | 2380 return new TameCommentNode(node); |
2165 case 11: // Document Fragment | 2381 case 11: // Document Fragment |
2166 return new TameBackedNode(node, editable, editable); | 2382 return new TameBackedNode(node); |
2167 default: | 2383 default: |
2168 return new TameOpaqueNode(node, editable); | 2384 return new TameOpaqueNode(node); |
2169 } | 2385 } |
2170 } | 2386 } |
2171 | 2387 |
2172 /** | 2388 /** |
2173 * returns a tame DOM node. | 2389 * returns a tame DOM node. |
2174 * @param {Node} node | 2390 * @param {Node} node |
2175 * @param {boolean} editable | 2391 * @param {boolean} foreign |
2176 * @see <a href="http://www.w3.org/TR/DOM-Level-2-HTML/html.html" | 2392 * @see <a href="http://www.w3.org/TR/DOM-Level-2-HTML/html.html" |
2177 * >DOM Level 2</a> | 2393 * >DOM Level 2</a> |
2178 */ | 2394 */ |
2179 function defaultTameNode(node, editable, foreign) { | 2395 function defaultTameNode(node, foreign) { |
2180 if (node === null || node === void 0) { return null; } | 2396 if (node === null || node === void 0) { return null; } |
2181 node = makeDOMAccessible(node); | 2397 node = makeDOMAccessible(node); |
2182 // TODO(mikesamuel): make sure it really is a DOM node | 2398 // TODO(mikesamuel): make sure it really is a DOM node |
2183 | 2399 |
2184 if (taming.hasTameTwin(node)) { | 2400 if (taming.hasTameTwin(node)) { |
2185 return taming.tame(node); | 2401 return taming.tame(node); |
2186 } | 2402 } |
2187 | 2403 |
2404 if (foreign) { | |
2405 vdocContainsForeignNodes = true; | |
2406 } | |
2407 | |
2188 var tamed = foreign | 2408 var tamed = foreign |
2189 ? new TameForeignNode(node, editable) | 2409 ? new TameForeignNode(node) |
2190 : makeTameNodeByType(node, editable); | 2410 : makeTameNodeByType(node); |
2191 tamed = finishNode(tamed); | 2411 tamed = finishNode(tamed); |
2192 | 2412 |
2193 return tamed; | 2413 return tamed; |
2194 } | 2414 } |
2195 | 2415 |
2196 function tameRelatedNode(node, editable, tameNodeCtor) { | 2416 function tameRelatedNode(node, tameNodeCtor) { |
2197 if (node === null || node === void 0) { return null; } | 2417 if (node === null || node === void 0) { return null; } |
2198 if (node === np(tameDocument).feralContainerNode) { | 2418 if (node === np(tameDocument).feralContainerNode) { |
2199 if (np(tameDocument).editable && !editable) { | |
2200 // FIXME: return a non-editable version instead | |
2201 throw new Error(NOT_EDITABLE); | |
2202 } | |
2203 return tameDocument; | 2419 return tameDocument; |
2204 } | 2420 } |
2205 | 2421 |
2206 node = makeDOMAccessible(node); | 2422 node = makeDOMAccessible(node); |
2207 | 2423 |
2208 // Catch errors because node might be from a different domain. | 2424 // Catch errors because node might be from a different domain. |
2209 try { | 2425 try { |
2210 var docElem = node.ownerDocument.documentElement; | 2426 var docElem = node.ownerDocument.documentElement; |
2211 for (var ancestor = node; | 2427 for (var ancestor = node; |
2212 ancestor; | 2428 ancestor; |
2213 ancestor = makeDOMAccessible(ancestor.parentNode)) { | 2429 ancestor = makeDOMAccessible(ancestor.parentNode)) { |
2214 if (idClassPattern.test(ancestor.className)) { | 2430 if (idClassPattern.test(ancestor.className)) { |
2215 return tameNodeCtor(node, editable); | 2431 return tameNodeCtor(node); |
2216 } else if (ancestor === docElem) { | 2432 } else if (ancestor === docElem) { |
2217 return null; | 2433 return null; |
2218 } | 2434 } |
2219 } | 2435 } |
2220 return tameNodeCtor(node, editable); | 2436 return tameNodeCtor(node); |
2221 } catch (e) {} | 2437 } catch (e) {} |
2222 return null; | 2438 return null; |
2223 } | 2439 } |
2224 | 2440 |
2225 domicile.tameNodeAsForeign = function(node) { | 2441 domicile.tameNodeAsForeign = function(node) { |
2226 return defaultTameNode(node, true, true); | 2442 return defaultTameNode(node, true); |
2227 }; | 2443 }; |
2228 | 2444 |
2229 /** | 2445 /** |
2230 * Returns the length of a raw DOM Nodelist object, working around | 2446 * Returns the length of a raw DOM Nodelist object, working around |
2231 * NamedNodeMap bugs in IE, Opera, and Safari as discussed at | 2447 * NamedNodeMap bugs in IE, Opera, and Safari as discussed at |
2232 * http://code.google.com/p/google-caja/issues/detail?id=935 | 2448 * http://code.google.com/p/google-caja/issues/detail?id=935 |
2233 * | 2449 * |
2234 * @param nodeList a DOM NodeList. | 2450 * @param nodeList a DOM NodeList. |
2235 * | 2451 * |
2236 * @return the number of nodes in the NodeList. | 2452 * @return the number of nodes in the NodeList. |
2237 */ | 2453 */ |
2238 function getNodeListLength(nodeList) { | 2454 function getNodeListLength(nodeList) { |
2239 var limit = nodeList.length; | 2455 var limit = nodeList.length; |
2240 if (limit !== +limit) { limit = 1/0; } | 2456 if (limit !== +limit) { limit = 1/0; } |
2241 return limit; | 2457 return limit; |
2242 } | 2458 } |
2243 | 2459 |
2460 function nodeListEqualsArray(nodeList, array) { | |
2461 var nll = getNodeListLength(nodeList); | |
2462 if (nll !== array.length) { | |
2463 return false; | |
2464 } else { | |
2465 for (var i = 0; i < nll; i++) { | |
2466 if (nodeList[i] !== array[i]) { | |
2467 return false; | |
2468 } | |
2469 } | |
2470 return true; | |
2471 } | |
2472 } | |
2473 | |
2474 // Commentary on foreign node children in NodeLists: | |
2475 // | |
2476 // The children of a foreign node are an implementation detail which | |
2477 // guest code should not be permitted to see. Therefore, we must hide them | |
2478 // from appearing in NodeLists. This would be a straightforward matter of | |
2479 // filtering, except that NodeLists are "live", reflecting DOM changes | |
2480 // immediately; and DOM changes change the membership and numeric indexes | |
2481 // of the NodeList. | |
2482 // | |
2483 // One could imagine caching the outcomes: given an index, scan the host | |
2484 // list until the required number of visible-to-guest nodes have been | |
2485 // found, cache the indexes and node, and then validate the cache entry | |
2486 // later by comparing indexes, but that is not sufficient; consider if a | |
2487 // foreign child is deleted, and at the same time a guest-visible node | |
2488 // is added in a similar document position; then the index of a guest node | |
2489 // which is after that position *should* increase, but this cache cannot | |
2490 // tell. | |
2491 // | |
2492 // Therefore, we do cache the list, but we must re-validate the cache | |
2493 // from 0 up to the desired index on every access. | |
2494 /** | |
2495 * This is NOT a node list taming. This is a component for performing | |
2496 * foreign node filtering only. | |
2497 */ | |
2498 function NodeListFilter(feralNodeList) { | |
2499 feralNodeList = makeDOMAccessible(feralNodeList); | |
2500 var expectation = []; | |
2501 var filteredCache = []; | |
2502 | |
2503 function calcUpTo(index) { | |
2504 var feralLength = getNodeListLength(feralNodeList); | |
2505 var feralIndex = 0; | |
2506 | |
2507 // Validate cache | |
2508 if (feralLength < expectation.length) { | |
2509 expectation = []; | |
2510 filteredCache = []; | |
2511 feralIndex = 0; | |
2512 } else { | |
2513 for ( | |
2514 ; | |
2515 feralIndex < expectation.length && feralIndex < feralLength; | |
2516 feralIndex++) { | |
2517 if (feralNodeList[feralIndex] !== expectation[feralIndex]) { | |
2518 expectation = []; | |
2519 filteredCache = []; | |
2520 feralIndex = 0; | |
2521 break; | |
2522 } | |
2523 } | |
2524 } | |
2525 | |
2526 // Extend cache | |
2527 nodeListScan: for ( | |
2528 ; | |
2529 feralIndex < feralLength && filteredCache.length <= index; | |
2530 feralIndex++) { | |
2531 var node = feralNodeList[feralIndex]; | |
2532 expectation.push(node); | |
2533 makeDOMAccessible(node); | |
2534 // Filter out foreign nodes' descendants | |
2535 walkUp: for ( | |
2536 var ancestor = makeDOMAccessible(node.parentNode); | |
2537 ancestor !== null; | |
2538 ancestor = makeDOMAccessible(ancestor.parentNode)) { | |
2539 if (taming.hasTameTwin(ancestor)) { | |
2540 if (taming.tame(ancestor) instanceof TameForeignNode) { | |
2541 // Every foreign node is already tamed as foreign, by | |
2542 // definition. | |
2543 continue nodeListScan; | |
2544 } else { | |
2545 // Reached a node known to be non-foreign. | |
2546 break walkUp; | |
2547 } | |
2548 } | |
2549 } | |
2550 // Not a foreign node descendant, so include it in the list. | |
2551 filteredCache.push(node); | |
2552 } | |
2553 } | |
2554 return cajaVM.def({ | |
2555 getLength: function() { | |
2556 if (vdocContainsForeignNodes) { | |
2557 calcUpTo(Infinity); | |
2558 return filteredCache.length; | |
2559 } else { | |
2560 return getNodeListLength(feralNodeList); | |
2561 } | |
2562 }, | |
2563 item: function(i) { | |
2564 if (vdocContainsForeignNodes) { | |
2565 calcUpTo(i); | |
2566 return filteredCache[i]; | |
2567 } else { | |
2568 return feralNodeList[i]; | |
2569 } | |
2570 } | |
2571 }); | |
2572 } | |
2573 | |
2244 /** | 2574 /** |
2245 * Constructs a NodeList-like object. | 2575 * Constructs a NodeList-like object. |
2246 * | 2576 * |
2247 * @param tamed a JavaScript array that will be populated and decorated | 2577 * @param tamed a JavaScript array that will be populated and decorated |
2248 * with the DOM NodeList API. If it has existing elements they will | 2578 * with the DOM NodeList API. If it has existing elements they will |
2249 * precede the actual NodeList elements. | 2579 * precede the actual NodeList elements. |
2250 * @param nodeList an array-like object supporting a "length" property | 2580 * @param nodeList an array-like object supporting a "length" property |
2251 * and "[]" numeric indexing, or a raw DOM NodeList; | 2581 * and "[]" numeric indexing, or a raw DOM NodeList; |
2252 * @param editable whether the tame nodes wrapped by this object | |
2253 * should permit editing. | |
2254 * @param opt_tameNodeCtor a function for constructing tame nodes | 2582 * @param opt_tameNodeCtor a function for constructing tame nodes |
2255 * out of raw DOM nodes. | 2583 * out of raw DOM nodes. |
2256 */ | 2584 */ |
2257 function mixinNodeList(tamed, nodeList, editable, opt_tameNodeCtor) { | 2585 function mixinNodeList(tamed, nodeList, opt_tameNodeCtor) { |
2258 // TODO(kpreid): Under a true ES5 environment, node lists should be | 2586 // TODO(kpreid): Under a true ES5 environment, node lists should be |
2259 // proxies so that they preserve liveness of the original lists. | 2587 // proxies so that they preserve liveness of the original lists. |
2260 // This should be controlled by an option. | 2588 // This should be controlled by an option. |
2261 | 2589 // UPDATE: We have live NodeLists as TameNodeList and TameOptionsList. |
2262 var limit = getNodeListLength(nodeList); | 2590 // This is not live, but is used in less-mainstream cases. |
2591 | |
2592 var visibleList = new NodeListFilter(nodeList); | |
2593 | |
2594 var limit = visibleList.getLength(); | |
2263 if (limit > 0 && !opt_tameNodeCtor) { | 2595 if (limit > 0 && !opt_tameNodeCtor) { |
2264 throw 'Internal: Nonempty mixinNodeList() without a tameNodeCtor'; | 2596 throw 'Internal: Nonempty mixinNodeList() without a tameNodeCtor'; |
2265 } | 2597 } |
2266 | 2598 |
2267 for (var i = tamed.length, j = 0; j < limit && nodeList[+j]; ++i, ++j) { | 2599 for (var i = tamed.length, j = 0; |
2268 tamed[+i] = opt_tameNodeCtor(nodeList[+j], editable); | 2600 j < limit && visibleList.item(j); |
2601 ++i, ++j) { | |
2602 tamed[+i] = opt_tameNodeCtor(visibleList.item(+j)); | |
2269 } | 2603 } |
2270 | 2604 |
2271 // Guard against accidental leakage of untamed nodes | 2605 // Guard against accidental leakage of untamed nodes |
2272 nodeList = null; | 2606 nodeList = visibleList = null; |
2273 | 2607 |
2274 tamed.item = cajaVM.def(function (k) { | 2608 tamed.item = cajaVM.def(function (k) { |
2275 k &= 0x7fffffff; | 2609 k &= 0x7fffffff; |
2276 if (k !== k) { throw new Error(); } | 2610 if (k !== k) { throw new Error(); } |
2277 return tamed[+k] || null; | 2611 return tamed[+k] || null; |
2278 }); | 2612 }); |
2279 | 2613 |
2280 return tamed; | 2614 return tamed; |
2281 } | 2615 } |
2282 | 2616 |
2283 function rebuildTameListConstructors(ArrayLike) { | 2617 function rebuildTameListConstructors(ArrayLike) { |
2284 TameNodeList = makeTameNodeList(); | 2618 TameNodeList = makeTameNodeList(); |
2285 TameNodeList.prototype = Object.create(ArrayLike.prototype); | 2619 TameNodeList.prototype = Object.create(ArrayLike.prototype); |
2286 Object.defineProperty(TameNodeList.prototype, 'constructor', | 2620 Object.defineProperty(TameNodeList.prototype, 'constructor', |
2287 { value: TameNodeList }); | 2621 { value: TameNodeList }); |
2288 Object.freeze(TameNodeList.prototype); | 2622 Object.freeze(TameNodeList.prototype); |
2289 Object.freeze(TameNodeList); | 2623 Object.freeze(TameNodeList); |
2290 TameOptionsList = makeTameOptionsList(); | 2624 TameOptionsList = makeTameOptionsList(); |
2291 TameOptionsList.prototype = Object.create(ArrayLike.prototype); | 2625 TameOptionsList.prototype = Object.create(ArrayLike.prototype); |
2292 Object.defineProperty(TameOptionsList.prototype, 'constructor', | 2626 Object.defineProperty(TameOptionsList.prototype, 'constructor', |
2293 { value: TameOptionsList }); | 2627 { value: TameOptionsList }); |
2294 Object.freeze(TameOptionsList.prototype); | 2628 Object.freeze(TameOptionsList.prototype); |
2295 Object.freeze(TameOptionsList); | 2629 Object.freeze(TameOptionsList); |
2296 } | 2630 } |
2297 | 2631 |
2298 function makeTameNodeList() { | 2632 function makeTameNodeList() { |
2299 return function TNL(nodeList, editable, tameNodeCtor) { | 2633 return function TNL(nodeList, tameNodeCtor) { |
2300 nodeList = makeDOMAccessible(nodeList); | 2634 var visibleList = new NodeListFilter(nodeList); |
2301 function getItem(i) { | 2635 function getItem(i) { |
2302 i = +i; | 2636 i = +i; |
2303 if (i >= nodeList.length) { return void 0; } | 2637 if (i >= visibleList.getLength()) { return void 0; } |
2304 return tameNodeCtor(nodeList[i], editable); | 2638 return tameNodeCtor(visibleList.item(i)); |
2305 } | 2639 } |
2306 function getLength() { | 2640 var getLength = visibleList.getLength.bind(visibleList); |
2307 return nodeList.length; | |
2308 } | |
2309 var len = +getLength(); | 2641 var len = +getLength(); |
2310 var ArrayLike = cajaVM.makeArrayLike(len); | 2642 var ArrayLike = cajaVM.makeArrayLike(len); |
2311 if (!(TameNodeList.prototype instanceof ArrayLike)) { | 2643 if (!(TameNodeList.prototype instanceof ArrayLike)) { |
2312 rebuildTameListConstructors(ArrayLike); | 2644 rebuildTameListConstructors(ArrayLike); |
2313 } | 2645 } |
2314 var result = ArrayLike(TameNodeList.prototype, getItem, getLength); | 2646 var result = ArrayLike(TameNodeList.prototype, getItem, getLength); |
2315 Object.defineProperty(result, 'item', | 2647 Object.defineProperty(result, 'item', |
2316 { value: Object.freeze(getItem) }); | 2648 { value: Object.freeze(getItem) }); |
2317 return result; | 2649 return result; |
2318 }; | 2650 }; |
2319 } | 2651 } |
2320 | 2652 |
2321 var TameNodeList = Object.freeze(makeTameNodeList()); | 2653 var TameNodeList = Object.freeze(makeTameNodeList()); |
2322 | 2654 |
2323 function makeTameOptionsList() { | 2655 function makeTameOptionsList() { |
2324 return function TOL(nodeList, editable, opt_tameNodeCtor) { | 2656 return function TOL(nodeList, opt_tameNodeCtor) { |
2325 nodeList = makeDOMAccessible(nodeList); | 2657 var visibleList = new NodeListFilter(nodeList); |
2326 function getItem(i) { | 2658 function getItem(i) { |
2327 i = +i; | 2659 i = +i; |
2328 return opt_tameNodeCtor(nodeList[i], editable); | 2660 return opt_tameNodeCtor(visibleList.item(i)); |
2329 } | 2661 } |
2330 function getLength() { return nodeList.length; } | 2662 var getLength = visibleList.getLength.bind(visibleList); |
2331 var len = +getLength(); | 2663 var len = +getLength(); |
2332 var ArrayLike = cajaVM.makeArrayLike(len); | 2664 var ArrayLike = cajaVM.makeArrayLike(len); |
2333 if (!(TameOptionsList.prototype instanceof ArrayLike)) { | 2665 if (!(TameOptionsList.prototype instanceof ArrayLike)) { |
2334 rebuildTameListConstructors(ArrayLike); | 2666 rebuildTameListConstructors(ArrayLike); |
2335 } | 2667 } |
2336 var result = ArrayLike( | 2668 var result = ArrayLike( |
2337 TameOptionsList.prototype, getItem, getLength); | 2669 TameOptionsList.prototype, getItem, getLength); |
2338 Object.defineProperty(result, 'selectedIndex', { | 2670 Object.defineProperty(result, 'selectedIndex', { |
2339 get: function () { return +nodeList.selectedIndex; } | 2671 get: function () { return +nodeList.selectedIndex; } |
2340 }); | 2672 }); |
(...skipping 14 matching lines...) Expand all Loading... | |
2355 } | 2687 } |
2356 | 2688 |
2357 /** | 2689 /** |
2358 * Constructs an HTMLCollection-like object which indexes its elements | 2690 * Constructs an HTMLCollection-like object which indexes its elements |
2359 * based on their NAME attribute. | 2691 * based on their NAME attribute. |
2360 * | 2692 * |
2361 * @param tamed a JavaScript array that will be populated and decorated | 2693 * @param tamed a JavaScript array that will be populated and decorated |
2362 * with the DOM HTMLCollection API. | 2694 * with the DOM HTMLCollection API. |
2363 * @param nodeList an array-like object supporting a "length" property | 2695 * @param nodeList an array-like object supporting a "length" property |
2364 * and "[]" numeric indexing. | 2696 * and "[]" numeric indexing. |
2365 * @param editable whether the tame nodes wrapped by this object | |
2366 * should permit editing. | |
2367 * @param opt_tameNodeCtor a function for constructing tame nodes | 2697 * @param opt_tameNodeCtor a function for constructing tame nodes |
2368 * out of raw DOM nodes. | 2698 * out of raw DOM nodes. |
2369 * | 2699 * |
2370 * TODO(kpreid): Per | 2700 * TODO(kpreid): Per |
2371 * <http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-75708506> | 2701 * <http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-75708506> |
2372 * this should be looking up ids as well as names. (And not returning | 2702 * this should be looking up ids as well as names. (And not returning |
2373 * nodelists, but is that for compatibility?) | 2703 * nodelists, but is that for compatibility?) |
2374 */ | 2704 */ |
2375 function mixinHTMLCollection(tamed, nodeList, editable, | 2705 function mixinHTMLCollection(tamed, nodeList, opt_tameNodeCtor) { |
2376 opt_tameNodeCtor) { | 2706 mixinNodeList(tamed, nodeList, opt_tameNodeCtor); |
2377 mixinNodeList(tamed, nodeList, editable, opt_tameNodeCtor); | |
2378 | 2707 |
2379 var tameNodesByName = {}; | 2708 var tameNodesByName = {}; |
2380 var tameNode; | 2709 var tameNode; |
2381 | 2710 |
2382 for (var i = 0; i < tamed.length && (tameNode = tamed[+i]); ++i) { | 2711 for (var i = 0; i < tamed.length && (tameNode = tamed[+i]); ++i) { |
2383 var name = void 0; | 2712 var name = void 0; |
2384 if (tameNode.getAttribute) { name = tameNode.getAttribute('name'); } | 2713 if (tameNode.getAttribute) { name = tameNode.getAttribute('name'); } |
2385 if (name && !(name.charAt(name.length - 1) === '_' || (name in tamed) | 2714 if (name && !(name.charAt(name.length - 1) === '_' || (name in tamed) |
2386 || name === String(name & 0x7fffffff))) { | 2715 || name === String(name & 0x7fffffff))) { |
2387 if (!tameNodesByName[name]) { tameNodesByName[name] = []; } | 2716 if (!tameNodesByName[name]) { tameNodesByName[name] = []; } |
(...skipping 18 matching lines...) Expand all Loading... | |
2406 if (Object.prototype.hasOwnProperty.call(tamed, name)) { | 2735 if (Object.prototype.hasOwnProperty.call(tamed, name)) { |
2407 return cajaVM.passesGuard(TameNodeT, tamed[name]) | 2736 return cajaVM.passesGuard(TameNodeT, tamed[name]) |
2408 ? tamed[name] : tamed[name][0]; | 2737 ? tamed[name] : tamed[name][0]; |
2409 } | 2738 } |
2410 return null; | 2739 return null; |
2411 }); | 2740 }); |
2412 | 2741 |
2413 return tamed; | 2742 return tamed; |
2414 } | 2743 } |
2415 | 2744 |
2416 function tameHTMLCollection(nodeList, editable, opt_tameNodeCtor) { | 2745 function tameHTMLCollection(nodeList, opt_tameNodeCtor) { |
2417 return Object.freeze( | 2746 return Object.freeze( |
2418 mixinHTMLCollection([], nodeList, editable, opt_tameNodeCtor)); | 2747 mixinHTMLCollection([], nodeList, opt_tameNodeCtor)); |
2419 } | 2748 } |
2420 | 2749 |
2421 function tameGetElementsByTagName(rootNode, tagName, editable) { | 2750 function tameGetElementsByTagName(rootNode, tagName) { |
2422 tagName = String(tagName); | 2751 tagName = String(tagName); |
2423 var eflags = 0; | 2752 var eflags = 0; |
2424 if (tagName !== '*') { | 2753 if (tagName !== '*') { |
2425 tagName = tagName.toLowerCase(); | 2754 tagName = tagName.toLowerCase(); |
2426 tagName = virtualToRealElementName(tagName); | 2755 tagName = virtualToRealElementName(tagName); |
2427 } | 2756 } |
2428 return new TameNodeList(rootNode.getElementsByTagName(tagName), | 2757 return new TameNodeList(rootNode.getElementsByTagName(tagName), |
2429 editable, defaultTameNode); | 2758 defaultTameNode); |
2430 } | 2759 } |
2431 | 2760 |
2432 /** | 2761 /** |
2433 * Implements http://www.whatwg.org/specs/web-apps/current-work/#dom-docum ent-getelementsbyclassname | 2762 * Implements http://www.whatwg.org/specs/web-apps/current-work/#dom-docum ent-getelementsbyclassname |
2434 * using an existing implementation on browsers that have one. | 2763 * using an existing implementation on browsers that have one. |
2435 */ | 2764 */ |
2436 function tameGetElementsByClassName(rootNode, className, editable) { | 2765 function tameGetElementsByClassName(rootNode, className) { |
2437 className = String(className); | 2766 className = String(className); |
2438 | 2767 |
2439 // The quotes below are taken from the HTML5 draft referenced above. | 2768 // The quotes below are taken from the HTML5 draft referenced above. |
2440 | 2769 |
2441 // "having obtained the classes by splitting a string on spaces" | 2770 // "having obtained the classes by splitting a string on spaces" |
2442 // Instead of using split, we use match with the global modifier so that | 2771 // Instead of using split, we use match with the global modifier so that |
2443 // we don't have to remove leading and trailing spaces. | 2772 // we don't have to remove leading and trailing spaces. |
2444 var classes = className.match(/[^\t\n\f\r ]+/g); | 2773 var classes = className.match(/[^\t\n\f\r ]+/g); |
2445 | 2774 |
2446 // Filter out classnames in the restricted namespace. | 2775 // Filter out classnames in the restricted namespace. |
(...skipping 12 matching lines...) Expand all Loading... | |
2459 // htmlEl.ownerDocument.getElementsByClassName(htmlEl.className) | 2788 // htmlEl.ownerDocument.getElementsByClassName(htmlEl.className) |
2460 // will return an HtmlCollection containing htmlElement iff | 2789 // will return an HtmlCollection containing htmlElement iff |
2461 // htmlEl.className contains a non-space character. | 2790 // htmlEl.className contains a non-space character. |
2462 return fakeNodeList([]); | 2791 return fakeNodeList([]); |
2463 } | 2792 } |
2464 | 2793 |
2465 // "unordered set of unique space-separated tokens representing classes" | 2794 // "unordered set of unique space-separated tokens representing classes" |
2466 if (typeof rootNode.getElementsByClassName === 'function') { | 2795 if (typeof rootNode.getElementsByClassName === 'function') { |
2467 return new TameNodeList( | 2796 return new TameNodeList( |
2468 rootNode.getElementsByClassName( | 2797 rootNode.getElementsByClassName( |
2469 classes.join(' ')), editable, defaultTameNode); | 2798 classes.join(' ')), defaultTameNode); |
2470 } else { | 2799 } else { |
2471 // Add spaces around each class so that we can use indexOf later to | 2800 // Add spaces around each class so that we can use indexOf later to |
2472 // find a match. | 2801 // find a match. |
2473 // This use of indexOf is strictly incorrect since | 2802 // This use of indexOf is strictly incorrect since |
2474 // http://www.whatwg.org/specs/web-apps/current-work/#reflecting-conte nt-attributes-in-dom-attributes | 2803 // http://www.whatwg.org/specs/web-apps/current-work/#reflecting-conte nt-attributes-in-dom-attributes |
2475 // does not normalize spaces in unordered sets of unique | 2804 // does not normalize spaces in unordered sets of unique |
2476 // space-separated tokens. This is not a problem since HTML5 | 2805 // space-separated tokens. This is not a problem since HTML5 |
2477 // compliant implementations already have a getElementsByClassName | 2806 // compliant implementations already have a getElementsByClassName |
2478 // implementation, and legacy | 2807 // implementation, and legacy |
2479 // implementations do normalize according to comments on issue 935. | 2808 // implementations do normalize according to comments on issue 935. |
(...skipping 17 matching lines...) Expand all Loading... | |
2497 candidate_loop: | 2826 candidate_loop: |
2498 for (var j = 0, candidate, k = -1; | 2827 for (var j = 0, candidate, k = -1; |
2499 j < limit && (candidate = candidates[+j]); | 2828 j < limit && (candidate = candidates[+j]); |
2500 ++j) { | 2829 ++j) { |
2501 var candidateClass = ' ' + candidate.className + ' '; | 2830 var candidateClass = ' ' + candidate.className + ' '; |
2502 for (var i = nClasses; --i >= 0;) { | 2831 for (var i = nClasses; --i >= 0;) { |
2503 if (-1 === candidateClass.indexOf(classes[+i])) { | 2832 if (-1 === candidateClass.indexOf(classes[+i])) { |
2504 continue candidate_loop; | 2833 continue candidate_loop; |
2505 } | 2834 } |
2506 } | 2835 } |
2507 var tamed = defaultTameNode(candidate, editable); | 2836 var tamed = defaultTameNode(candidate); |
2508 if (tamed) { | 2837 if (tamed) { |
2509 matches[++k] = tamed; | 2838 matches[++k] = tamed; |
2510 } | 2839 } |
2511 } | 2840 } |
2512 // "the method must return a live NodeList object" | 2841 // "the method must return a live NodeList object" |
2513 return fakeNodeList(matches); | 2842 return fakeNodeList(matches); |
2514 } | 2843 } |
2515 } | 2844 } |
2516 | 2845 |
2517 function makeEventHandlerWrapper(thisNode, listener) { | 2846 function makeEventHandlerWrapper(thisNode, listener) { |
2518 domitaModules.ensureValidCallback(listener); | 2847 domitaModules.ensureValidCallback(listener); |
2519 function wrapper(event) { | 2848 function wrapper(event) { |
2520 return plugin_dispatchEvent( | 2849 return plugin_dispatchEvent( |
2521 thisNode, event, rulebreaker.getId(tameWindow), listener); | 2850 thisNode, event, rulebreaker.getId(tameWindow), listener); |
2522 } | 2851 } |
2523 return wrapper; | 2852 return wrapper; |
2524 } | 2853 } |
2525 | 2854 |
2526 var NOT_EDITABLE = "Node not editable."; | |
2527 var INDEX_SIZE_ERROR = "Index size error."; | |
2528 | |
2529 // Implementation of EventTarget::addEventListener | 2855 // Implementation of EventTarget::addEventListener |
2530 function tameAddEventListener(name, listener, useCapture) { | 2856 function tameAddEventListener(name, listener, useCapture) { |
2531 var feral = np(this).feral; | 2857 var feral = np(this).feral; |
2532 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 2858 np(this).policy.requireEditable(); |
2533 if (!np(this).wrappedListeners) { np(this).wrappedListeners = []; } | 2859 if (!np(this).wrappedListeners) { np(this).wrappedListeners = []; } |
2534 useCapture = Boolean(useCapture); | 2860 useCapture = Boolean(useCapture); |
2535 var wrappedListener = makeEventHandlerWrapper(np(this).feral, listener); | 2861 var wrappedListener = makeEventHandlerWrapper(np(this).feral, listener); |
2536 wrappedListener = bridal.addEventListener( | 2862 wrappedListener = bridal.addEventListener( |
2537 np(this).feral, name, wrappedListener, useCapture); | 2863 np(this).feral, name, wrappedListener, useCapture); |
2538 wrappedListener._d_originalListener = listener; | 2864 wrappedListener._d_originalListener = listener; |
2539 np(this).wrappedListeners.push(wrappedListener); | 2865 np(this).wrappedListeners.push(wrappedListener); |
2540 } | 2866 } |
2541 | 2867 |
2542 // Implementation of EventTarget::removeEventListener | 2868 // Implementation of EventTarget::removeEventListener |
2543 function tameRemoveEventListener(name, listener, useCapture) { | 2869 function tameRemoveEventListener(name, listener, useCapture) { |
2544 var self = TameNodeT.coerce(this); | 2870 var self = TameNodeT.coerce(this); |
2545 var feral = np(self).feral; | 2871 var feral = np(self).feral; |
2546 if (!np(self).editable) { throw new Error(NOT_EDITABLE); } | 2872 np(this).policy.requireEditable(); |
2547 if (!np(this).wrappedListeners) { return; } | 2873 if (!np(this).wrappedListeners) { return; } |
2548 var wrappedListener = null; | 2874 var wrappedListener = null; |
2549 for (var i = np(this).wrappedListeners.length; --i >= 0;) { | 2875 for (var i = np(this).wrappedListeners.length; --i >= 0;) { |
2550 if (np(this).wrappedListeners[+i]._d_originalListener === listener) { | 2876 if (np(this).wrappedListeners[+i]._d_originalListener === listener) { |
2551 wrappedListener = np(this).wrappedListeners[+i]; | 2877 wrappedListener = np(this).wrappedListeners[+i]; |
2552 arrayRemove(np(this).wrappedListeners, i, i); | 2878 arrayRemove(np(this).wrappedListeners, i, i); |
2553 break; | 2879 break; |
2554 } | 2880 } |
2555 } | 2881 } |
2556 if (!wrappedListener) { return; } | 2882 if (!wrappedListener) { return; } |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2596 traceStartup("DT: about to make TameNode"); | 2922 traceStartup("DT: about to make TameNode"); |
2597 | 2923 |
2598 /** | 2924 /** |
2599 * Base class for a Node wrapper. Do not create directly -- use the | 2925 * Base class for a Node wrapper. Do not create directly -- use the |
2600 * tameNode factory instead. | 2926 * tameNode factory instead. |
2601 * | 2927 * |
2602 * NOTE that all TameNodes should have the TameNodeT trademark, but it is | 2928 * NOTE that all TameNodes should have the TameNodeT trademark, but it is |
2603 * not applied here since that freezes the object, and also because of the | 2929 * not applied here since that freezes the object, and also because of the |
2604 * forwarding proxies used for catching expando properties. | 2930 * forwarding proxies used for catching expando properties. |
2605 * | 2931 * |
2606 * @param {boolean} editable true if the node's value, attributes, | 2932 * @param {policy} Mutability policy to apply. |
2607 * children, | |
2608 * or custom properties are mutable. | |
2609 * @constructor | 2933 * @constructor |
2610 */ | 2934 */ |
2611 function TameNode(editable) { | 2935 function TameNode(policy) { |
2612 TameNodeConf.confide(this); | 2936 TameNodeConf.confide(this); |
2613 np(this).editable = editable; | 2937 if (!policy || !policy.requireEditable) { |
2938 throw new Error("Domado internal error: Policy missing or invalid"); | |
2939 } | |
2940 np(this).policy = policy; | |
2614 return this; | 2941 return this; |
2615 } | 2942 } |
2616 inertCtor(TameNode, Object, 'Node'); | 2943 inertCtor(TameNode, Object, 'Node'); |
2617 traceStartup("DT: about to DPA TameNode"); | 2944 traceStartup("DT: about to DPA TameNode"); |
2618 definePropertiesAwesomely(TameNode.prototype, { | 2945 definePropertiesAwesomely(TameNode.prototype, { |
2619 // tameDocument is not yet defined at this point so can't be a constant | 2946 // tameDocument is not yet defined at this point so can't be a constant |
2620 ownerDocument: { | 2947 ownerDocument: { |
2621 enumerable: canHaveEnumerableAccessors, | 2948 enumerable: canHaveEnumerableAccessors, |
2622 get: cajaVM.def(function () { | 2949 get: cajaVM.def(function () { |
2623 return tameDocument; | 2950 return tameDocument; |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2659 // abstract TameNode.prototype.replaceChild | 2986 // abstract TameNode.prototype.replaceChild |
2660 // abstract TameNode.prototype.firstChild | 2987 // abstract TameNode.prototype.firstChild |
2661 // abstract TameNode.prototype.lastChild | 2988 // abstract TameNode.prototype.lastChild |
2662 // abstract TameNode.prototype.nextSibling | 2989 // abstract TameNode.prototype.nextSibling |
2663 // abstract TameNode.prototype.previousSibling | 2990 // abstract TameNode.prototype.previousSibling |
2664 // abstract TameNode.prototype.parentNode | 2991 // abstract TameNode.prototype.parentNode |
2665 // abstract TameNode.prototype.getElementsByTagName | 2992 // abstract TameNode.prototype.getElementsByTagName |
2666 // abstract TameNode.prototype.getElementsByClassName | 2993 // abstract TameNode.prototype.getElementsByClassName |
2667 // abstract TameNode.prototype.childNodes | 2994 // abstract TameNode.prototype.childNodes |
2668 // abstract TameNode.prototype.attributes | 2995 // abstract TameNode.prototype.attributes |
2669 var tameNodePublicMembers = [ | |
2670 'cloneNode', | |
2671 'appendChild', 'insertBefore', 'removeChild', 'replaceChild', | |
2672 'dispatchEvent', 'hasChildNodes' | |
2673 ]; | |
2674 traceStartup("DT: about to defend TameNode"); | 2996 traceStartup("DT: about to defend TameNode"); |
2675 cajaVM.def(TameNode); // and its prototype | 2997 cajaVM.def(TameNode); // and its prototype |
2676 | 2998 |
2677 traceStartup("DT: about to make TameBackedNode"); | 2999 traceStartup("DT: about to make TameBackedNode"); |
2678 | 3000 |
2679 /** | 3001 /** |
2680 * A tame node that is backed by a real node. | 3002 * A tame node that is backed by a real node. |
2681 * | 3003 * |
2682 * Note that the constructor returns a proxy which delegates to 'this'; | 3004 * Note that the constructor returns a proxy which delegates to 'this'; |
2683 * subclasses should apply properties to 'this' but return the proxy. | 3005 * subclasses should apply properties to 'this' but return the proxy. |
2684 * | 3006 * |
2685 * @param {boolean} childrenEditable true iff the child list is mutable. | |
2686 * @param {Function} opt_proxyType The constructor of the proxy handler | 3007 * @param {Function} opt_proxyType The constructor of the proxy handler |
2687 * to use, defaulting to ExpandoProxyHandler. | 3008 * to use, defaulting to ExpandoProxyHandler. |
2688 * @constructor | 3009 * @constructor |
2689 */ | 3010 */ |
2690 function TameBackedNode(node, editable, childrenEditable, opt_proxyType) { | 3011 function TameBackedNode(node, opt_policy, opt_proxyType) { |
2691 node = makeDOMAccessible(node); | 3012 node = makeDOMAccessible(node); |
2692 | 3013 |
2693 if (!node) { | 3014 if (!node) { |
2694 throw new Error('Creating tame node with undefined native delegate'); | 3015 throw new Error('Creating tame node with undefined native delegate'); |
2695 } | 3016 } |
2696 | 3017 |
2697 TameNode.call(this, editable); | 3018 // Determine access policy |
3019 var parent = makeDOMAccessible(node.parentNode); | |
3020 var parentPolicy; | |
3021 if (!parent || | |
3022 idClassPattern.test(parent.className) || | |
3023 idClassPattern.test(node.className)) { | |
3024 parentPolicy = null; | |
3025 } else { | |
3026 // Parent is inside the vdoc. | |
3027 parentPolicy = np(defaultTameNode(parent)).policy; | |
3028 } | |
3029 var policy; | |
3030 if (opt_policy) { | |
3031 if (parentPolicy) { | |
3032 parentPolicy.childPolicy.assertRestrictedBy(opt_policy); | |
3033 } | |
3034 policy = opt_policy; | |
3035 //console.log("", parent, "->", node, "policy explicit", | |
3036 // policy.toString()); | |
3037 } else if (idClassPattern.test(node.className)) { | |
3038 // Virtual document root -- stop implicit recursion and define the | |
3039 // root policy. If we wanted to be able to define a "entire DOM | |
3040 // read-only" policy, this is where to hook it in. | |
3041 policy = nodePolicyEditable; | |
3042 //console.log("", parent, "->", node, "root policy", | |
3043 // policy.toString()); | |
3044 } else if (parentPolicy) { | |
3045 policy = parentPolicy.childPolicy; | |
3046 //console.log("", parent, "->", node, "policy via parent", | |
3047 // parentPolicy.toString(), policy.toString()); | |
3048 } else { | |
3049 policy = nodePolicyEditable; | |
3050 //console.log("", parent, "->", node, "policy isolated", | |
3051 // policy.toString()); | |
3052 } | |
3053 | |
3054 TameNode.call(this, policy); | |
2698 | 3055 |
2699 np(this).feral = node; | 3056 np(this).feral = node; |
2700 np(this).childrenEditable = editable && childrenEditable; | |
2701 | 3057 |
2702 if (domitaModules.proxiesAvailable) { | 3058 if (domitaModules.proxiesAvailable) { |
2703 np(this).proxyHandler = new (opt_proxyType || ExpandoProxyHandler)( | 3059 np(this).proxyHandler = new (opt_proxyType || ExpandoProxyHandler)( |
2704 this, editable, getNodeExpandoStorage(node)); | 3060 this, policy.editable, getNodeExpandoStorage(node)); |
2705 } | 3061 } |
2706 } | 3062 } |
2707 inertCtor(TameBackedNode, TameNode); | 3063 inertCtor(TameBackedNode, TameNode); |
2708 definePropertiesAwesomely(TameBackedNode.prototype, { | 3064 definePropertiesAwesomely(TameBackedNode.prototype, { |
2709 nodeType: NP.ro, | 3065 nodeType: NP.ro, |
2710 nodeName: NP.ro, | 3066 nodeName: NP.ro, |
2711 nodeValue: NP.ro, | 3067 nodeValue: NP.ro, |
2712 firstChild: NP_tameDescendant, | 3068 firstChild: NP_tameDescendant, // TODO(kpreid): Must be disableable |
2713 lastChild: NP_tameDescendant, | 3069 lastChild: NP_tameDescendant, |
2714 nextSibling: NP.related, | 3070 nextSibling: NP.related, |
2715 previousSibling: NP.related, | 3071 previousSibling: NP.related, |
2716 parentNode: NP.related, | 3072 parentNode: NP.related, |
2717 childNodes: { | 3073 childNodes: { |
2718 enumerable: true, | 3074 enumerable: true, |
2719 get: cajaVM.def(function () { | 3075 get: cajaVM.def(function () { |
2720 return new TameNodeList(np(this).feral.childNodes, | 3076 var privates = np(this); |
2721 np(this).childrenEditable, defaultTameNode); | 3077 if (privates.policy.childrenVisible) { |
3078 return new TameNodeList(np(this).feral.childNodes, | |
3079 defaultTameNode); | |
3080 } else { | |
3081 return fakeNodeList([]); | |
3082 } | |
2722 }) | 3083 }) |
2723 }, | 3084 }, |
2724 attributes: { | 3085 attributes: { |
2725 enumerable: true, | 3086 enumerable: true, |
2726 get: cajaVM.def(function () { | 3087 get: cajaVM.def(function () { |
2727 var thisNode = np(this).feral; | 3088 var privates = np(this); |
2728 var tameNodeCtor = function(node, editable) { | 3089 if (privates.policy.attributesVisible) { |
2729 return new TameBackedAttributeNode(node, editable, thisNode); | 3090 var thisNode = privates.feral; |
2730 }; | 3091 var tameNodeCtor = function(node) { |
2731 return new TameNodeList( | 3092 return new TameBackedAttributeNode(node, thisNode); |
2732 thisNode.attributes, thisNode, tameNodeCtor); | 3093 }; |
3094 // TODO(kpreid): There is no test which caught a previous | |
3095 // editability policy failure here | |
3096 return new TameNodeList(thisNode.attributes, tameNodeCtor); | |
3097 } else { | |
3098 return fakeNodeList([]); | |
3099 } | |
2733 }) | 3100 }) |
2734 } | 3101 } |
2735 }); | 3102 }); |
2736 TameBackedNode.prototype.cloneNode = nodeMethod(function (deep) { | 3103 TameBackedNode.prototype.cloneNode = nodeMethod(function (deep) { |
3104 np(this).policy.requireUnrestricted(); | |
2737 var clone = bridal.cloneNode(np(this).feral, Boolean(deep)); | 3105 var clone = bridal.cloneNode(np(this).feral, Boolean(deep)); |
2738 // From http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-3A0ED0A4 | 3106 // From http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-3A0ED0A4 |
2739 // "Note that cloning an immutable subtree results in a mutable copy" | 3107 // "Note that cloning an immutable subtree results in a mutable copy" |
2740 return defaultTameNode(clone, true); | 3108 return defaultTameNode(clone); |
2741 }); | 3109 }); |
3110 /** Is it OK to make 'child' a child of 'parent'? */ | |
3111 function checkAdoption(parent, child) { | |
3112 // Child must be editable since appendChild can remove it from its | |
3113 // parent. | |
3114 np(parent).policy.requireChildrenEditable(); | |
3115 np(child).policy.requireEditable(); | |
3116 // Sanity check: this cannot currently happen but if it does then we | |
3117 // need to rethink the calculation of policies. | |
3118 np(parent).policy.childPolicy.assertRestrictedBy(np(child).policy); | |
3119 } | |
2742 TameBackedNode.prototype.appendChild = nodeMethod(function (child) { | 3120 TameBackedNode.prototype.appendChild = nodeMethod(function (child) { |
2743 child = child || {}; | 3121 child = child || {}; |
2744 // Child must be editable since appendChild can remove it from its | |
2745 // parent. | |
2746 child = TameNodeT.coerce(child); | 3122 child = TameNodeT.coerce(child); |
2747 if (!np(this).childrenEditable || !np(child).editable) { | 3123 |
2748 throw new Error(NOT_EDITABLE); | 3124 checkAdoption(this, child); |
2749 } | 3125 |
2750 np(this).feral.appendChild(np(child).feral); | 3126 np(this).feral.appendChild(np(child).feral); |
2751 return child; | 3127 return child; |
2752 }); | 3128 }); |
2753 TameBackedNode.prototype.insertBefore = nodeMethod( | 3129 TameBackedNode.prototype.insertBefore = nodeMethod( |
2754 function(toInsert, child) { | 3130 function(toInsert, child) { |
2755 toInsert = TameNodeT.coerce(toInsert); | 3131 toInsert = TameNodeT.coerce(toInsert); |
2756 if (child === void 0) { child = null; } | 3132 if (child === void 0) { child = null; } |
3133 | |
2757 if (child !== null) { | 3134 if (child !== null) { |
2758 child = TameNodeT.coerce(child); | 3135 child = TameNodeT.coerce(child); |
2759 if (!np(child).editable) { | 3136 // TODO(kpreid): This child is not being mutated except for its |
2760 throw new Error(NOT_EDITABLE); | 3137 // previousSibling, so why are we rejecting here? |
2761 } | 3138 np(child).policy.requireEditable(); |
2762 } | 3139 } |
2763 if (!np(this).childrenEditable || !np(toInsert).editable) { | 3140 checkAdoption(this, toInsert); |
2764 throw new Error(NOT_EDITABLE); | 3141 |
2765 } | |
2766 np(this).feral.insertBefore( | 3142 np(this).feral.insertBefore( |
2767 np(toInsert).feral, child !== null ? np(child).feral : null); | 3143 np(toInsert).feral, child !== null ? np(child).feral : null); |
2768 return toInsert; | 3144 return toInsert; |
2769 }); | 3145 }); |
2770 TameBackedNode.prototype.removeChild = nodeMethod(function(child) { | 3146 TameBackedNode.prototype.removeChild = nodeMethod(function(child) { |
2771 child = TameNodeT.coerce(child); | 3147 child = TameNodeT.coerce(child); |
2772 if (!np(this).childrenEditable || !np(child).editable) { | 3148 np(this).policy.requireChildrenEditable(); |
2773 throw new Error(NOT_EDITABLE); | 3149 np(child).policy.requireEditable(); |
2774 } | |
2775 np(this).feral.removeChild(np(child).feral); | 3150 np(this).feral.removeChild(np(child).feral); |
2776 return child; | 3151 return child; |
2777 }); | 3152 }); |
2778 TameBackedNode.prototype.replaceChild = nodeMethod( | 3153 TameBackedNode.prototype.replaceChild = nodeMethod( |
2779 function(newChild, oldChild) { | 3154 function(newChild, oldChild) { |
2780 newChild = TameNodeT.coerce(newChild); | 3155 newChild = TameNodeT.coerce(newChild); |
2781 oldChild = TameNodeT.coerce(oldChild); | 3156 oldChild = TameNodeT.coerce(oldChild); |
2782 if (!np(this).childrenEditable || !np(newChild).editable | 3157 |
2783 || !np(oldChild).editable) { | 3158 checkAdoption(this, newChild); |
2784 throw new Error(NOT_EDITABLE); | 3159 np(oldChild).policy.requireEditable(); |
2785 } | 3160 |
2786 np(this).feral.replaceChild(np(newChild).feral, np(oldChild).feral); | 3161 np(this).feral.replaceChild(np(newChild).feral, np(oldChild).feral); |
2787 return oldChild; | 3162 return oldChild; |
2788 }); | 3163 }); |
2789 TameBackedNode.prototype.hasChildNodes = nodeMethod(function() { | 3164 TameBackedNode.prototype.hasChildNodes = nodeMethod(function() { |
2790 return !!np(this).feral.hasChildNodes(); | 3165 if (np(this).policy.childrenVisible) { |
3166 return !!np(this).feral.hasChildNodes(); | |
3167 } else { | |
3168 return false; | |
3169 } | |
2791 }); | 3170 }); |
2792 // http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget | 3171 // http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget |
2793 // "The EventTarget interface is implemented by all Nodes" | 3172 // "The EventTarget interface is implemented by all Nodes" |
2794 TameBackedNode.prototype.dispatchEvent = nodeMethod(function(evt) { | 3173 TameBackedNode.prototype.dispatchEvent = nodeMethod(function(evt) { |
2795 evt = TameEventT.coerce(evt); | 3174 evt = TameEventT.coerce(evt); |
2796 bridal.dispatchEvent(np(this).feral, TameEventConf.p(evt).feral); | 3175 bridal.dispatchEvent(np(this).feral, TameEventConf.p(evt).feral); |
2797 }); | 3176 }); |
2798 | 3177 |
2799 if (docEl.contains) { // typeof is 'object' on IE | 3178 if (docEl.contains) { // typeof is 'object' on IE |
2800 TameBackedNode.prototype.contains = nodeMethod(function (other) { | 3179 TameBackedNode.prototype.contains = nodeMethod(function (other) { |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2849 } | 3228 } |
2850 } | 3229 } |
2851 cajaVM.def(TameBackedNode); // and its prototype | 3230 cajaVM.def(TameBackedNode); // and its prototype |
2852 | 3231 |
2853 traceStartup("DT: about to make TamePseudoNode"); | 3232 traceStartup("DT: about to make TamePseudoNode"); |
2854 | 3233 |
2855 /** | 3234 /** |
2856 * A fake node that is not backed by a real DOM node. | 3235 * A fake node that is not backed by a real DOM node. |
2857 * @constructor | 3236 * @constructor |
2858 */ | 3237 */ |
2859 function TamePseudoNode(editable) { | 3238 function TamePseudoNode() { |
2860 TameNode.call(this, editable); | 3239 // Note inconsistency: we have an editable policy, for the sake of our |
3240 // children, but don't actually allow direct mutation. | |
3241 TameNode.call(this, nodePolicyEditable); | |
2861 | 3242 |
2862 if (domitaModules.proxiesAvailable) { | 3243 if (domitaModules.proxiesAvailable) { |
2863 // finishNode will wrap 'this' with an actual proxy later. | 3244 // finishNode will wrap 'this' with an actual proxy later. |
2864 np(this).proxyHandler = new ExpandoProxyHandler(this, editable, {}); | 3245 np(this).proxyHandler = new ExpandoProxyHandler(this, true, {}); |
2865 } | 3246 } |
2866 } | 3247 } |
2867 inertCtor(TamePseudoNode, TameNode); | 3248 inertCtor(TamePseudoNode, TameNode); |
2868 TamePseudoNode.prototype.appendChild = | 3249 TamePseudoNode.prototype.appendChild = |
2869 TamePseudoNode.prototype.insertBefore = | 3250 TamePseudoNode.prototype.insertBefore = |
2870 TamePseudoNode.prototype.removeChild = | 3251 TamePseudoNode.prototype.removeChild = |
2871 TamePseudoNode.prototype.replaceChild = nodeMethod(function () { | 3252 TamePseudoNode.prototype.replaceChild = nodeMethod(function () { |
2872 if (typeof console !== 'undefined') { | 3253 if (typeof console !== 'undefined') { |
2873 console.log("Node not editable; no action performed."); | 3254 console.log("Node not editable; no action performed."); |
2874 } | 3255 } |
(...skipping 28 matching lines...) Expand all Loading... | |
2903 var siblings = parentNode.childNodes; | 3284 var siblings = parentNode.childNodes; |
2904 for (var i = siblings.length; --i >= 1;) { | 3285 for (var i = siblings.length; --i >= 1;) { |
2905 if (siblings[+i] === self) { return siblings[i - 1]; } | 3286 if (siblings[+i] === self) { return siblings[i - 1]; } |
2906 } | 3287 } |
2907 return null; | 3288 return null; |
2908 })} | 3289 })} |
2909 }); | 3290 }); |
2910 cajaVM.def(TamePseudoNode); // and its prototype | 3291 cajaVM.def(TamePseudoNode); // and its prototype |
2911 | 3292 |
2912 traceStartup("DT: done fundamental nodes"); | 3293 traceStartup("DT: done fundamental nodes"); |
2913 traceStartup("DT: about to define makeRestrictedNodeType"); | |
2914 | |
2915 function makeRestrictedNodeType(whitelist) { | |
2916 function ForeignOrOpaqueNode(node, editable) { | |
2917 TameBackedNode.call(this, node, editable, editable); | |
2918 } | |
2919 var nodeType = ForeignOrOpaqueNode; // other name is for debug hint | |
2920 inherit(nodeType, TameBackedNode); | |
2921 for (var safe in whitelist) { | |
2922 // Any non-own property is overridden to be opaque below. | |
2923 var descriptor = (whitelist[safe] === 0) | |
2924 ? domitaModules.getPropertyDescriptor( | |
2925 TameBackedNode.prototype, safe) | |
2926 : { | |
2927 value: whitelist[safe], | |
2928 writable: false, | |
2929 configurable: false, | |
2930 enumerable: true | |
2931 }; | |
2932 Object.defineProperty(nodeType.prototype, safe, descriptor); | |
2933 } | |
2934 definePropertiesAwesomely(nodeType.prototype, { | |
2935 attributes: { | |
2936 enumerable: canHaveEnumerableAccessors, | |
2937 get: nodeMethod(function () { | |
2938 return new TameNodeList([], false, undefined); | |
2939 }) | |
2940 } | |
2941 }); | |
2942 function throwRestricted() { | |
2943 throw new Error('Node is restricted'); | |
2944 } | |
2945 cajaVM.def(throwRestricted); | |
2946 for (var i = tameNodePublicMembers.length; --i >= 0;) { | |
2947 var k = tameNodePublicMembers[+i]; | |
2948 if (!nodeType.prototype.hasOwnProperty(k)) { | |
2949 if (typeof TameBackedNode.prototype[k] === 'Function') { | |
2950 nodeType.prototype[k] = throwRestricted; | |
2951 } else { | |
2952 Object.defineProperty(nodeType.prototype, k, { | |
2953 enumerable: canHaveEnumerableAccessors, | |
2954 get: throwRestricted | |
2955 }); | |
2956 } | |
2957 } | |
2958 } | |
2959 return cajaVM.def(nodeType); // and its prototype | |
2960 } | |
2961 | 3294 |
2962 traceStartup("DT: about to make TameOpaqueNode"); | 3295 traceStartup("DT: about to make TameOpaqueNode"); |
2963 | 3296 |
2964 // An opaque node is traversible but not manipulable by guest code. This | 3297 // An opaque node is traversible but not manipulable by guest code. This |
2965 // is the default taming for unrecognized nodes or nodes not explicitly | 3298 // is the default taming for unrecognized nodes or nodes not explicitly |
2966 // whitelisted. | 3299 // whitelisted. |
2967 var TameOpaqueNode = makeRestrictedNodeType({ | 3300 function TameOpaqueNode(node) { |
2968 nodeValue: 0, | 3301 TameBackedNode.call(this, node, nodePolicyOpaque); |
2969 nodeType: 0, | 3302 } |
2970 nodeName: 0, | 3303 inertCtor(TameOpaqueNode, TameBackedNode); |
2971 nextSibling: 0, | 3304 cajaVM.def(TameOpaqueNode); |
2972 previousSibling: 0, | |
2973 firstChild: 0, | |
2974 lastChild: 0, | |
2975 parentNode: 0, | |
2976 childNodes: 0, | |
2977 ownerDocument: 0, | |
2978 hasChildNodes: 0 | |
2979 }); | |
2980 | |
2981 traceStartup("DT: about to make TameForeignNode"); | |
2982 | 3305 |
2983 // A foreign node is one supplied by some external system to the guest | 3306 // A foreign node is one supplied by some external system to the guest |
2984 // code, which the guest code may lay out within its own DOM tree but may | 3307 // code, which the guest code may lay out within its own DOM tree but may |
2985 // not traverse into in any way. | 3308 // not traverse into in any way. |
2986 // | 3309 function TameForeignNode(node) { |
2987 // TODO(ihab.awad): The taming chosen for foreign nodes is very | 3310 TameBackedNode.call(this, node, nodePolicyForeign); |
2988 // restrictive and could be relaxed, but only after careful consideration. | 3311 } |
2989 // The below choices are for simple safety, e.g., exposing a foreign | 3312 inertCtor(TameForeignNode, TameBackedNode); |
2990 // node's | 3313 TameForeignNode.prototype.getElementsByTagName = function (tagName) { |
2991 // siblings when the foreign node has been added to some DOM tree outside | 3314 // needed because TameForeignNode doesn't inherit TameElement |
2992 // this domicile might be dangerous. | 3315 return fakeNodeList([]); |
2993 var TameForeignNode = makeRestrictedNodeType({ | 3316 }; |
2994 nodeValue: 0, | 3317 TameForeignNode.prototype.getElementsByClassName = function (className) { |
2995 nodeType: 0, | 3318 // needed because TameForeignNode doesn't inherit TameElement |
2996 nodeName: 0, | 3319 return fakeNodeList([]); |
2997 nextSibling: undefined, | 3320 }; |
2998 previousSibling: undefined, | 3321 cajaVM.def(TameForeignNode); |
2999 firstChild: undefined, | |
3000 lastChild: undefined, | |
3001 parentNode: undefined, | |
3002 childNodes: Object.freeze([]), | |
3003 ownerDocument: undefined, | |
3004 getElementsByTagName: function() { return Object.freeze([]); }, | |
3005 getElementsByClassName: function() { return Object.freeze([]); }, | |
3006 hasChildNodes: function() { return false; } | |
3007 }); | |
3008 | 3322 |
3009 traceStartup("DT: about to make TameTextNode"); | 3323 traceStartup("DT: about to make TameTextNode"); |
3010 | 3324 |
3011 function TameTextNode(node, editable) { | 3325 function TameTextNode(node) { |
3012 assert(node.nodeType === 3); | 3326 assert(node.nodeType === 3); |
3013 | 3327 TameBackedNode.call(this, node); |
3014 // The below should not be strictly necessary since childrenEditable for | |
3015 // TameScriptElements is always false, but it protects against tameNode | |
3016 // being called naively on a text node from container code. | |
3017 var pn = node.parentNode; | |
3018 if (editable && pn) { | |
3019 if (1 === pn.nodeType | |
3020 && !htmlSchema.element(pn.tagName).allowed) { | |
3021 // Do not allow mutation of text inside script elements. | |
3022 // See the testScriptLoading testcase for examples of exploits. | |
3023 editable = false; | |
3024 } | |
3025 } | |
3026 | |
3027 TameBackedNode.call(this, node, editable, editable); | |
3028 } | 3328 } |
3029 inertCtor(TameTextNode, TameBackedNode, 'Text'); | 3329 inertCtor(TameTextNode, TameBackedNode, 'Text'); |
3030 var textAccessor = { | 3330 var textAccessor = { |
3031 enumerable: true, | 3331 enumerable: true, |
3032 get: nodeMethod(function () { | 3332 get: nodeMethod(function () { |
3033 return np(this).feral.nodeValue; | 3333 return np(this).feral.nodeValue; |
3034 }), | 3334 }), |
3035 set: nodeMethod(function (value) { | 3335 set: nodeMethod(function (value) { |
3036 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 3336 np(this).policy.requireEditable(); |
3037 np(this).feral.nodeValue = String(value || ''); | 3337 np(this).feral.nodeValue = String(value || ''); |
3038 }) | 3338 }) |
3039 }; | 3339 }; |
3040 definePropertiesAwesomely(TameTextNode.prototype, { | 3340 definePropertiesAwesomely(TameTextNode.prototype, { |
3041 nodeValue: textAccessor, | 3341 nodeValue: textAccessor, |
3042 textContent: textAccessor, | 3342 textContent: textAccessor, |
3043 innerText: textAccessor, | 3343 innerText: textAccessor, |
3044 data: textAccessor | 3344 data: textAccessor |
3045 }); | 3345 }); |
3046 setOwn(TameTextNode.prototype, "toString", nodeMethod(function () { | 3346 setOwn(TameTextNode.prototype, "toString", nodeMethod(function () { |
3047 return '#text'; | 3347 return '#text'; |
3048 })); | 3348 })); |
3049 cajaVM.def(TameTextNode); // and its prototype | 3349 cajaVM.def(TameTextNode); // and its prototype |
3050 | 3350 |
3051 function TameCommentNode(node, editable) { | 3351 function TameCommentNode(node) { |
3052 assert(node.nodeType === 8); | 3352 assert(node.nodeType === 8); |
3053 TameBackedNode.call(this, node, editable, editable); | 3353 TameBackedNode.call(this, node); |
3054 } | 3354 } |
3055 inertCtor(TameCommentNode, TameBackedNode, 'CommentNode'); | 3355 inertCtor(TameCommentNode, TameBackedNode, 'CommentNode'); |
3056 setOwn(TameCommentNode.prototype, "toString", nodeMethod(function () { | 3356 setOwn(TameCommentNode.prototype, "toString", nodeMethod(function () { |
3057 return '#comment'; | 3357 return '#comment'; |
3058 })); | 3358 })); |
3059 cajaVM.def(TameCommentNode); // and its prototype | 3359 cajaVM.def(TameCommentNode); // and its prototype |
3060 | 3360 |
3061 traceStartup("DT: about to make TameBackedAttributeNode"); | 3361 traceStartup("DT: about to make TameBackedAttributeNode"); |
3062 /** | 3362 /** |
3063 * Plays the role of an Attr node for TameElement objects. | 3363 * Plays the role of an Attr node for TameElement objects. |
3064 */ | 3364 */ |
3065 function TameBackedAttributeNode(node, editable, ownerElement) { | 3365 function TameBackedAttributeNode(node, ownerElement) { |
3066 if (ownerElement === undefined) throw new Error( | 3366 if (ownerElement === undefined) throw new Error( |
3067 "ownerElement undefined"); | 3367 "ownerElement undefined"); |
3068 TameBackedNode.call(this, node, editable); | 3368 TameBackedNode.call(this, node, |
3369 np(defaultTameNode(ownerElement)).policy); | |
3069 np(this).ownerElement = ownerElement; | 3370 np(this).ownerElement = ownerElement; |
3070 } | 3371 } |
3071 inertCtor(TameBackedAttributeNode, TameBackedNode, 'Attr'); | 3372 inertCtor(TameBackedAttributeNode, TameBackedNode, 'Attr'); |
3072 setOwn(TameBackedAttributeNode.prototype, 'cloneNode', | 3373 setOwn(TameBackedAttributeNode.prototype, 'cloneNode', |
3073 nodeMethod(function (deep) { | 3374 nodeMethod(function (deep) { |
3074 var clone = bridal.cloneNode(np(this).feral, Boolean(deep)); | 3375 var clone = bridal.cloneNode(np(this).feral, Boolean(deep)); |
3075 // From http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-3A0ED0A4 | 3376 // From http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-3A0ED0A4 |
3076 // "Note that cloning an immutable subtree results in a mutable copy" | 3377 // "Note that cloning an immutable subtree results in a mutable copy" |
3077 return new TameBackedAttributeNode(clone, true, np(this).ownerElement); | 3378 return new TameBackedAttributeNode(clone, np(this).ownerElement); |
3078 })); | 3379 })); |
3079 var nameAccessor = { | 3380 var nameAccessor = { |
3080 enumerable: true, | 3381 enumerable: true, |
3081 get: nodeMethod(function () { | 3382 get: nodeMethod(function () { |
3082 var name = np(this).feral.name; | 3383 var name = np(this).feral.name; |
3083 if (cajaPrefRe.test(name)) { | 3384 if (cajaPrefRe.test(name)) { |
3084 name = name.substring(cajaPrefix.length); | 3385 name = name.substring(cajaPrefix.length); |
3085 } | 3386 } |
3086 return name; | 3387 return name; |
3087 }) | 3388 }) |
(...skipping 14 matching lines...) Expand all Loading... | |
3102 enumerable: true, | 3403 enumerable: true, |
3103 get: nodeMethod(function () { | 3404 get: nodeMethod(function () { |
3104 return this.ownerElement.hasAttribute(this.name); | 3405 return this.ownerElement.hasAttribute(this.name); |
3105 }) | 3406 }) |
3106 }, | 3407 }, |
3107 nodeValue: valueAccessor, | 3408 nodeValue: valueAccessor, |
3108 value: valueAccessor, | 3409 value: valueAccessor, |
3109 ownerElement: { | 3410 ownerElement: { |
3110 enumerable: true, | 3411 enumerable: true, |
3111 get: nodeMethod(function () { | 3412 get: nodeMethod(function () { |
3112 return defaultTameNode(np(this).ownerElement, np(this).editable); | 3413 return defaultTameNode(np(this).ownerElement); |
3113 }) | 3414 }) |
3114 }, | 3415 }, |
3115 nodeType: P_constant(2), | 3416 nodeType: P_constant(2), |
3116 firstChild: P_UNIMPLEMENTED, | 3417 firstChild: P_UNIMPLEMENTED, |
3117 lastChild: P_UNIMPLEMENTED, | 3418 lastChild: P_UNIMPLEMENTED, |
3118 nextSibling: P_UNIMPLEMENTED, | 3419 nextSibling: P_UNIMPLEMENTED, |
3119 previousSibling: P_UNIMPLEMENTED, | 3420 previousSibling: P_UNIMPLEMENTED, |
3120 parentNode: P_UNIMPLEMENTED, | 3421 parentNode: P_UNIMPLEMENTED, |
3121 childNodes: P_UNIMPLEMENTED, | 3422 childNodes: P_UNIMPLEMENTED, |
3122 attributes: P_UNIMPLEMENTED | 3423 attributes: P_UNIMPLEMENTED |
(...skipping 24 matching lines...) Expand all Loading... | |
3147 if (Object.prototype.hasOwnProperty.call( | 3448 if (Object.prototype.hasOwnProperty.call( |
3148 seenAlready, attribName)) { | 3449 seenAlready, attribName)) { |
3149 return; | 3450 return; |
3150 } | 3451 } |
3151 seenAlready[attribName] = true; | 3452 seenAlready[attribName] = true; |
3152 | 3453 |
3153 Object.defineProperty(tameElementPrototype, attribName, { | 3454 Object.defineProperty(tameElementPrototype, attribName, { |
3154 enumerable: canHaveEnumerableAccessors, | 3455 enumerable: canHaveEnumerableAccessors, |
3155 configurable: false, | 3456 configurable: false, |
3156 set: nodeMethod(function eventHandlerSetter(listener) { | 3457 set: nodeMethod(function eventHandlerSetter(listener) { |
3157 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 3458 np(this).policy.requireEditable(); |
3158 if (!listener) { // Clear the current handler | 3459 if (!listener) { // Clear the current handler |
3159 np(this).feral[attribName] = null; | 3460 np(this).feral[attribName] = null; |
3160 } else { | 3461 } else { |
3161 // This handler cannot be copied from one node to another | 3462 // This handler cannot be copied from one node to another |
3162 // which is why getters are not yet supported. | 3463 // which is why getters are not yet supported. |
3163 np(this).feral[attribName] = makeEventHandlerWrapper( | 3464 np(this).feral[attribName] = makeEventHandlerWrapper( |
3164 np(this).feral, listener); | 3465 np(this).feral, listener); |
3165 } | 3466 } |
3166 return listener; | 3467 return listener; |
3167 }) | 3468 }) |
3168 }); | 3469 }); |
3169 })(html4Attrib.match(attrNameRe)[1]); | 3470 })(html4Attrib.match(attrNameRe)[1]); |
3170 } | 3471 } |
3171 } | 3472 } |
3172 } | 3473 } |
3173 | 3474 |
3174 traceStartup("DT: about to make TameElement"); | 3475 traceStartup("DT: about to make TameElement"); |
3175 /** | 3476 /** |
3176 * See comments on TameBackedNode regarding return value. | 3477 * See comments on TameBackedNode regarding return value. |
3177 * @constructor | 3478 * @constructor |
3178 */ | 3479 */ |
3179 function TameElement(node, editable, childrenEditable, opt_proxyType) { | 3480 function TameElement(node, opt_policy, opt_proxyType) { |
3180 assert(node.nodeType === 1); | 3481 assert(node.nodeType === 1); |
3181 var obj = TameBackedNode.call(this, node, editable, childrenEditable, | 3482 var obj = TameBackedNode.call(this, node, opt_policy, opt_proxyType); |
3182 opt_proxyType); | |
3183 np(this).geometryDelegate = node; | 3483 np(this).geometryDelegate = node; |
3184 return obj; | 3484 return obj; |
3185 } | 3485 } |
3186 nodeClasses.Element = inertCtor(TameElement, TameBackedNode, | 3486 nodeClasses.Element = inertCtor(TameElement, TameBackedNode, |
3187 'HTMLElement'); | 3487 'HTMLElement'); |
3188 registerElementScriptAttributeHandlers(TameElement.prototype); | 3488 registerElementScriptAttributeHandlers(TameElement.prototype); |
3189 TameElement.prototype.blur = nodeMethod(function () { | 3489 TameElement.prototype.blur = nodeMethod(function () { |
3190 np(this).feral.blur(); | 3490 np(this).feral.blur(); |
3191 }); | 3491 }); |
3192 TameElement.prototype.focus = nodeMethod(function () { | 3492 TameElement.prototype.focus = nodeMethod(function () { |
3193 if (domicile.isProcessingEvent) { | 3493 return domicile.handlingUserAction && np(this).feral.focus(); |
3194 np(this).feral.focus(); | |
3195 } | |
3196 }); | 3494 }); |
3197 // IE-specific method. Sets the element that will have focus when the | 3495 // IE-specific method. Sets the element that will have focus when the |
3198 // window has focus, without focusing the window. | 3496 // window has focus, without focusing the window. |
3199 if (docEl.setActive) { | 3497 if (docEl.setActive) { |
3200 TameElement.prototype.setActive = nodeMethod(function () { | 3498 TameElement.prototype.setActive = nodeMethod(function () { |
3201 if (domicile.isProcessingEvent) { | 3499 return domicile.handlingUserAction && np(this).feral.setActive(); |
3202 np(this).feral.setActive(); | |
3203 } | |
3204 }); | 3500 }); |
3205 } | 3501 } |
3206 // IE-specific method. | 3502 // IE-specific method. |
3207 if (docEl.hasFocus) { | 3503 if (docEl.hasFocus) { |
3208 TameElement.prototype.hasFocus = nodeMethod(function () { | 3504 TameElement.prototype.hasFocus = nodeMethod(function () { |
3209 return np(this).feral.hasFocus(); | 3505 return np(this).feral.hasFocus(); |
3210 }); | 3506 }); |
3211 } | 3507 } |
3212 TameElement.prototype.getAttribute = nodeMethod(function (attribName) { | 3508 TameElement.prototype.getAttribute = nodeMethod(function (attribName) { |
3509 if (!np(this).policy.attributesVisible) { return null; } | |
3213 var feral = np(this).feral; | 3510 var feral = np(this).feral; |
3214 attribName = String(attribName).toLowerCase(); | 3511 attribName = String(attribName).toLowerCase(); |
3215 if (/__$/.test(attribName)) { | 3512 if (/__$/.test(attribName)) { |
3216 throw new TypeError('Attributes may not end with __'); | 3513 throw new TypeError('Attributes may not end with __'); |
3217 } | 3514 } |
3218 var tagName = feral.tagName.toLowerCase(); | 3515 var tagName = feral.tagName.toLowerCase(); |
3219 var atype = htmlSchema.attribute(tagName, attribName).type; | 3516 var atype = htmlSchema.attribute(tagName, attribName).type; |
3220 if (atype === void 0) { | 3517 if (atype === void 0) { |
3221 return feral.getAttribute(cajaPrefix + attribName); | 3518 return feral.getAttribute(cajaPrefix + attribName); |
3222 } | 3519 } |
3223 var value = bridal.getAttribute(feral, attribName); | 3520 var value = bridal.getAttribute(feral, attribName); |
3224 if ('string' !== typeof value) { return value; } | 3521 if ('string' !== typeof value) { return value; } |
3225 return virtualizeAttributeValue(atype, value); | 3522 return virtualizeAttributeValue(atype, value); |
3226 }); | 3523 }); |
3227 TameElement.prototype.getAttributeNode = nodeMethod(function (name) { | 3524 TameElement.prototype.getAttributeNode = nodeMethod(function (name) { |
3525 if (!np(this).policy.attributesVisible) { return null; } | |
3228 var feral = np(this).feral; | 3526 var feral = np(this).feral; |
3229 var hostDomNode = feral.getAttributeNode(name); | 3527 var hostDomNode = feral.getAttributeNode(name); |
3230 if (hostDomNode === null) { return null; } | 3528 if (hostDomNode === null) { return null; } |
3231 return new TameBackedAttributeNode( | 3529 return new TameBackedAttributeNode(hostDomNode, feral); |
3232 hostDomNode, np(this).editable, feral); | |
3233 }); | 3530 }); |
3234 TameElement.prototype.hasAttribute = nodeMethod(function (attribName) { | 3531 TameElement.prototype.hasAttribute = nodeMethod(function (attribName) { |
3235 var feral = np(this).feral; | 3532 var feral = np(this).feral; |
3236 attribName = String(attribName).toLowerCase(); | 3533 attribName = String(attribName).toLowerCase(); |
3237 var tagName = feral.tagName.toLowerCase(); | 3534 var tagName = feral.tagName.toLowerCase(); |
3238 var atype = htmlSchema.attribute(tagName, attribName).type; | 3535 var atype = htmlSchema.attribute(tagName, attribName).type; |
3239 if (atype === void 0) { | 3536 if (atype === void 0) { |
3240 return bridal.hasAttribute(feral, cajaPrefix + attribName); | 3537 return bridal.hasAttribute(feral, cajaPrefix + attribName); |
3241 } else { | 3538 } else { |
3242 return bridal.hasAttribute(feral, attribName); | 3539 return bridal.hasAttribute(feral, attribName); |
3243 } | 3540 } |
3244 }); | 3541 }); |
3245 TameElement.prototype.setAttribute = nodeMethod( | 3542 TameElement.prototype.setAttribute = nodeMethod( |
3246 function (attribName, value) { | 3543 function (attribName, value) { |
3247 //console.debug("setAttribute", this, attribName, value); | 3544 //console.debug("setAttribute", this, attribName, value); |
3248 var feral = np(this).feral; | 3545 var feral = np(this).feral; |
3249 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 3546 np(this).policy.requireEditable(); |
3250 attribName = String(attribName).toLowerCase(); | 3547 attribName = String(attribName).toLowerCase(); |
3251 if (/__$/.test(attribName)) { | 3548 if (/__$/.test(attribName)) { |
3252 throw new TypeError('Attributes may not end with __'); | 3549 throw new TypeError('Attributes may not end with __'); |
3253 } | 3550 } |
3551 if (!np(this).policy.attributesVisible) { return null; } | |
3254 var tagName = feral.tagName.toLowerCase(); | 3552 var tagName = feral.tagName.toLowerCase(); |
3255 var atype = htmlSchema.attribute(tagName, attribName).type; | 3553 var atype = htmlSchema.attribute(tagName, attribName).type; |
3256 if (atype === void 0) { | 3554 if (atype === void 0) { |
3257 bridal.setAttribute(feral, cajaPrefix + attribName, value); | 3555 bridal.setAttribute(feral, cajaPrefix + attribName, value); |
3258 } else { | 3556 } else { |
3259 var sanitizedValue = rewriteAttribute( | 3557 var sanitizedValue = rewriteAttribute( |
3260 tagName, attribName, atype, value); | 3558 tagName, attribName, atype, value); |
3261 if (sanitizedValue !== null) { | 3559 if (sanitizedValue !== null) { |
3262 bridal.setAttribute(feral, attribName, sanitizedValue); | 3560 bridal.setAttribute(feral, attribName, sanitizedValue); |
3263 if (html4.ATTRIBS.hasOwnProperty(tagName + '::target') && | 3561 if (html4.ATTRIBS.hasOwnProperty(tagName + '::target') && |
3264 atype === html4.atype.URI) { | 3562 atype === html4.atype.URI) { |
3265 if (sanitizedValue.charAt(0) === '#') { | 3563 if (sanitizedValue.charAt(0) === '#') { |
3266 feral.removeAttribute('target'); | 3564 feral.removeAttribute('target'); |
3267 } else { | 3565 } else { |
3268 bridal.setAttribute(feral, 'target', | 3566 bridal.setAttribute(feral, 'target', |
3269 getSafeTargetAttribute(tagName, 'target', | 3567 getSafeTargetAttribute(tagName, 'target', |
3270 bridal.getAttribute(feral, 'target'))); | 3568 bridal.getAttribute(feral, 'target'))); |
3271 } | 3569 } |
3272 } | 3570 } |
3273 } | 3571 } |
3274 } | 3572 } |
3275 return value; | 3573 return value; |
3276 }); | 3574 }); |
3277 TameElement.prototype.removeAttribute = nodeMethod(function (attribName) { | 3575 TameElement.prototype.removeAttribute = nodeMethod(function (attribName) { |
3278 var feral = np(this).feral; | 3576 var feral = np(this).feral; |
3279 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 3577 np(this).policy.requireEditable(); |
3280 attribName = String(attribName).toLowerCase(); | 3578 attribName = String(attribName).toLowerCase(); |
3281 if (/__$/.test(attribName)) { | 3579 if (/__$/.test(attribName)) { |
3282 throw new TypeError('Attributes may not end with __'); | 3580 throw new TypeError('Attributes may not end with __'); |
3283 } | 3581 } |
3284 var tagName = feral.tagName.toLowerCase(); | 3582 var tagName = feral.tagName.toLowerCase(); |
3285 var atype = htmlSchema.attribute(tagName, attribName).type; | 3583 var atype = htmlSchema.attribute(tagName, attribName).type; |
3286 if (atype === void 0) { | 3584 if (atype === void 0) { |
3287 feral.removeAttribute(cajaPrefix + attribName); | 3585 feral.removeAttribute(cajaPrefix + attribName); |
3288 } else { | 3586 } else { |
3289 feral.removeAttribute(attribName); | 3587 feral.removeAttribute(attribName); |
3290 } | 3588 } |
3291 }); | 3589 }); |
3292 TameElement.prototype.getElementsByTagName = nodeMethod( | 3590 TameElement.prototype.getElementsByTagName = nodeMethod( |
3293 function(tagName) { | 3591 function(tagName) { |
3294 return tameGetElementsByTagName( | 3592 return tameGetElementsByTagName(np(this).feral, tagName); |
3295 np(this).feral, tagName, np(this).childrenEditable); | |
3296 }); | 3593 }); |
3297 TameElement.prototype.getElementsByClassName = nodeMethod( | 3594 TameElement.prototype.getElementsByClassName = nodeMethod( |
3298 function(className) { | 3595 function(className) { |
3299 return tameGetElementsByClassName( | 3596 return tameGetElementsByClassName(np(this).feral, className); |
3300 np(this).feral, className, np(this).childrenEditable); | |
3301 }); | 3597 }); |
3302 TameElement.prototype.getBoundingClientRect = nodeMethod(function () { | 3598 TameElement.prototype.getBoundingClientRect = nodeMethod(function () { |
3303 var feral = np(this).feral; | 3599 var feral = np(this).feral; |
3304 var elRect = bridal.getBoundingClientRect(feral); | 3600 var elRect = bridal.getBoundingClientRect(feral); |
3305 var vdoc = bridal.getBoundingClientRect( | 3601 var vdoc = bridal.getBoundingClientRect( |
3306 np(this.ownerDocument).feralContainerNode); | 3602 np(this.ownerDocument).feralContainerNode); |
3307 var vdocLeft = vdoc.left, vdocTop = vdoc.top; | 3603 var vdocLeft = vdoc.left, vdocTop = vdoc.top; |
3308 return ({ | 3604 return ({ |
3309 top: elRect.top - vdocTop, | 3605 top: elRect.top - vdocTop, |
3310 left: elRect.left - vdocLeft, | 3606 left: elRect.left - vdocLeft, |
3311 right: elRect.right - vdocLeft, | 3607 right: elRect.right - vdocLeft, |
3312 bottom: elRect.bottom - vdocTop | 3608 bottom: elRect.bottom - vdocTop |
3313 }); | 3609 }); |
3314 }); | 3610 }); |
3315 TameElement.prototype.updateStyle = nodeMethod(function (style) { | 3611 TameElement.prototype.updateStyle = nodeMethod(function (style) { |
3316 var feral = np(this).feral; | 3612 var feral = np(this).feral; |
3317 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 3613 np(this).policy.requireEditable(); |
3318 var cssPropertiesAndValues = cssSealerUnsealerPair.unseal(style); | 3614 var cssPropertiesAndValues = cssSealerUnsealerPair.unseal(style); |
3319 if (!cssPropertiesAndValues) { throw new Error(); } | 3615 if (!cssPropertiesAndValues) { throw new Error(); } |
3320 | 3616 |
3321 var styleNode = feral.style; | 3617 var styleNode = feral.style; |
3322 for (var i = 0; i < cssPropertiesAndValues.length; i += 2) { | 3618 for (var i = 0; i < cssPropertiesAndValues.length; i += 2) { |
3323 var propName = cssPropertiesAndValues[+i]; | 3619 var propName = cssPropertiesAndValues[+i]; |
3324 var propValue = cssPropertiesAndValues[i + 1]; | 3620 var propValue = cssPropertiesAndValues[i + 1]; |
3325 // If the propertyName differs between DOM and CSS, there will | 3621 // If the propertyName differs between DOM and CSS, there will |
3326 // be a semicolon between the two. | 3622 // be a semicolon between the two. |
3327 // E.g., 'background-color;backgroundColor' | 3623 // E.g., 'background-color;backgroundColor' |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3363 extendedAccessors: true, | 3659 extendedAccessors: true, |
3364 enumerable: true, | 3660 enumerable: true, |
3365 get: nodeMethod(function (prop) { | 3661 get: nodeMethod(function (prop) { |
3366 return np(this).geometryDelegate[prop]; | 3662 return np(this).geometryDelegate[prop]; |
3367 }) | 3663 }) |
3368 }; | 3664 }; |
3369 var geometryDelegatePropertySettable = | 3665 var geometryDelegatePropertySettable = |
3370 Object.create(geometryDelegateProperty); | 3666 Object.create(geometryDelegateProperty); |
3371 geometryDelegatePropertySettable.set = | 3667 geometryDelegatePropertySettable.set = |
3372 nodeMethod(function (value, prop) { | 3668 nodeMethod(function (value, prop) { |
3373 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 3669 np(this).policy.requireEditable(); |
3374 np(this).geometryDelegate[prop] = +value; | 3670 np(this).geometryDelegate[prop] = +value; |
3375 }); | 3671 }); |
3376 definePropertiesAwesomely(TameElement.prototype, { | 3672 definePropertiesAwesomely(TameElement.prototype, { |
3377 clientLeft: geometryDelegateProperty, | 3673 clientLeft: geometryDelegateProperty, |
3378 clientTop: geometryDelegateProperty, | 3674 clientTop: geometryDelegateProperty, |
3379 clientWidth: geometryDelegateProperty, | 3675 clientWidth: geometryDelegateProperty, |
3380 clientHeight: geometryDelegateProperty, | 3676 clientHeight: geometryDelegateProperty, |
3381 offsetLeft: geometryDelegateProperty, | 3677 offsetLeft: geometryDelegateProperty, |
3382 offsetTop: geometryDelegateProperty, | 3678 offsetTop: geometryDelegateProperty, |
3383 offsetWidth: geometryDelegateProperty, | 3679 offsetWidth: geometryDelegateProperty, |
3384 offsetHeight: geometryDelegateProperty, | 3680 offsetHeight: geometryDelegateProperty, |
3385 scrollLeft: geometryDelegatePropertySettable, | 3681 scrollLeft: geometryDelegatePropertySettable, |
3386 scrollTop: geometryDelegatePropertySettable, | 3682 scrollTop: geometryDelegatePropertySettable, |
3387 scrollWidth: geometryDelegateProperty, | 3683 scrollWidth: geometryDelegateProperty, |
3388 scrollHeight: geometryDelegateProperty | 3684 scrollHeight: geometryDelegateProperty |
3389 }); | 3685 }); |
3390 })(); | 3686 })(); |
3391 var innerTextProp = { | 3687 var textContentProp = { |
3392 enumerable: true, | 3688 enumerable: true, |
3393 get: nodeMethod(function () { | 3689 get: nodeMethod(function () { |
3394 var text = []; | 3690 var text = []; |
3395 innerTextOf(np(this).feral, text); | 3691 innerTextOf(np(this).feral, text); |
3396 return text.join(''); | 3692 return text.join(''); |
3397 }), | 3693 }), |
3398 set: nodeMethod(function (newText) { | 3694 set: nodeMethod(function (newText) { |
3399 // This operation changes the child node list (but not other | 3695 // This operation changes the child node list (but not other |
3400 // properties | 3696 // properties of the element) so it checks childrenEditable. Note that |
3401 // of the element) so it checks childrenEditable. Note that this check | 3697 // this check is critical to security, as else a client can set the |
3402 // is critical to security, as else a client can set the innerHTML of | 3698 // textContent of a <script> element to execute scripts. |
3403 // a <script> element to execute scripts. | 3699 np(this).policy.requireChildrenEditable(); |
3404 if (!np(this).childrenEditable) { throw new Error(NOT_EDITABLE); } | |
3405 var newTextStr = newText != null ? String(newText) : ''; | 3700 var newTextStr = newText != null ? String(newText) : ''; |
3406 var el = np(this).feral; | 3701 var el = np(this).feral; |
3407 for (var c; (c = el.firstChild);) { el.removeChild(c); } | 3702 for (var c; (c = el.firstChild);) { el.removeChild(c); } |
3408 if (newTextStr) { | 3703 if (newTextStr) { |
3409 el.appendChild(el.ownerDocument.createTextNode(newTextStr)); | 3704 el.appendChild(el.ownerDocument.createTextNode(newTextStr)); |
3410 } | 3705 } |
3411 }) | 3706 }) |
3412 }; | 3707 }; |
3413 var tagNameAttr = { | 3708 var tagNameAttr = { |
3414 enumerable: true, | 3709 enumerable: true, |
3415 get: nodeMethod(function () { | 3710 get: nodeMethod(function () { |
3416 return realToVirtualElementName(String(np(this).feral.tagName)); | 3711 return realToVirtualElementName(String(np(this).feral.tagName)); |
3417 }) | 3712 }) |
3418 }; | 3713 }; |
3419 definePropertiesAwesomely(TameElement.prototype, { | 3714 definePropertiesAwesomely(TameElement.prototype, { |
3420 id: NP.filterAttr(defaultToEmptyStr, identity), | 3715 id: NP.filterAttr(defaultToEmptyStr, identity), |
3421 className: { | 3716 className: { |
3422 enumerable: true, | 3717 enumerable: true, |
3423 get: nodeMethod(function () { | 3718 get: nodeMethod(function () { |
3424 return this.getAttribute('class') || ''; | 3719 return this.getAttribute('class') || ''; |
3425 }), | 3720 }), |
3426 set: nodeMethod(function (classes) { | 3721 set: nodeMethod(function (classes) { |
3427 return this.setAttribute('class', String(classes)); | 3722 return this.setAttribute('class', String(classes)); |
3428 }) | 3723 }) |
3429 }, | 3724 }, |
3430 title: NP.filterAttr(defaultToEmptyStr, String), | 3725 title: NP.filterAttr(defaultToEmptyStr, String), |
3431 dir: NP.filterAttr(defaultToEmptyStr, String), | 3726 dir: NP.filterAttr(defaultToEmptyStr, String), |
3432 innerText: innerTextProp, | 3727 textContent: textContentProp, |
3433 textContent: innerTextProp, | 3728 innerText: textContentProp, |
3729 // Note: Per MDN, innerText is actually subtly different than | |
3730 // textContent, in that innerText does not include text hidden via | |
3731 // styles, per MDN. We do not implement this difference. | |
3434 nodeName: tagNameAttr, | 3732 nodeName: tagNameAttr, |
3435 tagName: tagNameAttr, | 3733 tagName: tagNameAttr, |
3436 style: NP.filter( | 3734 style: NP.filter( |
3437 false, | 3735 false, |
3438 nodeMethod(function (styleNode) { | 3736 nodeMethod(function (styleNode) { |
3439 TameStyle || buildTameStyle(); | 3737 TameStyle || buildTameStyle(); |
3440 return new TameStyle(styleNode, np(this).editable, this); | 3738 return new TameStyle(styleNode, np(this).policy.editable, this); |
3441 }), | 3739 }), |
3442 true, identity), | 3740 true, identity), |
3443 innerHTML: { | 3741 innerHTML: { |
3444 enumerable: true, | 3742 enumerable: true, |
3445 get: nodeMethod(function () { | 3743 get: nodeMethod(function () { |
3446 var node = np(this).feral; | 3744 return htmlFragmentSerialization(this); |
3447 var tagName = node.tagName.toLowerCase(); | |
3448 var schemaElem = htmlSchema.element(tagName); | |
3449 if (!schemaElem.allowed) { | |
3450 return ''; // unknown node | |
3451 } | |
3452 var innerHtml = node.innerHTML; | |
3453 if (schemaElem.contentIsCDATA) { | |
3454 innerHtml = html.escapeAttrib(innerHtml); | |
3455 } else if (schemaElem.contentIsRCDATA) { | |
3456 // Make sure we return PCDATA. | |
3457 // For RCDATA we only need to escape & if they're not part of an | |
3458 // entity. | |
3459 innerHtml = html.normalizeRCData(innerHtml); | |
3460 } else { | |
3461 // If we blessed the resulting HTML, then this would round trip | |
3462 // better but it would still not survive appending, and it would | |
3463 // propagate event handlers where the setter of innerHTML does not | |
3464 // expect it to. | |
3465 innerHtml = tameInnerHtml(innerHtml); | |
3466 } | |
3467 return innerHtml; | |
3468 }), | 3745 }), |
3469 set: nodeMethod(function (htmlFragment) { | 3746 set: nodeMethod(function (htmlFragment) { |
3470 // This operation changes the child node list (but not other | 3747 // This operation changes the child node list (but not other |
3471 // properties of the element) so it checks childrenEditable. Note | 3748 // properties of the element) so it checks childrenEditable. Note |
3472 // that | 3749 // that this check is critical to security, as else a client can set |
3473 // this check is critical to security, as else a client can set the | 3750 // the innerHTML of a <script> element to execute scripts. |
3474 // innerHTML of a <script> element to execute scripts. | 3751 np(this).policy.requireChildrenEditable(); |
3475 if (!np(this).childrenEditable) { throw new Error(NOT_EDITABLE); } | |
3476 var node = np(this).feral; | 3752 var node = np(this).feral; |
3477 var schemaElem = htmlSchema.element(node.tagName); | 3753 var schemaElem = htmlSchema.element(node.tagName); |
3478 if (!schemaElem.allowed) { throw new Error(); } | 3754 if (!schemaElem.allowed) { throw new Error(); } |
3479 var isRCDATA = schemaElem.contentIsRCDATA; | 3755 var isRCDATA = schemaElem.contentIsRCDATA; |
3480 var htmlFragmentString; | 3756 var htmlFragmentString; |
3481 if (!isRCDATA && htmlFragment instanceof Html) { | 3757 if (!isRCDATA && htmlFragment instanceof Html) { |
3482 htmlFragmentString = '' + safeHtml(htmlFragment); | 3758 htmlFragmentString = '' + safeHtml(htmlFragment); |
3483 } else if (htmlFragment === null) { | 3759 } else if (htmlFragment === null) { |
3484 htmlFragmentString = ''; | 3760 htmlFragmentString = ''; |
3485 } else { | 3761 } else { |
(...skipping 16 matching lines...) Expand all Loading... | |
3502 if (!feralOffsetParent) { | 3778 if (!feralOffsetParent) { |
3503 return feralOffsetParent; | 3779 return feralOffsetParent; |
3504 } else if (feralOffsetParent === containerNode) { | 3780 } else if (feralOffsetParent === containerNode) { |
3505 // Return the body if the node is contained in the body. This is | 3781 // Return the body if the node is contained in the body. This is |
3506 // emulating how browsers treat offsetParent and the real <BODY>. | 3782 // emulating how browsers treat offsetParent and the real <BODY>. |
3507 var feralBody = np(tameDocument.body).feral; | 3783 var feralBody = np(tameDocument.body).feral; |
3508 for (var ancestor = makeDOMAccessible(np(this).feral.parentNode); | 3784 for (var ancestor = makeDOMAccessible(np(this).feral.parentNode); |
3509 ancestor !== containerNode; | 3785 ancestor !== containerNode; |
3510 ancestor = makeDOMAccessible(ancestor.parentNode)) { | 3786 ancestor = makeDOMAccessible(ancestor.parentNode)) { |
3511 if (ancestor === feralBody) { | 3787 if (ancestor === feralBody) { |
3512 return defaultTameNode(feralBody, np(this).editable); | 3788 return defaultTameNode(feralBody); |
3513 } | 3789 } |
3514 } | 3790 } |
3515 return null; | 3791 return null; |
3516 } else { | 3792 } else { |
3517 return tameRelatedNode(feralOffsetParent, np(this).editable, | 3793 return tameRelatedNode(feralOffsetParent, defaultTameNode); |
3518 defaultTameNode); | |
3519 } | 3794 } |
3520 }) | 3795 }) |
3521 }, | 3796 }, |
3522 accessKey: NP.rw, | 3797 accessKey: NP.rw, |
3523 tabIndex: NP.rw | 3798 tabIndex: NP.rw |
3524 }); | 3799 }); |
3525 cajaVM.def(TameElement); // and its prototype | 3800 cajaVM.def(TameElement); // and its prototype |
3526 | 3801 |
3527 traceStartup("DT: starting defineElement"); | 3802 traceStartup("DT: starting defineElement"); |
3528 | 3803 |
3529 /** | 3804 /** |
3530 * Define a taming class for a subclass of HTMLElement. | 3805 * Define a taming class for a subclass of HTMLElement. |
3531 * | 3806 * |
3532 * @param {Array} record.superclass The tame superclass constructor | 3807 * @param {Array} record.superclass The tame superclass constructor |
3533 * (defaults to TameElement) with parameters (this, node, editable, | 3808 * (defaults to TameElement) with parameters (this, node, policy, |
3534 * childrenEditable, opt_proxyType). | 3809 * opt_proxyType). |
3535 * @param {Array} record.names The element names which should be tamed | 3810 * @param {Array} record.names The element names which should be tamed |
3536 * using this class. | 3811 * using this class. |
3537 * @param {String} record.domClass The DOM-specified class name. | 3812 * @param {String} record.domClass The DOM-specified class name. |
3538 * @param {Object} record.properties The custom properties this class | 3813 * @param {Object} record.properties The custom properties this class |
3539 * should have (in the format accepted by definePropertiesAwesomely). | 3814 * should have (in the format accepted by definePropertiesAwesomely). |
3540 * @param {function} record.construct Code to invoke at the end of | 3815 * @param {function} record.construct Code to invoke at the end of |
3541 * construction; takes and returns self. | 3816 * construction; takes and returns self. |
3542 * @param {boolean} record.forceChildrenNotEditable Whether to force the | 3817 * @param {boolean} record.forceChildrenNotEditable Whether to force the |
3543 * childrenEditable flag to be false regardless of the value of | 3818 * child node list and child nodes to not be mutable. |
3544 * editable. | |
3545 * @return {function} The constructor. | 3819 * @return {function} The constructor. |
3546 */ | 3820 */ |
3547 function defineElement(record) { | 3821 function defineElement(record) { |
3548 var superclass = record.superclass || TameElement; | 3822 var superclass = record.superclass || TameElement; |
3549 var proxyType = record.proxyType; | 3823 var proxyType = record.proxyType; |
3550 var construct = record.construct || identity; | 3824 var construct = record.construct || identity; |
3551 var shouldBeVirtualized = record.virtualized || false; | 3825 var shouldBeVirtualized = "virtualized" in record |
3552 var forceChildrenNotEditable = record.forceChildrenNotEditable; | 3826 ? record.virtualized : false; |
3553 function TameSpecificElement(node, editable, childrenEditable) { | 3827 var opt_policy = record.forceChildrenNotEditable |
3828 ? nodePolicyReadOnlyChildren : null; | |
3829 function TameSpecificElement(node) { | |
3554 var isVirtualized = htmlSchema.isVirtualizedElementName(node.tagName); | 3830 var isVirtualized = htmlSchema.isVirtualizedElementName(node.tagName); |
3555 if (!isVirtualized !== !shouldBeVirtualized) { | 3831 if (shouldBeVirtualized !== null && |
Jasvir
2012/11/06 04:52:25
A little !too !clever. ;)
kpreid2
2012/11/06 19:19:54
This is a standard formulation of boolean xor. Do
| |
3832 !isVirtualized !== !shouldBeVirtualized) { | |
3556 throw new Error("Domado internal inconsistency: " + node.tagName + | 3833 throw new Error("Domado internal inconsistency: " + node.tagName + |
3557 " has inconsistent virtualization state with class " + | 3834 " has inconsistent virtualization state with class " + |
3558 record.domClass); | 3835 record.domClass); |
3559 } | 3836 } |
3560 superclass.call(this, | 3837 superclass.call(this, node, opt_policy, proxyType); |
3561 node, | |
3562 editable, | |
3563 childrenEditable && !forceChildrenNotEditable, | |
3564 proxyType); | |
3565 construct.call(this); | 3838 construct.call(this); |
3566 } | 3839 } |
3567 inertCtor(TameSpecificElement, superclass, record.domClass); | 3840 inertCtor(TameSpecificElement, superclass, record.domClass); |
3568 definePropertiesAwesomely(TameSpecificElement.prototype, | 3841 definePropertiesAwesomely(TameSpecificElement.prototype, |
3569 record.properties || {}); | 3842 record.properties || {}); |
3570 // Note: cajaVM.def will be applied to all registered node classes | 3843 // Note: cajaVM.def will be applied to all registered node classes |
3571 // later, so users of defineElement don't need to. | 3844 // later, so users of defineElement don't need to. |
3572 return TameSpecificElement; | 3845 return TameSpecificElement; |
3573 } | 3846 } |
3574 cajaVM.def(defineElement); | 3847 cajaVM.def(defineElement); |
(...skipping 255 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3830 return Object.freeze(tameGradient); | 4103 return Object.freeze(tameGradient); |
3831 } | 4104 } |
3832 function enforceFinite(value, name) { | 4105 function enforceFinite(value, name) { |
3833 enforceType(value, 'number', name); | 4106 enforceType(value, 'number', name); |
3834 if (!isFinite(value)) { | 4107 if (!isFinite(value)) { |
3835 throw new Error("NOT_SUPPORTED_ERR"); | 4108 throw new Error("NOT_SUPPORTED_ERR"); |
3836 // TODO(kpreid): should be a DOMException per spec | 4109 // TODO(kpreid): should be a DOMException per spec |
3837 } | 4110 } |
3838 } | 4111 } |
3839 | 4112 |
3840 function TameCanvasElement(node, editable) { | 4113 function TameCanvasElement(node) { |
3841 // TODO(kpreid): review whether this can use defineElement | 4114 // TODO(kpreid): review whether this can use defineElement |
3842 TameElement.call(this, node, editable, editable); | 4115 TameElement.call(this, node); |
3843 | 4116 |
3844 // helpers for tame context | 4117 // helpers for tame context |
3845 var context = makeDOMAccessible(node.getContext('2d')); | 4118 var context = makeDOMAccessible(node.getContext('2d')); |
3846 function tameFloatsOp(count, verb) { | 4119 function tameFloatsOp(count, verb) { |
3847 var m = makeFunctionAccessible(context[verb]); | 4120 var m = makeFunctionAccessible(context[verb]); |
3848 return cajaVM.def(function () { | 4121 return cajaVM.def(function () { |
3849 if (arguments.length !== count) { | 4122 if (arguments.length !== count) { |
3850 throw new Error(verb + ' takes ' + count + ' args, not ' + | 4123 throw new Error(verb + ' takes ' + count + ' args, not ' + |
3851 arguments.length); | 4124 arguments.length); |
3852 } | 4125 } |
(...skipping 348 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4201 StringTest([ | 4474 StringTest([ |
4202 "top", | 4475 "top", |
4203 "hanging", | 4476 "hanging", |
4204 "middle", | 4477 "middle", |
4205 "alphabetic", | 4478 "alphabetic", |
4206 "ideographic", | 4479 "ideographic", |
4207 "bottom" | 4480 "bottom" |
4208 ])) | 4481 ])) |
4209 }); | 4482 }); |
4210 TameContext2DConf.confide(tameContext2d); | 4483 TameContext2DConf.confide(tameContext2d); |
4211 TameContext2DConf.p(tameContext2d).editable = np(this).editable; | 4484 TameContext2DConf.p(tameContext2d).policy = np(this).policy; |
4212 TameContext2DConf.p(tameContext2d).feral = context; | 4485 TameContext2DConf.p(tameContext2d).feral = context; |
4213 cajaVM.def(tameContext2d); | 4486 cajaVM.def(tameContext2d); |
4214 taming.permitUntaming(tameContext2d); | 4487 taming.permitUntaming(tameContext2d); |
4215 } // end of TameCanvasElement | 4488 } // end of TameCanvasElement |
4216 inertCtor(TameCanvasElement, TameElement, 'HTMLCanvasElement'); | 4489 inertCtor(TameCanvasElement, TameElement, 'HTMLCanvasElement'); |
4217 TameCanvasElement.prototype.getContext = function (contextId) { | 4490 TameCanvasElement.prototype.getContext = function (contextId) { |
4218 | 4491 |
4219 // TODO(kpreid): We can refine this by inventing a | 4492 // TODO(kpreid): We can refine this by inventing a ReadOnlyCanvas |
4220 // ReadOnlyCanvas object | 4493 // object to return in this situation, which allows getImageData and |
4221 // to return in this situation, which allows getImageData and | 4494 // so on but not any drawing. Not bothering to do that for now; if |
4222 // so on but | 4495 // you have a use for it let us know. |
4223 // not any drawing. Not bothering to do that for now; if | 4496 np(this).policy.requireEditable(); |
4224 // you have a use | |
4225 // for it let us know. | |
4226 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | |
4227 | 4497 |
4228 enforceType(contextId, 'string', 'contextId'); | 4498 enforceType(contextId, 'string', 'contextId'); |
4229 switch (contextId) { | 4499 switch (contextId) { |
4230 case '2d': | 4500 case '2d': |
4231 return np(this).tameContext2d; | 4501 return np(this).tameContext2d; |
4232 default: | 4502 default: |
4233 // http://dev.w3.org/html5/spec/the-canvas-element.html#the-canvas -element | 4503 // http://dev.w3.org/html5/spec/the-canvas-element.html#the-canvas -element |
4234 // says: The getContext(contextId, args...) method of the canvas | 4504 // says: The getContext(contextId, args...) method of the canvas |
4235 // element, when invoked, must run the following steps: | 4505 // element, when invoked, must run the following steps: |
4236 // [...] | 4506 // [...] |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4298 | 4568 |
4299 var TameFormElement = defineElement({ | 4569 var TameFormElement = defineElement({ |
4300 domClass: 'HTMLFormElement', | 4570 domClass: 'HTMLFormElement', |
4301 proxyType: FormElementAndExpandoProxyHandler, | 4571 proxyType: FormElementAndExpandoProxyHandler, |
4302 properties: { | 4572 properties: { |
4303 action: NP.filterAttr(defaultToEmptyStr, String), | 4573 action: NP.filterAttr(defaultToEmptyStr, String), |
4304 elements: { | 4574 elements: { |
4305 enumerable: true, | 4575 enumerable: true, |
4306 get: nodeMethod(function () { | 4576 get: nodeMethod(function () { |
4307 return tameHTMLCollection( | 4577 return tameHTMLCollection( |
4308 np(this).feral.elements, np(this).editable, defaultTameNode); | 4578 np(this).feral.elements, defaultTameNode); |
4309 }) | 4579 }) |
4310 }, | 4580 }, |
4311 enctype: NP.filterAttr(defaultToEmptyStr, String), | 4581 enctype: NP.filterAttr(defaultToEmptyStr, String), |
4312 method: NP.filterAttr(defaultToEmptyStr, String), | 4582 method: NP.filterAttr(defaultToEmptyStr, String), |
4313 target: NP.filterAttr(defaultToEmptyStr, String) | 4583 target: NP.filterAttr(defaultToEmptyStr, String) |
4314 }, | 4584 }, |
4315 construct: function () { | 4585 construct: function () { |
4316 // Freeze length at creation time since we aren't live. | 4586 // Freeze length at creation time since we aren't live. |
4317 // TODO(kpreid): Revise this when we have live node lists. | 4587 // TODO(kpreid): Revise this when we have live node lists. |
4318 Object.defineProperty(this, "length", { | 4588 Object.defineProperty(this, "length", { |
4319 value: np(this).feral.length | 4589 value: np(this).feral.length |
4320 }); | 4590 }); |
4321 } | 4591 } |
4322 }); | 4592 }); |
4593 // TODO(felix8a): need to test handlingUserAction. | |
4323 TameFormElement.prototype.submit = nodeMethod(function () { | 4594 TameFormElement.prototype.submit = nodeMethod(function () { |
4324 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4595 np(this).policy.requireEditable(); |
4325 return np(this).feral.submit(); | 4596 return domicile.handlingUserAction && np(this).feral.submit(); |
4326 }); | 4597 }); |
4327 TameFormElement.prototype.reset = nodeMethod(function () { | 4598 TameFormElement.prototype.reset = nodeMethod(function () { |
4328 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4599 np(this).policy.requireEditable(); |
4329 return np(this).feral.reset(); | 4600 return np(this).feral.reset(); |
4330 }); | 4601 }); |
4331 | 4602 |
4332 defineTrivialElement('HTMLHeadingElement'); | 4603 defineTrivialElement('HTMLHeadingElement'); |
4333 defineTrivialElement('HTMLHRElement'); | 4604 defineTrivialElement('HTMLHRElement'); |
4334 | 4605 |
4335 defineElement({ | 4606 defineElement({ |
4336 virtualized: true, | 4607 virtualized: true, |
4337 domClass: 'HTMLHeadElement' | 4608 domClass: 'HTMLHeadElement' |
4338 }); | 4609 }); |
(...skipping 17 matching lines...) Expand all Loading... | |
4356 construct: function () { | 4627 construct: function () { |
4357 np(this).childrenEditable = false; | 4628 np(this).childrenEditable = false; |
4358 }, | 4629 }, |
4359 properties: { | 4630 properties: { |
4360 align: { | 4631 align: { |
4361 enumerable: true, | 4632 enumerable: true, |
4362 get: nodeMethod(function () { | 4633 get: nodeMethod(function () { |
4363 return np(this).feral.align; | 4634 return np(this).feral.align; |
4364 }), | 4635 }), |
4365 set: nodeMethod(function (alignment) { | 4636 set: nodeMethod(function (alignment) { |
4366 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4637 np(this).policy.requireEditable(); |
4367 alignment = String(alignment); | 4638 alignment = String(alignment); |
4368 if (alignment === 'left' || | 4639 if (alignment === 'left' || |
4369 alignment === 'right' || | 4640 alignment === 'right' || |
4370 alignment === 'center') { | 4641 alignment === 'center') { |
4371 np(this).feral.align = alignment; | 4642 np(this).feral.align = alignment; |
4372 } | 4643 } |
4373 }) | 4644 }) |
4374 }, | 4645 }, |
4375 frameBorder: { | 4646 frameBorder: { |
4376 enumerable: true, | 4647 enumerable: true, |
4377 get: nodeMethod(function () { | 4648 get: nodeMethod(function () { |
4378 return np(this).feral.frameBorder; | 4649 return np(this).feral.frameBorder; |
4379 }), | 4650 }), |
4380 set: nodeMethod(function (border) { | 4651 set: nodeMethod(function (border) { |
4381 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4652 np(this).policy.requireEditable(); |
4382 border = String(border).toLowerCase(); | 4653 border = String(border).toLowerCase(); |
4383 if (border === '0' || border === '1' || | 4654 if (border === '0' || border === '1' || |
4384 border === 'no' || border === 'yes') { | 4655 border === 'no' || border === 'yes') { |
4385 np(this).feral.frameBorder = border; | 4656 np(this).feral.frameBorder = border; |
4386 } | 4657 } |
4387 }) | 4658 }) |
4388 }, | 4659 }, |
4389 height: NP.filterProp(identity, Number), | 4660 height: NP.filterProp(identity, Number), |
4390 width: NP.filterProp(identity, Number), | 4661 width: NP.filterProp(identity, Number), |
4391 src: P_blacklist, | 4662 src: P_blacklist, |
(...skipping 10 matching lines...) Expand all Loading... | |
4402 } | 4673 } |
4403 return null; | 4674 return null; |
4404 })); | 4675 })); |
4405 setOwn(TameIFrameElement.prototype, 'setAttribute', | 4676 setOwn(TameIFrameElement.prototype, 'setAttribute', |
4406 nodeMethod(function (attr, value) { | 4677 nodeMethod(function (attr, value) { |
4407 var attrLc = String(attr).toLowerCase(); | 4678 var attrLc = String(attr).toLowerCase(); |
4408 // The 'name' and 'src' attributes are whitelisted for all tags in | 4679 // The 'name' and 'src' attributes are whitelisted for all tags in |
4409 // html4-attributes-whitelist.json, since they're needed on tags | 4680 // html4-attributes-whitelist.json, since they're needed on tags |
4410 // like <img>. Because there's currently no way to filter attributes | 4681 // like <img>. Because there's currently no way to filter attributes |
4411 // based on the tag, we have to blacklist these two here. | 4682 // based on the tag, we have to blacklist these two here. |
4683 // TODO(kpreid): Don't we have per-attribute filtering now? | |
4412 if (attrLc !== 'name' && attrLc !== 'src') { | 4684 if (attrLc !== 'name' && attrLc !== 'src') { |
4413 return TameElement.prototype.setAttribute.call(this, attr, value); | 4685 return TameElement.prototype.setAttribute.call(this, attr, value); |
4414 } | 4686 } |
4415 if (typeof console !== 'undefined') | 4687 if (typeof console !== 'undefined') |
4416 console.error('Cannot set the [' + attrLc + | 4688 console.error('Cannot set the [' + attrLc + |
4417 '] attribute of an iframe.'); | 4689 '] attribute of an iframe.'); |
4418 return value; | 4690 return value; |
4419 })); | 4691 })); |
4420 | 4692 |
4421 var TameImageElement = defineElement({ | 4693 var TameImageElement = defineElement({ |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4482 | 4754 |
4483 defineElement({ | 4755 defineElement({ |
4484 superclass: TameFormField, | 4756 superclass: TameFormField, |
4485 domClass: 'HTMLSelectElement', | 4757 domClass: 'HTMLSelectElement', |
4486 properties: { | 4758 properties: { |
4487 multiple: NP.rw, | 4759 multiple: NP.rw, |
4488 options: { | 4760 options: { |
4489 enumerable: true, | 4761 enumerable: true, |
4490 get: nodeMethod(function () { | 4762 get: nodeMethod(function () { |
4491 return new TameOptionsList( | 4763 return new TameOptionsList( |
4492 np(this).feral.options, | 4764 np(this).feral.options, defaultTameNode, 'name'); |
4493 np(this).editable, | |
4494 defaultTameNode, 'name'); | |
4495 }) | 4765 }) |
4496 }, | 4766 }, |
4497 selectedIndex: NP.filterProp(identity, toInt), | 4767 selectedIndex: NP.filterProp(identity, toInt), |
4498 type: NP.ro | 4768 type: NP.ro |
4499 } | 4769 } |
4500 }); | 4770 }); |
4501 | 4771 |
4502 defineElement({ | 4772 defineElement({ |
4503 superclass: TameFormField, | 4773 superclass: TameFormField, |
4504 domClass: 'HTMLTextAreaElement', | 4774 domClass: 'HTMLTextAreaElement', |
(...skipping 22 matching lines...) Expand all Loading... | |
4527 // TODO(kpreid): Justify these specialized filters. | 4797 // TODO(kpreid): Justify these specialized filters. |
4528 value: NP.filterProp( | 4798 value: NP.filterProp( |
4529 function (x) { return x == null ? null : String(x); }, | 4799 function (x) { return x == null ? null : String(x); }, |
4530 function (x) { return x == null ? '' : '' + x; }) | 4800 function (x) { return x == null ? '' : '' + x; }) |
4531 } | 4801 } |
4532 }); | 4802 }); |
4533 ······ | 4803 ······ |
4534 defineTrivialElement('HTMLParagraphElement'); | 4804 defineTrivialElement('HTMLParagraphElement'); |
4535 defineTrivialElement('HTMLPreElement'); | 4805 defineTrivialElement('HTMLPreElement'); |
4536 | 4806 |
4537 function dynamicCodeDispatchMaker(that) { | 4807 function dynamicCodeDispatchMaker(privates) { |
4538 window.cajaDynamicScriptCounter = | 4808 window.cajaDynamicScriptCounter = |
4539 window.cajaDynamicScriptCounter ? | 4809 window.cajaDynamicScriptCounter ? |
4540 window.cajaDynamicScriptCounter + 1 : 0; | 4810 window.cajaDynamicScriptCounter + 1 : 0; |
4541 var name = "caja_dynamic_script" + | 4811 var name = "caja_dynamic_script" + |
4542 window.cajaDynamicScriptCounter + '___'; | 4812 window.cajaDynamicScriptCounter + '___'; |
4543 window[name] = function() { | 4813 window[name] = function() { |
4544 try { | 4814 try { |
4545 if (that.src && | 4815 if (privates.src && |
4546 'function' === typeof domicile.evaluateUntrustedExternalScript) { | 4816 'function' === typeof domicile.evaluateUntrustedExternalScript) { |
4547 domicile.evaluateUntrustedExternalScript(that.src); | 4817 domicile.evaluateUntrustedExternalScript(privates.src); |
4548 } | 4818 } |
4549 } finally { | 4819 } finally { |
4550 window[name] = undefined; | 4820 window[name] = undefined; |
4551 } | 4821 } |
4552 }; | 4822 }; |
4553 return name + "();"; | 4823 return name + "();"; |
4554 } | 4824 } |
4555 | 4825 |
4556 var TameScriptElement = defineElement({ | 4826 var TameScriptElement = defineElement({ |
4557 domClass: 'HTMLScriptElement', | 4827 domClass: 'HTMLScriptElement', |
4558 forceChildrenNotEditable: true, | 4828 forceChildrenNotEditable: true, |
4559 properties: { | 4829 properties: { |
4560 src: NP.filter(false, identity, true, identity) | 4830 src: NP.filter(false, identity, true, identity) |
4561 }, | 4831 }, |
4562 construct: function () { | 4832 construct: function () { |
4563 var script = np(this); | 4833 var privates = np(this); |
4564 script.feral.appendChild( | 4834 privates.feral.appendChild( |
4565 document.createTextNode( | 4835 document.createTextNode( |
4566 dynamicCodeDispatchMaker(script))); | 4836 dynamicCodeDispatchMaker(privates))); |
4567 } | 4837 } |
4568 }); | 4838 }); |
4569 | 4839 |
4570 setOwn(TameScriptElement.prototype, 'setAttribute', nodeMethod( | 4840 setOwn(TameScriptElement.prototype, 'setAttribute', nodeMethod( |
4571 function (attrib, value) { | 4841 function (attrib, value) { |
4572 var feral = np(this).feral; | 4842 var feral = np(this).feral; |
4573 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4843 np(this).policy.requireEditable(); |
4574 TameElement.prototype.setAttribute.call(this, attrib, value); | 4844 TameElement.prototype.setAttribute.call(this, attrib, value); |
4575 var attribName = String(attrib).toLowerCase(); | 4845 var attribName = String(attrib).toLowerCase(); |
4576 if ("src" === attribName) { | 4846 if ("src" === attribName) { |
4577 np(this).src = String(value); | 4847 np(this).src = String(value); |
4578 } | 4848 } |
4579 })); | 4849 })); |
4580 | 4850 |
4581 defineTrivialElement('HTMLSpanElement'); | 4851 defineTrivialElement('HTMLSpanElement'); |
4582 | 4852 |
4583 defineElement({ | 4853 defineElement({ |
4584 domClass: 'HTMLTableColElement', | 4854 domClass: 'HTMLTableColElement', |
4585 properties: { | 4855 properties: { |
4586 align: NP.filterProp(identity, identity), | 4856 align: NP.filterProp(identity, identity), |
4587 vAlign: NP.filterProp(identity, identity) | 4857 vAlign: NP.filterProp(identity, identity) |
4588 } | 4858 } |
4589 }); | 4859 }); |
4590 ······ | 4860 ······ |
4591 defineTrivialElement('HTMLCaptionElement'); | 4861 defineTrivialElement('HTMLTableCaptionElement'); |
4592 ······ | 4862 ······ |
4593 var TameTableCellElement = defineElement({ | 4863 var TameTableCellElement = defineElement({ |
4594 domClass: 'HTMLTableCellElement', | 4864 domClass: 'HTMLTableCellElement', |
4595 properties: { | 4865 properties: { |
4596 colSpan: NP.filterProp(identity, identity), | 4866 colSpan: NP.filterProp(identity, identity), |
4597 rowSpan: NP.filterProp(identity, identity), | 4867 rowSpan: NP.filterProp(identity, identity), |
4598 cellIndex: NP.ro, | 4868 cellIndex: NP.ro, |
4599 noWrap: NP.filterProp(identity, identity) // HTML5 Obsolete | 4869 noWrap: NP.filterProp(identity, identity) // HTML5 Obsolete |
4600 } | 4870 } |
4601 }); | 4871 }); |
(...skipping 14 matching lines...) Expand all Loading... | |
4616 | 4886 |
4617 var TameTableRowElement = defineElement({ | 4887 var TameTableRowElement = defineElement({ |
4618 domClass: 'HTMLTableRowElement', | 4888 domClass: 'HTMLTableRowElement', |
4619 properties: { | 4889 properties: { |
4620 cells: { | 4890 cells: { |
4621 // TODO(kpreid): It would be most pleasing to find a way to generali ze | 4891 // TODO(kpreid): It would be most pleasing to find a way to generali ze |
4622 // all the accessors which are of the form | 4892 // all the accessors which are of the form |
4623 // return new TameNodeList(np(this).feral...., ..., ...) | 4893 // return new TameNodeList(np(this).feral...., ..., ...) |
4624 enumerable: true, | 4894 enumerable: true, |
4625 get: nodeMethod(function () { | 4895 get: nodeMethod(function () { |
4626 return new TameNodeList( | 4896 return new TameNodeList(np(this).feral.cells, defaultTameNode); |
4627 np(this).feral.cells, np(this).editable, defaultTameNode); | |
4628 }) | 4897 }) |
4629 }, | 4898 }, |
4630 rowIndex: NP.ro, | 4899 rowIndex: NP.ro, |
4631 sectionRowIndex: NP.ro | 4900 sectionRowIndex: NP.ro |
4632 } | 4901 } |
4633 }); | 4902 }); |
4634 TameTableRowElement.prototype.insertCell = nodeMethod(function (index) { | 4903 TameTableRowElement.prototype.insertCell = nodeMethod(function (index) { |
4635 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4904 np(this).policy.requireEditable(); |
4636 requireIntIn(index, -1, np(this).feral.cells.length); | 4905 requireIntIn(index, -1, np(this).feral.cells.length); |
4637 return defaultTameNode( | 4906 return defaultTameNode( |
4638 np(this).feral.insertCell(index), | 4907 np(this).feral.insertCell(index), |
4639 np(this).editable); | 4908 np(this).editable); |
4640 }); | 4909 }); |
4641 TameTableRowElement.prototype.deleteCell = nodeMethod(function (index) { | 4910 TameTableRowElement.prototype.deleteCell = nodeMethod(function (index) { |
4642 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4911 np(this).policy.requireEditable(); |
4643 requireIntIn(index, -1, np(this).feral.cells.length); | 4912 requireIntIn(index, -1, np(this).feral.cells.length); |
4644 np(this).feral.deleteCell(index); | 4913 np(this).feral.deleteCell(index); |
4645 }); | 4914 }); |
4646 | 4915 |
4647 var TameTableSectionElement = defineElement({ | 4916 var TameTableSectionElement = defineElement({ |
4648 domClass: 'HTMLTableSectionElement', | 4917 domClass: 'HTMLTableSectionElement', |
4649 properties: { | 4918 properties: { |
4650 rows: { | 4919 rows: { |
4651 enumerable: true, | 4920 enumerable: true, |
4652 get: nodeMethod(function () { | 4921 get: nodeMethod(function () { |
4653 return new TameNodeList( | 4922 return new TameNodeList(np(this).feral.rows, defaultTameNode); |
4654 np(this).feral.rows, np(this).editable, defaultTameNode); | |
4655 }) | 4923 }) |
4656 } | 4924 } |
4657 } | 4925 } |
4658 }); | 4926 }); |
4659 TameTableSectionElement.prototype.insertRow = nodeMethod(function(index) { | 4927 TameTableSectionElement.prototype.insertRow = nodeMethod(function(index) { |
4660 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4928 np(this).policy.requireEditable(); |
4661 requireIntIn(index, -1, np(this).feral.rows.length); | 4929 requireIntIn(index, -1, np(this).feral.rows.length); |
4662 return defaultTameNode(np(this).feral.insertRow(index), | 4930 return defaultTameNode(np(this).feral.insertRow(index)); |
4663 np(this).editable); | |
4664 }); | 4931 }); |
4665 TameTableSectionElement.prototype.deleteRow = nodeMethod(function(index) { | 4932 TameTableSectionElement.prototype.deleteRow = nodeMethod(function(index) { |
4666 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4933 np(this).policy.requireEditable(); |
4667 requireIntIn(index, -1, np(this).feral.rows.length); | 4934 requireIntIn(index, -1, np(this).feral.rows.length); |
4668 np(this).feral.deleteRow(index); | 4935 np(this).feral.deleteRow(index); |
4669 }); | 4936 }); |
4670 | 4937 |
4671 var TameTableElement = defineElement({ | 4938 var TameTableElement = defineElement({ |
4672 superclass: TameTableSectionElement, // nonstandard but sound | 4939 superclass: TameTableSectionElement, // nonstandard but sound |
4673 domClass: 'HTMLTableElement', | 4940 domClass: 'HTMLTableElement', |
4674 properties: { | 4941 properties: { |
4675 tBodies: { | 4942 tBodies: { |
4676 enumerable: true, | 4943 enumerable: true, |
4677 get: nodeMethod(function () { | 4944 get: nodeMethod(function () { |
4678 return new TameNodeList( | 4945 if (np(this).policy.childrenVisible) { |
4679 np(this).feral.tBodies, np(this).editable, defaultTameNode); | 4946 return new TameNodeList(np(this).feral.tBodies, |
4947 defaultTameNode); | |
4948 } else { | |
4949 return fakeNodeList([]); | |
4950 } | |
4680 }) | 4951 }) |
4681 }, | 4952 }, |
4682 tHead: NP_tameDescendant, | 4953 tHead: NP_tameDescendant, |
4683 tFoot: NP_tameDescendant, | 4954 tFoot: NP_tameDescendant, |
4684 cellPadding: NP.filterAttr(Number, fromInt), | 4955 cellPadding: NP.filterAttr(Number, fromInt), |
4685 cellSpacing: NP.filterAttr(Number, fromInt), | 4956 cellSpacing: NP.filterAttr(Number, fromInt), |
4686 border: NP.filterAttr(Number, fromInt) | 4957 border: NP.filterAttr(Number, fromInt) |
4687 } | 4958 } |
4688 }); | 4959 }); |
4689 TameTableElement.prototype.createTHead = nodeMethod(function () { | 4960 TameTableElement.prototype.createTHead = nodeMethod(function () { |
4690 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4961 np(this).policy.requireEditable(); |
4691 return defaultTameNode(np(this).feral.createTHead(), np(this).editable); | 4962 return defaultTameNode(np(this).feral.createTHead()); |
4692 }); | 4963 }); |
4693 TameTableElement.prototype.deleteTHead = nodeMethod(function () { | 4964 TameTableElement.prototype.deleteTHead = nodeMethod(function () { |
4694 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4965 np(this).policy.requireEditable(); |
4695 np(this).feral.deleteTHead(); | 4966 np(this).feral.deleteTHead(); |
4696 }); | 4967 }); |
4697 TameTableElement.prototype.createTFoot = nodeMethod(function () { | 4968 TameTableElement.prototype.createTFoot = nodeMethod(function () { |
4698 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4969 np(this).policy.requireEditable(); |
4699 return defaultTameNode(np(this).feral.createTFoot(), np(this).editable); | 4970 return defaultTameNode(np(this).feral.createTFoot()); |
4700 }); | 4971 }); |
4701 TameTableElement.prototype.deleteTFoot = nodeMethod(function () { | 4972 TameTableElement.prototype.deleteTFoot = nodeMethod(function () { |
4702 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4973 np(this).policy.requireEditable(); |
4703 np(this).feral.deleteTFoot(); | 4974 np(this).feral.deleteTFoot(); |
4704 }); | 4975 }); |
4705 TameTableElement.prototype.createCaption = nodeMethod(function () { | 4976 TameTableElement.prototype.createCaption = nodeMethod(function () { |
4706 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4977 np(this).policy.requireEditable(); |
4707 return defaultTameNode(np(this).feral.createCaption(), np(this).editable ); | 4978 return defaultTameNode(np(this).feral.createCaption()); |
4708 }); | 4979 }); |
4709 TameTableElement.prototype.deleteCaption = nodeMethod(function () { | 4980 TameTableElement.prototype.deleteCaption = nodeMethod(function () { |
4710 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4981 np(this).policy.requireEditable(); |
4711 np(this).feral.deleteCaption(); | 4982 np(this).feral.deleteCaption(); |
4712 }); | 4983 }); |
4713 TameTableElement.prototype.insertRow = nodeMethod(function (index) { | 4984 TameTableElement.prototype.insertRow = nodeMethod(function (index) { |
4714 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4985 np(this).policy.requireEditable(); |
4715 requireIntIn(index, -1, np(this).feral.rows.length); | 4986 requireIntIn(index, -1, np(this).feral.rows.length); |
4716 return defaultTameNode(np(this).feral.insertRow(index), | 4987 return defaultTameNode(np(this).feral.insertRow(index)); |
4717 np(this).editable); | |
4718 }); | 4988 }); |
4719 TameTableElement.prototype.deleteRow = nodeMethod(function (index) { | 4989 TameTableElement.prototype.deleteRow = nodeMethod(function (index) { |
4720 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4990 np(this).policy.requireEditable(); |
4721 requireIntIn(index, -1, np(this).feral.rows.length); | 4991 requireIntIn(index, -1, np(this).feral.rows.length); |
4722 np(this).feral.deleteRow(index); | 4992 np(this).feral.deleteRow(index); |
4723 }); | 4993 }); |
4724 | 4994 |
4725 defineElement({ | 4995 defineElement({ |
4726 virtualized: true, | 4996 virtualized: true, |
4727 domClass: 'HTMLTitleElement' | 4997 domClass: 'HTMLTitleElement' |
4728 }); | 4998 }); |
4729 ······ | 4999 ······ |
4730 defineTrivialElement('HTMLUListElement'); | 5000 defineTrivialElement('HTMLUListElement'); |
4731 | 5001 |
5002 defineElement({ | |
5003 virtualized: null, | |
5004 domClass: 'HTMLUnknownElement' | |
5005 }); | |
5006 ······ | |
4732 traceStartup('DT: done with specific elements'); | 5007 traceStartup('DT: done with specific elements'); |
4733 | 5008 |
4734 // Oddball constructors. There are only two of these and we implement | 5009 // Oddball constructors. There are only two of these and we implement |
4735 // both. (Caveat: In actual browsers, new Image().constructor == Image | 5010 // both. (Caveat: In actual browsers, new Image().constructor == Image |
4736 // != HTMLImageElement. We don't implement that.) | 5011 // != HTMLImageElement. We don't implement that.) |
4737 ······ | 5012 ······ |
4738 // Per https://developer.mozilla.org/en-US/docs/DOM/Image as of 2012-09-24 | 5013 // Per https://developer.mozilla.org/en-US/docs/DOM/Image as of 2012-09-24 |
4739 function TameImageFun(width, height) { | 5014 function TameImageFun(width, height) { |
4740 var element = tameDocument.createElement('img'); | 5015 var element = tameDocument.createElement('img'); |
4741 if (width !== undefined) { element.width = width; } | 5016 if (width !== undefined) { element.width = width; } |
(...skipping 29 matching lines...) Expand all Loading... | |
4771 } | 5046 } |
4772 return taming.tame(event); | 5047 return taming.tame(event); |
4773 } | 5048 } |
4774 | 5049 |
4775 var ep = TameEventConf.p.bind(TameEventConf); | 5050 var ep = TameEventConf.p.bind(TameEventConf); |
4776 | 5051 |
4777 var EP_RELATED = { | 5052 var EP_RELATED = { |
4778 enumerable: true, | 5053 enumerable: true, |
4779 extendedAccessors: true, | 5054 extendedAccessors: true, |
4780 get: eventMethod(function (prop) { | 5055 get: eventMethod(function (prop) { |
4781 // TODO(kpreid): Isn't it unsafe to be always editable=true here? | 5056 return tameRelatedNode(ep(this).feral[prop], defaultTameNode); |
4782 return tameRelatedNode(ep(this).feral[prop], true, | |
4783 defaultTameNode); | |
4784 }) | 5057 }) |
4785 }; | 5058 }; |
4786 | 5059 |
4787 function P_e_view(transform) { | 5060 function P_e_view(transform) { |
4788 return { | 5061 return { |
4789 enumerable: true, | 5062 enumerable: true, |
4790 extendedAccessors: true, | 5063 extendedAccessors: true, |
4791 get: eventMethod(function (prop) { | 5064 get: eventMethod(function (prop) { |
4792 return transform(ep(this).feral[prop]); | 5065 return transform(ep(this).feral[prop]); |
4793 }) | 5066 }) |
(...skipping 12 matching lines...) Expand all Loading... | |
4806 enumerable: true, | 5079 enumerable: true, |
4807 get: eventMethod(function () { | 5080 get: eventMethod(function () { |
4808 return bridal.untameEventType(String(ep(this).feral.type)); | 5081 return bridal.untameEventType(String(ep(this).feral.type)); |
4809 }) | 5082 }) |
4810 }, | 5083 }, |
4811 target: { | 5084 target: { |
4812 enumerable: true, | 5085 enumerable: true, |
4813 get: eventMethod(function () { | 5086 get: eventMethod(function () { |
4814 var event = ep(this).feral; | 5087 var event = ep(this).feral; |
4815 return tameRelatedNode( | 5088 return tameRelatedNode( |
4816 event.target || event.srcElement, true, defaultTameNode); | 5089 event.target || event.srcElement, defaultTameNode); |
4817 }) | 5090 }) |
4818 }, | 5091 }, |
4819 srcElement: { | 5092 srcElement: { |
4820 enumerable: true, | 5093 enumerable: true, |
4821 get: eventMethod(function () { | 5094 get: eventMethod(function () { |
4822 return tameRelatedNode(ep(this).feral.srcElement, true, | 5095 return tameRelatedNode(ep(this).feral.srcElement, defaultTameNode); |
4823 defaultTameNode); | |
4824 }) | 5096 }) |
4825 }, | 5097 }, |
4826 currentTarget: { | 5098 currentTarget: { |
4827 enumerable: true, | 5099 enumerable: true, |
4828 get: eventMethod(function () { | 5100 get: eventMethod(function () { |
4829 var e = ep(this).feral; | 5101 var e = ep(this).feral; |
4830 return tameRelatedNode(e.currentTarget, true, defaultTameNode); | 5102 return tameRelatedNode(e.currentTarget, defaultTameNode); |
4831 }) | 5103 }) |
4832 }, | 5104 }, |
4833 relatedTarget: { | 5105 relatedTarget: { |
4834 enumerable: true, | 5106 enumerable: true, |
4835 get: eventMethod(function () { | 5107 get: eventMethod(function () { |
4836 var e = ep(this).feral; | 5108 var e = ep(this).feral; |
4837 var t = e.relatedTarget; | 5109 var t = e.relatedTarget; |
4838 if (!t) { | 5110 if (!t) { |
4839 if (e.type === 'mouseout') { | 5111 if (e.type === 'mouseout') { |
4840 t = e.toElement; | 5112 t = e.toElement; |
4841 } else if (e.type === 'mouseover') { | 5113 } else if (e.type === 'mouseover') { |
4842 t = e.fromElement; | 5114 t = e.fromElement; |
4843 } | 5115 } |
4844 } | 5116 } |
4845 return tameRelatedNode(t, true, defaultTameNode); | 5117 return tameRelatedNode(t, defaultTameNode); |
4846 }), | 5118 }), |
4847 // relatedTarget is read-only. this dummy setter is because some code | 5119 // relatedTarget is read-only. this dummy setter is because some code |
4848 // tries to workaround IE by setting a relatedTarget when it's not | 5120 // tries to workaround IE by setting a relatedTarget when it's not |
4849 // set. | 5121 // set. |
4850 // code in a sandbox can't tell the difference between "falsey because | 5122 // code in a sandbox can't tell the difference between "falsey because |
4851 // relatedTarget is not supported" and "falsey because relatedTarget | 5123 // relatedTarget is not supported" and "falsey because relatedTarget |
4852 // is outside sandbox". | 5124 // is outside sandbox". |
4853 set: eventMethod(function () {}) | 5125 set: eventMethod(function () {}) |
4854 }, | 5126 }, |
4855 fromElement: EP_RELATED, | 5127 fromElement: EP_RELATED, |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4911 inherit(TameCustomHTMLEvent, TameEvent); | 5183 inherit(TameCustomHTMLEvent, TameEvent); |
4912 TameCustomHTMLEvent.prototype.initEvent | 5184 TameCustomHTMLEvent.prototype.initEvent |
4913 = eventMethod(function (type, bubbles, cancelable) { | 5185 = eventMethod(function (type, bubbles, cancelable) { |
4914 bridal.initEvent(ep(this).feral, type, bubbles, cancelable); | 5186 bridal.initEvent(ep(this).feral, type, bubbles, cancelable); |
4915 }); | 5187 }); |
4916 setOwn(TameCustomHTMLEvent.prototype, "toString", eventMethod(function () { | 5188 setOwn(TameCustomHTMLEvent.prototype, "toString", eventMethod(function () { |
4917 return '[Fake CustomEvent]'; | 5189 return '[Fake CustomEvent]'; |
4918 })); | 5190 })); |
4919 cajaVM.def(TameCustomHTMLEvent); // and its prototype | 5191 cajaVM.def(TameCustomHTMLEvent); // and its prototype |
4920 | 5192 |
4921 function TameHTMLDocument(doc, container, domain, editable) { | 5193 function TameHTMLDocument(doc, container, domain) { |
4922 traceStartup("DT: TameHTMLDocument begin"); | 5194 traceStartup("DT: TameHTMLDocument begin"); |
4923 TamePseudoNode.call(this, editable); | 5195 TamePseudoNode.call(this); |
4924 | 5196 |
4925 np(this).feralDoc = doc; | 5197 np(this).feralDoc = doc; |
4926 np(this).feralContainerNode = container; | 5198 np(this).feralContainerNode = container; |
4927 np(this).onLoadListeners = []; | 5199 np(this).onLoadListeners = []; |
4928 np(this).onDCLListeners = []; | 5200 np(this).onDCLListeners = []; |
4929 | 5201 |
4930 traceStartup("DT: TameHTMLDocument done private"); | 5202 traceStartup("DT: TameHTMLDocument done private"); |
4931 | 5203 |
4932 var tameContainer = defaultTameNode(container, editable); | 5204 var tameContainer = defaultTameNode(container); |
4933 np(this).tameContainerNode = tameContainer; | 5205 np(this).tameContainerNode = tameContainer; |
4934 | 5206 |
4935 definePropertiesAwesomely(this, { | 5207 definePropertiesAwesomely(this, { |
4936 domain: P_constant(domain) | 5208 domain: P_constant(domain) |
4937 }); | 5209 }); |
4938 | 5210 |
4939 installLocation(this); | 5211 installLocation(this); |
4940 } | 5212 } |
4941 inertCtor(TameHTMLDocument, TamePseudoNode, 'HTMLDocument'); | 5213 inertCtor(TameHTMLDocument, TamePseudoNode, 'HTMLDocument'); |
4942 definePropertiesAwesomely(TameHTMLDocument.prototype, { | 5214 definePropertiesAwesomely(TameHTMLDocument.prototype, { |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
4974 for (n = this.firstChild; n; n = n.nextSibling) { | 5246 for (n = this.firstChild; n; n = n.nextSibling) { |
4975 if (n.nodeType === 1) { return n; } | 5247 if (n.nodeType === 1) { return n; } |
4976 } | 5248 } |
4977 // None of our children are elements, fail | 5249 // None of our children are elements, fail |
4978 return null; | 5250 return null; |
4979 })}, | 5251 })}, |
4980 forms: { enumerable: true, get: nodeMethod(function () { | 5252 forms: { enumerable: true, get: nodeMethod(function () { |
4981 var tameForms = []; | 5253 var tameForms = []; |
4982 for (var i = 0; i < document.forms.length; i++) { | 5254 for (var i = 0; i < document.forms.length; i++) { |
4983 var tameForm = tameRelatedNode( | 5255 var tameForm = tameRelatedNode( |
4984 makeDOMAccessible(document.forms).item(i), | 5256 makeDOMAccessible(document.forms).item(i), defaultTameNode); |
4985 np(this).editable, defaultTameNode); | |
4986 // tameRelatedNode returns null if the node is not part of | 5257 // tameRelatedNode returns null if the node is not part of |
4987 // this node's virtual document. | 5258 // this node's virtual document. |
4988 if (tameForm !== null) { tameForms.push(tameForm); } | 5259 if (tameForm !== null) { tameForms.push(tameForm); } |
4989 } | 5260 } |
4990 return fakeNodeList(tameForms); | 5261 return fakeNodeList(tameForms); |
4991 })}, | 5262 })}, |
4992 title: { | 5263 title: { |
4993 // TODO(kpreid): get the title element pointer in conformant way | 5264 // TODO(kpreid): get the title element pointer in conformant way |
4994 | 5265 |
4995 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.htm l#document.title | 5266 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.htm l#document.title |
4996 // as of 2012-08-14 | 5267 // as of 2012-08-14 |
4997 enumerable: true, | 5268 enumerable: true, |
4998 get: nodeMethod(function() { | 5269 get: nodeMethod(function() { |
4999 var titleEl = this.getElementsByTagName('title')[0]; | 5270 var titleEl = this.getElementsByTagName('title')[0]; |
5000 return trimHTML5Spaces(titleEl.textContent); | 5271 return trimHTML5Spaces(titleEl.textContent); |
5001 }), | 5272 }), |
5002 set: nodeMethod(function(value) { | 5273 set: nodeMethod(function(value) { |
5003 var titleEl = this.getElementsByTagName('title')[0]; | 5274 var titleEl = this.getElementsByTagName('title')[0]; |
5004 titleEl.textContent = value; | 5275 titleEl.textContent = value; |
5005 }) | 5276 }) |
5006 }, | 5277 }, |
5007 compatMode: P_constant('CSS1Compat'), | 5278 compatMode: P_constant('CSS1Compat'), |
5008 ownerDocument: P_constant(null) | 5279 ownerDocument: P_constant(null) |
5009 }); | 5280 }); |
5010 TameHTMLDocument.prototype.getElementsByTagName = nodeMethod( | 5281 TameHTMLDocument.prototype.getElementsByTagName = nodeMethod( |
5011 function (tagName) { | 5282 function (tagName) { |
5012 tagName = String(tagName).toLowerCase(); | 5283 tagName = String(tagName).toLowerCase(); |
5013 return tameGetElementsByTagName( | 5284 return tameGetElementsByTagName(np(this).feralContainerNode, tagName); |
5014 np(this).feralContainerNode, tagName, np(this).editable); | |
5015 }); | 5285 }); |
5016 TameHTMLDocument.prototype.getElementsByClassName = nodeMethod( | 5286 TameHTMLDocument.prototype.getElementsByClassName = nodeMethod( |
5017 function (className) { | 5287 function (className) { |
5018 return tameGetElementsByClassName( | 5288 return tameGetElementsByClassName( |
5019 np(this).feralContainerNode, className, np(this).editable); | 5289 np(this).feralContainerNode, className); |
5020 }); | 5290 }); |
5021 TameHTMLDocument.prototype.addEventListener = | 5291 TameHTMLDocument.prototype.addEventListener = |
5022 nodeMethod(function (name, listener, useCapture) { | 5292 nodeMethod(function (name, listener, useCapture) { |
5023 if (name === 'DOMContentLoaded') { | 5293 if (name === 'DOMContentLoaded') { |
5024 domitaModules.ensureValidCallback(listener); | 5294 domitaModules.ensureValidCallback(listener); |
5025 np(tameDocument).onDCLListeners.push(listener); | 5295 np(tameDocument).onDCLListeners.push(listener); |
5026 } else { | 5296 } else { |
5027 return np(this).tameContainerNode.addEventListener( | 5297 return np(this).tameContainerNode.addEventListener( |
5028 name, listener, useCapture); | 5298 name, listener, useCapture); |
5029 } | 5299 } |
5030 }); | 5300 }); |
5031 TameHTMLDocument.prototype.removeEventListener = | 5301 TameHTMLDocument.prototype.removeEventListener = |
5032 nodeMethod(function (name, listener, useCapture) { | 5302 nodeMethod(function (name, listener, useCapture) { |
5033 return np(this).tameContainerNode.removeEventListener( | 5303 return np(this).tameContainerNode.removeEventListener( |
5034 name, listener, useCapture); | 5304 name, listener, useCapture); |
5035 }); | 5305 }); |
5036 TameHTMLDocument.prototype.createComment = nodeMethod(function (text) { | 5306 TameHTMLDocument.prototype.createComment = nodeMethod(function (text) { |
5037 return defaultTameNode(np(this).feralDoc.createComment(" "), true); | 5307 return defaultTameNode(np(this).feralDoc.createComment(" ")); |
5038 }); | 5308 }); |
5039 TameHTMLDocument.prototype.createDocumentFragment = nodeMethod(function () { | 5309 TameHTMLDocument.prototype.createDocumentFragment = nodeMethod(function () { |
5040 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 5310 np(this).policy.requireEditable(); |
5041 return defaultTameNode(np(this).feralDoc.createDocumentFragment(), true) ; | 5311 return defaultTameNode(np(this).feralDoc.createDocumentFragment()); |
5042 }); | 5312 }); |
5043 TameHTMLDocument.prototype.createElement = nodeMethod(function (tagName) { | 5313 TameHTMLDocument.prototype.createElement = nodeMethod(function (tagName) { |
5044 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 5314 np(this).policy.requireEditable(); |
5045 tagName = String(tagName).toLowerCase(); | 5315 tagName = String(tagName).toLowerCase(); |
5046 tagName = htmlSchema.virtualToRealElementName(tagName); | 5316 tagName = htmlSchema.virtualToRealElementName(tagName); |
5047 var newEl = np(this).feralDoc.createElement(tagName); | 5317 var newEl = np(this).feralDoc.createElement(tagName); |
5048 if ("canvas" == tagName) { | 5318 if ("canvas" == tagName) { |
5049 bridal.initCanvasElement(newEl); | 5319 bridal.initCanvasElement(newEl); |
5050 } | 5320 } |
5051 if (elementPolicies.hasOwnProperty(tagName)) { | 5321 if (elementPolicies.hasOwnProperty(tagName)) { |
5052 var attribs = elementPolicies[tagName]([]); | 5322 var attribs = elementPolicies[tagName]([]); |
5053 if (attribs) { | 5323 if (attribs) { |
5054 for (var i = 0; i < attribs.length; i += 2) { | 5324 for (var i = 0; i < attribs.length; i += 2) { |
5055 bridal.setAttribute(newEl, attribs[+i], attribs[i + 1]); | 5325 bridal.setAttribute(newEl, attribs[+i], attribs[i + 1]); |
5056 } | 5326 } |
5057 } | 5327 } |
5058 } | 5328 } |
5059 return defaultTameNode(newEl, true); | 5329 return defaultTameNode(newEl); |
5060 }); | 5330 }); |
5061 TameHTMLDocument.prototype.createTextNode = nodeMethod(function (text) { | 5331 TameHTMLDocument.prototype.createTextNode = nodeMethod(function (text) { |
5062 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 5332 np(this).policy.requireEditable(); |
5063 return defaultTameNode(np(this).feralDoc.createTextNode( | 5333 return defaultTameNode(np(this).feralDoc.createTextNode( |
5064 text !== null && text !== void 0 ? '' + text : ''), true); | 5334 text !== null && text !== void 0 ? '' + text : '')); |
5065 }); | 5335 }); |
5066 TameHTMLDocument.prototype.getElementById = nodeMethod(function (id) { | 5336 TameHTMLDocument.prototype.getElementById = nodeMethod(function (id) { |
5067 id += idSuffix; | 5337 id += idSuffix; |
5068 var node = np(this).feralDoc.getElementById(id); | 5338 var node = np(this).feralDoc.getElementById(id); |
5069 return defaultTameNode(node, np(this).editable); | 5339 return defaultTameNode(node); |
5070 }); | 5340 }); |
5071 // http://www.w3.org/TR/DOM-Level-2-Events/events.html | 5341 // http://www.w3.org/TR/DOM-Level-2-Events/events.html |
5072 // #Events-DocumentEvent-createEvent | 5342 // #Events-DocumentEvent-createEvent |
5073 TameHTMLDocument.prototype.createEvent = nodeMethod(function (type) { | 5343 TameHTMLDocument.prototype.createEvent = nodeMethod(function (type) { |
5074 type = String(type); | 5344 type = String(type); |
5075 if (type !== 'HTMLEvents') { | 5345 if (type !== 'HTMLEvents') { |
5076 // See https://developer.mozilla.org/en/DOM/document.createEvent#Notes | 5346 // See https://developer.mozilla.org/en/DOM/document.createEvent#Notes |
5077 // for a long list of event ypes. | 5347 // for a long list of event ypes. |
5078 // See http://www.w3.org/TR/DOM-Level-2-Events/events.html | 5348 // See http://www.w3.org/TR/DOM-Level-2-Events/events.html |
5079 // #Events-eventgroupings | 5349 // #Events-eventgroupings |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5142 } | 5412 } |
5143 listeners = np(self).onLoadListeners; | 5413 listeners = np(self).onLoadListeners; |
5144 np(self).onLoadListeners = []; | 5414 np(self).onLoadListeners = []; |
5145 for (var i = 0, n = listeners.length; i < n; ++i) { | 5415 for (var i = 0, n = listeners.length; i < n; ++i) { |
5146 window.setTimeout(listeners[+i], 0); | 5416 window.setTimeout(listeners[+i], 0); |
5147 } | 5417 } |
5148 }); | 5418 }); |
5149 | 5419 |
5150 // For JavaScript handlers. See function dispatchEvent below | 5420 // For JavaScript handlers. See function dispatchEvent below |
5151 domicile.handlers = []; | 5421 domicile.handlers = []; |
5152 domicile.TameHTMLDocument = TameHTMLDocument; // Exposed for testing | |
5153 domicile.tameNode = cajaVM.def(defaultTameNode); | 5422 domicile.tameNode = cajaVM.def(defaultTameNode); |
5154 domicile.feralNode = cajaVM.def(function (tame) { | 5423 domicile.feralNode = cajaVM.def(function (tame) { |
5155 return np(tame).feral; // NOTE: will be undefined for pseudo nodes | 5424 return np(tame).feral; // NOTE: will be undefined for pseudo nodes |
5156 }); | 5425 }); |
5157 domicile.tameEvent = cajaVM.def(tameEvent); | 5426 domicile.tameEvent = cajaVM.def(tameEvent); |
5158 domicile.blessHtml = cajaVM.def(blessHtml); | 5427 domicile.blessHtml = cajaVM.def(blessHtml); |
5159 domicile.blessCss = cajaVM.def(function (var_args) { | 5428 domicile.blessCss = cajaVM.def(function (var_args) { |
5160 var arr = []; | 5429 var arr = []; |
5161 for (var i = 0, n = arguments.length; i < n; ++i) { | 5430 for (var i = 0, n = arguments.length; i < n; ++i) { |
5162 arr[+i] = arguments[+i]; | 5431 arr[+i] = arguments[+i]; |
5163 } | 5432 } |
5164 return cssSealerUnsealerPair.seal(arr); | 5433 return cssSealerUnsealerPair.seal(arr); |
5165 }); | 5434 }); |
5166 domicile.htmlAttr = cajaVM.def(function (s) { | 5435 domicile.htmlAttr = cajaVM.def(function (s) { |
5167 return html.escapeAttrib(String(s || '')); | 5436 return html.escapeAttrib(String(s || '')); |
5168 }); | 5437 }); |
5169 domicile.html = cajaVM.def(safeHtml); | 5438 domicile.html = cajaVM.def(safeHtml); |
5170 domicile.fetchUri = cajaVM.def(function (uri, mime, callback) { | 5439 domicile.fetchUri = cajaVM.def(function (uri, mime, callback) { |
5171 uriFetch(naiveUriPolicy, uri, mime, callback); | 5440 uriFetch(naiveUriPolicy, uri, mime, callback); |
5172 }); | 5441 }); |
5173 domicile.rewriteUri = cajaVM.def(function (uri, mimeType) { | 5442 domicile.rewriteUri = cajaVM.def(function (uri, mimeType) { |
5174 var s = rewriteAttribute(null, null, html4.atype.URI, uri); | 5443 // (SAME_DOCUMENT, SANDBOXED) is chosen as the "reasonable" set of |
5175 if (!s) { throw new Error(); } | 5444 // defaults for this function, which is only used by TCB components |
5176 return s; | 5445 // to rewrite URIs for sources of data. We assume these sources of |
5446 // data provide no exit from the sandbox, and their effects are shown | |
5447 // in the same HTML document view as the Caja guest. | |
5448 // TODO(ihab.awad): Rename this function to something more specific | |
5449 return uriRewrite( | |
5450 naiveUriPolicy, | |
5451 String(uri), | |
5452 html4.ueffects.SAME_DOCUMENT, | |
5453 html4.ltypes.SANDBOXED, | |
5454 {}); | |
5177 }); | 5455 }); |
5178 domicile.suffix = cajaVM.def(function (nmtokens) { | 5456 domicile.suffix = cajaVM.def(function (nmtokens) { |
5179 var p = String(nmtokens).replace(/^\s+|\s+$/g, '').split(/\s+/g); | 5457 var p = String(nmtokens).replace(/^\s+|\s+$/g, '').split(/\s+/g); |
5180 var out = []; | 5458 var out = []; |
5181 for (var i = 0; i < p.length; ++i) { | 5459 for (var i = 0; i < p.length; ++i) { |
5182 var nmtoken = rewriteAttribute(null, null, html4.atype.ID, p[+i]); | 5460 var nmtoken = rewriteAttribute(null, null, html4.atype.ID, p[+i]); |
5183 if (!nmtoken) { throw new Error(nmtokens); } | 5461 if (!nmtoken) { throw new Error(nmtokens); } |
5184 out.push(nmtoken); | 5462 out.push(nmtoken); |
5185 } | 5463 } |
5186 return out.join(' '); | 5464 return out.join(' '); |
(...skipping 219 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5406 // Note: nodeClasses.XMLHttpRequest is a ctor that *can* be directly | 5684 // Note: nodeClasses.XMLHttpRequest is a ctor that *can* be directly |
5407 // called by cajoled code, so we do not use inertCtor(). | 5685 // called by cajoled code, so we do not use inertCtor(). |
5408 nodeClasses.XMLHttpRequest = domitaModules.TameXMLHttpRequest( | 5686 nodeClasses.XMLHttpRequest = domitaModules.TameXMLHttpRequest( |
5409 taming, | 5687 taming, |
5410 rulebreaker, | 5688 rulebreaker, |
5411 domitaModules.XMLHttpRequestCtor( | 5689 domitaModules.XMLHttpRequestCtor( |
5412 makeDOMAccessible, | 5690 makeDOMAccessible, |
5413 makeFunctionAccessible(window.XMLHttpRequest), | 5691 makeFunctionAccessible(window.XMLHttpRequest), |
5414 makeFunctionAccessible(window.ActiveXObject), | 5692 makeFunctionAccessible(window.ActiveXObject), |
5415 makeFunctionAccessible(window.XDomainRequest)), | 5693 makeFunctionAccessible(window.XDomainRequest)), |
5416 naiveUriPolicy); | 5694 naiveUriPolicy, |
5695 function () { return domicile.pseudoLocation.href; }); | |
5417 cajaVM.def(nodeClasses.XMLHttpRequest); | 5696 cajaVM.def(nodeClasses.XMLHttpRequest); |
5418 traceStartup("DT: done for XMLHttpRequest"); | 5697 traceStartup("DT: done for XMLHttpRequest"); |
5419 | 5698 |
5420 /** | 5699 /** |
5421 * given a number, outputs the equivalent css text. | 5700 * given a number, outputs the equivalent css text. |
5422 * @param {number} num | 5701 * @param {number} num |
5423 * @return {string} an CSS representation of a number suitable for both ht ml | 5702 * @return {string} an CSS representation of a number suitable for both ht ml |
5424 * attribs and plain text. | 5703 * attribs and plain text. |
5425 */ | 5704 */ |
5426 domicile.cssNumber = cajaVM.def(function (num) { | 5705 domicile.cssNumber = cajaVM.def(function (num) { |
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5556 }); | 5835 }); |
5557 } | 5836 } |
5558 | 5837 |
5559 traceStartup("DT: about to do TameHTMLDocument"); | 5838 traceStartup("DT: about to do TameHTMLDocument"); |
5560 var tameDocument = new TameHTMLDocument( | 5839 var tameDocument = new TameHTMLDocument( |
5561 document, | 5840 document, |
5562 containerNode, | 5841 containerNode, |
5563 // TODO(jasvir): Properly wire up document.domain | 5842 // TODO(jasvir): Properly wire up document.domain |
5564 // by untangling the cyclic dependence between | 5843 // by untangling the cyclic dependence between |
5565 // TameWindow and TameDocument | 5844 // TameWindow and TameDocument |
5566 String(undefined || 'nosuchhost.invalid'), | 5845 String(undefined || 'nosuchhost.invalid')); |
5567 true); | |
5568 traceStartup("DT: finished TameHTMLDocument"); | 5846 traceStartup("DT: finished TameHTMLDocument"); |
5569 domicile.htmlEmitterTarget = containerNode; | 5847 domicile.htmlEmitterTarget = containerNode; |
5570 | 5848 |
5571 // See spec at http://www.whatwg.org/specs/web-apps/current-work/multipage /browsers.html#navigator | 5849 // See spec at http://www.whatwg.org/specs/web-apps/current-work/multipage /browsers.html#navigator |
5572 // We don't attempt to hide or abstract userAgent details since | 5850 // We don't attempt to hide or abstract userAgent details since |
5573 // they are discoverable via side-channels we don't control. | 5851 // they are discoverable via side-channels we don't control. |
5574 var navigator = makeDOMAccessible(window.navigator); | 5852 var navigator = makeDOMAccessible(window.navigator); |
5575 var tameNavigator = cajaVM.def({ | 5853 var tameNavigator = cajaVM.def({ |
5576 appName: String(navigator.appName), | 5854 appName: String(navigator.appName), |
5577 appVersion: String(navigator.appVersion), | 5855 appVersion: String(navigator.appVersion), |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5633 * <p> | 5911 * <p> |
5634 * We can't provide access to the tamed window directly from document | 5912 * We can't provide access to the tamed window directly from document |
5635 * since it is the global scope of valija code, and so access to another | 5913 * since it is the global scope of valija code, and so access to another |
5636 * module's tamed window provides an unbounded amount of authority. | 5914 * module's tamed window provides an unbounded amount of authority. |
5637 * <p> | 5915 * <p> |
5638 * Instead, we expose styling, positioning, and sizing properties | 5916 * Instead, we expose styling, positioning, and sizing properties |
5639 * via this class. All of this authority is already available from the | 5917 * via this class. All of this authority is already available from the |
5640 * document. | 5918 * document. |
5641 */ | 5919 */ |
5642 function TameDefaultView() { | 5920 function TameDefaultView() { |
5643 // TODO(kpreid): The caller passes document's editable flag; this does n ot | |
5644 // take such a parameter. Which is right? | |
5645 // TODO(mikesamuel): Implement in terms of | 5921 // TODO(mikesamuel): Implement in terms of |
5646 // http://www.w3.org/TR/cssom-view/#the-windowview-interface | 5922 // http://www.w3.org/TR/cssom-view/#the-windowview-interface |
5647 // TODO: expose a read-only version of the document | 5923 // TODO: expose a read-only version of the document |
5648 this.document = tameDocument; | 5924 this.document = tameDocument; |
5649 // Exposing an editable default view that pointed to a read-only | 5925 // Exposing an editable default view that pointed to a read-only |
5650 // tameDocument via document.defaultView would allow escalation of | 5926 // tameDocument via document.defaultView would allow escalation of |
5651 // authority. | 5927 // authority. |
5652 assert(np(tameDocument).editable); | 5928 assert(np(tameDocument).policy.editable); |
5653 taming.permitUntaming(this); | 5929 taming.permitUntaming(this); |
5654 } | 5930 } |
5655 | 5931 |
5656 // Under ES53, the set/clear pairs get invoked with 'this' bound | 5932 // Under ES53, the set/clear pairs get invoked with 'this' bound |
5657 // to USELESS, which causes problems on Chrome unless they're wrpaped | 5933 // to USELESS, which causes problems on Chrome unless they're wrpaped |
5658 // this way. | 5934 // this way. |
5659 tameSetAndClear( | 5935 tameSetAndClear( |
5660 TameWindow.prototype, | 5936 TameWindow.prototype, |
5661 function (code, millis) { return window.setTimeout(code, millis); }, | 5937 function (code, millis) { return window.setTimeout(code, millis); }, |
5662 function (id) { return window.clearTimeout(id); }, | 5938 function (id) { return window.clearTimeout(id); }, |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5760 // events. | 6036 // events. |
5761 // opera: defined only on Opera. | 6037 // opera: defined only on Opera. |
5762 }, (function (propertyName, value) { | 6038 }, (function (propertyName, value) { |
5763 TameWindow.prototype[propertyName] = value; | 6039 TameWindow.prototype[propertyName] = value; |
5764 TameDefaultView.prototype[propertyName] = value; | 6040 TameDefaultView.prototype[propertyName] = value; |
5765 })); | 6041 })); |
5766 ······ | 6042 ······ |
5767 cajaVM.def(TameWindow); // and its prototype | 6043 cajaVM.def(TameWindow); // and its prototype |
5768 | 6044 |
5769 var tameWindow = new TameWindow(); | 6045 var tameWindow = new TameWindow(); |
5770 var tameDefaultView = new TameDefaultView(np(tameDocument).editable); | 6046 var tameDefaultView = new TameDefaultView(); |
5771 | 6047 |
5772 // Getters for properties which are installed on window AND defaultView. | 6048 // Getters for properties which are installed on window AND defaultView. |
5773 // See doc comment of TameDefaultView regarding authority to expose here. | 6049 // See doc comment of TameDefaultView regarding authority to expose here. |
5774 forOwnKeys({ | 6050 forOwnKeys({ |
5775 pageXOffset: function () { return this.scrollX; }, | 6051 pageXOffset: function () { return this.scrollX; }, |
5776 pageYOffset: function () { return this.scrollY; }, | 6052 pageYOffset: function () { return this.scrollY; }, |
5777 scrollX: function () { | 6053 scrollX: function () { |
5778 return np(tameDocument).feralContainerNode.scrollLeft; }, | 6054 return np(tameDocument).feralContainerNode.scrollLeft; }, |
5779 scrollY: function () { | 6055 scrollY: function () { |
5780 return np(tameDocument).feralContainerNode.scrollTop; }, | 6056 return np(tameDocument).feralContainerNode.scrollTop; }, |
(...skipping 16 matching lines...) Expand all Loading... | |
5797 // Attach reflexive properties to 'window' object | 6073 // Attach reflexive properties to 'window' object |
5798 var windowProps = ['top', 'self', 'opener', 'parent', 'window']; | 6074 var windowProps = ['top', 'self', 'opener', 'parent', 'window']; |
5799 var wpLen = windowProps.length; | 6075 var wpLen = windowProps.length; |
5800 for (var i = 0; i < wpLen; ++i) { | 6076 for (var i = 0; i < wpLen; ++i) { |
5801 var prop = windowProps[+i]; | 6077 var prop = windowProps[+i]; |
5802 tameWindow[prop] = tameWindow; | 6078 tameWindow[prop] = tameWindow; |
5803 } | 6079 } |
5804 | 6080 |
5805 Object.freeze(tameDefaultView); | 6081 Object.freeze(tameDefaultView); |
5806 | 6082 |
5807 if (np(tameDocument).editable) { | 6083 if (np(tameDocument).policy.editable) { |
5808 tameDocument.defaultView = tameDefaultView; | 6084 tameDocument.defaultView = tameDefaultView; |
5809 | 6085 |
5810 // Hook for document.write support. | 6086 // Hook for document.write support. |
5811 domicile.sanitizeAttrs = sanitizeAttrs; | 6087 domicile.sanitizeAttrs = sanitizeAttrs; |
5812 } | 6088 } |
5813 | 6089 |
5814 // Iterate over all node classes, assigning them to the Window object | 6090 // Iterate over all node classes, assigning them to the Window object |
5815 // under their DOM Level 2 standard name. They have been frozen above. | 6091 // under their DOM Level 2 standard name. They have been frozen above. |
5816 for (var name in nodeClasses) { | 6092 for (var name in nodeClasses) { |
5817 var ctor = nodeClasses[name]; | 6093 var ctor = nodeClasses[name]; |
5818 Object.defineProperty(tameWindow, name, { | 6094 Object.defineProperty(tameWindow, name, { |
5819 enumerable: true, | 6095 enumerable: true, |
5820 configurable: true, | 6096 configurable: true, |
5821 writable: true, | 6097 writable: true, |
5822 value: ctor | 6098 value: ctor |
5823 }); | 6099 }); |
5824 } | 6100 } |
5825 | 6101 |
5826 // TODO(ihab.awad): Build a more sophisticated virtual class hierarchy by | 6102 // TODO(ihab.awad): Build a more sophisticated virtual class hierarchy by |
5827 // creating a table of actual subclasses and instantiating tame nodes by | 6103 // having a table of subclass relationships and implementing them. |
5828 // table lookups. This will allow the client code to see a truly consisten t | 6104 |
5829 // DOM class hierarchy. | |
5830 | |
5831 // This is a list of all HTML-specific element node classes defined by | |
5832 // DOM Level 2 HTML, <http://www.w3.org/TR/DOM-Level-2-HTML/html.html>. | |
5833 // If a node class name in this list is not defined using defineElement or | 6105 // If a node class name in this list is not defined using defineElement or |
5834 // inertCtor above, then it will now be bound to the HTMLElement class. | 6106 // inertCtor above, then it will now be bound to the HTMLElement class. |
5835 var allDomNodeClasses = [ | 6107 var allDomNodeClasses = htmlSchema.getAllKnownScriptInterfaces(); |
5836 'HTMLAnchorElement', | |
5837 'HTMLAppletElement', | |
5838 'HTMLAreaElement', | |
5839 'HTMLBaseElement', | |
5840 'HTMLBaseFontElement', | |
5841 'HTMLBodyElement', | |
5842 'HTMLBRElement', | |
5843 'HTMLButtonElement', | |
5844 'HTMLDirectoryElement', | |
5845 'HTMLDivElement', | |
5846 'HTMLDListElement', | |
5847 'HTMLFieldSetElement', | |
5848 'HTMLFontElement', | |
5849 'HTMLFormElement', | |
5850 'HTMLFrameElement', | |
5851 'HTMLFrameSetElement', | |
5852 'HTMLHeadElement', | |
5853 'HTMLHeadingElement', | |
5854 'HTMLHRElement', | |
5855 'HTMLHtmlElement', | |
5856 'HTMLIFrameElement', | |
5857 'HTMLImageElement', | |
5858 'HTMLInputElement', | |
5859 'HTMLIsIndexElement', | |
5860 'HTMLLabelElement', | |
5861 'HTMLLegendElement', | |
5862 'HTMLLIElement', | |
5863 'HTMLLinkElement', | |
5864 'HTMLMapElement', | |
5865 'HTMLMenuElement', | |
5866 'HTMLMetaElement', | |
5867 'HTMLModElement', | |
5868 'HTMLNavElement', | |
5869 'HTMLObjectElement', | |
5870 'HTMLOListElement', | |
5871 'HTMLOptGroupElement', | |
5872 'HTMLOptionElement', | |
5873 'HTMLParagraphElement', | |
5874 'HTMLParamElement', | |
5875 'HTMLPreElement', | |
5876 'HTMLQuoteElement', | |
5877 'HTMLScriptElement', | |
5878 'HTMLSelectElement', | |
5879 'HTMLStyleElement', | |
5880 'HTMLTableCaptionElement', | |
5881 'HTMLTableCellElement', | |
5882 'HTMLTableColElement', | |
5883 'HTMLTableElement', | |
5884 'HTMLTableRowElement', | |
5885 'HTMLTableSectionElement', | |
5886 'HTMLTextAreaElement', | |
5887 'HTMLTitleElement', | |
5888 'HTMLUListElement' | |
5889 ]; | |
5890 | |
5891 var defaultNodeClassCtor = nodeClasses.HTMLElement; | 6108 var defaultNodeClassCtor = nodeClasses.HTMLElement; |
5892 for (var i = 0; i < allDomNodeClasses.length; i++) { | 6109 for (var i = 0; i < allDomNodeClasses.length; i++) { |
5893 var className = allDomNodeClasses[+i]; | 6110 var className = allDomNodeClasses[+i]; |
5894 if (!(className in tameWindow)) { | 6111 if (!(className in tameWindow)) { |
5895 Object.defineProperty(tameWindow, className, { | 6112 Object.defineProperty(tameWindow, className, { |
5896 enumerable: true, | 6113 enumerable: true, |
5897 configurable: true, | 6114 configurable: true, |
5898 writable: true, | 6115 writable: true, |
5899 value: defaultNodeClassCtor | 6116 value: defaultNodeClassCtor |
5900 }); | 6117 }); |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
5953 | 6170 |
5954 traceStartup("DT: all done"); | 6171 traceStartup("DT: all done"); |
5955 | 6172 |
5956 return domicile; | 6173 return domicile; |
5957 } | 6174 } |
5958 | 6175 |
5959 /** | 6176 /** |
5960 * Function called from rewritten event handlers to dispatch an event safely . | 6177 * Function called from rewritten event handlers to dispatch an event safely . |
5961 */ | 6178 */ |
5962 function plugin_dispatchEvent(thisNode, event, pluginId, handler) { | 6179 function plugin_dispatchEvent(thisNode, event, pluginId, handler) { |
5963 event = makeDOMAccessible( | 6180 var window = bridalMaker.getWindow(thisNode, makeDOMAccessible); |
5964 event || bridalMaker.getWindow(thisNode, makeDOMAccessible).event); | 6181 event = makeDOMAccessible(event || window.event); |
5965 // support currentTarget on IE[678] | 6182 // support currentTarget on IE[678] |
5966 if (!event.currentTarget) { | 6183 if (!event.currentTarget) { |
5967 event.currentTarget = thisNode; | 6184 event.currentTarget = thisNode; |
5968 } | 6185 } |
5969 var imports = rulebreaker.getImports(pluginId); | 6186 var imports = rulebreaker.getImports(pluginId); |
5970 var domicile = windowToDomicile.get(imports); | 6187 var domicile = windowToDomicile.get(imports); |
5971 var node = domicile.tameNode(thisNode, true); | 6188 var node = domicile.tameNode(thisNode); |
6189 var isUserAction = eventIsUserAction(event, window); | |
5972 try { | 6190 try { |
5973 return plugin_dispatchToHandler( | 6191 return dispatch( |
5974 pluginId, handler, [ node, domicile.tameEvent(event), node ]); | 6192 isUserAction, pluginId, handler, |
6193 [ node, domicile.tameEvent(event), node ]); | |
5975 } catch (ex) { | 6194 } catch (ex) { |
5976 imports.onerror(ex.message, 'unknown', 0); | 6195 imports.onerror(ex.message, 'unknown', 0); |
5977 } | 6196 } |
5978 } | 6197 } |
5979 | 6198 |
6199 /** | |
6200 * Return true if event is a user action that can be expected to do | |
6201 * click(), focus(), etc. | |
6202 */ | |
6203 function eventIsUserAction(event, window) { | |
6204 if (!(event instanceof window.UIEvent)) { return false; } | |
6205 switch (event.type) { | |
6206 case 'click': | |
6207 case 'dblclick': | |
6208 case 'keypress': | |
6209 case 'keydown': | |
6210 case 'keyup': | |
6211 case 'mousedown': | |
6212 case 'mouseup': | |
6213 case 'touchstart': | |
6214 case 'touchend': | |
6215 return true; | |
6216 } | |
6217 return false; | |
6218 } | |
6219 | |
6220 /** | |
6221 * Called when user clicks on a javascript: link. | |
6222 */ | |
5980 function plugin_dispatchToHandler(pluginId, handler, args) { | 6223 function plugin_dispatchToHandler(pluginId, handler, args) { |
5981 var sig = ('' + handler).match(/^function\b[^\)]*\)/); | 6224 return dispatch(true, pluginId, handler, args); |
6225 } | |
6226 | |
6227 function dispatch(isUserAction, pluginId, handler, args) { | |
5982 var domicile = windowToDomicile.get(rulebreaker.getImports(pluginId)); | 6228 var domicile = windowToDomicile.get(rulebreaker.getImports(pluginId)); |
5983 if (domicile.domitaTrace & 0x1 && typeof console != 'undefined') { | 6229 if (domicile.domitaTrace & 0x1 && typeof console != 'undefined') { |
6230 var sig = ('' + handler).match(/^function\b[^\)]*\)/); | |
5984 console.log( | 6231 console.log( |
5985 'Dispatch pluginId=' + pluginId + | 6232 'Dispatch pluginId=' + pluginId + |
5986 ', handler=' + (sig ? sig[0] : handler) + | 6233 ', handler=' + (sig ? sig[0] : handler) + |
5987 ', args=' + args); | 6234 ', args=' + args); |
5988 } | 6235 } |
5989 switch (typeof handler) { | 6236 switch (typeof handler) { |
5990 case 'number': | 6237 case 'number': |
5991 handler = domicile.handlers[+handler]; | 6238 handler = domicile.handlers[+handler]; |
5992 break; | 6239 break; |
5993 case 'string': | 6240 case 'string': |
5994 var fn = void 0; | 6241 var fn = void 0; |
5995 fn = domicile.window[handler]; | 6242 fn = domicile.window[handler]; |
5996 handler = fn && typeof fn.call === 'function' ? fn : void 0; | 6243 handler = fn && typeof fn.call === 'function' ? fn : void 0; |
5997 break; | 6244 break; |
5998 case 'function': case 'object': break; | 6245 case 'function': case 'object': break; |
5999 default: | 6246 default: |
6000 throw new Error( | 6247 throw new Error( |
6001 'Expected function as event handler, not ' + typeof handler); | 6248 'Expected function as event handler, not ' + typeof handler); |
6002 } | 6249 } |
6003 domicile.isProcessingEvent = true; | 6250 domicile.handlingUserAction = isUserAction; |
6004 try { | 6251 try { |
6005 return handler.call.apply(handler, args); | 6252 return handler.call.apply(handler, args); |
6006 } catch (ex) { | 6253 } catch (ex) { |
6007 // guard against IE discarding finally blocks | 6254 // guard against IE discarding finally blocks |
6255 domicile.handlingUserAction = false; | |
6008 throw ex; | 6256 throw ex; |
6009 } finally { | 6257 } finally { |
6010 domicile.isProcessingEvent = false; | 6258 domicile.handlingUserAction = false; |
6011 } | 6259 } |
6012 } | 6260 } |
6013 | 6261 |
6014 return cajaVM.def({ | 6262 return cajaVM.def({ |
6015 attachDocument: attachDocument, | 6263 attachDocument: attachDocument, |
6016 plugin_dispatchEvent: plugin_dispatchEvent, | 6264 plugin_dispatchEvent: plugin_dispatchEvent, |
6017 plugin_dispatchToHandler: plugin_dispatchToHandler, | 6265 plugin_dispatchToHandler: plugin_dispatchToHandler, |
6018 getDomicileForWindow: windowToDomicile.get.bind(windowToDomicile) | 6266 getDomicileForWindow: windowToDomicile.get.bind(windowToDomicile) |
6019 }); | 6267 }); |
6020 }); | 6268 }); |
6021 })(); | 6269 })(); |
6022 | 6270 |
6023 // Exports for closure compiler. | 6271 // Exports for closure compiler. |
6024 if (typeof window !== 'undefined') { | 6272 if (typeof window !== 'undefined') { |
6025 window['Domado'] = Domado; | 6273 window['Domado'] = Domado; |
6026 } | 6274 } |
LEFT | RIGHT |