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 |
923 * ES5/3 taming membrane and work with the taming-frame system. If | 927 * ES5/3 taming membrane and work with the taming-frame system. If |
924 * running under SES, pass null instead. | 928 * running under SES, pass null instead. |
925 * @return A record of functions attachDocument, dispatchEvent, and | 929 * @return A record of functions attachDocument, dispatchEvent, and |
926 * dispatchToHandler. | 930 * dispatchToHandler. |
927 */ | 931 */ |
928 return function Domado(opt_rulebreaker) { | 932 return cajaVM.def(function Domado(opt_rulebreaker) { |
929 // Everything in this scope but not in function attachDocument() below | 933 // Everything in this scope but not in function attachDocument() below |
930 // does not contain lexical references to a particular DOM instance, but | 934 // does not contain lexical references to a particular DOM instance, but |
931 // may have some kind of privileged access to Domado internals. | 935 // may have some kind of privileged access to Domado internals. |
932 | 936 |
933 // This is only used if opt_rulebreaker is absent (because the plugin ids | 937 // This is only used if opt_rulebreaker is absent (because the plugin ids |
934 // are normally managed by es53 when it is not). TODO(kpreid): Is there a | 938 // are normally managed by es53 when it is not). TODO(kpreid): Is there a |
935 // more sensible place to put this management, which would be used in both | 939 // more sensible place to put this management, which would be used in both |
936 // modes? | 940 // modes? |
937 var importsToId = new WeakMap(true); | 941 var importsToId = new WeakMap(true); |
938 var idToImports = []; | 942 var idToImports = []; |
(...skipping 424 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 154 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1689 switch (attrType) { | 1811 switch (attrType) { |
1690 case html4.atype.GLOBAL_NAME: | 1812 case html4.atype.GLOBAL_NAME: |
1691 case html4.atype.ID: | 1813 case html4.atype.ID: |
1692 case html4.atype.IDREF: | 1814 case html4.atype.IDREF: |
1693 return unsuffix(realValue, idSuffix, null); | 1815 return unsuffix(realValue, idSuffix, null); |
1694 case html4.atype.IDREFS: | 1816 case html4.atype.IDREFS: |
1695 return realValue.replace(ID_LIST_PARTS_PATTERN, | 1817 return realValue.replace(ID_LIST_PARTS_PATTERN, |
1696 function(_, id, spaces) { | 1818 function(_, id, spaces) { |
1697 return unsuffix(id, idSuffix, '') + (spaces ? ' ' : ''); | 1819 return unsuffix(id, idSuffix, '') + (spaces ? ' ' : ''); |
1698 }); | 1820 }); |
| 1821 case html4.atype.URI: |
| 1822 if (realValue && '#' === realValue.charAt(0)) { |
| 1823 return unsuffix(realValue, idSuffix, realValue); |
| 1824 } else { |
| 1825 return realValue; |
| 1826 } |
1699 case html4.atype.URI_FRAGMENT: | 1827 case html4.atype.URI_FRAGMENT: |
1700 if (realValue && '#' === realValue.charAt(0)) { | 1828 if (realValue && '#' === realValue.charAt(0)) { |
1701 realValue = unsuffix(realValue.substring(1), idSuffix, null); | 1829 return unsuffix(realValue, idSuffix, null); |
1702 return realValue ? '#' + realValue : null; | |
1703 } else { | 1830 } else { |
1704 return null; | 1831 return null; |
1705 } | 1832 } |
1706 break; | |
1707 default: | 1833 default: |
1708 return realValue; | 1834 return realValue; |
1709 } | 1835 } |
1710 } | 1836 } |
1711 | |
1712 /** | |
1713 * Undoes some of the changes made by sanitizeHtml, e.g. stripping ID | |
1714 * prefixes. | |
1715 */ | |
1716 function tameInnerHtml(htmlText) { | |
1717 var out = []; | |
1718 innerHtmlTamer(htmlText, out); | |
1719 return out.join(''); | |
1720 } | |
1721 var innerHtmlTamer = html.makeSaxParser({ | |
1722 startTag: function (tagName, attribs, out) { | |
1723 tagName = realToVirtualElementName(tagName); | |
1724 out.push('<', tagName); | |
1725 for (var i = 0; i < attribs.length; i += 2) { | |
1726 var aname = '' + attribs[+i]; | |
1727 var atype = htmlSchema.attribute(tagName, aname).type; | |
1728 var value = attribs[i + 1]; | |
1729 if (aname !== 'target' && atype !== void 0) { | |
1730 value = virtualizeAttributeValue(atype, value); | |
1731 if (typeof value === 'string') { | |
1732 out.push(' ', aname, '="', html.escapeAttrib(value), '"'); | |
1733 } | |
1734 } else if (cajaPrefRe.test(aname)) { | |
1735 out.push(' ', aname.substring(cajaPrefix.length), '="', | |
1736 html.escapeAttrib(value), '"'); | |
1737 } | |
1738 } | |
1739 out.push('>'); | |
1740 }, | |
1741 endTag: function (tagName, out) { | |
1742 var rempty = htmlSchema.element(tagName).empty; | |
1743 tagName = realToVirtualElementName(tagName); | |
1744 var vempty = htmlSchema.element(tagName).empty; | |
1745 if (vempty && !rempty) { | |
1746 // omit end tag because the browser doesn't see the virtualized | |
1747 // element as empty | |
1748 return; | |
1749 } | |
1750 out.push('</', tagName, '>'); | |
1751 }, | |
1752 pcdata: function (text, out) { out.push(text); }, | |
1753 rcdata: function (text, out) { out.push(text); }, | |
1754 cdata: function (text, out) { out.push(text); } | |
1755 }); | |
1756 | 1837 |
1757 function getSafeTargetAttribute(tagName, attribName, value) { | 1838 function getSafeTargetAttribute(tagName, attribName, value) { |
1758 if (value !== null) { | 1839 if (value !== null) { |
1759 value = String(value); | 1840 value = String(value); |
1760 for (var i = 0; i < optTargetAttributePresets.whitelist.length; ++i) { | 1841 for (var i = 0; i < optTargetAttributePresets.whitelist.length; ++i) { |
1761 if (optTargetAttributePresets.whitelist[i] === value) { | 1842 if (optTargetAttributePresets.whitelist[i] === value) { |
1762 return value; | 1843 return value; |
1763 } | 1844 } |
1764 } | 1845 } |
1765 } | 1846 } |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1847 + '___.plugin_dispatchEvent___(' | 1928 + '___.plugin_dispatchEvent___(' |
1848 + 'this, event, ' + pluginId + ', ' | 1929 + 'this, event, ' + pluginId + ', ' |
1849 + fnNameExpr + ');'; | 1930 + fnNameExpr + ');'; |
1850 if (attribName === 'onsubmit') { | 1931 if (attribName === 'onsubmit') { |
1851 trustedHandler = | 1932 trustedHandler = |
1852 'try { ' + trustedHandler + ' } finally { return false; }'; | 1933 'try { ' + trustedHandler + ' } finally { return false; }'; |
1853 } | 1934 } |
1854 return trustedHandler; | 1935 return trustedHandler; |
1855 case html4.atype.URI: | 1936 case html4.atype.URI: |
1856 value = String(value); | 1937 value = String(value); |
1857 // URI fragments reference contents within the document and arent su
bject | 1938 // URI fragments reference contents within the document and |
1858 // to the URI policy | 1939 // aren't subject to the URI policy |
1859 if (value.charAt(0) === '#' && isValidId(value.substring(1))) { | 1940 if (value.charAt(0) === '#' && isValidId(value.substring(1))) { |
1860 return value + idSuffix; | 1941 return value + idSuffix; |
1861 } | 1942 } |
1862 value = URI.utils.resolve(domicile.pseudoLocation.href, value); | 1943 value = URI.utils.resolve(domicile.pseudoLocation.href, value); |
1863 if (!naiveUriPolicy) { return null; } | 1944 if (!naiveUriPolicy) { return null; } |
1864 var schemaAttr = htmlSchema.attribute(tagName, attribName); | 1945 var schemaAttr = htmlSchema.attribute(tagName, attribName); |
1865 return uriRewrite( | 1946 return uriRewrite( |
1866 naiveUriPolicy, | 1947 naiveUriPolicy, |
1867 value, | 1948 value, |
1868 schemaAttr.uriEffect, | 1949 schemaAttr.uriEffect, |
(...skipping 29 matching lines...) Expand all Loading... |
1898 css.push(propName + ' : ' + propValue); | 1979 css.push(propName + ' : ' + propValue); |
1899 } | 1980 } |
1900 return css.join(' ; '); | 1981 return css.join(' ; '); |
1901 // Frames are ambient, so disallow reference. | 1982 // Frames are ambient, so disallow reference. |
1902 case html4.atype.FRAME_TARGET: | 1983 case html4.atype.FRAME_TARGET: |
1903 return getSafeTargetAttribute(tagName, attribName, value); | 1984 return getSafeTargetAttribute(tagName, attribName, value); |
1904 default: | 1985 default: |
1905 return null; | 1986 return null; |
1906 } | 1987 } |
1907 } | 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 } |
1908 | 2124 |
1909 // Property descriptors which are independent of any feral object. | 2125 // Property descriptors which are independent of any feral object. |
1910 /** | 2126 /** |
1911 * Property descriptor which throws on access. | 2127 * Property descriptor which throws on access. |
1912 */ | 2128 */ |
1913 var P_UNIMPLEMENTED = { | 2129 var P_UNIMPLEMENTED = { |
1914 enumerable: true, | 2130 enumerable: true, |
1915 get: cajaVM.def(function () { | 2131 get: cajaVM.def(function () { |
1916 throw new Error('Not implemented'); | 2132 throw new Error('Not implemented'); |
1917 }) | 2133 }) |
1918 }; | 2134 }; |
1919 /** | 2135 /** |
1920 * Property descriptor for an unsettable constant attribute (like DOM | 2136 * Property descriptor for an unsettable constant attribute (like DOM |
1921 * attributes such as nodeType). | 2137 * attributes such as nodeType). |
1922 */ | 2138 */ |
1923 function P_constant(value) { | 2139 function P_constant(value) { |
1924 return { enumerable: true, value: value }; | 2140 return { enumerable: true, value: value }; |
1925 } | 2141 } |
1926 | 2142 |
1927 /** | 2143 /** |
1928 * Construct property descriptors suitable for taming objects which use | 2144 * Construct property descriptors suitable for taming objects which use |
1929 * the specified confidence, such that confidence.p(obj).feral is the | 2145 * the specified confidence, such that confidence.p(obj).feral is the |
1930 * 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 |
1931 * editable/readonly flag. | 2147 * policy object for writability decisions. |
1932 * | 2148 * |
1933 * Lowercase properties are property descriptors; uppercase ones are | 2149 * Lowercase properties are property descriptors; uppercase ones are |
1934 * constructors for parameterized property descriptors. | 2150 * constructors for parameterized property descriptors. |
1935 */ | 2151 */ |
1936 function PropertyTaming(confidence) { | 2152 function PropertyTaming(confidence) { |
1937 var p = confidence.p; | 2153 var p = confidence.p; |
1938 var method = confidence.protectMethod; | 2154 var method = confidence.protectMethod; |
1939 | 2155 |
1940 return cajaVM.def({ | 2156 return cajaVM.def({ |
1941 /** | 2157 /** |
(...skipping 12 matching lines...) Expand all Loading... |
1954 * Property descriptor for properties which have the value the feral | 2170 * Property descriptor for properties which have the value the feral |
1955 * object does, and are assignable if the wrapper is editable. | 2171 * object does, and are assignable if the wrapper is editable. |
1956 */ | 2172 */ |
1957 rw: { | 2173 rw: { |
1958 enumerable: true, | 2174 enumerable: true, |
1959 extendedAccessors: true, | 2175 extendedAccessors: true, |
1960 get: method(function (prop) { | 2176 get: method(function (prop) { |
1961 return p(this).feral[prop]; | 2177 return p(this).feral[prop]; |
1962 }), | 2178 }), |
1963 set: method(function (value, prop) { | 2179 set: method(function (value, prop) { |
1964 if (!p(this).editable) { throw new Error(NOT_EDITABLE); } | 2180 var privates = p(this); |
1965 p(this).feral[prop] = value; | 2181 privates.policy.requireEditable(); |
| 2182 privates.feral[prop] = value; |
1966 }) | 2183 }) |
1967 }, | 2184 }, |
1968 | 2185 |
1969 /** | 2186 /** |
1970 * Property descriptor for properties which have the value the feral | 2187 * Property descriptor for properties which have the value the feral |
1971 * object does, and are assignable (with a predicate restricting the | 2188 * object does, and are assignable (with a predicate restricting the |
1972 * values which may be assigned) if the wrapper is editable. | 2189 * values which may be assigned) if the wrapper is editable. |
1973 * TODO(kpreid): Use guards instead of predicates. | 2190 * TODO(kpreid): Use guards instead of predicates. |
1974 */ | 2191 */ |
1975 RWCond: function (predicate) { | 2192 RWCond: function (predicate) { |
1976 return { | 2193 return { |
1977 enumerable: true, | 2194 enumerable: true, |
1978 extendedAccessors: true, | 2195 extendedAccessors: true, |
1979 get: method(function (prop) { | 2196 get: method(function (prop) { |
1980 return p(this).feral[prop]; | 2197 return p(this).feral[prop]; |
1981 }), | 2198 }), |
1982 set: method(function (value, prop) { | 2199 set: method(function (value, prop) { |
1983 var privates = p(this); | 2200 var privates = p(this); |
1984 if (!privates.editable) { throw new Error(NOT_EDITABLE); } | 2201 privates.policy.requireEditable(); |
1985 if (predicate(value)) { | 2202 if (predicate(value)) { |
1986 privates.feral[prop] = value; | 2203 privates.feral[prop] = value; |
1987 } | 2204 } |
1988 }) | 2205 }) |
1989 }; | 2206 }; |
1990 }, | 2207 }, |
1991 | 2208 |
1992 /** | 2209 /** |
1993 * Property descriptor for properties which have a different name | 2210 * Property descriptor for properties which have a different name |
1994 * 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... |
2010 }, | 2227 }, |
2011 | 2228 |
2012 /** | 2229 /** |
2013 * Property descriptor for forwarded properties which have node values | 2230 * Property descriptor for forwarded properties which have node values |
2014 * 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. |
2015 */ | 2232 */ |
2016 related: { | 2233 related: { |
2017 enumerable: true, | 2234 enumerable: true, |
2018 extendedAccessors: true, | 2235 extendedAccessors: true, |
2019 get: method(function (prop) { | 2236 get: method(function (prop) { |
2020 if (!('editable' in p(this))) { | 2237 var privates = p(this); |
2021 throw new Error( | 2238 if (privates.policy.upwardNavigation) { |
2022 "Internal error: related property tamer can only" | 2239 // TODO(kpreid): Can we move this check *into* tameRelatedNode? |
2023 + " be applied to objects with an editable flag"); | 2240 return tameRelatedNode(privates.feral[prop], defaultTameNode); |
| 2241 } else { |
| 2242 return null; |
2024 } | 2243 } |
2025 return tameRelatedNode(p(this).feral[prop], | |
2026 p(this).editable, | |
2027 defaultTameNode); | |
2028 }) | 2244 }) |
2029 }, | 2245 }, |
2030 | 2246 |
2031 /** | 2247 /** |
2032 * Property descriptor which maps to an attribute or property, is | 2248 * Property descriptor which maps to an attribute or property, is |
2033 * assignable, and has the value transformed in some way. | 2249 * assignable, and has the value transformed in some way. |
2034 * @param {boolean} useAttrGetter true if the getter should delegate | 2250 * @param {boolean} useAttrGetter true if the getter should delegate |
2035 * to {@code this.getAttribute}. That method is assumed to | 2251 * to {@code this.getAttribute}. That method is assumed to |
2036 * already be trusted though {@code toValue} is still called on | 2252 * already be trusted though {@code toValue} is still called on |
2037 * the result. | 2253 * the result. |
(...skipping 25 matching lines...) Expand all Loading... |
2063 : method(function (name) { | 2279 : method(function (name) { |
2064 return toValue.call(this, p(this).feral[name]); | 2280 return toValue.call(this, p(this).feral[name]); |
2065 }) | 2281 }) |
2066 }; | 2282 }; |
2067 if (fromValue) { | 2283 if (fromValue) { |
2068 desc.set = useAttrSetter | 2284 desc.set = useAttrSetter |
2069 ? method(function (value, name) { | 2285 ? method(function (value, name) { |
2070 this.setAttribute(name, fromValue.call(this, value)); | 2286 this.setAttribute(name, fromValue.call(this, value)); |
2071 }) | 2287 }) |
2072 : method(function (value, name) { | 2288 : method(function (value, name) { |
2073 if (!p(this).editable) { throw new Error(NOT_EDITABLE); } | 2289 var privates = p(this); |
2074 p(this).feral[name] = fromValue.call(this, value); | 2290 privates.policy.requireEditable(); |
| 2291 privates.feral[name] = fromValue.call(this, value); |
2075 }); | 2292 }); |
2076 } | 2293 } |
2077 return desc; | 2294 return desc; |
2078 }, | 2295 }, |
2079 filterAttr: function(toValue, fromValue) { | 2296 filterAttr: function(toValue, fromValue) { |
2080 return NP.filter(true, toValue, true, fromValue); | 2297 return NP.filter(true, toValue, true, fromValue); |
2081 }, | 2298 }, |
2082 filterProp: function(toValue, fromValue) { | 2299 filterProp: function(toValue, fromValue) { |
2083 return NP.filter(false, toValue, false, fromValue); | 2300 return NP.filter(false, toValue, false, fromValue); |
2084 } | 2301 } |
2085 }); | 2302 }); |
2086 } | 2303 } |
2087 cajaVM.def(PropertyTaming); // and its prototype | 2304 cajaVM.def(PropertyTaming); // and its prototype |
2088 | 2305 |
2089 // TODO(kpreid): We have completely unrelated things called 'np' and 'NP'. | 2306 // TODO(kpreid): We have completely unrelated things called 'np' and 'NP'. |
2090 var NP = new PropertyTaming(TameNodeConf); | 2307 var NP = new PropertyTaming(TameNodeConf); |
2091 | 2308 |
2092 // Node-specific property accessors: | 2309 // Node-specific property accessors: |
2093 /** | 2310 /** |
2094 * Property descriptor for forwarded properties which have node values | 2311 * Property descriptor for forwarded properties which have node values |
2095 * and are descendants of this node. | 2312 * and are descendants of this node. |
2096 */ | 2313 */ |
2097 var NP_tameDescendant = { | 2314 var NP_tameDescendant = { |
2098 enumerable: true, | 2315 enumerable: true, |
2099 extendedAccessors: true, | 2316 extendedAccessors: true, |
2100 get: nodeMethod(function (prop) { | 2317 get: nodeMethod(function (prop) { |
2101 return defaultTameNode(np(this).feral[prop], | 2318 var privates = np(this); |
2102 np(this).childrenEditable); | 2319 if (privates.policy.childrenVisible) { |
| 2320 return defaultTameNode(np(this).feral[prop]); |
| 2321 } else { |
| 2322 return null; |
| 2323 } |
2103 }) | 2324 }) |
2104 }; | 2325 }; |
2105 | 2326 |
2106 var nodeExpandos = new WeakMap(true); | 2327 var nodeExpandos = new WeakMap(true); |
2107 /** | 2328 /** |
2108 * Return the object which stores expando properties for a given | 2329 * Return the object which stores expando properties for a given |
2109 * host DOM node. | 2330 * host DOM node. |
2110 */ | 2331 */ |
2111 function getNodeExpandoStorage(node) { | 2332 function getNodeExpandoStorage(node) { |
2112 var s = nodeExpandos.get(node); | 2333 var s = nodeExpandos.get(node); |
2113 if (s === undefined) { | 2334 if (s === undefined) { |
2114 nodeExpandos.set(node, s = {}); | 2335 nodeExpandos.set(node, s = {}); |
2115 } | 2336 } |
2116 return s; | 2337 return s; |
2117 } | 2338 } |
2118 ······ | 2339 ······ |
2119 var nodeClassNoImplWarnings = {}; | 2340 var nodeClassNoImplWarnings = {}; |
2120 | 2341 |
2121 function makeTameNodeByType(node, editable) { | 2342 function makeTameNodeByType(node) { |
2122 switch (node.nodeType) { | 2343 switch (node.nodeType) { |
2123 case 1: // Element | 2344 case 1: // Element |
2124 var tagName = node.tagName.toLowerCase(); | 2345 var tagName = node.tagName.toLowerCase(); |
2125 var schemaElem = htmlSchema.element(tagName); | 2346 var schemaElem = htmlSchema.element(tagName); |
2126 if (schemaElem.allowed || tagName === 'script') { | 2347 if (schemaElem.allowed || tagName === 'script') { |
2127 // <script> is specifically allowed because we make provisions | 2348 // <script> is specifically allowed because we make provisions |
2128 // for controlling its content and src. | 2349 // for controlling its content and src. |
2129 var domInterfaceName = schemaElem.domInterface; | 2350 var domInterfaceName = schemaElem.domInterface; |
2130 if (nodeTamers.hasOwnProperty(domInterfaceName)) { | 2351 if (nodeTamers.hasOwnProperty(domInterfaceName)) { |
2131 return new nodeTamers[domInterfaceName]( | 2352 return new nodeTamers[domInterfaceName](node); |
2132 node, editable, editable); | |
2133 } else { | 2353 } else { |
2134 if (!nodeClassNoImplWarnings[domInterfaceName]) { | 2354 if (!nodeClassNoImplWarnings[domInterfaceName]) { |
2135 nodeClassNoImplWarnings[domInterfaceName] = true; | 2355 nodeClassNoImplWarnings[domInterfaceName] = true; |
2136 if (typeof console !== 'undefined') { | 2356 if (typeof console !== 'undefined') { |
2137 console.warn("Domado: " + domInterfaceName + " is not " + | 2357 console.warn("Domado: " + domInterfaceName + " is not " + |
2138 "tamed; its specific properties/methods will not be " + | 2358 "tamed; its specific properties/methods will not be " + |
2139 "available on <" + | 2359 "available on <" + |
2140 htmlSchema.realToVirtualElementName(tagName) + ">."); | 2360 htmlSchema.realToVirtualElementName(tagName) + ">."); |
2141 } | 2361 } |
2142 } | 2362 } |
2143 return new TameElement(node, editable, editable); | 2363 return new TameElement(node); |
2144 } | 2364 } |
2145 } else { | 2365 } else { |
2146 // If an unrecognized or unsafe node, return a | 2366 // If an unrecognized or unsafe node, return a |
2147 // placeholder that doesn't prevent tree navigation, | 2367 // placeholder that doesn't prevent tree navigation, |
2148 // but that doesn't allow mutation or leak attribute | 2368 // but that doesn't allow mutation or leak attribute |
2149 // information. | 2369 // information. |
2150 return new TameOpaqueNode(node, editable); | 2370 return new TameOpaqueNode(node); |
2151 } | 2371 } |
2152 case 2: // Attr | 2372 case 2: // Attr |
2153 // Cannot generically wrap since we must have access to the | 2373 // Cannot generically wrap since we must have access to the |
2154 // owner element | 2374 // owner element |
2155 throw 'Internal: Attr nodes cannot be generically wrapped'; | 2375 throw 'Internal: Attr nodes cannot be generically wrapped'; |
2156 case 3: // Text | 2376 case 3: // Text |
2157 case 4: // CDATA Section Node | 2377 case 4: // CDATA Section Node |
2158 return new TameTextNode(node, editable); | 2378 return new TameTextNode(node); |
2159 case 8: // Comment | 2379 case 8: // Comment |
2160 return new TameCommentNode(node, editable); | 2380 return new TameCommentNode(node); |
2161 case 11: // Document Fragment | 2381 case 11: // Document Fragment |
2162 return new TameBackedNode(node, editable, editable); | 2382 return new TameBackedNode(node); |
2163 default: | 2383 default: |
2164 return new TameOpaqueNode(node, editable); | 2384 return new TameOpaqueNode(node); |
2165 } | 2385 } |
2166 } | 2386 } |
2167 | 2387 |
2168 /** | 2388 /** |
2169 * returns a tame DOM node. | 2389 * returns a tame DOM node. |
2170 * @param {Node} node | 2390 * @param {Node} node |
2171 * @param {boolean} editable | 2391 * @param {boolean} foreign |
2172 * @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" |
2173 * >DOM Level 2</a> | 2393 * >DOM Level 2</a> |
2174 */ | 2394 */ |
2175 function defaultTameNode(node, editable, foreign) { | 2395 function defaultTameNode(node, foreign) { |
2176 if (node === null || node === void 0) { return null; } | 2396 if (node === null || node === void 0) { return null; } |
2177 node = makeDOMAccessible(node); | 2397 node = makeDOMAccessible(node); |
2178 // TODO(mikesamuel): make sure it really is a DOM node | 2398 // TODO(mikesamuel): make sure it really is a DOM node |
2179 | 2399 |
2180 if (taming.hasTameTwin(node)) { | 2400 if (taming.hasTameTwin(node)) { |
2181 return taming.tame(node); | 2401 return taming.tame(node); |
2182 } | 2402 } |
2183 | 2403 |
| 2404 if (foreign) { |
| 2405 vdocContainsForeignNodes = true; |
| 2406 } |
| 2407 |
2184 var tamed = foreign | 2408 var tamed = foreign |
2185 ? new TameForeignNode(node, editable) | 2409 ? new TameForeignNode(node) |
2186 : makeTameNodeByType(node, editable); | 2410 : makeTameNodeByType(node); |
2187 tamed = finishNode(tamed); | 2411 tamed = finishNode(tamed); |
2188 | 2412 |
2189 return tamed; | 2413 return tamed; |
2190 } | 2414 } |
2191 | 2415 |
2192 function tameRelatedNode(node, editable, tameNodeCtor) { | 2416 function tameRelatedNode(node, tameNodeCtor) { |
2193 if (node === null || node === void 0) { return null; } | 2417 if (node === null || node === void 0) { return null; } |
2194 if (node === np(tameDocument).feralContainerNode) { | 2418 if (node === np(tameDocument).feralContainerNode) { |
2195 if (np(tameDocument).editable && !editable) { | |
2196 // FIXME: return a non-editable version instead | |
2197 throw new Error(NOT_EDITABLE); | |
2198 } | |
2199 return tameDocument; | 2419 return tameDocument; |
2200 } | 2420 } |
2201 | 2421 |
2202 node = makeDOMAccessible(node); | 2422 node = makeDOMAccessible(node); |
2203 | 2423 |
2204 // Catch errors because node might be from a different domain. | 2424 // Catch errors because node might be from a different domain. |
2205 try { | 2425 try { |
2206 var docElem = node.ownerDocument.documentElement; | 2426 var docElem = node.ownerDocument.documentElement; |
2207 for (var ancestor = node; | 2427 for (var ancestor = node; |
2208 ancestor; | 2428 ancestor; |
2209 ancestor = makeDOMAccessible(ancestor.parentNode)) { | 2429 ancestor = makeDOMAccessible(ancestor.parentNode)) { |
2210 if (idClassPattern.test(ancestor.className)) { | 2430 if (idClassPattern.test(ancestor.className)) { |
2211 return tameNodeCtor(node, editable); | 2431 return tameNodeCtor(node); |
2212 } else if (ancestor === docElem) { | 2432 } else if (ancestor === docElem) { |
2213 return null; | 2433 return null; |
2214 } | 2434 } |
2215 } | 2435 } |
2216 return tameNodeCtor(node, editable); | 2436 return tameNodeCtor(node); |
2217 } catch (e) {} | 2437 } catch (e) {} |
2218 return null; | 2438 return null; |
2219 } | 2439 } |
2220 | 2440 |
2221 domicile.tameNodeAsForeign = function(node) { | 2441 domicile.tameNodeAsForeign = function(node) { |
2222 return defaultTameNode(node, true, true); | 2442 return defaultTameNode(node, true); |
2223 }; | 2443 }; |
2224 | 2444 |
2225 /** | 2445 /** |
2226 * Returns the length of a raw DOM Nodelist object, working around | 2446 * Returns the length of a raw DOM Nodelist object, working around |
2227 * NamedNodeMap bugs in IE, Opera, and Safari as discussed at | 2447 * NamedNodeMap bugs in IE, Opera, and Safari as discussed at |
2228 * http://code.google.com/p/google-caja/issues/detail?id=935 | 2448 * http://code.google.com/p/google-caja/issues/detail?id=935 |
2229 * | 2449 * |
2230 * @param nodeList a DOM NodeList. | 2450 * @param nodeList a DOM NodeList. |
2231 * | 2451 * |
2232 * @return the number of nodes in the NodeList. | 2452 * @return the number of nodes in the NodeList. |
2233 */ | 2453 */ |
2234 function getNodeListLength(nodeList) { | 2454 function getNodeListLength(nodeList) { |
2235 var limit = nodeList.length; | 2455 var limit = nodeList.length; |
2236 if (limit !== +limit) { limit = 1/0; } | 2456 if (limit !== +limit) { limit = 1/0; } |
2237 return limit; | 2457 return limit; |
2238 } | 2458 } |
2239 | 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 |
2240 /** | 2574 /** |
2241 * Constructs a NodeList-like object. | 2575 * Constructs a NodeList-like object. |
2242 * | 2576 * |
2243 * @param tamed a JavaScript array that will be populated and decorated | 2577 * @param tamed a JavaScript array that will be populated and decorated |
2244 * 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 |
2245 * precede the actual NodeList elements. | 2579 * precede the actual NodeList elements. |
2246 * @param nodeList an array-like object supporting a "length" property | 2580 * @param nodeList an array-like object supporting a "length" property |
2247 * and "[]" numeric indexing, or a raw DOM NodeList; | 2581 * and "[]" numeric indexing, or a raw DOM NodeList; |
2248 * @param editable whether the tame nodes wrapped by this object | |
2249 * should permit editing. | |
2250 * @param opt_tameNodeCtor a function for constructing tame nodes | 2582 * @param opt_tameNodeCtor a function for constructing tame nodes |
2251 * out of raw DOM nodes. | 2583 * out of raw DOM nodes. |
2252 */ | 2584 */ |
2253 function mixinNodeList(tamed, nodeList, editable, opt_tameNodeCtor) { | 2585 function mixinNodeList(tamed, nodeList, opt_tameNodeCtor) { |
2254 // TODO(kpreid): Under a true ES5 environment, node lists should be | 2586 // TODO(kpreid): Under a true ES5 environment, node lists should be |
2255 // proxies so that they preserve liveness of the original lists. | 2587 // proxies so that they preserve liveness of the original lists. |
2256 // This should be controlled by an option. | 2588 // This should be controlled by an option. |
2257 | 2589 // UPDATE: We have live NodeLists as TameNodeList and TameOptionsList. |
2258 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(); |
2259 if (limit > 0 && !opt_tameNodeCtor) { | 2595 if (limit > 0 && !opt_tameNodeCtor) { |
2260 throw 'Internal: Nonempty mixinNodeList() without a tameNodeCtor'; | 2596 throw 'Internal: Nonempty mixinNodeList() without a tameNodeCtor'; |
2261 } | 2597 } |
2262 | 2598 |
2263 for (var i = tamed.length, j = 0; j < limit && nodeList[+j]; ++i, ++j) { | 2599 for (var i = tamed.length, j = 0; |
2264 tamed[+i] = opt_tameNodeCtor(nodeList[+j], editable); | 2600 j < limit && visibleList.item(j); |
| 2601 ++i, ++j) { |
| 2602 tamed[+i] = opt_tameNodeCtor(visibleList.item(+j)); |
2265 } | 2603 } |
2266 | 2604 |
2267 // Guard against accidental leakage of untamed nodes | 2605 // Guard against accidental leakage of untamed nodes |
2268 nodeList = null; | 2606 nodeList = visibleList = null; |
2269 | 2607 |
2270 tamed.item = cajaVM.def(function (k) { | 2608 tamed.item = cajaVM.def(function (k) { |
2271 k &= 0x7fffffff; | 2609 k &= 0x7fffffff; |
2272 if (k !== k) { throw new Error(); } | 2610 if (k !== k) { throw new Error(); } |
2273 return tamed[+k] || null; | 2611 return tamed[+k] || null; |
2274 }); | 2612 }); |
2275 | 2613 |
2276 return tamed; | 2614 return tamed; |
2277 } | 2615 } |
2278 | 2616 |
2279 function rebuildTameListConstructors(ArrayLike) { | 2617 function rebuildTameListConstructors(ArrayLike) { |
2280 TameNodeList = makeTameNodeList(); | 2618 TameNodeList = makeTameNodeList(); |
2281 TameNodeList.prototype = Object.create(ArrayLike.prototype); | 2619 TameNodeList.prototype = Object.create(ArrayLike.prototype); |
2282 Object.defineProperty(TameNodeList.prototype, 'constructor', | 2620 Object.defineProperty(TameNodeList.prototype, 'constructor', |
2283 { value: TameNodeList }); | 2621 { value: TameNodeList }); |
2284 Object.freeze(TameNodeList.prototype); | 2622 Object.freeze(TameNodeList.prototype); |
2285 Object.freeze(TameNodeList); | 2623 Object.freeze(TameNodeList); |
2286 TameOptionsList = makeTameOptionsList(); | 2624 TameOptionsList = makeTameOptionsList(); |
2287 TameOptionsList.prototype = Object.create(ArrayLike.prototype); | 2625 TameOptionsList.prototype = Object.create(ArrayLike.prototype); |
2288 Object.defineProperty(TameOptionsList.prototype, 'constructor', | 2626 Object.defineProperty(TameOptionsList.prototype, 'constructor', |
2289 { value: TameOptionsList }); | 2627 { value: TameOptionsList }); |
2290 Object.freeze(TameOptionsList.prototype); | 2628 Object.freeze(TameOptionsList.prototype); |
2291 Object.freeze(TameOptionsList); | 2629 Object.freeze(TameOptionsList); |
2292 } | 2630 } |
2293 | 2631 |
2294 function makeTameNodeList() { | 2632 function makeTameNodeList() { |
2295 return function TNL(nodeList, editable, tameNodeCtor) { | 2633 return function TNL(nodeList, tameNodeCtor) { |
2296 nodeList = makeDOMAccessible(nodeList); | 2634 var visibleList = new NodeListFilter(nodeList); |
2297 function getItem(i) { | 2635 function getItem(i) { |
2298 i = +i; | 2636 i = +i; |
2299 if (i >= nodeList.length) { return void 0; } | 2637 if (i >= visibleList.getLength()) { return void 0; } |
2300 return tameNodeCtor(nodeList[i], editable); | 2638 return tameNodeCtor(visibleList.item(i)); |
2301 } | 2639 } |
2302 function getLength() { | 2640 var getLength = visibleList.getLength.bind(visibleList); |
2303 return nodeList.length; | |
2304 } | |
2305 var len = +getLength(); | 2641 var len = +getLength(); |
2306 var ArrayLike = cajaVM.makeArrayLike(len); | 2642 var ArrayLike = cajaVM.makeArrayLike(len); |
2307 if (!(TameNodeList.prototype instanceof ArrayLike)) { | 2643 if (!(TameNodeList.prototype instanceof ArrayLike)) { |
2308 rebuildTameListConstructors(ArrayLike); | 2644 rebuildTameListConstructors(ArrayLike); |
2309 } | 2645 } |
2310 var result = ArrayLike(TameNodeList.prototype, getItem, getLength); | 2646 var result = ArrayLike(TameNodeList.prototype, getItem, getLength); |
2311 Object.defineProperty(result, 'item', | 2647 Object.defineProperty(result, 'item', |
2312 { value: Object.freeze(getItem) }); | 2648 { value: Object.freeze(getItem) }); |
2313 return result; | 2649 return result; |
2314 }; | 2650 }; |
2315 } | 2651 } |
2316 | 2652 |
2317 var TameNodeList = Object.freeze(makeTameNodeList()); | 2653 var TameNodeList = Object.freeze(makeTameNodeList()); |
2318 | 2654 |
2319 function makeTameOptionsList() { | 2655 function makeTameOptionsList() { |
2320 return function TOL(nodeList, editable, opt_tameNodeCtor) { | 2656 return function TOL(nodeList, opt_tameNodeCtor) { |
2321 nodeList = makeDOMAccessible(nodeList); | 2657 var visibleList = new NodeListFilter(nodeList); |
2322 function getItem(i) { | 2658 function getItem(i) { |
2323 i = +i; | 2659 i = +i; |
2324 return opt_tameNodeCtor(nodeList[i], editable); | 2660 return opt_tameNodeCtor(visibleList.item(i)); |
2325 } | 2661 } |
2326 function getLength() { return nodeList.length; } | 2662 var getLength = visibleList.getLength.bind(visibleList); |
2327 var len = +getLength(); | 2663 var len = +getLength(); |
2328 var ArrayLike = cajaVM.makeArrayLike(len); | 2664 var ArrayLike = cajaVM.makeArrayLike(len); |
2329 if (!(TameOptionsList.prototype instanceof ArrayLike)) { | 2665 if (!(TameOptionsList.prototype instanceof ArrayLike)) { |
2330 rebuildTameListConstructors(ArrayLike); | 2666 rebuildTameListConstructors(ArrayLike); |
2331 } | 2667 } |
2332 var result = ArrayLike( | 2668 var result = ArrayLike( |
2333 TameOptionsList.prototype, getItem, getLength); | 2669 TameOptionsList.prototype, getItem, getLength); |
2334 Object.defineProperty(result, 'selectedIndex', { | 2670 Object.defineProperty(result, 'selectedIndex', { |
2335 get: function () { return +nodeList.selectedIndex; } | 2671 get: function () { return +nodeList.selectedIndex; } |
2336 }); | 2672 }); |
(...skipping 14 matching lines...) Expand all Loading... |
2351 } | 2687 } |
2352 | 2688 |
2353 /** | 2689 /** |
2354 * Constructs an HTMLCollection-like object which indexes its elements | 2690 * Constructs an HTMLCollection-like object which indexes its elements |
2355 * based on their NAME attribute. | 2691 * based on their NAME attribute. |
2356 * | 2692 * |
2357 * @param tamed a JavaScript array that will be populated and decorated | 2693 * @param tamed a JavaScript array that will be populated and decorated |
2358 * with the DOM HTMLCollection API. | 2694 * with the DOM HTMLCollection API. |
2359 * @param nodeList an array-like object supporting a "length" property | 2695 * @param nodeList an array-like object supporting a "length" property |
2360 * and "[]" numeric indexing. | 2696 * and "[]" numeric indexing. |
2361 * @param editable whether the tame nodes wrapped by this object | |
2362 * should permit editing. | |
2363 * @param opt_tameNodeCtor a function for constructing tame nodes | 2697 * @param opt_tameNodeCtor a function for constructing tame nodes |
2364 * out of raw DOM nodes. | 2698 * out of raw DOM nodes. |
2365 * | 2699 * |
2366 * TODO(kpreid): Per | 2700 * TODO(kpreid): Per |
2367 * <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> |
2368 * 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 |
2369 * nodelists, but is that for compatibility?) | 2703 * nodelists, but is that for compatibility?) |
2370 */ | 2704 */ |
2371 function mixinHTMLCollection(tamed, nodeList, editable, | 2705 function mixinHTMLCollection(tamed, nodeList, opt_tameNodeCtor) { |
2372 opt_tameNodeCtor) { | 2706 mixinNodeList(tamed, nodeList, opt_tameNodeCtor); |
2373 mixinNodeList(tamed, nodeList, editable, opt_tameNodeCtor); | |
2374 | 2707 |
2375 var tameNodesByName = {}; | 2708 var tameNodesByName = {}; |
2376 var tameNode; | 2709 var tameNode; |
2377 | 2710 |
2378 for (var i = 0; i < tamed.length && (tameNode = tamed[+i]); ++i) { | 2711 for (var i = 0; i < tamed.length && (tameNode = tamed[+i]); ++i) { |
2379 var name = void 0; | 2712 var name = void 0; |
2380 if (tameNode.getAttribute) { name = tameNode.getAttribute('name'); } | 2713 if (tameNode.getAttribute) { name = tameNode.getAttribute('name'); } |
2381 if (name && !(name.charAt(name.length - 1) === '_' || (name in tamed) | 2714 if (name && !(name.charAt(name.length - 1) === '_' || (name in tamed) |
2382 || name === String(name & 0x7fffffff))) { | 2715 || name === String(name & 0x7fffffff))) { |
2383 if (!tameNodesByName[name]) { tameNodesByName[name] = []; } | 2716 if (!tameNodesByName[name]) { tameNodesByName[name] = []; } |
(...skipping 18 matching lines...) Expand all Loading... |
2402 if (Object.prototype.hasOwnProperty.call(tamed, name)) { | 2735 if (Object.prototype.hasOwnProperty.call(tamed, name)) { |
2403 return cajaVM.passesGuard(TameNodeT, tamed[name]) | 2736 return cajaVM.passesGuard(TameNodeT, tamed[name]) |
2404 ? tamed[name] : tamed[name][0]; | 2737 ? tamed[name] : tamed[name][0]; |
2405 } | 2738 } |
2406 return null; | 2739 return null; |
2407 }); | 2740 }); |
2408 | 2741 |
2409 return tamed; | 2742 return tamed; |
2410 } | 2743 } |
2411 | 2744 |
2412 function tameHTMLCollection(nodeList, editable, opt_tameNodeCtor) { | 2745 function tameHTMLCollection(nodeList, opt_tameNodeCtor) { |
2413 return Object.freeze( | 2746 return Object.freeze( |
2414 mixinHTMLCollection([], nodeList, editable, opt_tameNodeCtor)); | 2747 mixinHTMLCollection([], nodeList, opt_tameNodeCtor)); |
2415 } | 2748 } |
2416 | 2749 |
2417 function tameGetElementsByTagName(rootNode, tagName, editable) { | 2750 function tameGetElementsByTagName(rootNode, tagName) { |
2418 tagName = String(tagName); | 2751 tagName = String(tagName); |
2419 var eflags = 0; | 2752 var eflags = 0; |
2420 if (tagName !== '*') { | 2753 if (tagName !== '*') { |
2421 tagName = tagName.toLowerCase(); | 2754 tagName = tagName.toLowerCase(); |
2422 tagName = virtualToRealElementName(tagName); | 2755 tagName = virtualToRealElementName(tagName); |
2423 } | 2756 } |
2424 return new TameNodeList(rootNode.getElementsByTagName(tagName), | 2757 return new TameNodeList(rootNode.getElementsByTagName(tagName), |
2425 editable, defaultTameNode); | 2758 defaultTameNode); |
2426 } | 2759 } |
2427 | 2760 |
2428 /** | 2761 /** |
2429 * 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 |
2430 * using an existing implementation on browsers that have one. | 2763 * using an existing implementation on browsers that have one. |
2431 */ | 2764 */ |
2432 function tameGetElementsByClassName(rootNode, className, editable) { | 2765 function tameGetElementsByClassName(rootNode, className) { |
2433 className = String(className); | 2766 className = String(className); |
2434 | 2767 |
2435 // The quotes below are taken from the HTML5 draft referenced above. | 2768 // The quotes below are taken from the HTML5 draft referenced above. |
2436 | 2769 |
2437 // "having obtained the classes by splitting a string on spaces" | 2770 // "having obtained the classes by splitting a string on spaces" |
2438 // 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 |
2439 // we don't have to remove leading and trailing spaces. | 2772 // we don't have to remove leading and trailing spaces. |
2440 var classes = className.match(/[^\t\n\f\r ]+/g); | 2773 var classes = className.match(/[^\t\n\f\r ]+/g); |
2441 | 2774 |
2442 // Filter out classnames in the restricted namespace. | 2775 // Filter out classnames in the restricted namespace. |
(...skipping 12 matching lines...) Expand all Loading... |
2455 // htmlEl.ownerDocument.getElementsByClassName(htmlEl.className) | 2788 // htmlEl.ownerDocument.getElementsByClassName(htmlEl.className) |
2456 // will return an HtmlCollection containing htmlElement iff | 2789 // will return an HtmlCollection containing htmlElement iff |
2457 // htmlEl.className contains a non-space character. | 2790 // htmlEl.className contains a non-space character. |
2458 return fakeNodeList([]); | 2791 return fakeNodeList([]); |
2459 } | 2792 } |
2460 | 2793 |
2461 // "unordered set of unique space-separated tokens representing classes" | 2794 // "unordered set of unique space-separated tokens representing classes" |
2462 if (typeof rootNode.getElementsByClassName === 'function') { | 2795 if (typeof rootNode.getElementsByClassName === 'function') { |
2463 return new TameNodeList( | 2796 return new TameNodeList( |
2464 rootNode.getElementsByClassName( | 2797 rootNode.getElementsByClassName( |
2465 classes.join(' ')), editable, defaultTameNode); | 2798 classes.join(' ')), defaultTameNode); |
2466 } else { | 2799 } else { |
2467 // 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 |
2468 // find a match. | 2801 // find a match. |
2469 // This use of indexOf is strictly incorrect since | 2802 // This use of indexOf is strictly incorrect since |
2470 // 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 |
2471 // does not normalize spaces in unordered sets of unique | 2804 // does not normalize spaces in unordered sets of unique |
2472 // space-separated tokens. This is not a problem since HTML5 | 2805 // space-separated tokens. This is not a problem since HTML5 |
2473 // compliant implementations already have a getElementsByClassName | 2806 // compliant implementations already have a getElementsByClassName |
2474 // implementation, and legacy | 2807 // implementation, and legacy |
2475 // 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... |
2493 candidate_loop: | 2826 candidate_loop: |
2494 for (var j = 0, candidate, k = -1; | 2827 for (var j = 0, candidate, k = -1; |
2495 j < limit && (candidate = candidates[+j]); | 2828 j < limit && (candidate = candidates[+j]); |
2496 ++j) { | 2829 ++j) { |
2497 var candidateClass = ' ' + candidate.className + ' '; | 2830 var candidateClass = ' ' + candidate.className + ' '; |
2498 for (var i = nClasses; --i >= 0;) { | 2831 for (var i = nClasses; --i >= 0;) { |
2499 if (-1 === candidateClass.indexOf(classes[+i])) { | 2832 if (-1 === candidateClass.indexOf(classes[+i])) { |
2500 continue candidate_loop; | 2833 continue candidate_loop; |
2501 } | 2834 } |
2502 } | 2835 } |
2503 var tamed = defaultTameNode(candidate, editable); | 2836 var tamed = defaultTameNode(candidate); |
2504 if (tamed) { | 2837 if (tamed) { |
2505 matches[++k] = tamed; | 2838 matches[++k] = tamed; |
2506 } | 2839 } |
2507 } | 2840 } |
2508 // "the method must return a live NodeList object" | 2841 // "the method must return a live NodeList object" |
2509 return fakeNodeList(matches); | 2842 return fakeNodeList(matches); |
2510 } | 2843 } |
2511 } | 2844 } |
2512 | 2845 |
2513 function makeEventHandlerWrapper(thisNode, listener) { | 2846 function makeEventHandlerWrapper(thisNode, listener) { |
2514 domitaModules.ensureValidCallback(listener); | 2847 domitaModules.ensureValidCallback(listener); |
2515 function wrapper(event) { | 2848 function wrapper(event) { |
2516 return plugin_dispatchEvent( | 2849 return plugin_dispatchEvent( |
2517 thisNode, event, rulebreaker.getId(tameWindow), listener); | 2850 thisNode, event, rulebreaker.getId(tameWindow), listener); |
2518 } | 2851 } |
2519 return wrapper; | 2852 return wrapper; |
2520 } | 2853 } |
2521 | 2854 |
2522 var NOT_EDITABLE = "Node not editable."; | |
2523 var INDEX_SIZE_ERROR = "Index size error."; | |
2524 | |
2525 // Implementation of EventTarget::addEventListener | 2855 // Implementation of EventTarget::addEventListener |
2526 function tameAddEventListener(name, listener, useCapture) { | 2856 function tameAddEventListener(name, listener, useCapture) { |
2527 var feral = np(this).feral; | 2857 var feral = np(this).feral; |
2528 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 2858 np(this).policy.requireEditable(); |
2529 if (!np(this).wrappedListeners) { np(this).wrappedListeners = []; } | 2859 if (!np(this).wrappedListeners) { np(this).wrappedListeners = []; } |
2530 useCapture = Boolean(useCapture); | 2860 useCapture = Boolean(useCapture); |
2531 var wrappedListener = makeEventHandlerWrapper(np(this).feral, listener); | 2861 var wrappedListener = makeEventHandlerWrapper(np(this).feral, listener); |
2532 wrappedListener = bridal.addEventListener( | 2862 wrappedListener = bridal.addEventListener( |
2533 np(this).feral, name, wrappedListener, useCapture); | 2863 np(this).feral, name, wrappedListener, useCapture); |
2534 wrappedListener._d_originalListener = listener; | 2864 wrappedListener._d_originalListener = listener; |
2535 np(this).wrappedListeners.push(wrappedListener); | 2865 np(this).wrappedListeners.push(wrappedListener); |
2536 } | 2866 } |
2537 | 2867 |
2538 // Implementation of EventTarget::removeEventListener | 2868 // Implementation of EventTarget::removeEventListener |
2539 function tameRemoveEventListener(name, listener, useCapture) { | 2869 function tameRemoveEventListener(name, listener, useCapture) { |
2540 var self = TameNodeT.coerce(this); | 2870 var self = TameNodeT.coerce(this); |
2541 var feral = np(self).feral; | 2871 var feral = np(self).feral; |
2542 if (!np(self).editable) { throw new Error(NOT_EDITABLE); } | 2872 np(this).policy.requireEditable(); |
2543 if (!np(this).wrappedListeners) { return; } | 2873 if (!np(this).wrappedListeners) { return; } |
2544 var wrappedListener = null; | 2874 var wrappedListener = null; |
2545 for (var i = np(this).wrappedListeners.length; --i >= 0;) { | 2875 for (var i = np(this).wrappedListeners.length; --i >= 0;) { |
2546 if (np(this).wrappedListeners[+i]._d_originalListener === listener) { | 2876 if (np(this).wrappedListeners[+i]._d_originalListener === listener) { |
2547 wrappedListener = np(this).wrappedListeners[+i]; | 2877 wrappedListener = np(this).wrappedListeners[+i]; |
2548 arrayRemove(np(this).wrappedListeners, i, i); | 2878 arrayRemove(np(this).wrappedListeners, i, i); |
2549 break; | 2879 break; |
2550 } | 2880 } |
2551 } | 2881 } |
2552 if (!wrappedListener) { return; } | 2882 if (!wrappedListener) { return; } |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2592 traceStartup("DT: about to make TameNode"); | 2922 traceStartup("DT: about to make TameNode"); |
2593 | 2923 |
2594 /** | 2924 /** |
2595 * 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 |
2596 * tameNode factory instead. | 2926 * tameNode factory instead. |
2597 * | 2927 * |
2598 * 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 |
2599 * 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 |
2600 * forwarding proxies used for catching expando properties. | 2930 * forwarding proxies used for catching expando properties. |
2601 * | 2931 * |
2602 * @param {boolean} editable true if the node's value, attributes, | 2932 * @param {policy} Mutability policy to apply. |
2603 * children, | |
2604 * or custom properties are mutable. | |
2605 * @constructor | 2933 * @constructor |
2606 */ | 2934 */ |
2607 function TameNode(editable) { | 2935 function TameNode(policy) { |
2608 TameNodeConf.confide(this); | 2936 TameNodeConf.confide(this); |
2609 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; |
2610 return this; | 2941 return this; |
2611 } | 2942 } |
2612 inertCtor(TameNode, Object, 'Node'); | 2943 inertCtor(TameNode, Object, 'Node'); |
2613 traceStartup("DT: about to DPA TameNode"); | 2944 traceStartup("DT: about to DPA TameNode"); |
2614 definePropertiesAwesomely(TameNode.prototype, { | 2945 definePropertiesAwesomely(TameNode.prototype, { |
2615 // 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 |
2616 ownerDocument: { | 2947 ownerDocument: { |
2617 enumerable: canHaveEnumerableAccessors, | 2948 enumerable: canHaveEnumerableAccessors, |
2618 get: cajaVM.def(function () { | 2949 get: cajaVM.def(function () { |
2619 return tameDocument; | 2950 return tameDocument; |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2655 // abstract TameNode.prototype.replaceChild | 2986 // abstract TameNode.prototype.replaceChild |
2656 // abstract TameNode.prototype.firstChild | 2987 // abstract TameNode.prototype.firstChild |
2657 // abstract TameNode.prototype.lastChild | 2988 // abstract TameNode.prototype.lastChild |
2658 // abstract TameNode.prototype.nextSibling | 2989 // abstract TameNode.prototype.nextSibling |
2659 // abstract TameNode.prototype.previousSibling | 2990 // abstract TameNode.prototype.previousSibling |
2660 // abstract TameNode.prototype.parentNode | 2991 // abstract TameNode.prototype.parentNode |
2661 // abstract TameNode.prototype.getElementsByTagName | 2992 // abstract TameNode.prototype.getElementsByTagName |
2662 // abstract TameNode.prototype.getElementsByClassName | 2993 // abstract TameNode.prototype.getElementsByClassName |
2663 // abstract TameNode.prototype.childNodes | 2994 // abstract TameNode.prototype.childNodes |
2664 // abstract TameNode.prototype.attributes | 2995 // abstract TameNode.prototype.attributes |
2665 var tameNodePublicMembers = [ | |
2666 'cloneNode', | |
2667 'appendChild', 'insertBefore', 'removeChild', 'replaceChild', | |
2668 'dispatchEvent', 'hasChildNodes' | |
2669 ]; | |
2670 traceStartup("DT: about to defend TameNode"); | 2996 traceStartup("DT: about to defend TameNode"); |
2671 cajaVM.def(TameNode); // and its prototype | 2997 cajaVM.def(TameNode); // and its prototype |
2672 | 2998 |
2673 traceStartup("DT: about to make TameBackedNode"); | 2999 traceStartup("DT: about to make TameBackedNode"); |
2674 | 3000 |
2675 /** | 3001 /** |
2676 * A tame node that is backed by a real node. | 3002 * A tame node that is backed by a real node. |
2677 * | 3003 * |
2678 * Note that the constructor returns a proxy which delegates to 'this'; | 3004 * Note that the constructor returns a proxy which delegates to 'this'; |
2679 * subclasses should apply properties to 'this' but return the proxy. | 3005 * subclasses should apply properties to 'this' but return the proxy. |
2680 * | 3006 * |
2681 * @param {boolean} childrenEditable true iff the child list is mutable. | |
2682 * @param {Function} opt_proxyType The constructor of the proxy handler | 3007 * @param {Function} opt_proxyType The constructor of the proxy handler |
2683 * to use, defaulting to ExpandoProxyHandler. | 3008 * to use, defaulting to ExpandoProxyHandler. |
2684 * @constructor | 3009 * @constructor |
2685 */ | 3010 */ |
2686 function TameBackedNode(node, editable, childrenEditable, opt_proxyType) { | 3011 function TameBackedNode(node, opt_policy, opt_proxyType) { |
2687 node = makeDOMAccessible(node); | 3012 node = makeDOMAccessible(node); |
2688 | 3013 |
2689 if (!node) { | 3014 if (!node) { |
2690 throw new Error('Creating tame node with undefined native delegate'); | 3015 throw new Error('Creating tame node with undefined native delegate'); |
2691 } | 3016 } |
2692 | 3017 |
2693 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); |
2694 | 3055 |
2695 np(this).feral = node; | 3056 np(this).feral = node; |
2696 np(this).childrenEditable = editable && childrenEditable; | |
2697 | 3057 |
2698 if (domitaModules.proxiesAvailable) { | 3058 if (domitaModules.proxiesAvailable) { |
2699 np(this).proxyHandler = new (opt_proxyType || ExpandoProxyHandler)( | 3059 np(this).proxyHandler = new (opt_proxyType || ExpandoProxyHandler)( |
2700 this, editable, getNodeExpandoStorage(node)); | 3060 this, policy.editable, getNodeExpandoStorage(node)); |
2701 } | 3061 } |
2702 } | 3062 } |
2703 inertCtor(TameBackedNode, TameNode); | 3063 inertCtor(TameBackedNode, TameNode); |
2704 definePropertiesAwesomely(TameBackedNode.prototype, { | 3064 definePropertiesAwesomely(TameBackedNode.prototype, { |
2705 nodeType: NP.ro, | 3065 nodeType: NP.ro, |
2706 nodeName: NP.ro, | 3066 nodeName: NP.ro, |
2707 nodeValue: NP.ro, | 3067 nodeValue: NP.ro, |
2708 firstChild: NP_tameDescendant, | 3068 firstChild: NP_tameDescendant, // TODO(kpreid): Must be disableable |
2709 lastChild: NP_tameDescendant, | 3069 lastChild: NP_tameDescendant, |
2710 nextSibling: NP.related, | 3070 nextSibling: NP.related, |
2711 previousSibling: NP.related, | 3071 previousSibling: NP.related, |
2712 parentNode: NP.related, | 3072 parentNode: NP.related, |
2713 childNodes: { | 3073 childNodes: { |
2714 enumerable: true, | 3074 enumerable: true, |
2715 get: cajaVM.def(function () { | 3075 get: cajaVM.def(function () { |
2716 return new TameNodeList(np(this).feral.childNodes, | 3076 var privates = np(this); |
2717 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 } |
2718 }) | 3083 }) |
2719 }, | 3084 }, |
2720 attributes: { | 3085 attributes: { |
2721 enumerable: true, | 3086 enumerable: true, |
2722 get: cajaVM.def(function () { | 3087 get: cajaVM.def(function () { |
2723 var thisNode = np(this).feral; | 3088 var privates = np(this); |
2724 var tameNodeCtor = function(node, editable) { | 3089 if (privates.policy.attributesVisible) { |
2725 return new TameBackedAttributeNode(node, editable, thisNode); | 3090 var thisNode = privates.feral; |
2726 }; | 3091 var tameNodeCtor = function(node) { |
2727 return new TameNodeList( | 3092 return new TameBackedAttributeNode(node, thisNode); |
2728 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 } |
2729 }) | 3100 }) |
2730 } | 3101 } |
2731 }); | 3102 }); |
2732 TameBackedNode.prototype.cloneNode = nodeMethod(function (deep) { | 3103 TameBackedNode.prototype.cloneNode = nodeMethod(function (deep) { |
| 3104 np(this).policy.requireUnrestricted(); |
2733 var clone = bridal.cloneNode(np(this).feral, Boolean(deep)); | 3105 var clone = bridal.cloneNode(np(this).feral, Boolean(deep)); |
2734 // 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 |
2735 // "Note that cloning an immutable subtree results in a mutable copy" | 3107 // "Note that cloning an immutable subtree results in a mutable copy" |
2736 return defaultTameNode(clone, true); | 3108 return defaultTameNode(clone); |
2737 }); | 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 } |
2738 TameBackedNode.prototype.appendChild = nodeMethod(function (child) { | 3120 TameBackedNode.prototype.appendChild = nodeMethod(function (child) { |
2739 child = child || {}; | 3121 child = child || {}; |
2740 // Child must be editable since appendChild can remove it from its | |
2741 // parent. | |
2742 child = TameNodeT.coerce(child); | 3122 child = TameNodeT.coerce(child); |
2743 if (!np(this).childrenEditable || !np(child).editable) { | 3123 |
2744 throw new Error(NOT_EDITABLE); | 3124 checkAdoption(this, child); |
2745 } | 3125 |
2746 np(this).feral.appendChild(np(child).feral); | 3126 np(this).feral.appendChild(np(child).feral); |
2747 return child; | 3127 return child; |
2748 }); | 3128 }); |
2749 TameBackedNode.prototype.insertBefore = nodeMethod( | 3129 TameBackedNode.prototype.insertBefore = nodeMethod( |
2750 function(toInsert, child) { | 3130 function(toInsert, child) { |
2751 toInsert = TameNodeT.coerce(toInsert); | 3131 toInsert = TameNodeT.coerce(toInsert); |
2752 if (child === void 0) { child = null; } | 3132 if (child === void 0) { child = null; } |
| 3133 |
2753 if (child !== null) { | 3134 if (child !== null) { |
2754 child = TameNodeT.coerce(child); | 3135 child = TameNodeT.coerce(child); |
2755 if (!np(child).editable) { | 3136 // TODO(kpreid): This child is not being mutated except for its |
2756 throw new Error(NOT_EDITABLE); | 3137 // previousSibling, so why are we rejecting here? |
2757 } | 3138 np(child).policy.requireEditable(); |
2758 } | 3139 } |
2759 if (!np(this).childrenEditable || !np(toInsert).editable) { | 3140 checkAdoption(this, toInsert); |
2760 throw new Error(NOT_EDITABLE); | 3141 |
2761 } | |
2762 np(this).feral.insertBefore( | 3142 np(this).feral.insertBefore( |
2763 np(toInsert).feral, child !== null ? np(child).feral : null); | 3143 np(toInsert).feral, child !== null ? np(child).feral : null); |
2764 return toInsert; | 3144 return toInsert; |
2765 }); | 3145 }); |
2766 TameBackedNode.prototype.removeChild = nodeMethod(function(child) { | 3146 TameBackedNode.prototype.removeChild = nodeMethod(function(child) { |
2767 child = TameNodeT.coerce(child); | 3147 child = TameNodeT.coerce(child); |
2768 if (!np(this).childrenEditable || !np(child).editable) { | 3148 np(this).policy.requireChildrenEditable(); |
2769 throw new Error(NOT_EDITABLE); | 3149 np(child).policy.requireEditable(); |
2770 } | |
2771 np(this).feral.removeChild(np(child).feral); | 3150 np(this).feral.removeChild(np(child).feral); |
2772 return child; | 3151 return child; |
2773 }); | 3152 }); |
2774 TameBackedNode.prototype.replaceChild = nodeMethod( | 3153 TameBackedNode.prototype.replaceChild = nodeMethod( |
2775 function(newChild, oldChild) { | 3154 function(newChild, oldChild) { |
2776 newChild = TameNodeT.coerce(newChild); | 3155 newChild = TameNodeT.coerce(newChild); |
2777 oldChild = TameNodeT.coerce(oldChild); | 3156 oldChild = TameNodeT.coerce(oldChild); |
2778 if (!np(this).childrenEditable || !np(newChild).editable | 3157 |
2779 || !np(oldChild).editable) { | 3158 checkAdoption(this, newChild); |
2780 throw new Error(NOT_EDITABLE); | 3159 np(oldChild).policy.requireEditable(); |
2781 } | 3160 |
2782 np(this).feral.replaceChild(np(newChild).feral, np(oldChild).feral); | 3161 np(this).feral.replaceChild(np(newChild).feral, np(oldChild).feral); |
2783 return oldChild; | 3162 return oldChild; |
2784 }); | 3163 }); |
2785 TameBackedNode.prototype.hasChildNodes = nodeMethod(function() { | 3164 TameBackedNode.prototype.hasChildNodes = nodeMethod(function() { |
2786 return !!np(this).feral.hasChildNodes(); | 3165 if (np(this).policy.childrenVisible) { |
| 3166 return !!np(this).feral.hasChildNodes(); |
| 3167 } else { |
| 3168 return false; |
| 3169 } |
2787 }); | 3170 }); |
2788 // 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 |
2789 // "The EventTarget interface is implemented by all Nodes" | 3172 // "The EventTarget interface is implemented by all Nodes" |
2790 TameBackedNode.prototype.dispatchEvent = nodeMethod(function(evt) { | 3173 TameBackedNode.prototype.dispatchEvent = nodeMethod(function(evt) { |
2791 evt = TameEventT.coerce(evt); | 3174 evt = TameEventT.coerce(evt); |
2792 bridal.dispatchEvent(np(this).feral, TameEventConf.p(evt).feral); | 3175 bridal.dispatchEvent(np(this).feral, TameEventConf.p(evt).feral); |
2793 }); | 3176 }); |
2794 | 3177 |
2795 if (docEl.contains) { // typeof is 'object' on IE | 3178 if (docEl.contains) { // typeof is 'object' on IE |
2796 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... |
2845 } | 3228 } |
2846 } | 3229 } |
2847 cajaVM.def(TameBackedNode); // and its prototype | 3230 cajaVM.def(TameBackedNode); // and its prototype |
2848 | 3231 |
2849 traceStartup("DT: about to make TamePseudoNode"); | 3232 traceStartup("DT: about to make TamePseudoNode"); |
2850 | 3233 |
2851 /** | 3234 /** |
2852 * 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. |
2853 * @constructor | 3236 * @constructor |
2854 */ | 3237 */ |
2855 function TamePseudoNode(editable) { | 3238 function TamePseudoNode() { |
2856 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); |
2857 | 3242 |
2858 if (domitaModules.proxiesAvailable) { | 3243 if (domitaModules.proxiesAvailable) { |
2859 // finishNode will wrap 'this' with an actual proxy later. | 3244 // finishNode will wrap 'this' with an actual proxy later. |
2860 np(this).proxyHandler = new ExpandoProxyHandler(this, editable, {}); | 3245 np(this).proxyHandler = new ExpandoProxyHandler(this, true, {}); |
2861 } | 3246 } |
2862 } | 3247 } |
2863 inertCtor(TamePseudoNode, TameNode); | 3248 inertCtor(TamePseudoNode, TameNode); |
2864 TamePseudoNode.prototype.appendChild = | 3249 TamePseudoNode.prototype.appendChild = |
2865 TamePseudoNode.prototype.insertBefore = | 3250 TamePseudoNode.prototype.insertBefore = |
2866 TamePseudoNode.prototype.removeChild = | 3251 TamePseudoNode.prototype.removeChild = |
2867 TamePseudoNode.prototype.replaceChild = nodeMethod(function () { | 3252 TamePseudoNode.prototype.replaceChild = nodeMethod(function () { |
2868 if (typeof console !== 'undefined') { | 3253 if (typeof console !== 'undefined') { |
2869 console.log("Node not editable; no action performed."); | 3254 console.log("Node not editable; no action performed."); |
2870 } | 3255 } |
(...skipping 29 matching lines...) Expand all Loading... |
2900 for (var i = siblings.length; --i >= 1;) { | 3285 for (var i = siblings.length; --i >= 1;) { |
2901 if (siblings[+i] === self) { return siblings[i - 1]; } | 3286 if (siblings[+i] === self) { return siblings[i - 1]; } |
2902 } | 3287 } |
2903 return null; | 3288 return null; |
2904 })} | 3289 })} |
2905 }); | 3290 }); |
2906 cajaVM.def(TamePseudoNode); // and its prototype | 3291 cajaVM.def(TamePseudoNode); // and its prototype |
2907 | 3292 |
2908 traceStartup("DT: done fundamental nodes"); | 3293 traceStartup("DT: done fundamental nodes"); |
2909 | 3294 |
2910 var commonElementPropertyHandlers = (function() { | |
2911 var geometryDelegateProperty = { | |
2912 extendedAccessors: true, | |
2913 enumerable: true, | |
2914 get: nodeMethod(function (prop) { | |
2915 return np(this).geometryDelegate[prop]; | |
2916 }) | |
2917 }; | |
2918 var geometryDelegatePropertySettable = | |
2919 Object.create(geometryDelegateProperty); | |
2920 geometryDelegatePropertySettable.set = | |
2921 nodeMethod(function (value, prop) { | |
2922 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | |
2923 np(this).geometryDelegate[prop] = +value; | |
2924 }); | |
2925 return { | |
2926 clientWidth: geometryDelegateProperty, | |
2927 clientHeight: geometryDelegateProperty, | |
2928 offsetLeft: geometryDelegateProperty, | |
2929 offsetTop: geometryDelegateProperty, | |
2930 offsetWidth: geometryDelegateProperty, | |
2931 offsetHeight: geometryDelegateProperty, | |
2932 scrollLeft: geometryDelegatePropertySettable, | |
2933 scrollTop: geometryDelegatePropertySettable, | |
2934 scrollWidth: geometryDelegateProperty, | |
2935 scrollHeight: geometryDelegateProperty | |
2936 }; | |
2937 })(); | |
2938 | |
2939 traceStartup("DT: about to define makeRestrictedNodeType"); | |
2940 | |
2941 function makeRestrictedNodeType(whitelist) { | |
2942 function ForeignOrOpaqueNode(node, editable) { | |
2943 TameBackedNode.call(this, node, editable, editable); | |
2944 } | |
2945 var nodeType = ForeignOrOpaqueNode; // other name is for debug hint | |
2946 inherit(nodeType, TameBackedNode); | |
2947 for (var safe in whitelist) { | |
2948 // Any non-own property is overridden to be opaque below. | |
2949 var descriptor = (whitelist[safe] === 0) | |
2950 ? domitaModules.getPropertyDescriptor( | |
2951 TameBackedNode.prototype, safe) | |
2952 : { | |
2953 value: whitelist[safe], | |
2954 writable: false, | |
2955 configurable: false, | |
2956 enumerable: true | |
2957 }; | |
2958 Object.defineProperty(nodeType.prototype, safe, descriptor); | |
2959 } | |
2960 definePropertiesAwesomely(nodeType.prototype, { | |
2961 attributes: { | |
2962 enumerable: canHaveEnumerableAccessors, | |
2963 get: nodeMethod(function () { | |
2964 return new TameNodeList([], false, undefined); | |
2965 }) | |
2966 } | |
2967 }); | |
2968 function throwRestricted() { | |
2969 throw new Error('Node is restricted'); | |
2970 } | |
2971 cajaVM.def(throwRestricted); | |
2972 for (var i = tameNodePublicMembers.length; --i >= 0;) { | |
2973 var k = tameNodePublicMembers[+i]; | |
2974 if (!nodeType.prototype.hasOwnProperty(k)) { | |
2975 if (typeof TameBackedNode.prototype[k] === 'Function') { | |
2976 nodeType.prototype[k] = throwRestricted; | |
2977 } else { | |
2978 Object.defineProperty(nodeType.prototype, k, { | |
2979 enumerable: canHaveEnumerableAccessors, | |
2980 get: throwRestricted | |
2981 }); | |
2982 } | |
2983 } | |
2984 } | |
2985 return cajaVM.def(nodeType); // and its prototype | |
2986 } | |
2987 | |
2988 traceStartup("DT: about to make TameOpaqueNode"); | 3295 traceStartup("DT: about to make TameOpaqueNode"); |
2989 | 3296 |
2990 // 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 |
2991 // is the default taming for unrecognized nodes or nodes not explicitly | 3298 // is the default taming for unrecognized nodes or nodes not explicitly |
2992 // whitelisted. | 3299 // whitelisted. |
2993 var TameOpaqueNode = makeRestrictedNodeType({ | 3300 function TameOpaqueNode(node) { |
2994 nodeValue: 0, | 3301 TameBackedNode.call(this, node, nodePolicyOpaque); |
2995 nodeType: 0, | 3302 } |
2996 nodeName: 0, | 3303 inertCtor(TameOpaqueNode, TameBackedNode); |
2997 nextSibling: 0, | 3304 cajaVM.def(TameOpaqueNode); |
2998 previousSibling: 0, | |
2999 firstChild: 0, | |
3000 lastChild: 0, | |
3001 parentNode: 0, | |
3002 childNodes: 0, | |
3003 ownerDocument: 0, | |
3004 hasChildNodes: 0 | |
3005 }); | |
3006 | |
3007 traceStartup("DT: about to make TameForeignNode"); | |
3008 | 3305 |
3009 // 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 |
3010 // 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 |
3011 // not traverse into in any way. | 3308 // not traverse into in any way. |
3012 // | 3309 function TameForeignNode(node) { |
3013 // TODO(ihab.awad): The taming chosen for foreign nodes is very | 3310 TameBackedNode.call(this, node, nodePolicyForeign); |
3014 // restrictive and could be relaxed, but only after careful consideration. | 3311 } |
3015 // The below choices are for simple safety, e.g., exposing a foreign | 3312 inertCtor(TameForeignNode, TameBackedNode); |
3016 // node's | 3313 TameForeignNode.prototype.getElementsByTagName = function (tagName) { |
3017 // siblings when the foreign node has been added to some DOM tree outside | 3314 // needed because TameForeignNode doesn't inherit TameElement |
3018 // this domicile might be dangerous. | 3315 return fakeNodeList([]); |
3019 var TameForeignNode = makeRestrictedNodeType({ | 3316 }; |
3020 nodeValue: 0, | 3317 TameForeignNode.prototype.getElementsByClassName = function (className) { |
3021 nodeType: 0, | 3318 // needed because TameForeignNode doesn't inherit TameElement |
3022 nodeName: 0, | 3319 return fakeNodeList([]); |
3023 nextSibling: undefined, | 3320 }; |
3024 previousSibling: undefined, | 3321 cajaVM.def(TameForeignNode); |
3025 firstChild: undefined, | |
3026 lastChild: undefined, | |
3027 parentNode: undefined, | |
3028 childNodes: Object.freeze([]), | |
3029 ownerDocument: undefined, | |
3030 getElementsByTagName: function() { return Object.freeze([]); }, | |
3031 getElementsByClassName: function() { return Object.freeze([]); }, | |
3032 hasChildNodes: function() { return false; } | |
3033 }); | |
3034 | 3322 |
3035 traceStartup("DT: about to make TameTextNode"); | 3323 traceStartup("DT: about to make TameTextNode"); |
3036 | 3324 |
3037 function TameTextNode(node, editable) { | 3325 function TameTextNode(node) { |
3038 assert(node.nodeType === 3); | 3326 assert(node.nodeType === 3); |
3039 | 3327 TameBackedNode.call(this, node); |
3040 // The below should not be strictly necessary since childrenEditable for | |
3041 // TameScriptElements is always false, but it protects against tameNode | |
3042 // being called naively on a text node from container code. | |
3043 var pn = node.parentNode; | |
3044 if (editable && pn) { | |
3045 if (1 === pn.nodeType | |
3046 && !htmlSchema.element(pn.tagName).allowed) { | |
3047 // Do not allow mutation of text inside script elements. | |
3048 // See the testScriptLoading testcase for examples of exploits. | |
3049 editable = false; | |
3050 } | |
3051 } | |
3052 | |
3053 TameBackedNode.call(this, node, editable, editable); | |
3054 } | 3328 } |
3055 inertCtor(TameTextNode, TameBackedNode, 'Text'); | 3329 inertCtor(TameTextNode, TameBackedNode, 'Text'); |
3056 var textAccessor = { | 3330 var textAccessor = { |
3057 enumerable: true, | 3331 enumerable: true, |
3058 get: nodeMethod(function () { | 3332 get: nodeMethod(function () { |
3059 return np(this).feral.nodeValue; | 3333 return np(this).feral.nodeValue; |
3060 }), | 3334 }), |
3061 set: nodeMethod(function (value) { | 3335 set: nodeMethod(function (value) { |
3062 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 3336 np(this).policy.requireEditable(); |
3063 np(this).feral.nodeValue = String(value || ''); | 3337 np(this).feral.nodeValue = String(value || ''); |
3064 }) | 3338 }) |
3065 }; | 3339 }; |
3066 definePropertiesAwesomely(TameTextNode.prototype, { | 3340 definePropertiesAwesomely(TameTextNode.prototype, { |
3067 nodeValue: textAccessor, | 3341 nodeValue: textAccessor, |
3068 textContent: textAccessor, | 3342 textContent: textAccessor, |
3069 innerText: textAccessor, | 3343 innerText: textAccessor, |
3070 data: textAccessor | 3344 data: textAccessor |
3071 }); | 3345 }); |
3072 setOwn(TameTextNode.prototype, "toString", nodeMethod(function () { | 3346 setOwn(TameTextNode.prototype, "toString", nodeMethod(function () { |
3073 return '#text'; | 3347 return '#text'; |
3074 })); | 3348 })); |
3075 cajaVM.def(TameTextNode); // and its prototype | 3349 cajaVM.def(TameTextNode); // and its prototype |
3076 | 3350 |
3077 function TameCommentNode(node, editable) { | 3351 function TameCommentNode(node) { |
3078 assert(node.nodeType === 8); | 3352 assert(node.nodeType === 8); |
3079 TameBackedNode.call(this, node, editable, editable); | 3353 TameBackedNode.call(this, node); |
3080 } | 3354 } |
3081 inertCtor(TameCommentNode, TameBackedNode, 'CommentNode'); | 3355 inertCtor(TameCommentNode, TameBackedNode, 'CommentNode'); |
3082 setOwn(TameCommentNode.prototype, "toString", nodeMethod(function () { | 3356 setOwn(TameCommentNode.prototype, "toString", nodeMethod(function () { |
3083 return '#comment'; | 3357 return '#comment'; |
3084 })); | 3358 })); |
3085 cajaVM.def(TameCommentNode); // and its prototype | 3359 cajaVM.def(TameCommentNode); // and its prototype |
3086 | 3360 |
3087 traceStartup("DT: about to make TameBackedAttributeNode"); | 3361 traceStartup("DT: about to make TameBackedAttributeNode"); |
3088 /** | 3362 /** |
3089 * Plays the role of an Attr node for TameElement objects. | 3363 * Plays the role of an Attr node for TameElement objects. |
3090 */ | 3364 */ |
3091 function TameBackedAttributeNode(node, editable, ownerElement) { | 3365 function TameBackedAttributeNode(node, ownerElement) { |
3092 if (ownerElement === undefined) throw new Error( | 3366 if (ownerElement === undefined) throw new Error( |
3093 "ownerElement undefined"); | 3367 "ownerElement undefined"); |
3094 TameBackedNode.call(this, node, editable); | 3368 TameBackedNode.call(this, node, |
| 3369 np(defaultTameNode(ownerElement)).policy); |
3095 np(this).ownerElement = ownerElement; | 3370 np(this).ownerElement = ownerElement; |
3096 } | 3371 } |
3097 inertCtor(TameBackedAttributeNode, TameBackedNode, 'Attr'); | 3372 inertCtor(TameBackedAttributeNode, TameBackedNode, 'Attr'); |
3098 setOwn(TameBackedAttributeNode.prototype, 'cloneNode', | 3373 setOwn(TameBackedAttributeNode.prototype, 'cloneNode', |
3099 nodeMethod(function (deep) { | 3374 nodeMethod(function (deep) { |
3100 var clone = bridal.cloneNode(np(this).feral, Boolean(deep)); | 3375 var clone = bridal.cloneNode(np(this).feral, Boolean(deep)); |
3101 // 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 |
3102 // "Note that cloning an immutable subtree results in a mutable copy" | 3377 // "Note that cloning an immutable subtree results in a mutable copy" |
3103 return new TameBackedAttributeNode(clone, true, np(this).ownerElement); | 3378 return new TameBackedAttributeNode(clone, np(this).ownerElement); |
3104 })); | 3379 })); |
3105 var nameAccessor = { | 3380 var nameAccessor = { |
3106 enumerable: true, | 3381 enumerable: true, |
3107 get: nodeMethod(function () { | 3382 get: nodeMethod(function () { |
3108 var name = np(this).feral.name; | 3383 var name = np(this).feral.name; |
3109 if (cajaPrefRe.test(name)) { | 3384 if (cajaPrefRe.test(name)) { |
3110 name = name.substring(cajaPrefix.length); | 3385 name = name.substring(cajaPrefix.length); |
3111 } | 3386 } |
3112 return name; | 3387 return name; |
3113 }) | 3388 }) |
(...skipping 14 matching lines...) Expand all Loading... |
3128 enumerable: true, | 3403 enumerable: true, |
3129 get: nodeMethod(function () { | 3404 get: nodeMethod(function () { |
3130 return this.ownerElement.hasAttribute(this.name); | 3405 return this.ownerElement.hasAttribute(this.name); |
3131 }) | 3406 }) |
3132 }, | 3407 }, |
3133 nodeValue: valueAccessor, | 3408 nodeValue: valueAccessor, |
3134 value: valueAccessor, | 3409 value: valueAccessor, |
3135 ownerElement: { | 3410 ownerElement: { |
3136 enumerable: true, | 3411 enumerable: true, |
3137 get: nodeMethod(function () { | 3412 get: nodeMethod(function () { |
3138 return defaultTameNode(np(this).ownerElement, np(this).editable); | 3413 return defaultTameNode(np(this).ownerElement); |
3139 }) | 3414 }) |
3140 }, | 3415 }, |
3141 nodeType: P_constant(2), | 3416 nodeType: P_constant(2), |
3142 firstChild: P_UNIMPLEMENTED, | 3417 firstChild: P_UNIMPLEMENTED, |
3143 lastChild: P_UNIMPLEMENTED, | 3418 lastChild: P_UNIMPLEMENTED, |
3144 nextSibling: P_UNIMPLEMENTED, | 3419 nextSibling: P_UNIMPLEMENTED, |
3145 previousSibling: P_UNIMPLEMENTED, | 3420 previousSibling: P_UNIMPLEMENTED, |
3146 parentNode: P_UNIMPLEMENTED, | 3421 parentNode: P_UNIMPLEMENTED, |
3147 childNodes: P_UNIMPLEMENTED, | 3422 childNodes: P_UNIMPLEMENTED, |
3148 attributes: P_UNIMPLEMENTED | 3423 attributes: P_UNIMPLEMENTED |
(...skipping 24 matching lines...) Expand all Loading... |
3173 if (Object.prototype.hasOwnProperty.call( | 3448 if (Object.prototype.hasOwnProperty.call( |
3174 seenAlready, attribName)) { | 3449 seenAlready, attribName)) { |
3175 return; | 3450 return; |
3176 } | 3451 } |
3177 seenAlready[attribName] = true; | 3452 seenAlready[attribName] = true; |
3178 | 3453 |
3179 Object.defineProperty(tameElementPrototype, attribName, { | 3454 Object.defineProperty(tameElementPrototype, attribName, { |
3180 enumerable: canHaveEnumerableAccessors, | 3455 enumerable: canHaveEnumerableAccessors, |
3181 configurable: false, | 3456 configurable: false, |
3182 set: nodeMethod(function eventHandlerSetter(listener) { | 3457 set: nodeMethod(function eventHandlerSetter(listener) { |
3183 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 3458 np(this).policy.requireEditable(); |
3184 if (!listener) { // Clear the current handler | 3459 if (!listener) { // Clear the current handler |
3185 np(this).feral[attribName] = null; | 3460 np(this).feral[attribName] = null; |
3186 } else { | 3461 } else { |
3187 // This handler cannot be copied from one node to another | 3462 // This handler cannot be copied from one node to another |
3188 // which is why getters are not yet supported. | 3463 // which is why getters are not yet supported. |
3189 np(this).feral[attribName] = makeEventHandlerWrapper( | 3464 np(this).feral[attribName] = makeEventHandlerWrapper( |
3190 np(this).feral, listener); | 3465 np(this).feral, listener); |
3191 } | 3466 } |
3192 return listener; | 3467 return listener; |
3193 }) | 3468 }) |
3194 }); | 3469 }); |
3195 })(html4Attrib.match(attrNameRe)[1]); | 3470 })(html4Attrib.match(attrNameRe)[1]); |
3196 } | 3471 } |
3197 } | 3472 } |
3198 } | 3473 } |
3199 | 3474 |
3200 traceStartup("DT: about to make TameElement"); | 3475 traceStartup("DT: about to make TameElement"); |
3201 /** | 3476 /** |
3202 * See comments on TameBackedNode regarding return value. | 3477 * See comments on TameBackedNode regarding return value. |
3203 * @constructor | 3478 * @constructor |
3204 */ | 3479 */ |
3205 function TameElement(node, editable, childrenEditable, opt_proxyType) { | 3480 function TameElement(node, opt_policy, opt_proxyType) { |
3206 assert(node.nodeType === 1); | 3481 assert(node.nodeType === 1); |
3207 var obj = TameBackedNode.call(this, node, editable, childrenEditable, | 3482 var obj = TameBackedNode.call(this, node, opt_policy, opt_proxyType); |
3208 opt_proxyType); | |
3209 np(this).geometryDelegate = node; | 3483 np(this).geometryDelegate = node; |
3210 return obj; | 3484 return obj; |
3211 } | 3485 } |
3212 nodeClasses.Element = inertCtor(TameElement, TameBackedNode, | 3486 nodeClasses.Element = inertCtor(TameElement, TameBackedNode, |
3213 'HTMLElement'); | 3487 'HTMLElement'); |
3214 registerElementScriptAttributeHandlers(TameElement.prototype); | 3488 registerElementScriptAttributeHandlers(TameElement.prototype); |
3215 TameElement.prototype.blur = nodeMethod(function () { | 3489 TameElement.prototype.blur = nodeMethod(function () { |
3216 np(this).feral.blur(); | 3490 np(this).feral.blur(); |
3217 }); | 3491 }); |
3218 TameElement.prototype.focus = nodeMethod(function () { | 3492 TameElement.prototype.focus = nodeMethod(function () { |
3219 if (domicile.isProcessingEvent) { | 3493 return domicile.handlingUserAction && np(this).feral.focus(); |
3220 np(this).feral.focus(); | |
3221 } | |
3222 }); | 3494 }); |
3223 // 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 |
3224 // window has focus, without focusing the window. | 3496 // window has focus, without focusing the window. |
3225 if (docEl.setActive) { | 3497 if (docEl.setActive) { |
3226 TameElement.prototype.setActive = nodeMethod(function () { | 3498 TameElement.prototype.setActive = nodeMethod(function () { |
3227 if (domicile.isProcessingEvent) { | 3499 return domicile.handlingUserAction && np(this).feral.setActive(); |
3228 np(this).feral.setActive(); | |
3229 } | |
3230 }); | 3500 }); |
3231 } | 3501 } |
3232 // IE-specific method. | 3502 // IE-specific method. |
3233 if (docEl.hasFocus) { | 3503 if (docEl.hasFocus) { |
3234 TameElement.prototype.hasFocus = nodeMethod(function () { | 3504 TameElement.prototype.hasFocus = nodeMethod(function () { |
3235 return np(this).feral.hasFocus(); | 3505 return np(this).feral.hasFocus(); |
3236 }); | 3506 }); |
3237 } | 3507 } |
3238 TameElement.prototype.getAttribute = nodeMethod(function (attribName) { | 3508 TameElement.prototype.getAttribute = nodeMethod(function (attribName) { |
| 3509 if (!np(this).policy.attributesVisible) { return null; } |
3239 var feral = np(this).feral; | 3510 var feral = np(this).feral; |
3240 attribName = String(attribName).toLowerCase(); | 3511 attribName = String(attribName).toLowerCase(); |
3241 if (/__$/.test(attribName)) { | 3512 if (/__$/.test(attribName)) { |
3242 throw new TypeError('Attributes may not end with __'); | 3513 throw new TypeError('Attributes may not end with __'); |
3243 } | 3514 } |
3244 var tagName = feral.tagName.toLowerCase(); | 3515 var tagName = feral.tagName.toLowerCase(); |
3245 var atype = htmlSchema.attribute(tagName, attribName).type; | 3516 var atype = htmlSchema.attribute(tagName, attribName).type; |
3246 if (atype === void 0) { | 3517 if (atype === void 0) { |
3247 return feral.getAttribute(cajaPrefix + attribName); | 3518 return feral.getAttribute(cajaPrefix + attribName); |
3248 } | 3519 } |
3249 var value = bridal.getAttribute(feral, attribName); | 3520 var value = bridal.getAttribute(feral, attribName); |
3250 if ('string' !== typeof value) { return value; } | 3521 if ('string' !== typeof value) { return value; } |
3251 return virtualizeAttributeValue(atype, value); | 3522 return virtualizeAttributeValue(atype, value); |
3252 }); | 3523 }); |
3253 TameElement.prototype.getAttributeNode = nodeMethod(function (name) { | 3524 TameElement.prototype.getAttributeNode = nodeMethod(function (name) { |
| 3525 if (!np(this).policy.attributesVisible) { return null; } |
3254 var feral = np(this).feral; | 3526 var feral = np(this).feral; |
3255 var hostDomNode = feral.getAttributeNode(name); | 3527 var hostDomNode = feral.getAttributeNode(name); |
3256 if (hostDomNode === null) { return null; } | 3528 if (hostDomNode === null) { return null; } |
3257 return new TameBackedAttributeNode( | 3529 return new TameBackedAttributeNode(hostDomNode, feral); |
3258 hostDomNode, np(this).editable, feral); | |
3259 }); | 3530 }); |
3260 TameElement.prototype.hasAttribute = nodeMethod(function (attribName) { | 3531 TameElement.prototype.hasAttribute = nodeMethod(function (attribName) { |
3261 var feral = np(this).feral; | 3532 var feral = np(this).feral; |
3262 attribName = String(attribName).toLowerCase(); | 3533 attribName = String(attribName).toLowerCase(); |
3263 var tagName = feral.tagName.toLowerCase(); | 3534 var tagName = feral.tagName.toLowerCase(); |
3264 var atype = htmlSchema.attribute(tagName, attribName).type; | 3535 var atype = htmlSchema.attribute(tagName, attribName).type; |
3265 if (atype === void 0) { | 3536 if (atype === void 0) { |
3266 return bridal.hasAttribute(feral, cajaPrefix + attribName); | 3537 return bridal.hasAttribute(feral, cajaPrefix + attribName); |
3267 } else { | 3538 } else { |
3268 return bridal.hasAttribute(feral, attribName); | 3539 return bridal.hasAttribute(feral, attribName); |
3269 } | 3540 } |
3270 }); | 3541 }); |
3271 TameElement.prototype.setAttribute = nodeMethod( | 3542 TameElement.prototype.setAttribute = nodeMethod( |
3272 function (attribName, value) { | 3543 function (attribName, value) { |
3273 //console.debug("setAttribute", this, attribName, value); | 3544 //console.debug("setAttribute", this, attribName, value); |
3274 var feral = np(this).feral; | 3545 var feral = np(this).feral; |
3275 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 3546 np(this).policy.requireEditable(); |
3276 attribName = String(attribName).toLowerCase(); | 3547 attribName = String(attribName).toLowerCase(); |
3277 if (/__$/.test(attribName)) { | 3548 if (/__$/.test(attribName)) { |
3278 throw new TypeError('Attributes may not end with __'); | 3549 throw new TypeError('Attributes may not end with __'); |
3279 } | 3550 } |
| 3551 if (!np(this).policy.attributesVisible) { return null; } |
3280 var tagName = feral.tagName.toLowerCase(); | 3552 var tagName = feral.tagName.toLowerCase(); |
3281 var atype = htmlSchema.attribute(tagName, attribName).type; | 3553 var atype = htmlSchema.attribute(tagName, attribName).type; |
3282 if (atype === void 0) { | 3554 if (atype === void 0) { |
3283 bridal.setAttribute(feral, cajaPrefix + attribName, value); | 3555 bridal.setAttribute(feral, cajaPrefix + attribName, value); |
3284 } else { | 3556 } else { |
3285 var sanitizedValue = rewriteAttribute( | 3557 var sanitizedValue = rewriteAttribute( |
3286 tagName, attribName, atype, value); | 3558 tagName, attribName, atype, value); |
3287 if (sanitizedValue !== null) { | 3559 if (sanitizedValue !== null) { |
3288 bridal.setAttribute(feral, attribName, sanitizedValue); | 3560 bridal.setAttribute(feral, attribName, sanitizedValue); |
3289 if (html4.ATTRIBS.hasOwnProperty(tagName + '::target') && | 3561 if (html4.ATTRIBS.hasOwnProperty(tagName + '::target') && |
3290 atype === html4.atype.URI) { | 3562 atype === html4.atype.URI) { |
3291 if (sanitizedValue.charAt(0) === '#') { | 3563 if (sanitizedValue.charAt(0) === '#') { |
3292 feral.removeAttribute('target'); | 3564 feral.removeAttribute('target'); |
3293 } else { | 3565 } else { |
3294 bridal.setAttribute(feral, 'target', | 3566 bridal.setAttribute(feral, 'target', |
3295 getSafeTargetAttribute(tagName, 'target', | 3567 getSafeTargetAttribute(tagName, 'target', |
3296 bridal.getAttribute(feral, 'target'))); | 3568 bridal.getAttribute(feral, 'target'))); |
3297 } | 3569 } |
3298 } | 3570 } |
3299 } | 3571 } |
3300 } | 3572 } |
3301 return value; | 3573 return value; |
3302 }); | 3574 }); |
3303 TameElement.prototype.removeAttribute = nodeMethod(function (attribName) { | 3575 TameElement.prototype.removeAttribute = nodeMethod(function (attribName) { |
3304 var feral = np(this).feral; | 3576 var feral = np(this).feral; |
3305 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 3577 np(this).policy.requireEditable(); |
3306 attribName = String(attribName).toLowerCase(); | 3578 attribName = String(attribName).toLowerCase(); |
3307 if (/__$/.test(attribName)) { | 3579 if (/__$/.test(attribName)) { |
3308 throw new TypeError('Attributes may not end with __'); | 3580 throw new TypeError('Attributes may not end with __'); |
3309 } | 3581 } |
3310 var tagName = feral.tagName.toLowerCase(); | 3582 var tagName = feral.tagName.toLowerCase(); |
3311 var atype = htmlSchema.attribute(tagName, attribName).type; | 3583 var atype = htmlSchema.attribute(tagName, attribName).type; |
3312 if (atype === void 0) { | 3584 if (atype === void 0) { |
3313 feral.removeAttribute(cajaPrefix + attribName); | 3585 feral.removeAttribute(cajaPrefix + attribName); |
3314 } else { | 3586 } else { |
3315 feral.removeAttribute(attribName); | 3587 feral.removeAttribute(attribName); |
3316 } | 3588 } |
3317 }); | 3589 }); |
3318 TameElement.prototype.getElementsByTagName = nodeMethod( | 3590 TameElement.prototype.getElementsByTagName = nodeMethod( |
3319 function(tagName) { | 3591 function(tagName) { |
3320 return tameGetElementsByTagName( | 3592 return tameGetElementsByTagName(np(this).feral, tagName); |
3321 np(this).feral, tagName, np(this).childrenEditable); | |
3322 }); | 3593 }); |
3323 TameElement.prototype.getElementsByClassName = nodeMethod( | 3594 TameElement.prototype.getElementsByClassName = nodeMethod( |
3324 function(className) { | 3595 function(className) { |
3325 return tameGetElementsByClassName( | 3596 return tameGetElementsByClassName(np(this).feral, className); |
3326 np(this).feral, className, np(this).childrenEditable); | |
3327 }); | 3597 }); |
3328 TameElement.prototype.getBoundingClientRect = nodeMethod(function () { | 3598 TameElement.prototype.getBoundingClientRect = nodeMethod(function () { |
3329 var feral = np(this).feral; | 3599 var feral = np(this).feral; |
3330 var elRect = bridal.getBoundingClientRect(feral); | 3600 var elRect = bridal.getBoundingClientRect(feral); |
3331 var vdoc = bridal.getBoundingClientRect( | 3601 var vdoc = bridal.getBoundingClientRect( |
3332 np(this.ownerDocument).feralContainerNode); | 3602 np(this.ownerDocument).feralContainerNode); |
3333 var vdocLeft = vdoc.left, vdocTop = vdoc.top; | 3603 var vdocLeft = vdoc.left, vdocTop = vdoc.top; |
3334 return ({ | 3604 return ({ |
3335 top: elRect.top - vdocTop, | 3605 top: elRect.top - vdocTop, |
3336 left: elRect.left - vdocLeft, | 3606 left: elRect.left - vdocLeft, |
3337 right: elRect.right - vdocLeft, | 3607 right: elRect.right - vdocLeft, |
3338 bottom: elRect.bottom - vdocTop | 3608 bottom: elRect.bottom - vdocTop |
3339 }); | 3609 }); |
3340 }); | 3610 }); |
3341 TameElement.prototype.updateStyle = nodeMethod(function (style) { | 3611 TameElement.prototype.updateStyle = nodeMethod(function (style) { |
3342 var feral = np(this).feral; | 3612 var feral = np(this).feral; |
3343 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 3613 np(this).policy.requireEditable(); |
3344 var cssPropertiesAndValues = cssSealerUnsealerPair.unseal(style); | 3614 var cssPropertiesAndValues = cssSealerUnsealerPair.unseal(style); |
3345 if (!cssPropertiesAndValues) { throw new Error(); } | 3615 if (!cssPropertiesAndValues) { throw new Error(); } |
3346 | 3616 |
3347 var styleNode = feral.style; | 3617 var styleNode = feral.style; |
3348 for (var i = 0; i < cssPropertiesAndValues.length; i += 2) { | 3618 for (var i = 0; i < cssPropertiesAndValues.length; i += 2) { |
3349 var propName = cssPropertiesAndValues[+i]; | 3619 var propName = cssPropertiesAndValues[+i]; |
3350 var propValue = cssPropertiesAndValues[i + 1]; | 3620 var propValue = cssPropertiesAndValues[i + 1]; |
3351 // If the propertyName differs between DOM and CSS, there will | 3621 // If the propertyName differs between DOM and CSS, there will |
3352 // be a semicolon between the two. | 3622 // be a semicolon between the two. |
3353 // E.g., 'background-color;backgroundColor' | 3623 // E.g., 'background-color;backgroundColor' |
(...skipping 23 matching lines...) Expand all Loading... |
3377 out[out.length] = rawNode.data; | 3647 out[out.length] = rawNode.data; |
3378 break; | 3648 break; |
3379 case 11: // Document Fragment | 3649 case 11: // Document Fragment |
3380 for (var c = rawNode.firstChild; c; c = c.nextSibling) { | 3650 for (var c = rawNode.firstChild; c; c = c.nextSibling) { |
3381 c = makeDOMAccessible(c); | 3651 c = makeDOMAccessible(c); |
3382 innerTextOf(c, out); | 3652 innerTextOf(c, out); |
3383 } | 3653 } |
3384 break; | 3654 break; |
3385 } | 3655 } |
3386 } | 3656 } |
3387 definePropertiesAwesomely(TameElement.prototype, | 3657 (function() { |
3388 commonElementPropertyHandlers); | 3658 var geometryDelegateProperty = { |
3389 var innerTextProp = { | 3659 extendedAccessors: true, |
| 3660 enumerable: true, |
| 3661 get: nodeMethod(function (prop) { |
| 3662 return np(this).geometryDelegate[prop]; |
| 3663 }) |
| 3664 }; |
| 3665 var geometryDelegatePropertySettable = |
| 3666 Object.create(geometryDelegateProperty); |
| 3667 geometryDelegatePropertySettable.set = |
| 3668 nodeMethod(function (value, prop) { |
| 3669 np(this).policy.requireEditable(); |
| 3670 np(this).geometryDelegate[prop] = +value; |
| 3671 }); |
| 3672 definePropertiesAwesomely(TameElement.prototype, { |
| 3673 clientLeft: geometryDelegateProperty, |
| 3674 clientTop: geometryDelegateProperty, |
| 3675 clientWidth: geometryDelegateProperty, |
| 3676 clientHeight: geometryDelegateProperty, |
| 3677 offsetLeft: geometryDelegateProperty, |
| 3678 offsetTop: geometryDelegateProperty, |
| 3679 offsetWidth: geometryDelegateProperty, |
| 3680 offsetHeight: geometryDelegateProperty, |
| 3681 scrollLeft: geometryDelegatePropertySettable, |
| 3682 scrollTop: geometryDelegatePropertySettable, |
| 3683 scrollWidth: geometryDelegateProperty, |
| 3684 scrollHeight: geometryDelegateProperty |
| 3685 }); |
| 3686 })(); |
| 3687 var textContentProp = { |
3390 enumerable: true, | 3688 enumerable: true, |
3391 get: nodeMethod(function () { | 3689 get: nodeMethod(function () { |
3392 var text = []; | 3690 var text = []; |
3393 innerTextOf(np(this).feral, text); | 3691 innerTextOf(np(this).feral, text); |
3394 return text.join(''); | 3692 return text.join(''); |
3395 }), | 3693 }), |
3396 set: nodeMethod(function (newText) { | 3694 set: nodeMethod(function (newText) { |
3397 // This operation changes the child node list (but not other | 3695 // This operation changes the child node list (but not other |
3398 // properties | 3696 // properties of the element) so it checks childrenEditable. Note that |
3399 // 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 |
3400 // is critical to security, as else a client can set the innerHTML of | 3698 // textContent of a <script> element to execute scripts. |
3401 // a <script> element to execute scripts. | 3699 np(this).policy.requireChildrenEditable(); |
3402 if (!np(this).childrenEditable) { throw new Error(NOT_EDITABLE); } | |
3403 var newTextStr = newText != null ? String(newText) : ''; | 3700 var newTextStr = newText != null ? String(newText) : ''; |
3404 var el = np(this).feral; | 3701 var el = np(this).feral; |
3405 for (var c; (c = el.firstChild);) { el.removeChild(c); } | 3702 for (var c; (c = el.firstChild);) { el.removeChild(c); } |
3406 if (newTextStr) { | 3703 if (newTextStr) { |
3407 el.appendChild(el.ownerDocument.createTextNode(newTextStr)); | 3704 el.appendChild(el.ownerDocument.createTextNode(newTextStr)); |
3408 } | 3705 } |
3409 }) | 3706 }) |
3410 }; | 3707 }; |
3411 var tagNameAttr = { | 3708 var tagNameAttr = { |
3412 enumerable: true, | 3709 enumerable: true, |
3413 get: nodeMethod(function () { | 3710 get: nodeMethod(function () { |
3414 return realToVirtualElementName(String(np(this).feral.tagName)); | 3711 return realToVirtualElementName(String(np(this).feral.tagName)); |
3415 }) | 3712 }) |
3416 }; | 3713 }; |
3417 definePropertiesAwesomely(TameElement.prototype, { | 3714 definePropertiesAwesomely(TameElement.prototype, { |
3418 id: NP.filterAttr(defaultToEmptyStr, identity), | 3715 id: NP.filterAttr(defaultToEmptyStr, identity), |
3419 className: { | 3716 className: { |
3420 enumerable: true, | 3717 enumerable: true, |
3421 get: nodeMethod(function () { | 3718 get: nodeMethod(function () { |
3422 return this.getAttribute('class') || ''; | 3719 return this.getAttribute('class') || ''; |
3423 }), | 3720 }), |
3424 set: nodeMethod(function (classes) { | 3721 set: nodeMethod(function (classes) { |
3425 return this.setAttribute('class', String(classes)); | 3722 return this.setAttribute('class', String(classes)); |
3426 }) | 3723 }) |
3427 }, | 3724 }, |
3428 title: NP.filterAttr(defaultToEmptyStr, String), | 3725 title: NP.filterAttr(defaultToEmptyStr, String), |
3429 dir: NP.filterAttr(defaultToEmptyStr, String), | 3726 dir: NP.filterAttr(defaultToEmptyStr, String), |
3430 innerText: innerTextProp, | 3727 textContent: textContentProp, |
3431 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. |
3432 nodeName: tagNameAttr, | 3732 nodeName: tagNameAttr, |
3433 tagName: tagNameAttr, | 3733 tagName: tagNameAttr, |
3434 style: NP.filter( | 3734 style: NP.filter( |
3435 false, | 3735 false, |
3436 nodeMethod(function (styleNode) { | 3736 nodeMethod(function (styleNode) { |
3437 TameStyle || buildTameStyle(); | 3737 TameStyle || buildTameStyle(); |
3438 return new TameStyle(styleNode, np(this).editable, this); | 3738 return new TameStyle(styleNode, np(this).policy.editable, this); |
3439 }), | 3739 }), |
3440 true, identity), | 3740 true, identity), |
3441 innerHTML: { | 3741 innerHTML: { |
3442 enumerable: true, | 3742 enumerable: true, |
3443 get: nodeMethod(function () { | 3743 get: nodeMethod(function () { |
3444 var node = np(this).feral; | 3744 return htmlFragmentSerialization(this); |
3445 var tagName = node.tagName.toLowerCase(); | |
3446 var schemaElem = htmlSchema.element(tagName); | |
3447 if (!schemaElem.allowed) { | |
3448 return ''; // unknown node | |
3449 } | |
3450 var innerHtml = node.innerHTML; | |
3451 if (schemaElem.contentIsCDATA) { | |
3452 innerHtml = html.escapeAttrib(innerHtml); | |
3453 } else if (schemaElem.contentIsRCDATA) { | |
3454 // Make sure we return PCDATA. | |
3455 // For RCDATA we only need to escape & if they're not part of an | |
3456 // entity. | |
3457 innerHtml = html.normalizeRCData(innerHtml); | |
3458 } else { | |
3459 // If we blessed the resulting HTML, then this would round trip | |
3460 // better but it would still not survive appending, and it would | |
3461 // propagate event handlers where the setter of innerHTML does not | |
3462 // expect it to. | |
3463 innerHtml = tameInnerHtml(innerHtml); | |
3464 } | |
3465 return innerHtml; | |
3466 }), | 3745 }), |
3467 set: nodeMethod(function (htmlFragment) { | 3746 set: nodeMethod(function (htmlFragment) { |
3468 // This operation changes the child node list (but not other | 3747 // This operation changes the child node list (but not other |
3469 // properties of the element) so it checks childrenEditable. Note | 3748 // properties of the element) so it checks childrenEditable. Note |
3470 // that | 3749 // that this check is critical to security, as else a client can set |
3471 // this check is critical to security, as else a client can set the | 3750 // the innerHTML of a <script> element to execute scripts. |
3472 // innerHTML of a <script> element to execute scripts. | 3751 np(this).policy.requireChildrenEditable(); |
3473 if (!np(this).childrenEditable) { throw new Error(NOT_EDITABLE); } | |
3474 var node = np(this).feral; | 3752 var node = np(this).feral; |
3475 var schemaElem = htmlSchema.element(node.tagName); | 3753 var schemaElem = htmlSchema.element(node.tagName); |
3476 if (!schemaElem.allowed) { throw new Error(); } | 3754 if (!schemaElem.allowed) { throw new Error(); } |
3477 var isRCDATA = schemaElem.contentIsRCDATA; | 3755 var isRCDATA = schemaElem.contentIsRCDATA; |
3478 var htmlFragmentString; | 3756 var htmlFragmentString; |
3479 if (!isRCDATA && htmlFragment instanceof Html) { | 3757 if (!isRCDATA && htmlFragment instanceof Html) { |
3480 htmlFragmentString = '' + safeHtml(htmlFragment); | 3758 htmlFragmentString = '' + safeHtml(htmlFragment); |
3481 } else if (htmlFragment === null) { | 3759 } else if (htmlFragment === null) { |
3482 htmlFragmentString = ''; | 3760 htmlFragmentString = ''; |
3483 } else { | 3761 } else { |
(...skipping 16 matching lines...) Expand all Loading... |
3500 if (!feralOffsetParent) { | 3778 if (!feralOffsetParent) { |
3501 return feralOffsetParent; | 3779 return feralOffsetParent; |
3502 } else if (feralOffsetParent === containerNode) { | 3780 } else if (feralOffsetParent === containerNode) { |
3503 // 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 |
3504 // emulating how browsers treat offsetParent and the real <BODY>. | 3782 // emulating how browsers treat offsetParent and the real <BODY>. |
3505 var feralBody = np(tameDocument.body).feral; | 3783 var feralBody = np(tameDocument.body).feral; |
3506 for (var ancestor = makeDOMAccessible(np(this).feral.parentNode); | 3784 for (var ancestor = makeDOMAccessible(np(this).feral.parentNode); |
3507 ancestor !== containerNode; | 3785 ancestor !== containerNode; |
3508 ancestor = makeDOMAccessible(ancestor.parentNode)) { | 3786 ancestor = makeDOMAccessible(ancestor.parentNode)) { |
3509 if (ancestor === feralBody) { | 3787 if (ancestor === feralBody) { |
3510 return defaultTameNode(feralBody, np(this).editable); | 3788 return defaultTameNode(feralBody); |
3511 } | 3789 } |
3512 } | 3790 } |
3513 return null; | 3791 return null; |
3514 } else { | 3792 } else { |
3515 return tameRelatedNode(feralOffsetParent, np(this).editable, | 3793 return tameRelatedNode(feralOffsetParent, defaultTameNode); |
3516 defaultTameNode); | |
3517 } | 3794 } |
3518 }) | 3795 }) |
3519 }, | 3796 }, |
3520 accessKey: NP.rw, | 3797 accessKey: NP.rw, |
3521 tabIndex: NP.rw | 3798 tabIndex: NP.rw |
3522 }); | 3799 }); |
3523 cajaVM.def(TameElement); // and its prototype | 3800 cajaVM.def(TameElement); // and its prototype |
3524 | 3801 |
3525 traceStartup("DT: starting defineElement"); | 3802 traceStartup("DT: starting defineElement"); |
3526 | 3803 |
3527 /** | 3804 /** |
3528 * Define a taming class for a subclass of HTMLElement. | 3805 * Define a taming class for a subclass of HTMLElement. |
3529 * | 3806 * |
3530 * @param {Array} record.superclass The tame superclass constructor | 3807 * @param {Array} record.superclass The tame superclass constructor |
3531 * (defaults to TameElement) with parameters (this, node, editable, | 3808 * (defaults to TameElement) with parameters (this, node, policy, |
3532 * childrenEditable, opt_proxyType). | 3809 * opt_proxyType). |
3533 * @param {Array} record.names The element names which should be tamed | 3810 * @param {Array} record.names The element names which should be tamed |
3534 * using this class. | 3811 * using this class. |
3535 * @param {String} record.domClass The DOM-specified class name. | 3812 * @param {String} record.domClass The DOM-specified class name. |
3536 * @param {Object} record.properties The custom properties this class | 3813 * @param {Object} record.properties The custom properties this class |
3537 * should have (in the format accepted by definePropertiesAwesomely). | 3814 * should have (in the format accepted by definePropertiesAwesomely). |
3538 * @param {function} record.construct Code to invoke at the end of | 3815 * @param {function} record.construct Code to invoke at the end of |
3539 * construction; takes and returns self. | 3816 * construction; takes and returns self. |
3540 * @param {boolean} record.forceChildrenNotEditable Whether to force the | 3817 * @param {boolean} record.forceChildrenNotEditable Whether to force the |
3541 * childrenEditable flag to be false regardless of the value of | 3818 * child node list and child nodes to not be mutable. |
3542 * editable. | |
3543 * @return {function} The constructor. | 3819 * @return {function} The constructor. |
3544 */ | 3820 */ |
3545 function defineElement(record) { | 3821 function defineElement(record) { |
3546 var superclass = record.superclass || TameElement; | 3822 var superclass = record.superclass || TameElement; |
3547 var proxyType = record.proxyType; | 3823 var proxyType = record.proxyType; |
3548 var construct = record.construct || identity; | 3824 var construct = record.construct || identity; |
3549 var shouldBeVirtualized = record.virtualized || false; | 3825 var shouldBeVirtualized = "virtualized" in record |
3550 var forceChildrenNotEditable = record.forceChildrenNotEditable; | 3826 ? record.virtualized : false; |
3551 function TameSpecificElement(node, editable, childrenEditable) { | 3827 var opt_policy = record.forceChildrenNotEditable |
| 3828 ? nodePolicyReadOnlyChildren : null; |
| 3829 function TameSpecificElement(node) { |
3552 var isVirtualized = htmlSchema.isVirtualizedElementName(node.tagName); | 3830 var isVirtualized = htmlSchema.isVirtualizedElementName(node.tagName); |
3553 if (!isVirtualized !== !shouldBeVirtualized) { | 3831 if (shouldBeVirtualized !== null && |
| 3832 !isVirtualized !== !shouldBeVirtualized) { |
3554 throw new Error("Domado internal inconsistency: " + node.tagName + | 3833 throw new Error("Domado internal inconsistency: " + node.tagName + |
3555 " has inconsistent virtualization state with class " + | 3834 " has inconsistent virtualization state with class " + |
3556 record.domClass); | 3835 record.domClass); |
3557 } | 3836 } |
3558 superclass.call(this, | 3837 superclass.call(this, node, opt_policy, proxyType); |
3559 node, | |
3560 editable, | |
3561 childrenEditable && !forceChildrenNotEditable, | |
3562 proxyType); | |
3563 construct.call(this); | 3838 construct.call(this); |
3564 } | 3839 } |
3565 inertCtor(TameSpecificElement, superclass, record.domClass); | 3840 inertCtor(TameSpecificElement, superclass, record.domClass); |
3566 definePropertiesAwesomely(TameSpecificElement.prototype, | 3841 definePropertiesAwesomely(TameSpecificElement.prototype, |
3567 record.properties || {}); | 3842 record.properties || {}); |
3568 // Note: cajaVM.def will be applied to all registered node classes | 3843 // Note: cajaVM.def will be applied to all registered node classes |
3569 // later, so users of defineElement don't need to. | 3844 // later, so users of defineElement don't need to. |
3570 return TameSpecificElement; | 3845 return TameSpecificElement; |
3571 } | 3846 } |
3572 cajaVM.def(defineElement); | 3847 cajaVM.def(defineElement); |
3573 ······ | 3848 ······ |
3574 /** | 3849 /** |
3575 * For elements which have no properties at all, but we want to define in | 3850 * For elements which have no properties at all, but we want to define in |
3576 * in order to be explicitly complete (suppress the no-implementation | 3851 * in order to be explicitly complete (suppress the no-implementation |
3577 * warning). | 3852 * warning). |
3578 */ | 3853 */ |
3579 function defineTrivialElement(domClass) { | 3854 function defineTrivialElement(domClass) { |
3580 return defineElement({domClass: domClass}); | 3855 return defineElement({domClass: domClass}); |
3581 } | 3856 } |
3582 | 3857 |
3583 defineElement({ | 3858 defineElement({ |
3584 domClass: 'HTMLAnchorElement', | 3859 domClass: 'HTMLAnchorElement', |
3585 properties: { | 3860 properties: { |
3586 hash: NP.filter(false, identity, true, identity), | 3861 hash: NP.filter( |
| 3862 false, |
| 3863 function (value) { return unsuffix(value, idSuffix, value); }, |
| 3864 false, |
| 3865 // TODO(felix8a): add suffix if href is self |
| 3866 identity), |
| 3867 // TODO(felix8a): fragment rewriting? |
3587 href: NP.filter(false, identity, true, identity) | 3868 href: NP.filter(false, identity, true, identity) |
3588 } | 3869 } |
3589 }); | 3870 }); |
3590 | 3871 |
3591 defineTrivialElement('HTMLBRElement'); | 3872 defineTrivialElement('HTMLBRElement'); |
3592 | 3873 |
3593 var TameBodyElement = defineElement({ | 3874 var TameBodyElement = defineElement({ |
3594 virtualized: true, | 3875 virtualized: true, |
3595 domClass: 'HTMLBodyElement' | 3876 domClass: 'HTMLBodyElement' |
3596 }); | 3877 }); |
3597 // TODO(kpreid): need the viewProperties | |
3598 setOwn(TameBodyElement.prototype, 'setAttribute', nodeMethod( | 3878 setOwn(TameBodyElement.prototype, 'setAttribute', nodeMethod( |
3599 function (attrib, value) { | 3879 function (attrib, value) { |
3600 TameElement.prototype.setAttribute.call(this, attrib, value); | 3880 TameElement.prototype.setAttribute.call(this, attrib, value); |
3601 var attribName = String(attrib).toLowerCase(); | 3881 var attribName = String(attrib).toLowerCase(); |
3602 // Window event handlers are exposed as content attributes on <body> | 3882 // Window event handlers are exposed as content attributes on <body> |
3603 // and <frameset> | 3883 // and <frameset> |
3604 // <http://www.whatwg.org/specs/web-apps/current-work/multipage/webappap
is.html#handler-window-onload> | 3884 // <http://www.whatwg.org/specs/web-apps/current-work/multipage/webappap
is.html#handler-window-onload> |
3605 // as of 2012-09-14 | 3885 // as of 2012-09-14 |
3606 // Note: We only currently implement onload. | 3886 // Note: We only currently implement onload. |
3607 if (attribName === 'onload') { | 3887 if (attribName === 'onload') { |
(...skipping 215 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3823 return Object.freeze(tameGradient); | 4103 return Object.freeze(tameGradient); |
3824 } | 4104 } |
3825 function enforceFinite(value, name) { | 4105 function enforceFinite(value, name) { |
3826 enforceType(value, 'number', name); | 4106 enforceType(value, 'number', name); |
3827 if (!isFinite(value)) { | 4107 if (!isFinite(value)) { |
3828 throw new Error("NOT_SUPPORTED_ERR"); | 4108 throw new Error("NOT_SUPPORTED_ERR"); |
3829 // TODO(kpreid): should be a DOMException per spec | 4109 // TODO(kpreid): should be a DOMException per spec |
3830 } | 4110 } |
3831 } | 4111 } |
3832 | 4112 |
3833 function TameCanvasElement(node, editable) { | 4113 function TameCanvasElement(node) { |
3834 // TODO(kpreid): review whether this can use defineElement | 4114 // TODO(kpreid): review whether this can use defineElement |
3835 TameElement.call(this, node, editable, editable); | 4115 TameElement.call(this, node); |
3836 | 4116 |
3837 // helpers for tame context | 4117 // helpers for tame context |
3838 var context = makeDOMAccessible(node.getContext('2d')); | 4118 var context = makeDOMAccessible(node.getContext('2d')); |
3839 function tameFloatsOp(count, verb) { | 4119 function tameFloatsOp(count, verb) { |
3840 var m = makeFunctionAccessible(context[verb]); | 4120 var m = makeFunctionAccessible(context[verb]); |
3841 return cajaVM.def(function () { | 4121 return cajaVM.def(function () { |
3842 if (arguments.length !== count) { | 4122 if (arguments.length !== count) { |
3843 throw new Error(verb + ' takes ' + count + ' args, not ' + | 4123 throw new Error(verb + ' takes ' + count + ' args, not ' + |
3844 arguments.length); | 4124 arguments.length); |
3845 } | 4125 } |
(...skipping 348 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4194 StringTest([ | 4474 StringTest([ |
4195 "top", | 4475 "top", |
4196 "hanging", | 4476 "hanging", |
4197 "middle", | 4477 "middle", |
4198 "alphabetic", | 4478 "alphabetic", |
4199 "ideographic", | 4479 "ideographic", |
4200 "bottom" | 4480 "bottom" |
4201 ])) | 4481 ])) |
4202 }); | 4482 }); |
4203 TameContext2DConf.confide(tameContext2d); | 4483 TameContext2DConf.confide(tameContext2d); |
4204 TameContext2DConf.p(tameContext2d).editable = np(this).editable; | 4484 TameContext2DConf.p(tameContext2d).policy = np(this).policy; |
4205 TameContext2DConf.p(tameContext2d).feral = context; | 4485 TameContext2DConf.p(tameContext2d).feral = context; |
4206 cajaVM.def(tameContext2d); | 4486 cajaVM.def(tameContext2d); |
4207 taming.permitUntaming(tameContext2d); | 4487 taming.permitUntaming(tameContext2d); |
4208 } // end of TameCanvasElement | 4488 } // end of TameCanvasElement |
4209 inertCtor(TameCanvasElement, TameElement, 'HTMLCanvasElement'); | 4489 inertCtor(TameCanvasElement, TameElement, 'HTMLCanvasElement'); |
4210 TameCanvasElement.prototype.getContext = function (contextId) { | 4490 TameCanvasElement.prototype.getContext = function (contextId) { |
4211 | 4491 |
4212 // TODO(kpreid): We can refine this by inventing a | 4492 // TODO(kpreid): We can refine this by inventing a ReadOnlyCanvas |
4213 // ReadOnlyCanvas object | 4493 // object to return in this situation, which allows getImageData and |
4214 // to return in this situation, which allows getImageData and | 4494 // so on but not any drawing. Not bothering to do that for now; if |
4215 // so on but | 4495 // you have a use for it let us know. |
4216 // not any drawing. Not bothering to do that for now; if | 4496 np(this).policy.requireEditable(); |
4217 // you have a use | |
4218 // for it let us know. | |
4219 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | |
4220 | 4497 |
4221 enforceType(contextId, 'string', 'contextId'); | 4498 enforceType(contextId, 'string', 'contextId'); |
4222 switch (contextId) { | 4499 switch (contextId) { |
4223 case '2d': | 4500 case '2d': |
4224 return np(this).tameContext2d; | 4501 return np(this).tameContext2d; |
4225 default: | 4502 default: |
4226 // 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 |
4227 // says: The getContext(contextId, args...) method of the canvas | 4504 // says: The getContext(contextId, args...) method of the canvas |
4228 // element, when invoked, must run the following steps: | 4505 // element, when invoked, must run the following steps: |
4229 // [...] | 4506 // [...] |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4291 | 4568 |
4292 var TameFormElement = defineElement({ | 4569 var TameFormElement = defineElement({ |
4293 domClass: 'HTMLFormElement', | 4570 domClass: 'HTMLFormElement', |
4294 proxyType: FormElementAndExpandoProxyHandler, | 4571 proxyType: FormElementAndExpandoProxyHandler, |
4295 properties: { | 4572 properties: { |
4296 action: NP.filterAttr(defaultToEmptyStr, String), | 4573 action: NP.filterAttr(defaultToEmptyStr, String), |
4297 elements: { | 4574 elements: { |
4298 enumerable: true, | 4575 enumerable: true, |
4299 get: nodeMethod(function () { | 4576 get: nodeMethod(function () { |
4300 return tameHTMLCollection( | 4577 return tameHTMLCollection( |
4301 np(this).feral.elements, np(this).editable, defaultTameNode); | 4578 np(this).feral.elements, defaultTameNode); |
4302 }) | 4579 }) |
4303 }, | 4580 }, |
4304 enctype: NP.filterAttr(defaultToEmptyStr, String), | 4581 enctype: NP.filterAttr(defaultToEmptyStr, String), |
4305 method: NP.filterAttr(defaultToEmptyStr, String), | 4582 method: NP.filterAttr(defaultToEmptyStr, String), |
4306 target: NP.filterAttr(defaultToEmptyStr, String) | 4583 target: NP.filterAttr(defaultToEmptyStr, String) |
4307 }, | 4584 }, |
4308 construct: function () { | 4585 construct: function () { |
4309 // Freeze length at creation time since we aren't live. | 4586 // Freeze length at creation time since we aren't live. |
4310 // TODO(kpreid): Revise this when we have live node lists. | 4587 // TODO(kpreid): Revise this when we have live node lists. |
4311 Object.defineProperty(this, "length", { | 4588 Object.defineProperty(this, "length", { |
4312 value: np(this).feral.length | 4589 value: np(this).feral.length |
4313 }); | 4590 }); |
4314 } | 4591 } |
4315 }); | 4592 }); |
| 4593 // TODO(felix8a): need to test handlingUserAction. |
4316 TameFormElement.prototype.submit = nodeMethod(function () { | 4594 TameFormElement.prototype.submit = nodeMethod(function () { |
4317 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4595 np(this).policy.requireEditable(); |
4318 return np(this).feral.submit(); | 4596 return domicile.handlingUserAction && np(this).feral.submit(); |
4319 }); | 4597 }); |
4320 TameFormElement.prototype.reset = nodeMethod(function () { | 4598 TameFormElement.prototype.reset = nodeMethod(function () { |
4321 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4599 np(this).policy.requireEditable(); |
4322 return np(this).feral.reset(); | 4600 return np(this).feral.reset(); |
4323 }); | 4601 }); |
4324 | 4602 |
4325 defineTrivialElement('HTMLHeadingElement'); | 4603 defineTrivialElement('HTMLHeadingElement'); |
4326 defineTrivialElement('HTMLHRElement'); | 4604 defineTrivialElement('HTMLHRElement'); |
4327 | 4605 |
4328 defineElement({ | 4606 defineElement({ |
4329 virtualized: true, | 4607 virtualized: true, |
4330 domClass: 'HTMLHeadElement' | 4608 domClass: 'HTMLHeadElement' |
4331 }); | 4609 }); |
4332 | 4610 |
4333 defineElement({ | 4611 defineElement({ |
4334 virtualized: true, | 4612 virtualized: true, |
4335 domClass: 'HTMLHtmlElement' | 4613 domClass: 'HTMLHtmlElement' |
4336 }); | 4614 }); |
4337 // TODO(kpreid): need the viewProperties | |
4338 | 4615 |
4339 var P_blacklist = { | 4616 var P_blacklist = { |
4340 enumerable: true, | 4617 enumerable: true, |
4341 extendedAccessors: true, | 4618 extendedAccessors: true, |
4342 get: nodeMethod(function () { return undefined; }), | 4619 get: nodeMethod(function () { return undefined; }), |
4343 set: nodeMethod(function (value, prop) { | 4620 set: nodeMethod(function (value, prop) { |
4344 if (typeof console !== 'undefined') | 4621 if (typeof console !== 'undefined') |
4345 console.error('Cannot set the [', prop, '] property of an iframe.'); | 4622 console.error('Cannot set the [', prop, '] property of an iframe.'); |
4346 }) | 4623 }) |
4347 }; | 4624 }; |
4348 var TameIFrameElement = defineElement({ | 4625 var TameIFrameElement = defineElement({ |
4349 domClass: 'HTMLIFrameElement', | 4626 domClass: 'HTMLIFrameElement', |
4350 construct: function () { | 4627 construct: function () { |
4351 np(this).childrenEditable = false; | 4628 np(this).childrenEditable = false; |
4352 }, | 4629 }, |
4353 properties: { | 4630 properties: { |
4354 align: { | 4631 align: { |
4355 enumerable: true, | 4632 enumerable: true, |
4356 get: nodeMethod(function () { | 4633 get: nodeMethod(function () { |
4357 return np(this).feral.align; | 4634 return np(this).feral.align; |
4358 }), | 4635 }), |
4359 set: nodeMethod(function (alignment) { | 4636 set: nodeMethod(function (alignment) { |
4360 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4637 np(this).policy.requireEditable(); |
4361 alignment = String(alignment); | 4638 alignment = String(alignment); |
4362 if (alignment === 'left' || | 4639 if (alignment === 'left' || |
4363 alignment === 'right' || | 4640 alignment === 'right' || |
4364 alignment === 'center') { | 4641 alignment === 'center') { |
4365 np(this).feral.align = alignment; | 4642 np(this).feral.align = alignment; |
4366 } | 4643 } |
4367 }) | 4644 }) |
4368 }, | 4645 }, |
4369 frameBorder: { | 4646 frameBorder: { |
4370 enumerable: true, | 4647 enumerable: true, |
4371 get: nodeMethod(function () { | 4648 get: nodeMethod(function () { |
4372 return np(this).feral.frameBorder; | 4649 return np(this).feral.frameBorder; |
4373 }), | 4650 }), |
4374 set: nodeMethod(function (border) { | 4651 set: nodeMethod(function (border) { |
4375 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4652 np(this).policy.requireEditable(); |
4376 border = String(border).toLowerCase(); | 4653 border = String(border).toLowerCase(); |
4377 if (border === '0' || border === '1' || | 4654 if (border === '0' || border === '1' || |
4378 border === 'no' || border === 'yes') { | 4655 border === 'no' || border === 'yes') { |
4379 np(this).feral.frameBorder = border; | 4656 np(this).feral.frameBorder = border; |
4380 } | 4657 } |
4381 }) | 4658 }) |
4382 }, | 4659 }, |
4383 height: NP.filterProp(identity, Number), | 4660 height: NP.filterProp(identity, Number), |
4384 width: NP.filterProp(identity, Number), | 4661 width: NP.filterProp(identity, Number), |
4385 src: P_blacklist, | 4662 src: P_blacklist, |
(...skipping 10 matching lines...) Expand all Loading... |
4396 } | 4673 } |
4397 return null; | 4674 return null; |
4398 })); | 4675 })); |
4399 setOwn(TameIFrameElement.prototype, 'setAttribute', | 4676 setOwn(TameIFrameElement.prototype, 'setAttribute', |
4400 nodeMethod(function (attr, value) { | 4677 nodeMethod(function (attr, value) { |
4401 var attrLc = String(attr).toLowerCase(); | 4678 var attrLc = String(attr).toLowerCase(); |
4402 // The 'name' and 'src' attributes are whitelisted for all tags in | 4679 // The 'name' and 'src' attributes are whitelisted for all tags in |
4403 // html4-attributes-whitelist.json, since they're needed on tags | 4680 // html4-attributes-whitelist.json, since they're needed on tags |
4404 // like <img>. Because there's currently no way to filter attributes | 4681 // like <img>. Because there's currently no way to filter attributes |
4405 // 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? |
4406 if (attrLc !== 'name' && attrLc !== 'src') { | 4684 if (attrLc !== 'name' && attrLc !== 'src') { |
4407 return TameElement.prototype.setAttribute.call(this, attr, value); | 4685 return TameElement.prototype.setAttribute.call(this, attr, value); |
4408 } | 4686 } |
4409 if (typeof console !== 'undefined') | 4687 if (typeof console !== 'undefined') |
4410 console.error('Cannot set the [' + attrLc + | 4688 console.error('Cannot set the [' + attrLc + |
4411 '] attribute of an iframe.'); | 4689 '] attribute of an iframe.'); |
4412 return value; | 4690 return value; |
4413 })); | 4691 })); |
4414 | 4692 |
4415 var TameImageElement = defineElement({ | 4693 var TameImageElement = defineElement({ |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4476 | 4754 |
4477 defineElement({ | 4755 defineElement({ |
4478 superclass: TameFormField, | 4756 superclass: TameFormField, |
4479 domClass: 'HTMLSelectElement', | 4757 domClass: 'HTMLSelectElement', |
4480 properties: { | 4758 properties: { |
4481 multiple: NP.rw, | 4759 multiple: NP.rw, |
4482 options: { | 4760 options: { |
4483 enumerable: true, | 4761 enumerable: true, |
4484 get: nodeMethod(function () { | 4762 get: nodeMethod(function () { |
4485 return new TameOptionsList( | 4763 return new TameOptionsList( |
4486 np(this).feral.options, | 4764 np(this).feral.options, defaultTameNode, 'name'); |
4487 np(this).editable, | |
4488 defaultTameNode, 'name'); | |
4489 }) | 4765 }) |
4490 }, | 4766 }, |
4491 selectedIndex: NP.filterProp(identity, toInt), | 4767 selectedIndex: NP.filterProp(identity, toInt), |
4492 type: NP.ro | 4768 type: NP.ro |
4493 } | 4769 } |
4494 }); | 4770 }); |
4495 | 4771 |
4496 defineElement({ | 4772 defineElement({ |
4497 superclass: TameFormField, | 4773 superclass: TameFormField, |
4498 domClass: 'HTMLTextAreaElement', | 4774 domClass: 'HTMLTextAreaElement', |
(...skipping 22 matching lines...) Expand all Loading... |
4521 // TODO(kpreid): Justify these specialized filters. | 4797 // TODO(kpreid): Justify these specialized filters. |
4522 value: NP.filterProp( | 4798 value: NP.filterProp( |
4523 function (x) { return x == null ? null : String(x); }, | 4799 function (x) { return x == null ? null : String(x); }, |
4524 function (x) { return x == null ? '' : '' + x; }) | 4800 function (x) { return x == null ? '' : '' + x; }) |
4525 } | 4801 } |
4526 }); | 4802 }); |
4527 ······ | 4803 ······ |
4528 defineTrivialElement('HTMLParagraphElement'); | 4804 defineTrivialElement('HTMLParagraphElement'); |
4529 defineTrivialElement('HTMLPreElement'); | 4805 defineTrivialElement('HTMLPreElement'); |
4530 | 4806 |
4531 function dynamicCodeDispatchMaker(that) { | 4807 function dynamicCodeDispatchMaker(privates) { |
4532 window.cajaDynamicScriptCounter = | 4808 window.cajaDynamicScriptCounter = |
4533 window.cajaDynamicScriptCounter ? | 4809 window.cajaDynamicScriptCounter ? |
4534 window.cajaDynamicScriptCounter + 1 : 0; | 4810 window.cajaDynamicScriptCounter + 1 : 0; |
4535 var name = "caja_dynamic_script" + | 4811 var name = "caja_dynamic_script" + |
4536 window.cajaDynamicScriptCounter + '___'; | 4812 window.cajaDynamicScriptCounter + '___'; |
4537 window[name] = function() { | 4813 window[name] = function() { |
4538 try { | 4814 try { |
4539 if (that.src && | 4815 if (privates.src && |
4540 'function' === typeof domicile.evaluateUntrustedExternalScript) { | 4816 'function' === typeof domicile.evaluateUntrustedExternalScript) { |
4541 domicile.evaluateUntrustedExternalScript(that.src); | 4817 domicile.evaluateUntrustedExternalScript(privates.src); |
4542 } | 4818 } |
4543 } finally { | 4819 } finally { |
4544 window[name] = undefined; | 4820 window[name] = undefined; |
4545 } | 4821 } |
4546 }; | 4822 }; |
4547 return name + "();"; | 4823 return name + "();"; |
4548 } | 4824 } |
4549 | 4825 |
4550 var TameScriptElement = defineElement({ | 4826 var TameScriptElement = defineElement({ |
4551 domClass: 'HTMLScriptElement', | 4827 domClass: 'HTMLScriptElement', |
4552 forceChildrenNotEditable: true, | 4828 forceChildrenNotEditable: true, |
4553 properties: { | 4829 properties: { |
4554 src: NP.filter(false, identity, true, identity) | 4830 src: NP.filter(false, identity, true, identity) |
4555 }, | 4831 }, |
4556 construct: function () { | 4832 construct: function () { |
4557 var script = np(this); | 4833 var privates = np(this); |
4558 script.feral.appendChild( | 4834 privates.feral.appendChild( |
4559 document.createTextNode( | 4835 document.createTextNode( |
4560 dynamicCodeDispatchMaker(script))); | 4836 dynamicCodeDispatchMaker(privates))); |
4561 } | 4837 } |
4562 }); | 4838 }); |
4563 | 4839 |
4564 setOwn(TameScriptElement.prototype, 'setAttribute', nodeMethod( | 4840 setOwn(TameScriptElement.prototype, 'setAttribute', nodeMethod( |
4565 function (attrib, value) { | 4841 function (attrib, value) { |
4566 var feral = np(this).feral; | 4842 var feral = np(this).feral; |
4567 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4843 np(this).policy.requireEditable(); |
4568 TameElement.prototype.setAttribute.call(this, attrib, value); | 4844 TameElement.prototype.setAttribute.call(this, attrib, value); |
4569 var attribName = String(attrib).toLowerCase(); | 4845 var attribName = String(attrib).toLowerCase(); |
4570 if ("src" === attribName) { | 4846 if ("src" === attribName) { |
4571 np(this).src = String(value); | 4847 np(this).src = String(value); |
4572 } | 4848 } |
4573 })); | 4849 })); |
4574 | 4850 |
4575 defineTrivialElement('HTMLSpanElement'); | 4851 defineTrivialElement('HTMLSpanElement'); |
4576 | 4852 |
4577 defineElement({ | 4853 defineElement({ |
4578 domClass: 'HTMLTableColElement', | 4854 domClass: 'HTMLTableColElement', |
4579 properties: { | 4855 properties: { |
4580 align: NP.filterProp(identity, identity), | 4856 align: NP.filterProp(identity, identity), |
4581 vAlign: NP.filterProp(identity, identity) | 4857 vAlign: NP.filterProp(identity, identity) |
4582 } | 4858 } |
4583 }); | 4859 }); |
4584 ······ | 4860 ······ |
4585 defineTrivialElement('HTMLCaptionElement'); | 4861 defineTrivialElement('HTMLTableCaptionElement'); |
4586 ······ | 4862 ······ |
4587 var TameTableCellElement = defineElement({ | 4863 var TameTableCellElement = defineElement({ |
4588 domClass: 'HTMLTableCellElement', | 4864 domClass: 'HTMLTableCellElement', |
4589 properties: { | 4865 properties: { |
4590 colSpan: NP.filterProp(identity, identity), | 4866 colSpan: NP.filterProp(identity, identity), |
4591 rowSpan: NP.filterProp(identity, identity), | 4867 rowSpan: NP.filterProp(identity, identity), |
4592 cellIndex: NP.ro, | 4868 cellIndex: NP.ro, |
4593 noWrap: NP.filterProp(identity, identity) // HTML5 Obsolete | 4869 noWrap: NP.filterProp(identity, identity) // HTML5 Obsolete |
4594 } | 4870 } |
4595 }); | 4871 }); |
(...skipping 14 matching lines...) Expand all Loading... |
4610 | 4886 |
4611 var TameTableRowElement = defineElement({ | 4887 var TameTableRowElement = defineElement({ |
4612 domClass: 'HTMLTableRowElement', | 4888 domClass: 'HTMLTableRowElement', |
4613 properties: { | 4889 properties: { |
4614 cells: { | 4890 cells: { |
4615 // 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 |
4616 // all the accessors which are of the form | 4892 // all the accessors which are of the form |
4617 // return new TameNodeList(np(this).feral...., ..., ...) | 4893 // return new TameNodeList(np(this).feral...., ..., ...) |
4618 enumerable: true, | 4894 enumerable: true, |
4619 get: nodeMethod(function () { | 4895 get: nodeMethod(function () { |
4620 return new TameNodeList( | 4896 return new TameNodeList(np(this).feral.cells, defaultTameNode); |
4621 np(this).feral.cells, np(this).editable, defaultTameNode); | |
4622 }) | 4897 }) |
4623 }, | 4898 }, |
4624 rowIndex: NP.ro, | 4899 rowIndex: NP.ro, |
4625 sectionRowIndex: NP.ro | 4900 sectionRowIndex: NP.ro |
4626 } | 4901 } |
4627 }); | 4902 }); |
4628 TameTableRowElement.prototype.insertCell = nodeMethod(function (index) { | 4903 TameTableRowElement.prototype.insertCell = nodeMethod(function (index) { |
4629 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4904 np(this).policy.requireEditable(); |
4630 requireIntIn(index, -1, np(this).feral.cells.length); | 4905 requireIntIn(index, -1, np(this).feral.cells.length); |
4631 return defaultTameNode( | 4906 return defaultTameNode( |
4632 np(this).feral.insertCell(index), | 4907 np(this).feral.insertCell(index), |
4633 np(this).editable); | 4908 np(this).editable); |
4634 }); | 4909 }); |
4635 TameTableRowElement.prototype.deleteCell = nodeMethod(function (index) { | 4910 TameTableRowElement.prototype.deleteCell = nodeMethod(function (index) { |
4636 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4911 np(this).policy.requireEditable(); |
4637 requireIntIn(index, -1, np(this).feral.cells.length); | 4912 requireIntIn(index, -1, np(this).feral.cells.length); |
4638 np(this).feral.deleteCell(index); | 4913 np(this).feral.deleteCell(index); |
4639 }); | 4914 }); |
4640 | 4915 |
4641 var TameTableSectionElement = defineElement({ | 4916 var TameTableSectionElement = defineElement({ |
4642 domClass: 'HTMLTableSectionElement', | 4917 domClass: 'HTMLTableSectionElement', |
4643 properties: { | 4918 properties: { |
4644 rows: { | 4919 rows: { |
4645 enumerable: true, | 4920 enumerable: true, |
4646 get: nodeMethod(function () { | 4921 get: nodeMethod(function () { |
4647 return new TameNodeList( | 4922 return new TameNodeList(np(this).feral.rows, defaultTameNode); |
4648 np(this).feral.rows, np(this).editable, defaultTameNode); | |
4649 }) | 4923 }) |
4650 } | 4924 } |
4651 } | 4925 } |
4652 }); | 4926 }); |
4653 TameTableSectionElement.prototype.insertRow = nodeMethod(function(index) { | 4927 TameTableSectionElement.prototype.insertRow = nodeMethod(function(index) { |
4654 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4928 np(this).policy.requireEditable(); |
4655 requireIntIn(index, -1, np(this).feral.rows.length); | 4929 requireIntIn(index, -1, np(this).feral.rows.length); |
4656 return defaultTameNode(np(this).feral.insertRow(index), | 4930 return defaultTameNode(np(this).feral.insertRow(index)); |
4657 np(this).editable); | |
4658 }); | 4931 }); |
4659 TameTableSectionElement.prototype.deleteRow = nodeMethod(function(index) { | 4932 TameTableSectionElement.prototype.deleteRow = nodeMethod(function(index) { |
4660 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4933 np(this).policy.requireEditable(); |
4661 requireIntIn(index, -1, np(this).feral.rows.length); | 4934 requireIntIn(index, -1, np(this).feral.rows.length); |
4662 np(this).feral.deleteRow(index); | 4935 np(this).feral.deleteRow(index); |
4663 }); | 4936 }); |
4664 | 4937 |
4665 var TameTableElement = defineElement({ | 4938 var TameTableElement = defineElement({ |
4666 superclass: TameTableSectionElement, // nonstandard but sound | 4939 superclass: TameTableSectionElement, // nonstandard but sound |
4667 domClass: 'HTMLTableElement', | 4940 domClass: 'HTMLTableElement', |
4668 properties: { | 4941 properties: { |
4669 tBodies: { | 4942 tBodies: { |
4670 enumerable: true, | 4943 enumerable: true, |
4671 get: nodeMethod(function () { | 4944 get: nodeMethod(function () { |
4672 return new TameNodeList( | 4945 if (np(this).policy.childrenVisible) { |
4673 np(this).feral.tBodies, np(this).editable, defaultTameNode); | 4946 return new TameNodeList(np(this).feral.tBodies, |
| 4947 defaultTameNode); |
| 4948 } else { |
| 4949 return fakeNodeList([]); |
| 4950 } |
4674 }) | 4951 }) |
4675 }, | 4952 }, |
4676 tHead: NP_tameDescendant, | 4953 tHead: NP_tameDescendant, |
4677 tFoot: NP_tameDescendant, | 4954 tFoot: NP_tameDescendant, |
4678 cellPadding: NP.filterAttr(Number, fromInt), | 4955 cellPadding: NP.filterAttr(Number, fromInt), |
4679 cellSpacing: NP.filterAttr(Number, fromInt), | 4956 cellSpacing: NP.filterAttr(Number, fromInt), |
4680 border: NP.filterAttr(Number, fromInt) | 4957 border: NP.filterAttr(Number, fromInt) |
4681 } | 4958 } |
4682 }); | 4959 }); |
4683 TameTableElement.prototype.createTHead = nodeMethod(function () { | 4960 TameTableElement.prototype.createTHead = nodeMethod(function () { |
4684 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4961 np(this).policy.requireEditable(); |
4685 return defaultTameNode(np(this).feral.createTHead(), np(this).editable); | 4962 return defaultTameNode(np(this).feral.createTHead()); |
4686 }); | 4963 }); |
4687 TameTableElement.prototype.deleteTHead = nodeMethod(function () { | 4964 TameTableElement.prototype.deleteTHead = nodeMethod(function () { |
4688 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4965 np(this).policy.requireEditable(); |
4689 np(this).feral.deleteTHead(); | 4966 np(this).feral.deleteTHead(); |
4690 }); | 4967 }); |
4691 TameTableElement.prototype.createTFoot = nodeMethod(function () { | 4968 TameTableElement.prototype.createTFoot = nodeMethod(function () { |
4692 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4969 np(this).policy.requireEditable(); |
4693 return defaultTameNode(np(this).feral.createTFoot(), np(this).editable); | 4970 return defaultTameNode(np(this).feral.createTFoot()); |
4694 }); | 4971 }); |
4695 TameTableElement.prototype.deleteTFoot = nodeMethod(function () { | 4972 TameTableElement.prototype.deleteTFoot = nodeMethod(function () { |
4696 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4973 np(this).policy.requireEditable(); |
4697 np(this).feral.deleteTFoot(); | 4974 np(this).feral.deleteTFoot(); |
4698 }); | 4975 }); |
4699 TameTableElement.prototype.createCaption = nodeMethod(function () { | 4976 TameTableElement.prototype.createCaption = nodeMethod(function () { |
4700 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4977 np(this).policy.requireEditable(); |
4701 return defaultTameNode(np(this).feral.createCaption(), np(this).editable
); | 4978 return defaultTameNode(np(this).feral.createCaption()); |
4702 }); | 4979 }); |
4703 TameTableElement.prototype.deleteCaption = nodeMethod(function () { | 4980 TameTableElement.prototype.deleteCaption = nodeMethod(function () { |
4704 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4981 np(this).policy.requireEditable(); |
4705 np(this).feral.deleteCaption(); | 4982 np(this).feral.deleteCaption(); |
4706 }); | 4983 }); |
4707 TameTableElement.prototype.insertRow = nodeMethod(function (index) { | 4984 TameTableElement.prototype.insertRow = nodeMethod(function (index) { |
4708 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4985 np(this).policy.requireEditable(); |
4709 requireIntIn(index, -1, np(this).feral.rows.length); | 4986 requireIntIn(index, -1, np(this).feral.rows.length); |
4710 return defaultTameNode(np(this).feral.insertRow(index), | 4987 return defaultTameNode(np(this).feral.insertRow(index)); |
4711 np(this).editable); | |
4712 }); | 4988 }); |
4713 TameTableElement.prototype.deleteRow = nodeMethod(function (index) { | 4989 TameTableElement.prototype.deleteRow = nodeMethod(function (index) { |
4714 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 4990 np(this).policy.requireEditable(); |
4715 requireIntIn(index, -1, np(this).feral.rows.length); | 4991 requireIntIn(index, -1, np(this).feral.rows.length); |
4716 np(this).feral.deleteRow(index); | 4992 np(this).feral.deleteRow(index); |
4717 }); | 4993 }); |
4718 | 4994 |
4719 defineElement({ | 4995 defineElement({ |
4720 virtualized: true, | 4996 virtualized: true, |
4721 domClass: 'HTMLTitleElement' | 4997 domClass: 'HTMLTitleElement' |
4722 }); | 4998 }); |
4723 ······ | 4999 ······ |
4724 defineTrivialElement('HTMLUListElement'); | 5000 defineTrivialElement('HTMLUListElement'); |
4725 | 5001 |
| 5002 defineElement({ |
| 5003 virtualized: null, |
| 5004 domClass: 'HTMLUnknownElement' |
| 5005 }); |
| 5006 ······ |
4726 traceStartup('DT: done with specific elements'); | 5007 traceStartup('DT: done with specific elements'); |
4727 | 5008 |
4728 // Oddball constructors. There are only two of these and we implement | 5009 // Oddball constructors. There are only two of these and we implement |
4729 // both. (Caveat: In actual browsers, new Image().constructor == Image | 5010 // both. (Caveat: In actual browsers, new Image().constructor == Image |
4730 // != HTMLImageElement. We don't implement that.) | 5011 // != HTMLImageElement. We don't implement that.) |
4731 ······ | 5012 ······ |
4732 // 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 |
4733 function TameImageFun(width, height) { | 5014 function TameImageFun(width, height) { |
4734 var element = tameDocument.createElement('img'); | 5015 var element = tameDocument.createElement('img'); |
4735 if (width !== undefined) { element.width = width; } | 5016 if (width !== undefined) { element.width = width; } |
(...skipping 29 matching lines...) Expand all Loading... |
4765 } | 5046 } |
4766 return taming.tame(event); | 5047 return taming.tame(event); |
4767 } | 5048 } |
4768 | 5049 |
4769 var ep = TameEventConf.p.bind(TameEventConf); | 5050 var ep = TameEventConf.p.bind(TameEventConf); |
4770 | 5051 |
4771 var EP_RELATED = { | 5052 var EP_RELATED = { |
4772 enumerable: true, | 5053 enumerable: true, |
4773 extendedAccessors: true, | 5054 extendedAccessors: true, |
4774 get: eventMethod(function (prop) { | 5055 get: eventMethod(function (prop) { |
4775 // TODO(kpreid): Isn't it unsafe to be always editable=true here? | 5056 return tameRelatedNode(ep(this).feral[prop], defaultTameNode); |
4776 return tameRelatedNode(ep(this).feral[prop], true, | |
4777 defaultTameNode); | |
4778 }) | 5057 }) |
4779 }; | 5058 }; |
4780 | 5059 |
4781 function P_e_view(transform) { | 5060 function P_e_view(transform) { |
4782 return { | 5061 return { |
4783 enumerable: true, | 5062 enumerable: true, |
4784 extendedAccessors: true, | 5063 extendedAccessors: true, |
4785 get: eventMethod(function (prop) { | 5064 get: eventMethod(function (prop) { |
4786 return transform(ep(this).feral[prop]); | 5065 return transform(ep(this).feral[prop]); |
4787 }) | 5066 }) |
(...skipping 12 matching lines...) Expand all Loading... |
4800 enumerable: true, | 5079 enumerable: true, |
4801 get: eventMethod(function () { | 5080 get: eventMethod(function () { |
4802 return bridal.untameEventType(String(ep(this).feral.type)); | 5081 return bridal.untameEventType(String(ep(this).feral.type)); |
4803 }) | 5082 }) |
4804 }, | 5083 }, |
4805 target: { | 5084 target: { |
4806 enumerable: true, | 5085 enumerable: true, |
4807 get: eventMethod(function () { | 5086 get: eventMethod(function () { |
4808 var event = ep(this).feral; | 5087 var event = ep(this).feral; |
4809 return tameRelatedNode( | 5088 return tameRelatedNode( |
4810 event.target || event.srcElement, true, defaultTameNode); | 5089 event.target || event.srcElement, defaultTameNode); |
4811 }) | 5090 }) |
4812 }, | 5091 }, |
4813 srcElement: { | 5092 srcElement: { |
4814 enumerable: true, | 5093 enumerable: true, |
4815 get: eventMethod(function () { | 5094 get: eventMethod(function () { |
4816 return tameRelatedNode(ep(this).feral.srcElement, true, | 5095 return tameRelatedNode(ep(this).feral.srcElement, defaultTameNode); |
4817 defaultTameNode); | |
4818 }) | 5096 }) |
4819 }, | 5097 }, |
4820 currentTarget: { | 5098 currentTarget: { |
4821 enumerable: true, | 5099 enumerable: true, |
4822 get: eventMethod(function () { | 5100 get: eventMethod(function () { |
4823 var e = ep(this).feral; | 5101 var e = ep(this).feral; |
4824 return tameRelatedNode(e.currentTarget, true, defaultTameNode); | 5102 return tameRelatedNode(e.currentTarget, defaultTameNode); |
4825 }) | 5103 }) |
4826 }, | 5104 }, |
4827 relatedTarget: { | 5105 relatedTarget: { |
4828 enumerable: true, | 5106 enumerable: true, |
4829 get: eventMethod(function () { | 5107 get: eventMethod(function () { |
4830 var e = ep(this).feral; | 5108 var e = ep(this).feral; |
4831 var t = e.relatedTarget; | 5109 var t = e.relatedTarget; |
4832 if (!t) { | 5110 if (!t) { |
4833 if (e.type === 'mouseout') { | 5111 if (e.type === 'mouseout') { |
4834 t = e.toElement; | 5112 t = e.toElement; |
4835 } else if (e.type === 'mouseover') { | 5113 } else if (e.type === 'mouseover') { |
4836 t = e.fromElement; | 5114 t = e.fromElement; |
4837 } | 5115 } |
4838 } | 5116 } |
4839 return tameRelatedNode(t, true, defaultTameNode); | 5117 return tameRelatedNode(t, defaultTameNode); |
4840 }), | 5118 }), |
4841 // relatedTarget is read-only. this dummy setter is because some code | 5119 // relatedTarget is read-only. this dummy setter is because some code |
4842 // 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 |
4843 // set. | 5121 // set. |
4844 // 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 |
4845 // relatedTarget is not supported" and "falsey because relatedTarget | 5123 // relatedTarget is not supported" and "falsey because relatedTarget |
4846 // is outside sandbox". | 5124 // is outside sandbox". |
4847 set: eventMethod(function () {}) | 5125 set: eventMethod(function () {}) |
4848 }, | 5126 }, |
4849 fromElement: EP_RELATED, | 5127 fromElement: EP_RELATED, |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4905 inherit(TameCustomHTMLEvent, TameEvent); | 5183 inherit(TameCustomHTMLEvent, TameEvent); |
4906 TameCustomHTMLEvent.prototype.initEvent | 5184 TameCustomHTMLEvent.prototype.initEvent |
4907 = eventMethod(function (type, bubbles, cancelable) { | 5185 = eventMethod(function (type, bubbles, cancelable) { |
4908 bridal.initEvent(ep(this).feral, type, bubbles, cancelable); | 5186 bridal.initEvent(ep(this).feral, type, bubbles, cancelable); |
4909 }); | 5187 }); |
4910 setOwn(TameCustomHTMLEvent.prototype, "toString", eventMethod(function ()
{ | 5188 setOwn(TameCustomHTMLEvent.prototype, "toString", eventMethod(function ()
{ |
4911 return '[Fake CustomEvent]'; | 5189 return '[Fake CustomEvent]'; |
4912 })); | 5190 })); |
4913 cajaVM.def(TameCustomHTMLEvent); // and its prototype | 5191 cajaVM.def(TameCustomHTMLEvent); // and its prototype |
4914 | 5192 |
4915 var viewProperties = { | 5193 function TameHTMLDocument(doc, container, domain) { |
4916 // We define all the window positional properties relative to | |
4917 // the fake body element to maintain the illusion that the fake | |
4918 // document is completely defined by the nodes under the fake body. | |
4919 clientLeft: { | |
4920 get: function () { | |
4921 return np(tameDocument).feralContainerNode.clientLeft; | |
4922 } | |
4923 }, | |
4924 clientTop: { | |
4925 get: function () { | |
4926 return np(tameDocument).feralContainerNode.clientTop; | |
4927 } | |
4928 }, | |
4929 clientHeight: { | |
4930 get: function () { | |
4931 return np(tameDocument).feralContainerNode.clientHeight; | |
4932 } | |
4933 }, | |
4934 clientWidth: { | |
4935 get: function () { | |
4936 return np(tameDocument).feralContainerNode.clientWidth; | |
4937 } | |
4938 }, | |
4939 offsetLeft: { | |
4940 get: function () { | |
4941 return np(tameDocument).feralContainerNode.offsetLeft; | |
4942 } | |
4943 }, | |
4944 offsetTop: { | |
4945 get: function () { | |
4946 return np(tameDocument).feralContainerNode.offsetTop; | |
4947 } | |
4948 }, | |
4949 offsetHeight: { | |
4950 get: function () { | |
4951 return np(tameDocument).feralContainerNode.offsetHeight; | |
4952 } | |
4953 }, | |
4954 offsetWidth: { | |
4955 get: function () { | |
4956 return np(tameDocument).feralContainerNode.offsetWidth; | |
4957 } | |
4958 }, | |
4959 // page{X,Y}Offset appear only as members of window, not on all elements | |
4960 // but http://www.howtocreate.co.uk/tutorials/javascript/browserwindow | |
4961 // says that they are identical to the scrollTop/Left on all browsers bu
t | |
4962 // old versions of Safari. | |
4963 pageXOffset: { | |
4964 get: function () { | |
4965 return np(tameDocument).feralContainerNode.scrollLeft; | |
4966 } | |
4967 }, | |
4968 pageYOffset: { | |
4969 get: function () { | |
4970 return np(tameDocument).feralContainerNode.scrollTop; | |
4971 } | |
4972 }, | |
4973 scrollLeft: { | |
4974 get: function () { | |
4975 return np(tameDocument).feralContainerNode.scrollLeft; | |
4976 }, | |
4977 set: function (x) { | |
4978 np(tameDocument).feralContainerNode.scrollLeft = +x; } | |
4979 }, | |
4980 scrollTop: { | |
4981 get: function () { | |
4982 return np(tameDocument).feralContainerNode.scrollTop; | |
4983 }, | |
4984 set: function (y) { | |
4985 np(tameDocument).feralContainerNode.scrollTop = +y; | |
4986 } | |
4987 }, | |
4988 scrollHeight: { | |
4989 get: function () { | |
4990 return np(tameDocument).feralContainerNode.scrollHeight; | |
4991 } | |
4992 }, | |
4993 scrollWidth: { | |
4994 get: function () { | |
4995 return np(tameDocument).feralContainerNode.scrollWidth; | |
4996 } | |
4997 } | |
4998 }; | |
4999 forOwnKeys(viewProperties, function (prop, desc) { | |
5000 cajaVM.def(desc.get); | |
5001 if (desc.set) cajaVM.def(desc.set); | |
5002 desc.enumerable = true; | |
5003 }); | |
5004 | |
5005 function TameHTMLDocument(doc, container, domain, editable) { | |
5006 traceStartup("DT: TameHTMLDocument begin"); | 5194 traceStartup("DT: TameHTMLDocument begin"); |
5007 TamePseudoNode.call(this, editable); | 5195 TamePseudoNode.call(this); |
5008 | 5196 |
5009 np(this).feralDoc = doc; | 5197 np(this).feralDoc = doc; |
5010 np(this).feralContainerNode = container; | 5198 np(this).feralContainerNode = container; |
5011 np(this).onLoadListeners = []; | 5199 np(this).onLoadListeners = []; |
5012 np(this).onDCLListeners = []; | 5200 np(this).onDCLListeners = []; |
5013 | 5201 |
5014 traceStartup("DT: TameHTMLDocument done private"); | 5202 traceStartup("DT: TameHTMLDocument done private"); |
5015 | 5203 |
5016 var tameContainer = defaultTameNode(container, editable); | 5204 var tameContainer = defaultTameNode(container); |
5017 np(this).tameContainerNode = tameContainer; | 5205 np(this).tameContainerNode = tameContainer; |
5018 | 5206 |
5019 definePropertiesAwesomely(this, { | 5207 definePropertiesAwesomely(this, { |
5020 domain: P_constant(domain) | 5208 domain: P_constant(domain) |
5021 }); | 5209 }); |
| 5210 |
| 5211 installLocation(this); |
5022 } | 5212 } |
5023 inertCtor(TameHTMLDocument, TamePseudoNode, 'HTMLDocument'); | 5213 inertCtor(TameHTMLDocument, TamePseudoNode, 'HTMLDocument'); |
5024 definePropertiesAwesomely(TameHTMLDocument.prototype, { | 5214 definePropertiesAwesomely(TameHTMLDocument.prototype, { |
5025 nodeType: P_constant(9), | 5215 nodeType: P_constant(9), |
5026 nodeName: P_constant('#document'), | 5216 nodeName: P_constant('#document'), |
5027 nodeValue: P_constant(null), | 5217 nodeValue: P_constant(null), |
5028 childNodes: { enumerable: true, get: nodeMethod(function () { | 5218 childNodes: { enumerable: true, get: nodeMethod(function () { |
5029 return np(this).tameContainerNode.childNodes; | 5219 return np(this).tameContainerNode.childNodes; |
5030 })}, | 5220 })}, |
5031 attributes: { enumerable: true, get: nodeMethod(function () { | 5221 attributes: { enumerable: true, get: nodeMethod(function () { |
(...skipping 24 matching lines...) Expand all Loading... |
5056 for (n = this.firstChild; n; n = n.nextSibling) { | 5246 for (n = this.firstChild; n; n = n.nextSibling) { |
5057 if (n.nodeType === 1) { return n; } | 5247 if (n.nodeType === 1) { return n; } |
5058 } | 5248 } |
5059 // None of our children are elements, fail | 5249 // None of our children are elements, fail |
5060 return null; | 5250 return null; |
5061 })}, | 5251 })}, |
5062 forms: { enumerable: true, get: nodeMethod(function () { | 5252 forms: { enumerable: true, get: nodeMethod(function () { |
5063 var tameForms = []; | 5253 var tameForms = []; |
5064 for (var i = 0; i < document.forms.length; i++) { | 5254 for (var i = 0; i < document.forms.length; i++) { |
5065 var tameForm = tameRelatedNode( | 5255 var tameForm = tameRelatedNode( |
5066 makeDOMAccessible(document.forms).item(i), | 5256 makeDOMAccessible(document.forms).item(i), defaultTameNode); |
5067 np(this).editable, defaultTameNode); | |
5068 // tameRelatedNode returns null if the node is not part of | 5257 // tameRelatedNode returns null if the node is not part of |
5069 // this node's virtual document. | 5258 // this node's virtual document. |
5070 if (tameForm !== null) { tameForms.push(tameForm); } | 5259 if (tameForm !== null) { tameForms.push(tameForm); } |
5071 } | 5260 } |
5072 return fakeNodeList(tameForms); | 5261 return fakeNodeList(tameForms); |
5073 })}, | 5262 })}, |
5074 title: { | 5263 title: { |
5075 // TODO(kpreid): get the title element pointer in conformant way | 5264 // TODO(kpreid): get the title element pointer in conformant way |
5076 | 5265 |
5077 // 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 |
5078 // as of 2012-08-14 | 5267 // as of 2012-08-14 |
5079 enumerable: true, | 5268 enumerable: true, |
5080 get: nodeMethod(function() { | 5269 get: nodeMethod(function() { |
5081 var titleEl = this.getElementsByTagName('title')[0]; | 5270 var titleEl = this.getElementsByTagName('title')[0]; |
5082 return trimHTML5Spaces(titleEl.textContent); | 5271 return trimHTML5Spaces(titleEl.textContent); |
5083 }), | 5272 }), |
5084 set: nodeMethod(function(value) { | 5273 set: nodeMethod(function(value) { |
5085 var titleEl = this.getElementsByTagName('title')[0]; | 5274 var titleEl = this.getElementsByTagName('title')[0]; |
5086 titleEl.textContent = value; | 5275 titleEl.textContent = value; |
5087 }) | 5276 }) |
5088 }, | 5277 }, |
5089 compatMode: P_constant('CSS1Compat'), | 5278 compatMode: P_constant('CSS1Compat'), |
5090 ownerDocument: P_constant(null) | 5279 ownerDocument: P_constant(null) |
5091 }); | 5280 }); |
5092 TameHTMLDocument.prototype.getElementsByTagName = nodeMethod( | 5281 TameHTMLDocument.prototype.getElementsByTagName = nodeMethod( |
5093 function (tagName) { | 5282 function (tagName) { |
5094 tagName = String(tagName).toLowerCase(); | 5283 tagName = String(tagName).toLowerCase(); |
5095 return tameGetElementsByTagName( | 5284 return tameGetElementsByTagName(np(this).feralContainerNode, tagName); |
5096 np(this).feralContainerNode, tagName, np(this).editable); | |
5097 }); | 5285 }); |
5098 TameHTMLDocument.prototype.getElementsByClassName = nodeMethod( | 5286 TameHTMLDocument.prototype.getElementsByClassName = nodeMethod( |
5099 function (className) { | 5287 function (className) { |
5100 return tameGetElementsByClassName( | 5288 return tameGetElementsByClassName( |
5101 np(this).feralContainerNode, className, np(this).editable); | 5289 np(this).feralContainerNode, className); |
5102 }); | 5290 }); |
5103 TameHTMLDocument.prototype.addEventListener = | 5291 TameHTMLDocument.prototype.addEventListener = |
5104 nodeMethod(function (name, listener, useCapture) { | 5292 nodeMethod(function (name, listener, useCapture) { |
5105 if (name === 'DOMContentLoaded') { | 5293 if (name === 'DOMContentLoaded') { |
5106 domitaModules.ensureValidCallback(listener); | 5294 domitaModules.ensureValidCallback(listener); |
5107 np(tameDocument).onDCLListeners.push(listener); | 5295 np(tameDocument).onDCLListeners.push(listener); |
5108 } else { | 5296 } else { |
5109 return np(this).tameContainerNode.addEventListener( | 5297 return np(this).tameContainerNode.addEventListener( |
5110 name, listener, useCapture); | 5298 name, listener, useCapture); |
5111 } | 5299 } |
5112 }); | 5300 }); |
5113 TameHTMLDocument.prototype.removeEventListener = | 5301 TameHTMLDocument.prototype.removeEventListener = |
5114 nodeMethod(function (name, listener, useCapture) { | 5302 nodeMethod(function (name, listener, useCapture) { |
5115 return np(this).tameContainerNode.removeEventListener( | 5303 return np(this).tameContainerNode.removeEventListener( |
5116 name, listener, useCapture); | 5304 name, listener, useCapture); |
5117 }); | 5305 }); |
5118 TameHTMLDocument.prototype.createComment = nodeMethod(function (text) { | 5306 TameHTMLDocument.prototype.createComment = nodeMethod(function (text) { |
5119 return defaultTameNode(np(this).feralDoc.createComment(" "), true); | 5307 return defaultTameNode(np(this).feralDoc.createComment(" ")); |
5120 }); | 5308 }); |
5121 TameHTMLDocument.prototype.createDocumentFragment = nodeMethod(function ()
{ | 5309 TameHTMLDocument.prototype.createDocumentFragment = nodeMethod(function ()
{ |
5122 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 5310 np(this).policy.requireEditable(); |
5123 return defaultTameNode(np(this).feralDoc.createDocumentFragment(), true)
; | 5311 return defaultTameNode(np(this).feralDoc.createDocumentFragment()); |
5124 }); | 5312 }); |
5125 TameHTMLDocument.prototype.createElement = nodeMethod(function (tagName) { | 5313 TameHTMLDocument.prototype.createElement = nodeMethod(function (tagName) { |
5126 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 5314 np(this).policy.requireEditable(); |
5127 tagName = String(tagName).toLowerCase(); | 5315 tagName = String(tagName).toLowerCase(); |
5128 tagName = htmlSchema.virtualToRealElementName(tagName); | 5316 tagName = htmlSchema.virtualToRealElementName(tagName); |
5129 var newEl = np(this).feralDoc.createElement(tagName); | 5317 var newEl = np(this).feralDoc.createElement(tagName); |
5130 if ("canvas" == tagName) { | 5318 if ("canvas" == tagName) { |
5131 bridal.initCanvasElement(newEl); | 5319 bridal.initCanvasElement(newEl); |
5132 } | 5320 } |
5133 if (elementPolicies.hasOwnProperty(tagName)) { | 5321 if (elementPolicies.hasOwnProperty(tagName)) { |
5134 var attribs = elementPolicies[tagName]([]); | 5322 var attribs = elementPolicies[tagName]([]); |
5135 if (attribs) { | 5323 if (attribs) { |
5136 for (var i = 0; i < attribs.length; i += 2) { | 5324 for (var i = 0; i < attribs.length; i += 2) { |
5137 bridal.setAttribute(newEl, attribs[+i], attribs[i + 1]); | 5325 bridal.setAttribute(newEl, attribs[+i], attribs[i + 1]); |
5138 } | 5326 } |
5139 } | 5327 } |
5140 } | 5328 } |
5141 return defaultTameNode(newEl, true); | 5329 return defaultTameNode(newEl); |
5142 }); | 5330 }); |
5143 TameHTMLDocument.prototype.createTextNode = nodeMethod(function (text) { | 5331 TameHTMLDocument.prototype.createTextNode = nodeMethod(function (text) { |
5144 if (!np(this).editable) { throw new Error(NOT_EDITABLE); } | 5332 np(this).policy.requireEditable(); |
5145 return defaultTameNode(np(this).feralDoc.createTextNode( | 5333 return defaultTameNode(np(this).feralDoc.createTextNode( |
5146 text !== null && text !== void 0 ? '' + text : ''), true); | 5334 text !== null && text !== void 0 ? '' + text : '')); |
5147 }); | 5335 }); |
5148 TameHTMLDocument.prototype.getElementById = nodeMethod(function (id) { | 5336 TameHTMLDocument.prototype.getElementById = nodeMethod(function (id) { |
5149 id += idSuffix; | 5337 id += idSuffix; |
5150 var node = np(this).feralDoc.getElementById(id); | 5338 var node = np(this).feralDoc.getElementById(id); |
5151 return defaultTameNode(node, np(this).editable); | 5339 return defaultTameNode(node); |
5152 }); | 5340 }); |
5153 // http://www.w3.org/TR/DOM-Level-2-Events/events.html | 5341 // http://www.w3.org/TR/DOM-Level-2-Events/events.html |
5154 // #Events-DocumentEvent-createEvent | 5342 // #Events-DocumentEvent-createEvent |
5155 TameHTMLDocument.prototype.createEvent = nodeMethod(function (type) { | 5343 TameHTMLDocument.prototype.createEvent = nodeMethod(function (type) { |
5156 type = String(type); | 5344 type = String(type); |
5157 if (type !== 'HTMLEvents') { | 5345 if (type !== 'HTMLEvents') { |
5158 // See https://developer.mozilla.org/en/DOM/document.createEvent#Notes | 5346 // See https://developer.mozilla.org/en/DOM/document.createEvent#Notes |
5159 // for a long list of event ypes. | 5347 // for a long list of event ypes. |
5160 // 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 |
5161 // #Events-eventgroupings | 5349 // #Events-eventgroupings |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5224 } | 5412 } |
5225 listeners = np(self).onLoadListeners; | 5413 listeners = np(self).onLoadListeners; |
5226 np(self).onLoadListeners = []; | 5414 np(self).onLoadListeners = []; |
5227 for (var i = 0, n = listeners.length; i < n; ++i) { | 5415 for (var i = 0, n = listeners.length; i < n; ++i) { |
5228 window.setTimeout(listeners[+i], 0); | 5416 window.setTimeout(listeners[+i], 0); |
5229 } | 5417 } |
5230 }); | 5418 }); |
5231 | 5419 |
5232 // For JavaScript handlers. See function dispatchEvent below | 5420 // For JavaScript handlers. See function dispatchEvent below |
5233 domicile.handlers = []; | 5421 domicile.handlers = []; |
5234 domicile.TameHTMLDocument = TameHTMLDocument; // Exposed for testing | |
5235 domicile.tameNode = cajaVM.def(defaultTameNode); | 5422 domicile.tameNode = cajaVM.def(defaultTameNode); |
5236 domicile.feralNode = cajaVM.def(function (tame) { | 5423 domicile.feralNode = cajaVM.def(function (tame) { |
5237 return np(tame).feral; // NOTE: will be undefined for pseudo nodes | 5424 return np(tame).feral; // NOTE: will be undefined for pseudo nodes |
5238 }); | 5425 }); |
5239 domicile.tameEvent = cajaVM.def(tameEvent); | 5426 domicile.tameEvent = cajaVM.def(tameEvent); |
5240 domicile.blessHtml = cajaVM.def(blessHtml); | 5427 domicile.blessHtml = cajaVM.def(blessHtml); |
5241 domicile.blessCss = cajaVM.def(function (var_args) { | 5428 domicile.blessCss = cajaVM.def(function (var_args) { |
5242 var arr = []; | 5429 var arr = []; |
5243 for (var i = 0, n = arguments.length; i < n; ++i) { | 5430 for (var i = 0, n = arguments.length; i < n; ++i) { |
5244 arr[+i] = arguments[+i]; | 5431 arr[+i] = arguments[+i]; |
5245 } | 5432 } |
5246 return cssSealerUnsealerPair.seal(arr); | 5433 return cssSealerUnsealerPair.seal(arr); |
5247 }); | 5434 }); |
5248 domicile.htmlAttr = cajaVM.def(function (s) { | 5435 domicile.htmlAttr = cajaVM.def(function (s) { |
5249 return html.escapeAttrib(String(s || '')); | 5436 return html.escapeAttrib(String(s || '')); |
5250 }); | 5437 }); |
5251 domicile.html = cajaVM.def(safeHtml); | 5438 domicile.html = cajaVM.def(safeHtml); |
5252 domicile.fetchUri = cajaVM.def(function (uri, mime, callback) { | 5439 domicile.fetchUri = cajaVM.def(function (uri, mime, callback) { |
5253 uriFetch(naiveUriPolicy, uri, mime, callback); | 5440 uriFetch(naiveUriPolicy, uri, mime, callback); |
5254 }); | 5441 }); |
5255 domicile.rewriteUri = cajaVM.def(function (uri, mimeType) { | 5442 domicile.rewriteUri = cajaVM.def(function (uri, mimeType) { |
5256 var s = rewriteAttribute(null, null, html4.atype.URI, uri); | 5443 // (SAME_DOCUMENT, SANDBOXED) is chosen as the "reasonable" set of |
5257 if (!s) { throw new Error(); } | 5444 // defaults for this function, which is only used by TCB components |
5258 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 {}); |
5259 }); | 5455 }); |
5260 domicile.suffix = cajaVM.def(function (nmtokens) { | 5456 domicile.suffix = cajaVM.def(function (nmtokens) { |
5261 var p = String(nmtokens).replace(/^\s+|\s+$/g, '').split(/\s+/g); | 5457 var p = String(nmtokens).replace(/^\s+|\s+$/g, '').split(/\s+/g); |
5262 var out = []; | 5458 var out = []; |
5263 for (var i = 0; i < p.length; ++i) { | 5459 for (var i = 0; i < p.length; ++i) { |
5264 var nmtoken = rewriteAttribute(null, null, html4.atype.ID, p[+i]); | 5460 var nmtoken = rewriteAttribute(null, null, html4.atype.ID, p[+i]); |
5265 if (!nmtoken) { throw new Error(nmtokens); } | 5461 if (!nmtoken) { throw new Error(nmtokens); } |
5266 out.push(nmtoken); | 5462 out.push(nmtoken); |
5267 } | 5463 } |
5268 return out.join(' '); | 5464 return out.join(' '); |
(...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5427 var canonName = | 5623 var canonName = |
5428 allCssProperties.getCanonicalPropFromCss(cssPropertyName); | 5624 allCssProperties.getCanonicalPropFromCss(cssPropertyName); |
5429 p.writeByCanonicalName(canonName, value); | 5625 p.writeByCanonicalName(canonName, value); |
5430 return true; | 5626 return true; |
5431 }) | 5627 }) |
5432 }); | 5628 }); |
5433 }); | 5629 }); |
5434 cajaVM.def(TameStyle); // and its prototype | 5630 cajaVM.def(TameStyle); // and its prototype |
5435 | 5631 |
5436 function isNestedInAnchor(el) { | 5632 function isNestedInAnchor(el) { |
5437 for (; el && el != containerNode; el = el.parentNode) { | 5633 for (; |
| 5634 el && el != containerNode; |
| 5635 el = makeDOMAccessible(el.parentNode)) { |
5438 if (el.tagName && el.tagName.toLowerCase() === 'a') { | 5636 if (el.tagName && el.tagName.toLowerCase() === 'a') { |
5439 return true; | 5637 return true; |
5440 } | 5638 } |
5441 } | 5639 } |
5442 return false; | 5640 return false; |
5443 } | 5641 } |
5444 | 5642 |
5445 traceStartup("DT: about to TameComputedStyle"); | 5643 traceStartup("DT: about to TameComputedStyle"); |
5446 | 5644 |
5447 TameComputedStyle = function (rawElement, pseudoElement) { | 5645 TameComputedStyle = function (rawElement, pseudoElement) { |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5486 // Note: nodeClasses.XMLHttpRequest is a ctor that *can* be directly | 5684 // Note: nodeClasses.XMLHttpRequest is a ctor that *can* be directly |
5487 // called by cajoled code, so we do not use inertCtor(). | 5685 // called by cajoled code, so we do not use inertCtor(). |
5488 nodeClasses.XMLHttpRequest = domitaModules.TameXMLHttpRequest( | 5686 nodeClasses.XMLHttpRequest = domitaModules.TameXMLHttpRequest( |
5489 taming, | 5687 taming, |
5490 rulebreaker, | 5688 rulebreaker, |
5491 domitaModules.XMLHttpRequestCtor( | 5689 domitaModules.XMLHttpRequestCtor( |
5492 makeDOMAccessible, | 5690 makeDOMAccessible, |
5493 makeFunctionAccessible(window.XMLHttpRequest), | 5691 makeFunctionAccessible(window.XMLHttpRequest), |
5494 makeFunctionAccessible(window.ActiveXObject), | 5692 makeFunctionAccessible(window.ActiveXObject), |
5495 makeFunctionAccessible(window.XDomainRequest)), | 5693 makeFunctionAccessible(window.XDomainRequest)), |
5496 naiveUriPolicy); | 5694 naiveUriPolicy, |
| 5695 function () { return domicile.pseudoLocation.href; }); |
5497 cajaVM.def(nodeClasses.XMLHttpRequest); | 5696 cajaVM.def(nodeClasses.XMLHttpRequest); |
5498 traceStartup("DT: done for XMLHttpRequest"); | 5697 traceStartup("DT: done for XMLHttpRequest"); |
5499 | 5698 |
5500 /** | 5699 /** |
5501 * given a number, outputs the equivalent css text. | 5700 * given a number, outputs the equivalent css text. |
5502 * @param {number} num | 5701 * @param {number} num |
5503 * @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 |
5504 * attribs and plain text. | 5703 * attribs and plain text. |
5505 */ | 5704 */ |
5506 domicile.cssNumber = cajaVM.def(function (num) { | 5705 domicile.cssNumber = cajaVM.def(function (num) { |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5590 | 5789 |
5591 // Freeze exported classes. Must occur before TameHTMLDocument is | 5790 // Freeze exported classes. Must occur before TameHTMLDocument is |
5592 // instantiated. | 5791 // instantiated. |
5593 for (var name in nodeClasses) { | 5792 for (var name in nodeClasses) { |
5594 var ctor = nodeClasses[name]; | 5793 var ctor = nodeClasses[name]; |
5595 cajaVM.def(ctor); // and its prototype | 5794 cajaVM.def(ctor); // and its prototype |
5596 } | 5795 } |
5597 Object.freeze(nodeClasses); | 5796 Object.freeze(nodeClasses); |
5598 // fail hard if late-added item wouldn't be frozen | 5797 // fail hard if late-added item wouldn't be frozen |
5599 | 5798 |
| 5799 // Location object -- used by Document and Window and so must be created |
| 5800 // before each. |
| 5801 function TameLocation() { |
| 5802 // TODO(mikesamuel): figure out a mechanism by which the container can |
| 5803 // specify the gadget's apparent URL. |
| 5804 // See http://www.whatwg.org/specs/web-apps/current-work/multipage/histo
ry.html#location0 |
| 5805 var tameLocation = this; |
| 5806 function defineLocationField(f, dflt) { |
| 5807 Object.defineProperty(tameLocation, f, { |
| 5808 configurable: false, |
| 5809 enumerable: true, |
| 5810 get: function() {· |
| 5811 return String(domicile.pseudoLocation[f] || dflt);· |
| 5812 } |
| 5813 }); |
| 5814 } |
| 5815 defineLocationField('href', 'http://nosuchhost.invalid:80/'); |
| 5816 defineLocationField('hash', ''); |
| 5817 defineLocationField('host', 'nosuchhost.invalid:80'); |
| 5818 defineLocationField('hostname', 'nosuchhost.invalid'); |
| 5819 defineLocationField('pathname', '/'); |
| 5820 defineLocationField('port', '80'); |
| 5821 defineLocationField('protocol', 'http:'); |
| 5822 defineLocationField('search', ''); |
| 5823 setOwn(tameLocation, 'toString', |
| 5824 cajaVM.def(function() { return tameLocation.href; })); |
| 5825 } |
| 5826 inertCtor(TameLocation, Object); |
| 5827 var tameLocation = new TameLocation(); |
| 5828 function installLocation(obj) { |
| 5829 Object.defineProperty(obj, "location", { |
| 5830 value: tameLocation, |
| 5831 configurable: false, |
| 5832 enumerable: true, |
| 5833 writable: false // Writable in browsers, but has a side-effect |
| 5834 // which we don't implement. |
| 5835 }); |
| 5836 } |
| 5837 |
5600 traceStartup("DT: about to do TameHTMLDocument"); | 5838 traceStartup("DT: about to do TameHTMLDocument"); |
5601 var tameDocument = new TameHTMLDocument( | 5839 var tameDocument = new TameHTMLDocument( |
5602 document, | 5840 document, |
5603 containerNode, | 5841 containerNode, |
5604 // TODO(jasvir): Properly wire up document.domain | 5842 // TODO(jasvir): Properly wire up document.domain |
5605 // by untangling the cyclic dependence between | 5843 // by untangling the cyclic dependence between |
5606 // TameWindow and TameDocument | 5844 // TameWindow and TameDocument |
5607 String(undefined || 'nosuchhost.invalid'), | 5845 String(undefined || 'nosuchhost.invalid')); |
5608 true); | |
5609 traceStartup("DT: finished TameHTMLDocument"); | 5846 traceStartup("DT: finished TameHTMLDocument"); |
5610 domicile.htmlEmitterTarget = containerNode; | 5847 domicile.htmlEmitterTarget = containerNode; |
5611 | 5848 |
5612 // 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 |
5613 // We don't attempt to hide or abstract userAgent details since | 5850 // We don't attempt to hide or abstract userAgent details since |
5614 // they are discoverable via side-channels we don't control. | 5851 // they are discoverable via side-channels we don't control. |
5615 var navigator = makeDOMAccessible(window.navigator); | 5852 var navigator = makeDOMAccessible(window.navigator); |
5616 var tameNavigator = cajaVM.def({ | 5853 var tameNavigator = cajaVM.def({ |
5617 appName: String(navigator.appName), | 5854 appName: String(navigator.appName), |
5618 appVersion: String(navigator.appVersion), | 5855 appVersion: String(navigator.appVersion), |
(...skipping 15 matching lines...) Expand all Loading... |
5634 // arbitrary ancestor nodes. | 5871 // arbitrary ancestor nodes. |
5635 'first-letter': true, | 5872 'first-letter': true, |
5636 'first-line': true | 5873 'first-line': true |
5637 }; | 5874 }; |
5638 | 5875 |
5639 /** | 5876 /** |
5640 * See http://www.whatwg.org/specs/web-apps/current-work/multipage/browser
s.html#window for the full API. | 5877 * See http://www.whatwg.org/specs/web-apps/current-work/multipage/browser
s.html#window for the full API. |
5641 */ | 5878 */ |
5642 function TameWindow() { | 5879 function TameWindow() { |
5643 | 5880 |
5644 // TODO(mikesamuel): figure out a mechanism by which the container can | |
5645 // specify the gadget's apparent URL. | |
5646 // See http://www.whatwg.org/specs/web-apps/current-work/multipage/histo
ry.html#location0 | |
5647 var tameLocation = {}; | |
5648 function defineLocationField(f, dflt) { | |
5649 Object.defineProperty(tameLocation, f, { | |
5650 configurable: false, | |
5651 enumerable: true, | |
5652 get: function() {· | |
5653 return String(domicile.pseudoLocation[f] || dflt);· | |
5654 } | |
5655 }); | |
5656 } | |
5657 defineLocationField('href', 'http://nosuchhost.invalid:80/'); | |
5658 defineLocationField('hash', ''); | |
5659 defineLocationField('host', 'nosuchhost.invalid:80'); | |
5660 defineLocationField('hostname', 'nosuchhost.invalid'); | |
5661 defineLocationField('pathname', '/'); | |
5662 defineLocationField('port', '80'); | |
5663 defineLocationField('protocol', 'http:'); | |
5664 defineLocationField('search', ''); | |
5665 setOwn(tameLocation, 'toString', | |
5666 cajaVM.def(function() { return tameLocation.href; })); | |
5667 | |
5668 // These descriptors were chosen to resemble actual ES5-supporting brows
er | 5881 // These descriptors were chosen to resemble actual ES5-supporting brows
er |
5669 // behavior. | 5882 // behavior. |
5670 // The document property is defined below. | 5883 // The document property is defined below. |
5671 Object.defineProperty(this, "location", { | 5884 installLocation(this); |
5672 value: tameLocation, | |
5673 configurable: false, | |
5674 enumerable: true, | |
5675 writable: false // Writable in browsers, but has a side-effect | |
5676 // which we don't implement. | |
5677 }); | |
5678 Object.defineProperty(this, "navigator", { | 5885 Object.defineProperty(this, "navigator", { |
5679 value: tameNavigator, | 5886 value: tameNavigator, |
5680 configurable: false, | 5887 configurable: false, |
5681 enumerable: true, | 5888 enumerable: true, |
5682 writable: false | 5889 writable: false |
5683 }); | 5890 }); |
5684 definePropertiesAwesomely(this, viewProperties); | |
5685 taming.permitUntaming(this); | 5891 taming.permitUntaming(this); |
5686 } | 5892 } |
5687 // Methods of TameWindow are established later. | 5893 // Methods of TameWindow are established later. |
5688 setOwn(TameWindow.prototype, "toString", cajaVM.def(function () { | 5894 setOwn(TameWindow.prototype, "toString", cajaVM.def(function () { |
5689 return "[domado object Window]"; | 5895 return "[domado object Window]"; |
5690 })); | 5896 })); |
5691 | 5897 |
5692 /** | 5898 /** |
5693 * An <a href= | 5899 * An <a href= |
5694 * href=http://www.w3.org/TR/DOM-Level-2-Views/views.html#Views-AbstractVi
ew | 5900 * href=http://www.w3.org/TR/DOM-Level-2-Views/views.html#Views-AbstractVi
ew |
(...skipping 10 matching lines...) Expand all Loading... |
5705 * <p> | 5911 * <p> |
5706 * 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 |
5707 * 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 |
5708 * module's tamed window provides an unbounded amount of authority. | 5914 * module's tamed window provides an unbounded amount of authority. |
5709 * <p> | 5915 * <p> |
5710 * Instead, we expose styling, positioning, and sizing properties | 5916 * Instead, we expose styling, positioning, and sizing properties |
5711 * 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 |
5712 * document. | 5918 * document. |
5713 */ | 5919 */ |
5714 function TameDefaultView() { | 5920 function TameDefaultView() { |
5715 // TODO(kpreid): The caller passes document's editable flag; this does n
ot | |
5716 // take such a parameter. Which is right? | |
5717 // TODO(mikesamuel): Implement in terms of | 5921 // TODO(mikesamuel): Implement in terms of |
5718 // http://www.w3.org/TR/cssom-view/#the-windowview-interface | 5922 // http://www.w3.org/TR/cssom-view/#the-windowview-interface |
5719 // TODO: expose a read-only version of the document | 5923 // TODO: expose a read-only version of the document |
5720 this.document = tameDocument; | 5924 this.document = tameDocument; |
5721 // Exposing an editable default view that pointed to a read-only | 5925 // Exposing an editable default view that pointed to a read-only |
5722 // tameDocument via document.defaultView would allow escalation of | 5926 // tameDocument via document.defaultView would allow escalation of |
5723 // authority. | 5927 // authority. |
5724 assert(np(tameDocument).editable); | 5928 assert(np(tameDocument).policy.editable); |
5725 definePropertiesAwesomely(this, viewProperties); | |
5726 taming.permitUntaming(this); | 5929 taming.permitUntaming(this); |
5727 } | 5930 } |
5728 | 5931 |
5729 // Under ES53, the set/clear pairs get invoked with 'this' bound | 5932 // Under ES53, the set/clear pairs get invoked with 'this' bound |
5730 // to USELESS, which causes problems on Chrome unless they're wrpaped | 5933 // to USELESS, which causes problems on Chrome unless they're wrpaped |
5731 // this way. | 5934 // this way. |
5732 tameSetAndClear( | 5935 tameSetAndClear( |
5733 TameWindow.prototype, | 5936 TameWindow.prototype, |
5734 function (code, millis) { return window.setTimeout(code, millis); }, | 5937 function (code, millis) { return window.setTimeout(code, millis); }, |
5735 function (id) { return window.clearTimeout(id); }, | 5938 function (id) { return window.clearTimeout(id); }, |
(...skipping 29 matching lines...) Expand all Loading... |
5765 } | 5968 } |
5766 } | 5969 } |
5767 listeners.length -= k; | 5970 listeners.length -= k; |
5768 } else { | 5971 } else { |
5769 tameDocument.removeEventListener(name, listener, useCapture); | 5972 tameDocument.removeEventListener(name, listener, useCapture); |
5770 } | 5973 } |
5771 }); | 5974 }); |
5772 TameWindow.prototype.dispatchEvent = cajaVM.def(function (evt) { | 5975 TameWindow.prototype.dispatchEvent = cajaVM.def(function (evt) { |
5773 // TODO(ihab.awad): Implement | 5976 // TODO(ihab.awad): Implement |
5774 }); | 5977 }); |
| 5978 |
| 5979 // Methods which are installed on window AND defaultView. |
| 5980 // See doc comment of TameDefaultView regarding authority to expose here. |
5775 forOwnKeys({ | 5981 forOwnKeys({ |
5776 scrollBy: cajaVM.def( | 5982 scrollBy: cajaVM.def( |
5777 function (dx, dy) { | 5983 function (dx, dy) { |
5778 // The window is always auto scrollable, so make the apparent wind
ow | 5984 // The window is always auto scrollable, so make the apparent wind
ow |
5779 // body scrollable if the gadget tries to scroll it. | 5985 // body scrollable if the gadget tries to scroll it. |
5780 if (dx || dy) { | 5986 if (dx || dy) { |
5781 makeScrollable(bridal, np(tameDocument).feralContainerNode); | 5987 makeScrollable(bridal, np(tameDocument).feralContainerNode); |
5782 } | 5988 } |
5783 tameScrollBy(np(tameDocument).feralContainerNode, dx, dy); | 5989 tameScrollBy(np(tameDocument).feralContainerNode, dx, dy); |
5784 }), | 5990 }), |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
5826 }) | 6032 }) |
5827 | 6033 |
5828 // NOT PROVIDED | 6034 // NOT PROVIDED |
5829 // event: a global on IE. We always define it in scopes that can handle | 6035 // event: a global on IE. We always define it in scopes that can handle |
5830 // events. | 6036 // events. |
5831 // opera: defined only on Opera. | 6037 // opera: defined only on Opera. |
5832 }, (function (propertyName, value) { | 6038 }, (function (propertyName, value) { |
5833 TameWindow.prototype[propertyName] = value; | 6039 TameWindow.prototype[propertyName] = value; |
5834 TameDefaultView.prototype[propertyName] = value; | 6040 TameDefaultView.prototype[propertyName] = value; |
5835 })); | 6041 })); |
| 6042 ······ |
5836 cajaVM.def(TameWindow); // and its prototype | 6043 cajaVM.def(TameWindow); // and its prototype |
5837 | 6044 |
5838 var tameWindow = new TameWindow(); | 6045 var tameWindow = new TameWindow(); |
5839 var tameDefaultView = new TameDefaultView(np(tameDocument).editable); | 6046 var tameDefaultView = new TameDefaultView(); |
5840 | 6047 |
| 6048 // Getters for properties which are installed on window AND defaultView. |
| 6049 // See doc comment of TameDefaultView regarding authority to expose here. |
5841 forOwnKeys({ | 6050 forOwnKeys({ |
| 6051 pageXOffset: function () { return this.scrollX; }, |
| 6052 pageYOffset: function () { return this.scrollY; }, |
| 6053 scrollX: function () { |
| 6054 return np(tameDocument).feralContainerNode.scrollLeft; }, |
| 6055 scrollY: function () { |
| 6056 return np(tameDocument).feralContainerNode.scrollTop; }, |
| 6057 ········ |
5842 innerHeight: function () { | 6058 innerHeight: function () { |
5843 return np(tameDocument).feralContainerNode.clientHeight; }, | 6059 return np(tameDocument).feralContainerNode.offsetHeight; }, |
5844 innerWidth: function () { | 6060 innerWidth: function () { |
5845 return np(tameDocument).feralContainerNode.clientWidth; }, | 6061 return np(tameDocument).feralContainerNode.offsetWidth; }, |
5846 outerHeight: function () { | 6062 outerHeight: function () { |
5847 return np(tameDocument).feralContainerNode.clientHeight; }, | 6063 return np(tameDocument).feralContainerNode.offsetHeight; }, |
5848 outerWidth: function () { | 6064 outerWidth: function () { |
5849 return np(tameDocument).feralContainerNode.clientWidth; } | 6065 return np(tameDocument).feralContainerNode.offsetWidth; } |
5850 }, function (propertyName, handler) { | 6066 }, function (propertyName, handler) { |
5851 // TODO(mikesamuel): define on prototype. | 6067 // TODO(mikesamuel): define on prototype. |
5852 var desc = {enumerable: canHaveEnumerableAccessors, get: handler}; | 6068 var desc = {enumerable: canHaveEnumerableAccessors, get: handler}; |
5853 Object.defineProperty(tameWindow, propertyName, desc); | 6069 Object.defineProperty(tameWindow, propertyName, desc); |
5854 Object.defineProperty(tameDefaultView, propertyName, desc); | 6070 Object.defineProperty(tameDefaultView, propertyName, desc); |
5855 }); | 6071 }); |
5856 | 6072 |
5857 // Attach reflexive properties to 'window' object | 6073 // Attach reflexive properties to 'window' object |
5858 var windowProps = ['top', 'self', 'opener', 'parent', 'window']; | 6074 var windowProps = ['top', 'self', 'opener', 'parent', 'window']; |
5859 var wpLen = windowProps.length; | 6075 var wpLen = windowProps.length; |
5860 for (var i = 0; i < wpLen; ++i) { | 6076 for (var i = 0; i < wpLen; ++i) { |
5861 var prop = windowProps[+i]; | 6077 var prop = windowProps[+i]; |
5862 tameWindow[prop] = tameWindow; | 6078 tameWindow[prop] = tameWindow; |
5863 } | 6079 } |
5864 | 6080 |
5865 Object.freeze(tameDefaultView); | 6081 Object.freeze(tameDefaultView); |
5866 | 6082 |
5867 if (np(tameDocument).editable) { | 6083 if (np(tameDocument).policy.editable) { |
5868 tameDocument.defaultView = tameDefaultView; | 6084 tameDocument.defaultView = tameDefaultView; |
5869 | 6085 |
5870 // Hook for document.write support. | 6086 // Hook for document.write support. |
5871 domicile.sanitizeAttrs = sanitizeAttrs; | 6087 domicile.sanitizeAttrs = sanitizeAttrs; |
5872 } | 6088 } |
5873 | 6089 |
5874 // Iterate over all node classes, assigning them to the Window object | 6090 // Iterate over all node classes, assigning them to the Window object |
5875 // 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. |
5876 for (var name in nodeClasses) { | 6092 for (var name in nodeClasses) { |
5877 var ctor = nodeClasses[name]; | 6093 var ctor = nodeClasses[name]; |
5878 Object.defineProperty(tameWindow, name, { | 6094 Object.defineProperty(tameWindow, name, { |
5879 enumerable: true, | 6095 enumerable: true, |
5880 configurable: true, | 6096 configurable: true, |
5881 writable: true, | 6097 writable: true, |
5882 value: ctor | 6098 value: ctor |
5883 }); | 6099 }); |
5884 } | 6100 } |
5885 | 6101 |
5886 // TODO(ihab.awad): Build a more sophisticated virtual class hierarchy by | 6102 // TODO(ihab.awad): Build a more sophisticated virtual class hierarchy by |
5887 // creating a table of actual subclasses and instantiating tame nodes by | 6103 // having a table of subclass relationships and implementing them. |
5888 // table lookups. This will allow the client code to see a truly consisten
t | 6104 |
5889 // DOM class hierarchy. | |
5890 | |
5891 // This is a list of all HTML-specific element node classes defined by | |
5892 // DOM Level 2 HTML, <http://www.w3.org/TR/DOM-Level-2-HTML/html.html>. | |
5893 // 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 |
5894 // 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. |
5895 var allDomNodeClasses = [ | 6107 var allDomNodeClasses = htmlSchema.getAllKnownScriptInterfaces(); |
5896 'HTMLAnchorElement', | |
5897 'HTMLAppletElement', | |
5898 'HTMLAreaElement', | |
5899 'HTMLBaseElement', | |
5900 'HTMLBaseFontElement', | |
5901 'HTMLBodyElement', | |
5902 'HTMLBRElement', | |
5903 'HTMLButtonElement', | |
5904 'HTMLDirectoryElement', | |
5905 'HTMLDivElement', | |
5906 'HTMLDListElement', | |
5907 'HTMLFieldSetElement', | |
5908 'HTMLFontElement', | |
5909 'HTMLFormElement', | |
5910 'HTMLFrameElement', | |
5911 'HTMLFrameSetElement', | |
5912 'HTMLHeadElement', | |
5913 'HTMLHeadingElement', | |
5914 'HTMLHRElement', | |
5915 'HTMLHtmlElement', | |
5916 'HTMLIFrameElement', | |
5917 'HTMLImageElement', | |
5918 'HTMLInputElement', | |
5919 'HTMLIsIndexElement', | |
5920 'HTMLLabelElement', | |
5921 'HTMLLegendElement', | |
5922 'HTMLLIElement', | |
5923 'HTMLLinkElement', | |
5924 'HTMLMapElement', | |
5925 'HTMLMenuElement', | |
5926 'HTMLMetaElement', | |
5927 'HTMLModElement', | |
5928 'HTMLNavElement', | |
5929 'HTMLObjectElement', | |
5930 'HTMLOListElement', | |
5931 'HTMLOptGroupElement', | |
5932 'HTMLOptionElement', | |
5933 'HTMLParagraphElement', | |
5934 'HTMLParamElement', | |
5935 'HTMLPreElement', | |
5936 'HTMLQuoteElement', | |
5937 'HTMLScriptElement', | |
5938 'HTMLSelectElement', | |
5939 'HTMLStyleElement', | |
5940 'HTMLTableCaptionElement', | |
5941 'HTMLTableCellElement', | |
5942 'HTMLTableColElement', | |
5943 'HTMLTableElement', | |
5944 'HTMLTableRowElement', | |
5945 'HTMLTableSectionElement', | |
5946 'HTMLTextAreaElement', | |
5947 'HTMLTitleElement', | |
5948 'HTMLUListElement' | |
5949 ]; | |
5950 | |
5951 var defaultNodeClassCtor = nodeClasses.HTMLElement; | 6108 var defaultNodeClassCtor = nodeClasses.HTMLElement; |
5952 for (var i = 0; i < allDomNodeClasses.length; i++) { | 6109 for (var i = 0; i < allDomNodeClasses.length; i++) { |
5953 var className = allDomNodeClasses[+i]; | 6110 var className = allDomNodeClasses[+i]; |
5954 if (!(className in tameWindow)) { | 6111 if (!(className in tameWindow)) { |
5955 Object.defineProperty(tameWindow, className, { | 6112 Object.defineProperty(tameWindow, className, { |
5956 enumerable: true, | 6113 enumerable: true, |
5957 configurable: true, | 6114 configurable: true, |
5958 writable: true, | 6115 writable: true, |
5959 value: defaultNodeClassCtor | 6116 value: defaultNodeClassCtor |
5960 }); | 6117 }); |
(...skipping 10 matching lines...) Expand all Loading... |
5971 Object.defineProperty(tameWindow, 'document', { | 6128 Object.defineProperty(tameWindow, 'document', { |
5972 value: tameDocument, | 6129 value: tameDocument, |
5973 configurable: false, | 6130 configurable: false, |
5974 enumerable: true, | 6131 enumerable: true, |
5975 writable: false | 6132 writable: false |
5976 }); | 6133 }); |
5977 | 6134 |
5978 pluginId = rulebreaker.getId(tameWindow); | 6135 pluginId = rulebreaker.getId(tameWindow); |
5979 windowToDomicile.set(tameWindow, domicile); | 6136 windowToDomicile.set(tameWindow, domicile); |
5980 | 6137 |
| 6138 // Install virtual UA stylesheet. |
| 6139 if (!document.caja_gadgetStylesheetInstalled) (function () { |
| 6140 document.caja_gadgetStylesheetInstalled = true; |
| 6141 ········ |
| 6142 var element = makeDOMAccessible(document.createElement("style")); |
| 6143 element.setAttribute("type", "text/css"); |
| 6144 element.textContent = ( |
| 6145 // Visually contains the virtual document |
| 6146 ".vdoc-container___ {" + |
| 6147 "position:relative!important;" + |
| 6148 "overflow:auto!important;" + |
| 6149 "clip:rect(auto,auto,auto,auto)!important;" + // paranoia |
| 6150 "}" + |
| 6151 |
| 6152 // Styles for HTML elements that we virtualize, and so do not get the |
| 6153 // normal UA stylesheet rules applied: |
| 6154 |
| 6155 // Should be the intersection of HTML5 spec's list and our virtualized |
| 6156 // (i.e. non-whitelisted) elements. Source: |
| 6157 // <http://www.whatwg.org/specs/web-apps/current-work/multipage/render
ing.html#the-css-user-agent-style-sheet-and-presentational-hints> |
| 6158 "caja-v-base,caja-v-basefont,caja-v-head,caja-v-link,caja-v-meta," + |
| 6159 "caja-v-noembed,caja-v-noframes,caja-v-param,caja-v-source," + |
| 6160 "caja-v-track,caja-v-title{" + |
| 6161 "display:none;" +· |
| 6162 "}" + |
| 6163 |
| 6164 "caja-v-html, caja-v-body {" + |
| 6165 "display:block;" + |
| 6166 "}" |
| 6167 ); |
| 6168 domicile.getCssContainer().appendChild(element); |
| 6169 })(); |
| 6170 |
5981 traceStartup("DT: all done"); | 6171 traceStartup("DT: all done"); |
5982 | 6172 |
5983 return domicile; | 6173 return domicile; |
5984 } | 6174 } |
5985 | 6175 |
5986 /** | 6176 /** |
5987 * Function called from rewritten event handlers to dispatch an event safely
. | 6177 * Function called from rewritten event handlers to dispatch an event safely
. |
5988 */ | 6178 */ |
5989 function plugin_dispatchEvent(thisNode, event, pluginId, handler) { | 6179 function plugin_dispatchEvent(thisNode, event, pluginId, handler) { |
5990 event = makeDOMAccessible( | 6180 var window = bridalMaker.getWindow(thisNode, makeDOMAccessible); |
5991 event || bridalMaker.getWindow(thisNode, makeDOMAccessible).event); | 6181 event = makeDOMAccessible(event || window.event); |
5992 // support currentTarget on IE[678] | 6182 // support currentTarget on IE[678] |
5993 if (!event.currentTarget) { | 6183 if (!event.currentTarget) { |
5994 event.currentTarget = thisNode; | 6184 event.currentTarget = thisNode; |
5995 } | 6185 } |
5996 var imports = rulebreaker.getImports(pluginId); | 6186 var imports = rulebreaker.getImports(pluginId); |
5997 var domicile = windowToDomicile.get(imports); | 6187 var domicile = windowToDomicile.get(imports); |
5998 var node = domicile.tameNode(thisNode, true); | 6188 var node = domicile.tameNode(thisNode); |
| 6189 var isUserAction = eventIsUserAction(event, window); |
5999 try { | 6190 try { |
6000 return plugin_dispatchToHandler( | 6191 return dispatch( |
6001 pluginId, handler, [ node, domicile.tameEvent(event), node ]); | 6192 isUserAction, pluginId, handler, |
| 6193 [ node, domicile.tameEvent(event), node ]); |
6002 } catch (ex) { | 6194 } catch (ex) { |
6003 imports.onerror(ex.message, 'unknown', 0); | 6195 imports.onerror(ex.message, 'unknown', 0); |
6004 } | 6196 } |
6005 } | 6197 } |
6006 | 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 */ |
6007 function plugin_dispatchToHandler(pluginId, handler, args) { | 6223 function plugin_dispatchToHandler(pluginId, handler, args) { |
6008 var sig = ('' + handler).match(/^function\b[^\)]*\)/); | 6224 return dispatch(true, pluginId, handler, args); |
| 6225 } |
| 6226 |
| 6227 function dispatch(isUserAction, pluginId, handler, args) { |
6009 var domicile = windowToDomicile.get(rulebreaker.getImports(pluginId)); | 6228 var domicile = windowToDomicile.get(rulebreaker.getImports(pluginId)); |
6010 if (domicile.domitaTrace & 0x1 && typeof console != 'undefined') { | 6229 if (domicile.domitaTrace & 0x1 && typeof console != 'undefined') { |
| 6230 var sig = ('' + handler).match(/^function\b[^\)]*\)/); |
6011 console.log( | 6231 console.log( |
6012 'Dispatch pluginId=' + pluginId + | 6232 'Dispatch pluginId=' + pluginId + |
6013 ', handler=' + (sig ? sig[0] : handler) + | 6233 ', handler=' + (sig ? sig[0] : handler) + |
6014 ', args=' + args); | 6234 ', args=' + args); |
6015 } | 6235 } |
6016 switch (typeof handler) { | 6236 switch (typeof handler) { |
6017 case 'number': | 6237 case 'number': |
6018 handler = domicile.handlers[+handler]; | 6238 handler = domicile.handlers[+handler]; |
6019 break; | 6239 break; |
6020 case 'string': | 6240 case 'string': |
6021 var fn = void 0; | 6241 var fn = void 0; |
6022 fn = domicile.window[handler]; | 6242 fn = domicile.window[handler]; |
6023 handler = fn && typeof fn.call === 'function' ? fn : void 0; | 6243 handler = fn && typeof fn.call === 'function' ? fn : void 0; |
6024 break; | 6244 break; |
6025 case 'function': case 'object': break; | 6245 case 'function': case 'object': break; |
6026 default: | 6246 default: |
6027 throw new Error( | 6247 throw new Error( |
6028 'Expected function as event handler, not ' + typeof handler); | 6248 'Expected function as event handler, not ' + typeof handler); |
6029 } | 6249 } |
6030 domicile.isProcessingEvent = true; | 6250 domicile.handlingUserAction = isUserAction; |
6031 try { | 6251 try { |
6032 return handler.call.apply(handler, args); | 6252 return handler.call.apply(handler, args); |
6033 } catch (ex) { | 6253 } catch (ex) { |
6034 // guard against IE discarding finally blocks | 6254 // guard against IE discarding finally blocks |
| 6255 domicile.handlingUserAction = false; |
6035 throw ex; | 6256 throw ex; |
6036 } finally { | 6257 } finally { |
6037 domicile.isProcessingEvent = false; | 6258 domicile.handlingUserAction = false; |
6038 } | 6259 } |
6039 } | 6260 } |
6040 | 6261 |
6041 return cajaVM.def({ | 6262 return cajaVM.def({ |
6042 attachDocument: attachDocument, | 6263 attachDocument: attachDocument, |
6043 plugin_dispatchEvent: plugin_dispatchEvent, | 6264 plugin_dispatchEvent: plugin_dispatchEvent, |
6044 plugin_dispatchToHandler: plugin_dispatchToHandler, | 6265 plugin_dispatchToHandler: plugin_dispatchToHandler, |
6045 getDomicileForWindow: windowToDomicile.get.bind(windowToDomicile) | 6266 getDomicileForWindow: windowToDomicile.get.bind(windowToDomicile) |
6046 }); | 6267 }); |
6047 }; | 6268 }); |
6048 })(); | 6269 })(); |
6049 | 6270 |
6050 // Exports for closure compiler. | 6271 // Exports for closure compiler. |
6051 if (typeof window !== 'undefined') { | 6272 if (typeof window !== 'undefined') { |
6052 window['Domado'] = Domado; | 6273 window['Domado'] = Domado; |
6053 } | 6274 } |
LEFT | RIGHT |