0
|
1 // Copyright (C) 2008 Google Inc.
|
|
2 //
|
|
3 // Licensed under the Apache License, Version 2.0 (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
|
|
6 //
|
|
7 // http://www.apache.org/licenses/LICENSE-2.0
|
|
8 //
|
|
9 // Unless required by applicable law or agreed to in writing, software
|
|
10 // distributed under the License is distributed on an "AS IS" BASIS,
|
|
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12 // See the License for the specific language governing permissions and
|
|
13 // limitations under the License.
|
|
14
|
|
15 /**
|
|
16 * @fileoverview
|
|
17 * A partially tamed browser object model based on
|
|
18 * <a href="http://www.w3.org/TR/DOM-Level-2-HTML/Overview.html"
|
|
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"
|
|
21 * >ECMAScript Language Bindings</a>.
|
|
22 *
|
|
23 * Caveats:<ul>
|
|
24 * <li>This is not a full implementation.
|
|
25 * <li>Security Review is pending.
|
|
26 * <li><code>===</code> and <code>!==</code> on node lists will not
|
|
27 * behave the same as with untamed node lists. Specifically, it is
|
|
28 * not always true that {@code nodeA.childNodes === nodeA.childNodes}.
|
|
29 * <li>Properties backed by setters/getters like {@code HTMLElement.innerHTML}
|
|
30 * will not appear to uncajoled code as DOM nodes do, since they are
|
|
31 * implemented using cajita property handlers.
|
|
32 * </ul>
|
|
33 *
|
|
34 * <p>
|
|
35 * TODO(ihab.awad): Our implementation of getAttribute (and friends)
|
|
36 * is such that standard DOM attributes which we disallow for security
|
|
37 * reasons (like 'form:enctype') are placed in the "virtual"
|
|
38 * attributes map (this.node___.attributes___). They appear to be
|
|
39 * settable and gettable, but their values are ignored and do not have
|
|
40 * the expected semantics per the DOM API. This is because we do not
|
|
41 * have a column in html4-defs.js stating that an attribute is valid
|
|
42 * but explicitly blacklisted. Alternatives would be to always throw
|
|
43 * upon access to these attributes; to make them always appear to be
|
|
44 * null; etc. Revisit this decision if needed.
|
|
45 *
|
|
46 * @author mikesamuel@gmail.com
|
|
47 * @requires console, document, window
|
|
48 * @requires clearInterval, clearTimeout, setInterval, setTimeout
|
|
49 * @requires ___, bridal, cajita, css, html, html4, unicode
|
|
50 * @provides attachDocumentStub, plugin_dispatchEvent___
|
|
51 * @overrides domitaModules
|
|
52 */
|
|
53
|
|
54 var domitaModules;
|
|
55 if (!domitaModules) { domitaModules = {}; }
|
|
56
|
|
57 domitaModules.classUtils = function() {
|
|
58
|
|
59 /**
|
|
60 * Add setter and getter hooks so that the caja {@code node.innerHTML = '...'}
|
|
61 * works as expected.
|
|
62 */
|
|
63 function exportFields(object, fields) {
|
|
64 for (var i = fields.length; --i >= 0;) {
|
|
65 var field = fields[i];
|
|
66 var fieldUCamel = field.charAt(0).toUpperCase() + field.substring(1);
|
|
67 var getterName = 'get' + fieldUCamel;
|
|
68 var setterName = 'set' + fieldUCamel;
|
|
69 var count = 0;
|
|
70 if (object[getterName]) {
|
|
71 ++count;
|
|
72 ___.useGetHandler(
|
|
73 object, field, object[getterName]);
|
|
74 }
|
|
75 if (object[setterName]) {
|
|
76 ++count;
|
|
77 ___.useSetHandler(
|
|
78 object, field, object[setterName]);
|
|
79 }
|
|
80 if (!count) {
|
|
81 throw new Error('Failed to export field ' + field + ' on ' + object);
|
|
82 }
|
|
83 }
|
|
84 }
|
|
85
|
|
86 /**
|
|
87 * Makes the first a subclass of the second.
|
|
88 */
|
|
89 function extend(subClass, baseClass) {
|
|
90 var noop = function () {};
|
|
91 noop.prototype = baseClass.prototype;
|
|
92 subClass.prototype = new noop();
|
|
93 subClass.prototype.constructor = subClass;
|
|
94 }
|
|
95
|
|
96 return {
|
|
97 exportFields: exportFields,
|
|
98 extend: extend
|
|
99 };
|
|
100 };
|
|
101
|
|
102 /** XMLHttpRequest or an equivalent on IE 6. */
|
|
103 domitaModules.XMLHttpRequestCtor = function (XMLHttpRequest, ActiveXObject) {
|
|
104 if (XMLHttpRequest) {
|
|
105 return XMLHttpRequest;
|
|
106 } else if (ActiveXObject) {
|
|
107 // The first time the ctor is called, find an ActiveX class supported by
|
|
108 // this version of IE.
|
|
109 var activeXClassId;
|
|
110 return function ActiveXObjectForIE() {
|
|
111 if (activeXClassId === void 0) {
|
|
112 activeXClassId = null;
|
|
113 /** Candidate Active X types. */
|
|
114 var activeXClassIds = [
|
|
115 'MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0',
|
|
116 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP',
|
|
117 'MICROSOFT.XMLHTTP.1.0', 'MICROSOFT.XMLHTTP.1',
|
|
118 'MICROSOFT.XMLHTTP'];
|
|
119 for (var i = 0, n = activeXClassIds.length; i < n; i++) {
|
|
120 var candidate = activeXClassIds[i];
|
|
121 try {
|
|
122 void new ActiveXObject(candidate);
|
|
123 activeXClassId = candidate;
|
|
124 break;
|
|
125 } catch (e) {
|
|
126 // do nothing; try next choice
|
|
127 }
|
|
128 }
|
|
129 activeXClassIds = null;
|
|
130 }
|
|
131 return new ActiveXObject(activeXClassId);
|
|
132 };
|
|
133 } else {
|
|
134 throw new Error('ActiveXObject not available');
|
|
135 }
|
|
136 };
|
|
137
|
|
138 domitaModules.TameXMLHttpRequest = function(
|
|
139 xmlHttpRequestMaker,
|
|
140 uriCallback) {
|
|
141 var classUtils = domitaModules.classUtils();
|
|
142
|
|
143 // See http://www.w3.org/TR/XMLHttpRequest/
|
|
144
|
|
145 // TODO(ihab.awad): Improve implementation (interleaving, memory leaks)
|
|
146 // per http://www.ilinsky.com/articles/XMLHttpRequest/
|
|
147
|
|
148 function TameXMLHttpRequest() {
|
|
149 this.xhr___ = new xmlHttpRequestMaker();
|
|
150 classUtils.exportFields(
|
|
151 this,
|
|
152 ['onreadystatechange', 'readyState', 'responseText', 'responseXML',
|
|
153 'status', 'statusText']);
|
|
154 }
|
|
155 TameXMLHttpRequest.prototype.setOnreadystatechange = function (handler) {
|
|
156 // TODO(ihab.awad): Do we need more attributes of the event than 'target'?
|
|
157 // May need to implement full "tame event" wrapper similar to DOM events.
|
|
158 var self = this;
|
|
159 this.xhr___.onreadystatechange = function(event) {
|
|
160 var evt = { target: self };
|
|
161 return ___.callPub(handler, 'call', [void 0, evt]);
|
|
162 };
|
|
163 // Store for later direct invocation if need be
|
|
164 this.handler___ = handler;
|
|
165 };
|
|
166 TameXMLHttpRequest.prototype.getReadyState = function () {
|
|
167 // The ready state should be a number
|
|
168 return Number(this.xhr___.readyState);
|
|
169 };
|
|
170 TameXMLHttpRequest.prototype.open = function (
|
|
171 method, URL, opt_async, opt_userName, opt_password) {
|
|
172 method = String(method);
|
|
173 // The XHR interface does not tell us the MIME type in advance, so we
|
|
174 // must assume the broadest possible.
|
|
175 var safeUri = uriCallback.rewrite(String(URL), "*/*");
|
|
176 // If the uriCallback rejects the URL, we throw an exception, but we do not
|
|
177 // put the URI in the exception so as not to put the caller at risk of some
|
|
178 // code in its stack sniffing the URI.
|
|
179 if (safeUri === void 0) { throw 'URI violates security policy'; }
|
|
180 switch (arguments.length) {
|
|
181 case 2:
|
|
182 this.async___ = true;
|
|
183 this.xhr___.open(method, safeUri);
|
|
184 break;
|
|
185 case 3:
|
|
186 this.async___ = opt_async;
|
|
187 this.xhr___.open(method, safeUri, Boolean(opt_async));
|
|
188 break;
|
|
189 case 4:
|
|
190 this.async___ = opt_async;
|
|
191 this.xhr___.open(
|
|
192 method, safeUri, Boolean(opt_async), String(opt_userName));
|
|
193 break;
|
|
194 case 5:
|
|
195 this.async___ = opt_async;
|
|
196 this.xhr___.open(
|
|
197 method, safeUri, Boolean(opt_async), String(opt_userName),
|
|
198 String(opt_password));
|
|
199 break;
|
|
200 default:
|
|
201 throw 'XMLHttpRequest cannot accept ' + arguments.length + ' arguments';
|
|
202 break;
|
|
203 }
|
|
204 };
|
|
205 TameXMLHttpRequest.prototype.setRequestHeader = function (label, value) {
|
|
206 this.xhr___.setRequestHeader(String(label), String(value));
|
|
207 };
|
|
208 TameXMLHttpRequest.prototype.send = function(opt_data) {
|
|
209 if (arguments.length === 0) {
|
|
210 // TODO(ihab.awad): send()-ing an empty string because send() with no
|
|
211 // args does not work on FF3, others?
|
|
212 this.xhr___.send('');
|
|
213 } else if (typeof opt_data === 'string') {
|
|
214 this.xhr___.send(opt_data);
|
|
215 } else /* if XML document */ {
|
|
216 // TODO(ihab.awad): Expect tamed XML document; unwrap and send
|
|
217 this.xhr___.send('');
|
|
218 }
|
|
219
|
|
220 // Firefox does not call the 'onreadystatechange' handler in
|
|
221 // the case of a synchronous XHR. We simulate this behavior by
|
|
222 // calling the handler explicitly.
|
|
223 if (this.xhr___.overrideMimeType) {
|
|
224 // This is Firefox
|
|
225 if (!this.async___ && this.handler___) {
|
|
226 var evt = { target: this };
|
|
227 ___.callPub(this.handler___, 'call', [void 0, evt]);
|
|
228 }
|
|
229 }
|
|
230 };
|
|
231 TameXMLHttpRequest.prototype.abort = function () {
|
|
232 this.xhr___.abort();
|
|
233 };
|
|
234 TameXMLHttpRequest.prototype.getAllResponseHeaders = function () {
|
|
235 var result = this.xhr___.getAllResponseHeaders();
|
|
236 return (result === undefined || result === null) ?
|
|
237 result : String(result);
|
|
238 };
|
|
239 TameXMLHttpRequest.prototype.getResponseHeader = function (headerName) {
|
|
240 var result = this.xhr___.getResponseHeader(String(headerName));
|
|
241 return (result === undefined || result === null) ?
|
|
242 result : String(result);
|
|
243 };
|
|
244 TameXMLHttpRequest.prototype.getResponseText = function () {
|
|
245 var result = this.xhr___.responseText;
|
|
246 return (result === undefined || result === null) ?
|
|
247 result : String(result);
|
|
248 };
|
|
249 TameXMLHttpRequest.prototype.getResponseXML = function () {
|
|
250 // TODO(ihab.awad): Implement a taming layer for XML. Requires generalizing
|
|
251 // the HTML node hierarchy as well so we have a unified implementation.
|
|
252 return {};
|
|
253 };
|
|
254 TameXMLHttpRequest.prototype.getStatus = function () {
|
|
255 var result = this.xhr___.status;
|
|
256 return (result === undefined || result === null) ?
|
|
257 result : Number(result);
|
|
258 };
|
|
259 TameXMLHttpRequest.prototype.getStatusText = function () {
|
|
260 var result = this.xhr___.statusText;
|
|
261 return (result === undefined || result === null) ?
|
|
262 result : String(result);
|
|
263 };
|
|
264 TameXMLHttpRequest.prototype.toString = function () {
|
|
265 return 'Not a real XMLHttpRequest';
|
|
266 };
|
|
267 ___.ctor(TameXMLHttpRequest, void 0, 'TameXMLHttpRequest');
|
|
268 ___.all2(___.grantTypedGeneric, TameXMLHttpRequest.prototype,
|
|
269 ['open', 'setRequestHeader', 'send', 'abort',
|
|
270 'getAllResponseHeaders', 'getResponseHeader']);
|
|
271
|
|
272 return TameXMLHttpRequest;
|
|
273 };
|
|
274
|
|
275 /**
|
|
276 * Add a tamed document implementation to a Gadget's global scope.
|
|
277 *
|
|
278 * @param {string} idSuffix a string suffix appended to all node IDs.
|
|
279 * @param {Object} uriCallback an object like <pre>{
|
|
280 * rewrite: function (uri, mimeType) { return safeUri }
|
|
281 * }</pre>.
|
|
282 * The rewrite function should be idempotent to allow rewritten HTML
|
|
283 * to be reinjected.
|
|
284 * @param {Object} imports the gadget's global scope.
|
|
285 * @param {Node} pseudoBodyNode an HTML node to act as the "body" of the
|
|
286 * virtual document provided to Cajoled code.
|
|
287 * @param {Object} optPseudoWindowLocation a record containing the
|
|
288 * properties of the browser "window.location" object, which will
|
|
289 * be provided to the Cajoled code.
|
|
290 */
|
|
291 var attachDocumentStub = (function () {
|
|
292 // Array Remove - By John Resig (MIT Licensed)
|
|
293 function arrayRemove(array, from, to) {
|
|
294 var rest = array.slice((to || from) + 1 || array.length);
|
|
295 array.length = from < 0 ? array.length + from : from;
|
|
296 return array.push.apply(array, rest);
|
|
297 }
|
|
298
|
|
299 var tameNodeTrademark = cajita.Trademark('tameNode');
|
|
300 var tameEventTrademark = cajita.Trademark('tameEvent');
|
|
301
|
|
302 // Define a wrapper type for known safe HTML, and a trademarker.
|
|
303 // This does not actually use the trademarking functions since trademarks
|
|
304 // cannot be applied to strings.
|
|
305 function Html(htmlFragment) { this.html___ = String(htmlFragment || ''); }
|
|
306 Html.prototype.valueOf = Html.prototype.toString
|
|
307 = function () { return this.html___; };
|
|
308 function safeHtml(htmlFragment) {
|
|
309 return (htmlFragment instanceof Html)
|
|
310 ? htmlFragment.html___
|
|
311 : html.escapeAttrib(String(htmlFragment || ''));
|
|
312 }
|
|
313 function blessHtml(htmlFragment) {
|
|
314 return (htmlFragment instanceof Html)
|
|
315 ? htmlFragment
|
|
316 : new Html(htmlFragment);
|
|
317 }
|
|
318
|
|
319 var XML_SPACE = '\t\n\r ';
|
|
320
|
|
321 var XML_NAME_PATTERN = new RegExp(
|
|
322 '^[' + unicode.LETTER + '_:][' + unicode.LETTER + unicode.DIGIT + '.\\-_:'
|
|
323 + unicode.COMBINING_CHAR + unicode.EXTENDER + ']*$');
|
|
324
|
|
325 var XML_NMTOKEN_PATTERN = new RegExp(
|
|
326 '^[' + unicode.LETTER + unicode.DIGIT + '.\\-_:'
|
|
327 + unicode.COMBINING_CHAR + unicode.EXTENDER + ']+$');
|
|
328
|
|
329 var XML_NMTOKENS_PATTERN = new RegExp(
|
|
330 '^(?:[' + XML_SPACE + ']*[' + unicode.LETTER + unicode.DIGIT + '.\\-_:'
|
|
331 + unicode.COMBINING_CHAR + unicode.EXTENDER + ']+)+[' + XML_SPACE + ']*$'
|
|
332 );
|
|
333
|
|
334 var JS_SPACE = '\t\n\r ';
|
|
335 // An identifier that does not end with __.
|
|
336 var JS_IDENT = '(?:[a-zA-Z_][a-zA-Z0-9$_]*[a-zA-Z0-9$]|[a-zA-Z])_?';
|
|
337 var SIMPLE_HANDLER_PATTERN = new RegExp(
|
|
338 '^[' + JS_SPACE + ']*'
|
|
339 + '(return[' + JS_SPACE + ']+)?' // Group 1 is present if it returns.
|
|
340 + '(' + JS_IDENT + ')[' + JS_SPACE + ']*' // Group 2 is a function name.
|
|
341 // Which can be passed optionally this node, and optionally the event.
|
|
342 + '\\((?:this'
|
|
343 + '(?:[' + JS_SPACE + ']*,[' + JS_SPACE + ']*event)?'
|
|
344 + '[' + JS_SPACE + ']*)?\\)'
|
|
345 // And it can end with a semicolon.
|
|
346 + '[' + JS_SPACE + ']*(?:;?[' + JS_SPACE + ']*)$');
|
|
347
|
|
348 /**
|
|
349 * Coerces the string to a valid XML Name.
|
|
350 * @see http://www.w3.org/TR/2000/REC-xml-20001006#NT-Name
|
|
351 */
|
|
352 function isXmlName(s) {
|
|
353 return XML_NAME_PATTERN.test(s);
|
|
354 }
|
|
355
|
|
356 /**
|
|
357 * Coerces the string to valid XML Nmtokens
|
|
358 * @see http://www.w3.org/TR/2000/REC-xml-20001006#NT-Nmtokens
|
|
359 */
|
|
360 function isXmlNmTokens(s) {
|
|
361 return XML_NMTOKENS_PATTERN.test(s);
|
|
362 }
|
|
363
|
|
364 // Trim whitespace from the beginning and end of a CSS string.
|
|
365
|
|
366 function trimCssSpaces(input) {
|
|
367 return input.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, '');
|
|
368 }
|
|
369
|
|
370 /**
|
|
371 * The plain text equivalent of a CSS string body.
|
|
372 * @param {string} s the body of a CSS string literal w/o quotes
|
|
373 * or CSS identifier.
|
|
374 * @return {string} plain text.
|
|
375 * {@updoc
|
|
376 * $ decodeCssString('')
|
|
377 * # ''
|
|
378 * $ decodeCssString('foo')
|
|
379 * # 'foo'
|
|
380 * $ decodeCssString('foo\\\nbar\\\r\nbaz\\\rboo\\\ffar')
|
|
381 * # 'foobarbazboofar'
|
|
382 * $ decodeCssString('foo\\000a bar\\000Abaz')
|
|
383 * # 'foo' + '\n' + 'bar' + '\u0ABA' + 'z'
|
|
384 * $ decodeCssString('foo\\\\bar\\\'baz')
|
|
385 * # "foo\\bar'baz"
|
|
386 * }
|
|
387 */
|
|
388 function decodeCssString(s) {
|
|
389 // Decode a CSS String literal.
|
|
390 // From http://www.w3.org/TR/CSS21/grammar.html
|
|
391 // string1 \"([^\n\r\f\\"]|\\{nl}|{escape})*\"
|
|
392 // unicode \\{h}{1,6}(\r\n|[ \t\r\n\f])?
|
|
393 // escape {unicode}|\\[^\r\n\f0-9a-f]
|
|
394 // s [ \t\r\n\f]+
|
|
395 // nl \n|\r\n|\r|\f
|
|
396 return s.replace(
|
|
397 /\\(?:(\r\n?|\n|\f)|([0-9a-f]{1,6})(?:\r\n?|[ \t\n\f])?|(.))/gi,
|
|
398 function (_, nl, hex, esc) {
|
|
399 return esc || (nl ? '' : String.fromCharCode(parseInt(hex, 16)));
|
|
400 });
|
|
401 }
|
|
402
|
|
403 /**
|
|
404 * Sanitize the 'style' attribute value of an HTML element.
|
|
405 *
|
|
406 * @param styleAttrValue the value of a 'style' attribute, which we
|
|
407 * assume has already been checked by the caller to be a plain String.
|
|
408 *
|
|
409 * @return a sanitized version of the attribute value.
|
|
410 */
|
|
411 function sanitizeStyleAttrValue(styleAttrValue) {
|
|
412 var sanitizedDeclarations = [];
|
|
413 var declarations = styleAttrValue.split(/;/g);
|
|
414
|
|
415 for (var i = 0; declarations && i < declarations.length; i++) {
|
|
416 var parts = declarations[i].split(':');
|
|
417 var property = trimCssSpaces(parts[0]).toLowerCase();
|
|
418 var value = trimCssSpaces(parts.slice(1).join(":"));
|
|
419 if (css.properties.hasOwnProperty(property)
|
|
420 && css.properties[property].test(value + ' ')) {
|
|
421 sanitizedDeclarations.push(property + ': ' + value);
|
|
422 }
|
|
423 }
|
|
424
|
|
425 return sanitizedDeclarations.join(' ; ');
|
|
426 }
|
|
427
|
|
428 function mimeTypeForAttr(tagName, attribName) {
|
|
429 if (attribName === 'src') {
|
|
430 if (tagName === 'img') { return 'image/*'; }
|
|
431 if (tagName === 'script') { return 'text/javascript'; }
|
|
432 }
|
|
433 return '*/*';
|
|
434 }
|
|
435
|
|
436 // TODO(ihab.awad): Does this work on IE, where console output
|
|
437 // goes to a DOM node?
|
|
438 function assert(cond) {
|
|
439 if (!cond) {
|
|
440 if (typeof console !== 'undefined') {
|
|
441 console.error('domita assertion failed');
|
|
442 console.trace();
|
|
443 }
|
|
444 throw new Error();
|
|
445 }
|
|
446 }
|
|
447
|
|
448 var classUtils = domitaModules.classUtils();
|
|
449
|
|
450 var cssSealerUnsealerPair = cajita.makeSealerUnsealerPair();
|
|
451
|
|
452 // Implementations of setTimeout, setInterval, clearTimeout, and
|
|
453 // clearInterval that only allow simple functions as timeouts and
|
|
454 // that treat timeout ids as capabilities.
|
|
455 // This is safe even if accessed across frame since the same
|
|
456 // trademark value is never used with more than one version of
|
|
457 // setTimeout.
|
|
458 var timeoutIdTrademark = cajita.Trademark('timeoutId');
|
|
459 function tameSetTimeout(timeout, delayMillis) {
|
|
460 // Existing browsers treat a timeout of null or undefined as a noop.
|
|
461 var timeoutId;
|
|
462 if (timeout) {
|
|
463 if (typeof timeout === 'string') {
|
|
464 throw new Error(
|
|
465 'setTimeout called with a string.'
|
|
466 + ' Please pass a function instead of a string of javascript');
|
|
467 }
|
|
468 timeoutId = setTimeout(
|
|
469 function () { ___.callPub(timeout, 'call', [___.USELESS]); },
|
|
470 delayMillis | 0);
|
|
471 } else {
|
|
472 // tameClearTimeout checks for NaN and handles it specially.
|
|
473 timeoutId = NaN;
|
|
474 }
|
|
475 return ___.freeze(___.stamp(timeoutIdTrademark,
|
|
476 { timeoutId___: timeoutId }));
|
|
477 }
|
|
478 ___.frozenFunc(tameSetTimeout);
|
|
479 function tameClearTimeout(timeoutId) {
|
|
480 ___.guard(timeoutIdTrademark, timeoutId);
|
|
481 var rawTimeoutId = timeoutId.timeoutId___;
|
|
482 // Skip NaN values created for null timeouts above.
|
|
483 if (rawTimeoutId === rawTimeoutId) { clearTimeout(rawTimeoutId); }
|
|
484 }
|
|
485 ___.frozenFunc(tameClearTimeout);
|
|
486 var intervalIdTrademark = cajita.Trademark('intervalId');
|
|
487 function tameSetInterval(interval, delayMillis) {
|
|
488 // Existing browsers treat an interval of null or undefined as a noop.
|
|
489 var intervalId;
|
|
490 if (interval) {
|
|
491 if (typeof interval === 'string') {
|
|
492 throw new Error(
|
|
493 'setInterval called with a string.'
|
|
494 + ' Please pass a function instead of a string of javascript');
|
|
495 }
|
|
496 intervalId = setInterval(
|
|
497 function () { ___.callPub(interval, 'call', [___.USELESS]); },
|
|
498 delayMillis | 0);
|
|
499 } else {
|
|
500 intervalId = NaN;
|
|
501 }
|
|
502 return ___.freeze(___.stamp(intervalIdTrademark,
|
|
503 { intervalId___: intervalId }));
|
|
504 }
|
|
505 ___.frozenFunc(tameSetInterval);
|
|
506 function tameClearInterval(intervalId) {
|
|
507 ___.guard(intervalIdTrademark, intervalId);
|
|
508 var rawIntervalId = intervalId.intervalId___;
|
|
509 if (rawIntervalId === rawIntervalId) { clearInterval(rawIntervalId); }
|
|
510 }
|
|
511 ___.frozenFunc(tameClearInterval);
|
|
512
|
|
513 function makeScrollable(element) {
|
|
514 var overflow;
|
|
515 if (element.currentStyle) {
|
|
516 overflow = element.currentStyle.overflow;
|
|
517 } else if (window.getComputedStyle) {
|
|
518 overflow = window.getComputedStyle(element, void 0).overflow;
|
|
519 } else {
|
|
520 overflow = null;
|
|
521 }
|
|
522 switch (overflow && overflow.toLowerCase()) {
|
|
523 case 'visible':
|
|
524 case 'hidden':
|
|
525 element.style.overflow = 'auto';
|
|
526 break;
|
|
527 }
|
|
528 }
|
|
529
|
|
530 /**
|
|
531 * Moves the given pixel within the element's frame of reference as close to
|
|
532 * the top-left-most pixel of the element's viewport as possible without
|
|
533 * moving the viewport beyond the bounds of the content.
|
|
534 * @param {number} x x-coord of a pixel in the element's frame of reference.
|
|
535 * @param {number} y y-coord of a pixel in the element's frame of reference.
|
|
536 */
|
|
537 function tameScrollTo(element, x, y) {
|
|
538 if (x !== +x || y !== +y || x < 0 || y < 0) {
|
|
539 throw new Error('Cannot scroll to ' + x + ':' + typeof x + ','
|
|
540 + y + ' : ' + typeof y);
|
|
541 }
|
|
542 element.scrollLeft = x;
|
|
543 element.scrollTop = y;
|
|
544 }
|
|
545
|
|
546 /**
|
|
547 * Moves the origin of the given element's view-port by the given offset.
|
|
548 * @param {number} dx a delta in pixels.
|
|
549 * @param {number} dy a delta in pixels.
|
|
550 */
|
|
551 function tameScrollBy(element, dx, dy) {
|
|
552 if (dx !== +dx || dy !== +dy) {
|
|
553 throw new Error('Cannot scroll by ' + dx + ':' + typeof dx + ', '
|
|
554 + dy + ':' + typeof dy);
|
|
555 }
|
|
556 element.scrollLeft += dx;
|
|
557 element.scrollTop += dy;
|
|
558 }
|
|
559
|
|
560 function guessPixelsFromCss(cssStr) {
|
|
561 if (!cssStr) { return 0; }
|
|
562 var m = cssStr.match(/^([0-9]+)/);
|
|
563 return m ? +m[1] : 0;
|
|
564 }
|
|
565
|
|
566 function tameResizeTo(element, w, h) {
|
|
567 if (w !== +w || h !== +h) {
|
|
568 throw new Error('Cannot resize to ' + w + ':' + typeof w + ', '
|
|
569 + h + ':' + typeof h);
|
|
570 }
|
|
571 element.style.width = w + 'px';
|
|
572 element.style.height = h + 'px';
|
|
573 }
|
|
574
|
|
575 function tameResizeBy(element, dw, dh) {
|
|
576 if (dw !== +dw || dh !== +dh) {
|
|
577 throw new Error('Cannot resize by ' + dw + ':' + typeof dw + ', '
|
|
578 + dh + ':' + typeof dh);
|
|
579 }
|
|
580 if (!dw && !dh) { return; }
|
|
581
|
|
582 // scrollWidth is width + padding + border.
|
|
583 // offsetWidth is width + padding + border, but excluding the non-visible
|
|
584 // area.
|
|
585 // clientWidth iw width + padding, and like offsetWidth, clips to the
|
|
586 // viewport.
|
|
587 // margin does not count in any of these calculations.
|
|
588 //
|
|
589 // scrollWidth/offsetWidth
|
|
590 // +------------+
|
|
591 // | |
|
|
592 //
|
|
593 // +----------------+
|
|
594 // | | Margin-top
|
|
595 // | +------------+ |
|
|
596 // | |############| | Border-top
|
|
597 // | |#+--------+#| |
|
|
598 // | |#| |#| | Padding-top
|
|
599 // | |#| +----+ |#| |
|
|
600 // | |#| | | |#| | Height
|
|
601 // | |#| | | |#| |
|
|
602 // | |#| +----+ |#| |
|
|
603 // | |#| |#| |
|
|
604 // | |#+--------+#| |
|
|
605 // | |############| |
|
|
606 // | +------------+ |
|
|
607 // | |
|
|
608 // +----------------+
|
|
609 //
|
|
610 // | |
|
|
611 // +--------+
|
|
612 // clientWidth (but excludes content outside viewport)
|
|
613
|
|
614 var style = element.currentStyle;
|
|
615 if (!style) {
|
|
616 style = window.getComputedStyle(element, void 0);
|
|
617 }
|
|
618
|
|
619 // We guess the padding since it's not always expressed in px on IE
|
|
620 var extraHeight = guessPixelsFromCss(style.paddingBottom)
|
|
621 + guessPixelsFromCss(style.paddingTop);
|
|
622 var extraWidth = guessPixelsFromCss(style.paddingLeft)
|
|
623 + guessPixelsFromCss(style.paddingRight);
|
|
624
|
|
625 var goalHeight = element.clientHeight + dh;
|
|
626 var goalWidth = element.clientWidth + dw;
|
|
627
|
|
628 var h = goalHeight - extraHeight;
|
|
629 var w = goalWidth - extraWidth;
|
|
630
|
|
631 if (dh) { element.style.height = Math.max(0, h) + 'px'; }
|
|
632 if (dw) { element.style.width = Math.max(0, w) + 'px'; }
|
|
633
|
|
634 // Correct if our guesses re padding and borders were wrong.
|
|
635 // We may still not be able to resize if e.g. the deltas would take
|
|
636 // a dimension negative.
|
|
637 if (dh && element.clientHeight !== goalHeight) {
|
|
638 var hError = element.clientHeight - goalHeight;
|
|
639 element.style.height = Math.max(0, h - hError) + 'px';
|
|
640 }
|
|
641 if (dw && element.clientWidth !== goalWidth) {
|
|
642 var wError = element.clientWidth - goalWidth;
|
|
643 element.style.width = Math.max(0, w - wError) + 'px';
|
|
644 }
|
|
645 }
|
|
646
|
|
647 // See above for a description of this function.
|
|
648 function attachDocumentStub(
|
|
649 idSuffix, uriCallback, imports, pseudoBodyNode, optPseudoWindowLocation) {
|
|
650 if (arguments.length < 4) {
|
|
651 throw new Error('arity mismatch: ' + arguments.length);
|
|
652 }
|
|
653 if (!optPseudoWindowLocation) {
|
|
654 optPseudoWindowLocation = {};
|
|
655 }
|
|
656 var elementPolicies = {};
|
|
657 elementPolicies.form = function (attribs) {
|
|
658 // Forms must have a gated onsubmit handler or they must have an
|
|
659 // external target.
|
|
660 var sawHandler = false;
|
|
661 for (var i = 0, n = attribs.length; i < n; i += 2) {
|
|
662 if (attribs[i] === 'onsubmit') {
|
|
663 sawHandler = true;
|
|
664 }
|
|
665 }
|
|
666 if (!sawHandler) {
|
|
667 attribs.push('onsubmit', 'return false');
|
|
668 }
|
|
669 return attribs;
|
|
670 };
|
|
671 elementPolicies.a = elementPolicies.area = function (attribs) {
|
|
672 // Anchor tags must have a target.
|
|
673 attribs.push('target', '_blank');
|
|
674 return attribs;
|
|
675 };
|
|
676
|
|
677
|
|
678 /** Sanitize HTML applying the appropriate transformations. */
|
|
679 function sanitizeHtml(htmlText) {
|
|
680 var out = [];
|
|
681 htmlSanitizer(htmlText, out);
|
|
682 return out.join('');
|
|
683 }
|
|
684 var htmlSanitizer = html.makeHtmlSanitizer(
|
|
685 function sanitizeAttributes(tagName, attribs) {
|
|
686 for (var i = 0; i < attribs.length; i += 2) {
|
|
687 var attribName = attribs[i];
|
|
688 var value = attribs[i + 1];
|
|
689 var atype = null, attribKey;
|
|
690 if ((attribKey = tagName + ':' + attribName,
|
|
691 html4.ATTRIBS.hasOwnProperty(attribKey))
|
|
692 || (attribKey = '*:' + attribName,
|
|
693 html4.ATTRIBS.hasOwnProperty(attribKey))) {
|
|
694 atype = html4.ATTRIBS[attribKey];
|
|
695 value = rewriteAttribute(tagName, attribName, atype, value);
|
|
696 } else {
|
|
697 value = null;
|
|
698 }
|
|
699 if (value !== null && value !== void 0) {
|
|
700 attribs[i + 1] = value;
|
|
701 } else {
|
|
702 attribs.splice(i, 2);
|
|
703 i -= 2;
|
|
704 }
|
|
705 }
|
|
706 var policy = elementPolicies[tagName];
|
|
707 if (policy && elementPolicies.hasOwnProperty(tagName)) {
|
|
708 return policy(attribs);
|
|
709 }
|
|
710 return attribs;
|
|
711 });
|
|
712
|
|
713 /**
|
|
714 * Undoes some of the changes made by sanitizeHtml, e.g. stripping ID
|
|
715 * prefixes.
|
|
716 */
|
|
717 function tameInnerHtml(htmlText) {
|
|
718 var out = [];
|
|
719 innerHtmlTamer(htmlText, out);
|
|
720 return out.join('');
|
|
721 }
|
|
722 var innerHtmlTamer = html.makeSaxParser({
|
|
723 startTag: function (tagName, attribs, out) {
|
|
724 out.push('<', tagName);
|
|
725 for (var i = 0; i < attribs.length; i += 2) {
|
|
726 var attribName = attribs[i];
|
|
727 if (attribName === 'target') { continue; }
|
|
728 var attribKey;
|
|
729 var atype;
|
|
730 if ((attribKey = tagName + ':' + attribName,
|
|
731 html4.ATTRIBS.hasOwnProperty(attribKey))
|
|
732 || (attribKey = '*:' + attribName,
|
|
733 html4.ATTRIBS.hasOwnProperty(attribKey))) {
|
|
734 atype = html4.ATTRIBS[attribKey];
|
|
735 } else {
|
|
736 return;
|
|
737 }
|
|
738 var value = attribs[i + 1];
|
|
739 switch (atype) {
|
|
740 case html4.atype.ID:
|
|
741 case html4.atype.IDREF:
|
|
742 case html4.atype.IDREFS:
|
|
743 if (value.length <= idSuffix.length
|
|
744 || (idSuffix
|
|
745 !== value.substring(value.length - idSuffix.length))) {
|
|
746 continue;
|
|
747 }
|
|
748 value = value.substring(0, value.length - idSuffix.length);
|
|
749 break;
|
|
750 }
|
|
751 if (value !== null) {
|
|
752 out.push(' ', attribName, '="', html.escapeAttrib(value), '"');
|
|
753 }
|
|
754 }
|
|
755 out.push('>');
|
|
756 },
|
|
757 endTag: function (name, out) { out.push('</', name, '>'); },
|
|
758 pcdata: function (text, out) { out.push(text); },
|
|
759 rcdata: function (text, out) { out.push(text); },
|
|
760 cdata: function (text, out) { out.push(text); }
|
|
761 });
|
|
762
|
|
763 var illegalSuffix = /__(?:\s|$)/;
|
|
764 /**
|
|
765 * Returns a normalized attribute value, or null if the attribute should
|
|
766 * be omitted.
|
|
767 * <p>This function satisfies the attribute rewriter interface defined in
|
|
768 * {@link html-sanitizer.js}. As such, the parameters are keys into
|
|
769 * data structures defined in {@link html4-defs.js}.
|
|
770 *
|
|
771 * @param {string} tagName a canonical tag name.
|
|
772 * @param {string} attribName a canonical tag name.
|
|
773 * @param type as defined in html4-defs.js.
|
|
774 *
|
|
775 * @return {string|null} null to indicate that the attribute should not
|
|
776 * be set.
|
|
777 */
|
|
778 function rewriteAttribute(tagName, attribName, type, value) {
|
|
779 switch (type) {
|
|
780 case html4.atype.ID:
|
|
781 case html4.atype.IDREF:
|
|
782 case html4.atype.IDREFS:
|
|
783 value = String(value);
|
|
784 if (value && !illegalSuffix.test(value) && isXmlName(value)) {
|
|
785 return value + idSuffix;
|
|
786 }
|
|
787 return null;
|
|
788 case html4.atype.CLASSES:
|
|
789 case html4.atype.GLOBAL_NAME:
|
|
790 case html4.atype.LOCAL_NAME:
|
|
791 value = String(value);
|
|
792 if (value && !illegalSuffix.test(value) && isXmlNmTokens(value)) {
|
|
793 return value;
|
|
794 }
|
|
795 return null;
|
|
796 case html4.atype.SCRIPT:
|
|
797 value = String(value);
|
|
798 // Translate a handler that calls a simple function like
|
|
799 // return foo(this, event)
|
|
800
|
|
801 // TODO(mikesamuel): integrate cajita compiler to allow arbitrary
|
|
802 // cajita in event handlers.
|
|
803 var match = value.match(SIMPLE_HANDLER_PATTERN);
|
|
804 if (!match) { return null; }
|
|
805 var doesReturn = match[1];
|
|
806 var fnName = match[2];
|
|
807 var pluginId = ___.getId(imports);
|
|
808 value = (doesReturn ? 'return ' : '') + 'plugin_dispatchEvent___('
|
|
809 + 'this, event, ' + pluginId + ', "'
|
|
810 + fnName + '");';
|
|
811 if (attribName === 'onsubmit') {
|
|
812 value = 'try { ' + value + ' } finally { return false; }';
|
|
813 }
|
|
814 return value;
|
|
815 case html4.atype.URI:
|
|
816 value = String(value);
|
|
817 if (!uriCallback) { return null; }
|
|
818 // TODO(mikesamuel): determine mime type properly.
|
|
819 return uriCallback.rewrite(
|
|
820 value, mimeTypeForAttr(tagName, attribName)) || null;
|
|
821 case html4.atype.STYLE:
|
|
822 if ('function' !== typeof value) {
|
|
823 return sanitizeStyleAttrValue(String(value));
|
|
824 }
|
|
825 var cssPropertiesAndValues = cssSealerUnsealerPair.unseal(value);
|
|
826 if (!cssPropertiesAndValues) { return null; }
|
|
827
|
|
828 var css = [];
|
|
829 for (var i = 0; i < cssPropertiesAndValues.length; i += 2) {
|
|
830 var propName = cssPropertiesAndValues[i];
|
|
831 var propValue = cssPropertiesAndValues[i + 1];
|
|
832 // If the propertyName differs between DOM and CSS, there will
|
|
833 // be a semicolon between the two.
|
|
834 // E.g., 'background-color;backgroundColor'
|
|
835 // See CssTemplate.toPropertyValueList.
|
|
836 var semi = propName.indexOf(';');
|
|
837 if (semi >= 0) { propName = propName.substring(0, semi); }
|
|
838 css.push(propName + ' : ' + propValue);
|
|
839 }
|
|
840 return css.join(' ; ');
|
|
841 case html4.atype.FRAME_TARGET:
|
|
842 // Frames are ambient, so disallow reference.
|
|
843 return null;
|
|
844 default:
|
|
845 return String(value);
|
|
846 }
|
|
847 }
|
|
848
|
|
849 function makeCache() {
|
|
850 var cache = cajita.newTable(false);
|
|
851 cache.set(null, null);
|
|
852 cache.set(void 0, null);
|
|
853 return cache;
|
|
854 }
|
|
855
|
|
856 var editableTameNodeCache = makeCache();
|
|
857 var readOnlyTameNodeCache = makeCache();
|
|
858
|
|
859 /**
|
|
860 * returns a tame DOM node.
|
|
861 * @param {Node} node
|
|
862 * @param {boolean} editable
|
|
863 * @see <a href="http://www.w3.org/TR/DOM-Level-2-HTML/html.html"
|
|
864 * >DOM Level 2</a>
|
|
865 */
|
|
866 function tameNode(node, editable) {
|
|
867 if (node === null || node === void 0) { return null; }
|
|
868 // TODO(mikesamuel): make sure it really is a DOM node
|
|
869
|
|
870 var cache = editable ? editableTameNodeCache : readOnlyTameNodeCache;
|
|
871 var tamed = cache.get(node);
|
|
872 if (tamed !== void 0) {
|
|
873 return tamed;
|
|
874 }
|
|
875
|
|
876 switch (node.nodeType) {
|
|
877 case 1: // Element
|
|
878 var tagName = node.tagName.toLowerCase();
|
|
879 switch (tagName) {
|
|
880 case 'a':
|
|
881 tamed = new TameAElement(node, editable);
|
|
882 break;
|
|
883 case 'form':
|
|
884 tamed = new TameFormElement(node, editable);
|
|
885 break;
|
|
886 case 'select':
|
|
887 case 'button':
|
|
888 case 'option':
|
|
889 case 'textarea':
|
|
890 case 'input':
|
|
891 tamed = new TameInputElement(node, editable);
|
|
892 break;
|
|
893 case 'img':
|
|
894 tamed = new TameImageElement(node, editable);
|
|
895 break;
|
|
896 case 'script':
|
|
897 tamed = new TameScriptElement(node, editable);
|
|
898 break;
|
|
899 case 'td':
|
|
900 case 'tr':
|
|
901 case 'thead':
|
|
902 case 'tfoot':
|
|
903 case 'tbody':
|
|
904 case 'th':
|
|
905 tamed = new TameTableCompElement(node, editable);
|
|
906 break;
|
|
907 case 'table':
|
|
908 tamed = new TameTableElement(node, editable);
|
|
909 break;
|
|
910 default:
|
|
911 if (!html4.ELEMENTS.hasOwnProperty(tagName)
|
|
912 || (html4.ELEMENTS[tagName] & html4.eflags.UNSAFE)) {
|
|
913 // If an unrecognized or unsafe node, return a
|
|
914 // placeholder that doesn't prevent tree navigation,
|
|
915 // but that doesn't allow mutation or leak attribute
|
|
916 // information.
|
|
917 tamed = new TameOpaqueNode(node, editable);
|
|
918 } else {
|
|
919 tamed = new TameElement(node, editable, editable);
|
|
920 }
|
|
921 break;
|
|
922 }
|
|
923 break;
|
|
924 case 2: // Attr
|
|
925 tamed = new TameAttrNode(node, editable);
|
|
926 break;
|
|
927 case 3: // Text
|
|
928 tamed = new TameTextNode(node, editable);
|
|
929 break;
|
|
930 case 8: // Comment
|
|
931 tamed = new TameCommentNode(node, editable);
|
|
932 break;
|
|
933 default:
|
|
934 tamed = new TameOpaqueNode(node, editable);
|
|
935 break;
|
|
936 }
|
|
937
|
|
938 if (node.nodeType === 1) {
|
|
939 cache.set(node, tamed);
|
|
940 }
|
|
941 return tamed;
|
|
942 }
|
|
943
|
|
944 function tameRelatedNode(node, editable) {
|
|
945 if (node === null || node === void 0) { return null; }
|
|
946 // catch errors because node might be from a different domain
|
|
947 try {
|
|
948 for (var ancestor = node; ancestor; ancestor = ancestor.parentNode) {
|
|
949 // TODO(mikesamuel): replace with cursors so that subtrees are
|
|
950 // delegable.
|
|
951 // TODO: handle multiple classes.
|
|
952 if (idClass === ancestor.className) {
|
|
953 return tameNode(node, editable);
|
|
954 }
|
|
955 }
|
|
956 } catch (e) {}
|
|
957 return null;
|
|
958 }
|
|
959
|
|
960 /**
|
|
961 * Returns a NodeList like object.
|
|
962 */
|
|
963 function tameNodeList(nodeList, editable, opt_keyAttrib) {
|
|
964 var tamed = [];
|
|
965 var node;
|
|
966
|
|
967 // Work around NamedNodeMap bugs in IE, Opera, and Safari as discussed
|
|
968 // at http://code.google.com/p/google-caja/issues/detail?id=935
|
|
969 var limit = nodeList.length;
|
|
970 if (limit !== +limit) { limit = 1/0; }
|
|
971 for (var i = 0; i < limit && (node = nodeList[i]); ++i) {
|
|
972 node = tameNode(nodeList.item(i), editable);
|
|
973 tamed[i] = node;
|
|
974 // Make the node available via its name if doing so would not mask
|
|
975 // any properties of tamed.
|
|
976 var key = opt_keyAttrib && node.getAttribute(opt_keyAttrib);
|
|
977 // TODO(mikesamuel): if key in tamed, we have an ambiguous match.
|
|
978 // Include neither? This may happen with radio buttons in a form's
|
|
979 // elements list.
|
|
980 if (key && !(key.charAt(key.length - 1) === '_' || (key in tamed)
|
|
981 || key === String(key & 0x7fffffff))) {
|
|
982 tamed[key] = node;
|
|
983 }
|
|
984 }
|
|
985 node = nodeList = null;
|
|
986
|
|
987 tamed.item = ___.frozenFunc(function (k) {
|
|
988 k &= 0x7fffffff;
|
|
989 if (k !== k) { throw new Error(); }
|
|
990 return tamed[k] || null;
|
|
991 });
|
|
992 // TODO(mikesamuel): if opt_keyAttrib, could implement getNamedItem
|
|
993 return cajita.freeze(tamed);
|
|
994 }
|
|
995
|
|
996 function tameGetElementsByTagName(rootNode, tagName, editable) {
|
|
997 tagName = String(tagName);
|
|
998 if (tagName !== '*') {
|
|
999 tagName = tagName.toLowerCase();
|
|
1000 if (!___.hasOwnProp(html4.ELEMENTS, tagName)
|
|
1001 || html4.ELEMENTS[tagName] & html4.ELEMENTS.UNSAFE) {
|
|
1002 // Allowing getElementsByTagName to work for opaque element types
|
|
1003 // would leak information about those elements.
|
|
1004 return new fakeNodeList([]);
|
|
1005 }
|
|
1006 }
|
|
1007 return tameNodeList(rootNode.getElementsByTagName(tagName), editable);
|
|
1008 }
|
|
1009
|
|
1010 /**
|
|
1011 * Implements http://www.whatwg.org/specs/web-apps/current-work/#dom-document-getelementsbyclassname
|
|
1012 * using an existing implementation on browsers that have one.
|
|
1013 */
|
|
1014 function tameGetElementsByClassName(rootNode, className, editable) {
|
|
1015 className = String(className);
|
|
1016
|
|
1017 // The quotes below are taken from the HTML5 draft referenced above.
|
|
1018
|
|
1019 // "having obtained the classes by splitting a string on spaces"
|
|
1020 // Instead of using split, we use match with the global modifier so that
|
|
1021 // we don't have to remove leading and trailing spaces.
|
|
1022 var classes = className.match(/[^\t\n\f\r ]+/g);
|
|
1023
|
|
1024 // Filter out classnames in the restricted namespace.
|
|
1025 for (var i = classes ? classes.length : 0; --i >= 0;) {
|
|
1026 var classi = classes[i];
|
|
1027 if (illegalSuffix.test(classi) || !isXmlNmTokens(classi)) {
|
|
1028 classes[i] = classes[classes.length - 1];
|
|
1029 --classes.length;
|
|
1030 }
|
|
1031 }
|
|
1032
|
|
1033 if (!classes || classes.length === 0) {
|
|
1034 // "If there are no tokens specified in the argument, then the method
|
|
1035 // must return an empty NodeList" [instead of all elements]
|
|
1036 // This means that
|
|
1037 // htmlEl.ownerDocument.getElementsByClassName(htmlEl.className)
|
|
1038 // will return an HtmlCollection containing htmlElement iff
|
|
1039 // htmlEl.className contains a non-space character.
|
|
1040 return fakeNodeList([]);
|
|
1041 }
|
|
1042
|
|
1043 // "unordered set of unique space-separated tokens representing classes"
|
|
1044 if (typeof rootNode.getElementsByClassName === 'function') {
|
|
1045 return tameNodeList(
|
|
1046 rootNode.getElementsByClassName(classes.join(' ')), editable);
|
|
1047 } else {
|
|
1048 // Add spaces around each class so that we can use indexOf later to find
|
|
1049 // a match.
|
|
1050 // This use of indexOf is strictly incorrect since
|
|
1051 // http://www.whatwg.org/specs/web-apps/current-work/#reflecting-content-attributes-in-dom-attributes
|
|
1052 // does not normalize spaces in unordered sets of unique space-separated
|
|
1053 // tokens. This is not a problem since HTML5 compliant implementations
|
|
1054 // already have a getElementsByClassName implementation, and legacy
|
|
1055 // implementations do normalize according to comments on issue 935.
|
|
1056
|
|
1057 // We assume standards mode, so the HTML5 requirement that
|
|
1058 // "If the document is in quirks mode, then the comparisons for the
|
|
1059 // classes must be done in an ASCII case-insensitive manner,"
|
|
1060 // is not operative.
|
|
1061 var nClasses = classes.length;
|
|
1062 for (var i = nClasses; --i >= 0;) {
|
|
1063 classes[i] = ' ' + classes[i] + ' ';
|
|
1064 }
|
|
1065
|
|
1066 // We comply with the requirement that the result is a list
|
|
1067 // "containing all the elements in the document, in tree order,"
|
|
1068 // since the spec for getElementsByTagName has the same language.
|
|
1069 var candidates = rootNode.getElementsByTagName('*');
|
|
1070 var matches = [];
|
|
1071 var limit = candidates.length;
|
|
1072 if (limit !== +limit) { limit = 1/0; } // See issue 935
|
|
1073 candidate_loop:
|
|
1074 for (var j = 0, candidate, k = -1;
|
|
1075 j < limit && (candidate = candidates[j]);
|
|
1076 ++j) {
|
|
1077 var candidateClass = ' ' + candidate.className + ' ';
|
|
1078 for (var i = nClasses; --i >= 0;) {
|
|
1079 if (-1 === candidateClass.indexOf(classes[i])) {
|
|
1080 continue candidate_loop;
|
|
1081 }
|
|
1082 }
|
|
1083 var tamed = tameNode(candidate, editable);
|
|
1084 if (tamed) {
|
|
1085 matches[++k] = tamed;
|
|
1086 }
|
|
1087 }
|
|
1088 // "the method must return a live NodeList object"
|
|
1089 return fakeNodeList(matches);
|
|
1090 }
|
|
1091 }
|
|
1092
|
|
1093 function makeEventHandlerWrapper(thisNode, listener) {
|
|
1094 if ('function' !== typeof listener
|
|
1095 // Allow disfunctions
|
|
1096 && !('object' === (typeof listener) && listener !== null
|
|
1097 && ___.canCallPub(listener, 'call'))) {
|
|
1098 throw new Error('Expected function not ' + typeof listener);
|
|
1099 }
|
|
1100 function wrapper(event) {
|
|
1101 return plugin_dispatchEvent___(
|
|
1102 thisNode, event, ___.getId(imports), listener);
|
|
1103 }
|
|
1104 return wrapper;
|
|
1105 }
|
|
1106
|
|
1107 var NOT_EDITABLE = "Node not editable.";
|
|
1108 var INVALID_SUFFIX = "Property names may not end in '__'.";
|
|
1109 var UNSAFE_TAGNAME = "Unsafe tag name.";
|
|
1110 var UNKNOWN_TAGNAME = "Unknown tag name.";
|
|
1111
|
|
1112 // Implementation of EventTarget::addEventListener
|
|
1113 function tameAddEventListener(name, listener, useCapture) {
|
|
1114 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1115 if (!this.wrappedListeners___) { this.wrappedListeners___ = []; }
|
|
1116 useCapture = Boolean(useCapture);
|
|
1117 var wrappedListener = makeEventHandlerWrapper(this.node___, listener);
|
|
1118 wrappedListener = bridal.addEventListener(
|
|
1119 this.node___, name, wrappedListener, useCapture);
|
|
1120 wrappedListener.originalListener___ = listener;
|
|
1121 this.wrappedListeners___.push(wrappedListener);
|
|
1122 }
|
|
1123
|
|
1124 // Implementation of EventTarget::removeEventListener
|
|
1125 function tameRemoveEventListener(name, listener, useCapture) {
|
|
1126 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1127 if (!this.wrappedListeners___) { return; }
|
|
1128 var wrappedListener = null;
|
|
1129 for (var i = this.wrappedListeners___.length; --i >= 0;) {
|
|
1130 if (this.wrappedListeners___[i].originalListener___ === listener) {
|
|
1131 wrappedListener = this.wrappedListeners___[i];
|
|
1132 arrayRemove(this.wrappedListeners___, i, i);
|
|
1133 break;
|
|
1134 }
|
|
1135 }
|
|
1136 if (!wrappedListener) { return; }
|
|
1137 bridal.removeEventListener(
|
|
1138 this.node___, name, wrappedListener, useCapture);
|
|
1139 }
|
|
1140
|
|
1141 // A map of tamed node classes, keyed by DOM Level 2 standard name, which
|
|
1142 // will be exposed to the client.
|
|
1143 var nodeClasses = {};
|
|
1144
|
|
1145 var tameNodeFields = [
|
|
1146 'nodeType', 'nodeValue', 'nodeName', 'firstChild',
|
|
1147 'lastChild', 'nextSibling', 'previousSibling', 'parentNode',
|
|
1148 'ownerDocument', 'childNodes', 'attributes'];
|
|
1149
|
|
1150 /**
|
|
1151 * Base class for a Node wrapper. Do not create directly -- use the
|
|
1152 * tameNode factory instead.
|
|
1153 * @param {boolean} editable true if the node's value, attributes, children,
|
|
1154 * or custom properties are mutable.
|
|
1155 * @constructor
|
|
1156 */
|
|
1157 function TameNode(editable) {
|
|
1158 this.editable___ = editable;
|
|
1159 ___.stamp(tameNodeTrademark, this, true);
|
|
1160 classUtils.exportFields(this, tameNodeFields);
|
|
1161 }
|
|
1162 TameNode.prototype.getOwnerDocument = function () {
|
|
1163 // TODO(mikesamuel): upward navigation breaks capability discipline.
|
|
1164 if (!this.editable___ && tameDocument.editable___) {
|
|
1165 throw new Error(NOT_EDITABLE);
|
|
1166 }
|
|
1167 return tameDocument;
|
|
1168 };
|
|
1169 nodeClasses.Node = TameNode;
|
|
1170 ___.ctor(TameNode, void 0, 'TameNode');
|
|
1171 // abstract TameNode.prototype.getNodeType
|
|
1172 // abstract TameNode.prototype.getNodeName
|
|
1173 // abstract TameNode.prototype.getNodeValue
|
|
1174 // abstract TameNode.prototype.cloneNode
|
|
1175 // abstract TameNode.prototype.appendChild
|
|
1176 // abstract TameNode.prototype.insertBefore
|
|
1177 // abstract TameNode.prototype.removeChild
|
|
1178 // abstract TameNode.prototype.replaceChild
|
|
1179 // abstract TameNode.prototype.getFirstChild
|
|
1180 // abstract TameNode.prototype.getLastChild
|
|
1181 // abstract TameNode.prototype.getNextSibling
|
|
1182 // abstract TameNode.prototype.getPreviousSibling
|
|
1183 // abstract TameNode.prototype.getParentNode
|
|
1184 // abstract TameNode.prototype.getElementsByTagName
|
|
1185 // abstract TameNode.prototype.getElementsByClassName
|
|
1186 // abstract TameNode.prototype.getChildNodes
|
|
1187 // abstract TameNode.prototype.getAttributes
|
|
1188 var tameNodeMembers = [
|
|
1189 'getNodeType', 'getNodeValue', 'getNodeName', 'cloneNode',
|
|
1190 'appendChild', 'insertBefore', 'removeChild', 'replaceChild',
|
|
1191 'getFirstChild', 'getLastChild', 'getNextSibling', 'getPreviousSibling',
|
|
1192 'getElementsByClassName', 'getElementsByTagName',
|
|
1193 'getOwnerDocument',
|
|
1194 'dispatchEvent',
|
|
1195 'hasChildNodes'
|
|
1196 ];
|
|
1197
|
|
1198
|
|
1199 /**
|
|
1200 * A tame node that is backed by a real node.
|
|
1201 * @param {boolean} childrenEditable true iff the child list is mutable.
|
|
1202 * @constructor
|
|
1203 */
|
|
1204 function TameBackedNode(node, editable, childrenEditable) {
|
|
1205 if (!node) {
|
|
1206 throw new Error('Creating tame node with undefined native delegate');
|
|
1207 }
|
|
1208 this.node___ = node;
|
|
1209 this.childrenEditable___ = editable && childrenEditable;
|
|
1210 TameNode.call(this, editable);
|
|
1211 }
|
|
1212 classUtils.extend(TameBackedNode, TameNode);
|
|
1213 TameBackedNode.prototype.getNodeType = function () {
|
|
1214 return this.node___.nodeType;
|
|
1215 };
|
|
1216 TameBackedNode.prototype.getNodeName = function () {
|
|
1217 return this.node___.nodeName;
|
|
1218 };
|
|
1219 TameBackedNode.prototype.getNodeValue = function () {
|
|
1220 return this.node___.nodeValue;
|
|
1221 };
|
|
1222 TameBackedNode.prototype.cloneNode = function (deep) {
|
|
1223 var clone = bridal.cloneNode(this.node___, Boolean(deep));
|
|
1224 // From http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-3A0ED0A4
|
|
1225 // "Note that cloning an immutable subtree results in a mutable copy"
|
|
1226 return tameNode(clone, true);
|
|
1227 };
|
|
1228 TameBackedNode.prototype.appendChild = function (child) {
|
|
1229 // Child must be editable since appendChild can remove it from its parent.
|
|
1230 cajita.guard(tameNodeTrademark, child);
|
|
1231 if (!this.childrenEditable___ || !child.editable___) {
|
|
1232 throw new Error(NOT_EDITABLE);
|
|
1233 }
|
|
1234 this.node___.appendChild(child.node___);
|
|
1235 };
|
|
1236 TameBackedNode.prototype.insertBefore = function (toInsert, child) {
|
|
1237 cajita.guard(tameNodeTrademark, toInsert);
|
|
1238 if (child === void 0) { child = null; }
|
|
1239 if (child !== null) { cajita.guard(tameNodeTrademark, child); }
|
|
1240 if (!this.childrenEditable___ || !toInsert.editable___) {
|
|
1241 throw new Error(NOT_EDITABLE);
|
|
1242 }
|
|
1243 this.node___.insertBefore(
|
|
1244 toInsert.node___, child !== null ? child.node___ : null);
|
|
1245 };
|
|
1246 TameBackedNode.prototype.removeChild = function (child) {
|
|
1247 cajita.guard(tameNodeTrademark, child);
|
|
1248 if (!this.childrenEditable___ || !child.editable___) {
|
|
1249 throw new Error(NOT_EDITABLE);
|
|
1250 }
|
|
1251 this.node___.removeChild(child.node___);
|
|
1252 };
|
|
1253 TameBackedNode.prototype.replaceChild = function (child, replacement) {
|
|
1254 cajita.guard(tameNodeTrademark, child);
|
|
1255 cajita.guard(tameNodeTrademark, replacement);
|
|
1256 if (!this.childrenEditable___ || !replacement.editable___) {
|
|
1257 throw new Error(NOT_EDITABLE);
|
|
1258 }
|
|
1259 this.node___.replaceChild(child.node___, replacement.node___);
|
|
1260 };
|
|
1261 TameBackedNode.prototype.getFirstChild = function () {
|
|
1262 return tameNode(this.node___.firstChild, this.childrenEditable___);
|
|
1263 };
|
|
1264 TameBackedNode.prototype.getLastChild = function () {
|
|
1265 return tameNode(this.node___.lastChild, this.childrenEditable___);
|
|
1266 };
|
|
1267 TameBackedNode.prototype.getNextSibling = function () {
|
|
1268 // TODO(mikesamuel): replace with cursors so that subtrees are delegable
|
|
1269 return tameNode(this.node___.nextSibling, this.editable___);
|
|
1270 };
|
|
1271 TameBackedNode.prototype.getPreviousSibling = function () {
|
|
1272 // TODO(mikesamuel): replace with cursors so that subtrees are delegable
|
|
1273 return tameNode(this.node___.previousSibling, this.editable___);
|
|
1274 };
|
|
1275 TameBackedNode.prototype.getParentNode = function () {
|
|
1276 var parent = this.node___.parentNode;
|
|
1277 if (parent === tameDocument.body___) {
|
|
1278 if (tameDocument.editable___ && !this.editable___) {
|
|
1279 // FIXME: return a non-editable version of body.
|
|
1280 throw new Error(NOT_EDITABLE);
|
|
1281 }
|
|
1282 return tameDocument.getBody();
|
|
1283 }
|
|
1284 return tameRelatedNode(this.node___.parentNode, this.editable___);
|
|
1285 };
|
|
1286 TameBackedNode.prototype.getElementsByTagName = function (tagName) {
|
|
1287 return tameGetElementsByTagName(
|
|
1288 this.node___, tagName, this.childrenEditable___);
|
|
1289 };
|
|
1290 TameBackedNode.prototype.getElementsByClassName = function (className) {
|
|
1291 return tameGetElementsByClassName(
|
|
1292 this.node___, className, this.childrenEditable___);
|
|
1293 };
|
|
1294 TameBackedNode.prototype.getChildNodes = function () {
|
|
1295 return tameNodeList(this.node___.childNodes, this.childrenEditable___);
|
|
1296 };
|
|
1297 TameBackedNode.prototype.getAttributes = function () {
|
|
1298 return tameNodeList(this.node___.attributes, this.editable___);
|
|
1299 };
|
|
1300 var endsWith__ = /__$/;
|
|
1301 // TODO(erights): Come up with some notion of a keeper chain so we can
|
|
1302 // say, "let every other keeper try to handle this first".
|
|
1303 TameBackedNode.prototype.handleRead___ = function (name) {
|
|
1304 name = String(name);
|
|
1305 if (endsWith__.test(name)) { return void 0; }
|
|
1306 var handlerName = name + '_getter___';
|
|
1307 if (this[handlerName]) {
|
|
1308 return this[handlerName]();
|
|
1309 }
|
|
1310 handlerName = handlerName.toLowerCase();
|
|
1311 if (this[handlerName]) {
|
|
1312 return this[handlerName]();
|
|
1313 }
|
|
1314 if (___.hasOwnProp(this.node___.properties___, name)) {
|
|
1315 return this.node___.properties___[name];
|
|
1316 } else {
|
|
1317 return void 0;
|
|
1318 }
|
|
1319 };
|
|
1320 TameBackedNode.prototype.handleCall___ = function (name, args) {
|
|
1321 name = String(name);
|
|
1322 if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); }
|
|
1323 var handlerName = name + '_handler___';
|
|
1324 if (this[handlerName]) {
|
|
1325 return this[handlerName].call(this, args);
|
|
1326 }
|
|
1327 handlerName = handlerName.toLowerCase();
|
|
1328 if (this[handlerName]) {
|
|
1329 return this[handlerName].call(this, args);
|
|
1330 }
|
|
1331 if (___.hasOwnProp(this.node___.properties___, name)) {
|
|
1332 return this.node___.properties___[name].call(this, args);
|
|
1333 } else {
|
|
1334 throw new TypeError(name + ' is not a function.');
|
|
1335 }
|
|
1336 };
|
|
1337 TameBackedNode.prototype.handleSet___ = function (name, val) {
|
|
1338 name = String(name);
|
|
1339 if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); }
|
|
1340 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1341 var handlerName = name + '_setter___';
|
|
1342 if (this[handlerName]) {
|
|
1343 return this[handlerName](val);
|
|
1344 }
|
|
1345 handlerName = handlerName.toLowerCase();
|
|
1346 if (this[handlerName]) {
|
|
1347 return this[handlerName](val);
|
|
1348 }
|
|
1349 if (!this.node___.properties___) {
|
|
1350 this.node___.properties___ = {};
|
|
1351 }
|
|
1352 this[name + '_canEnum___'] = true;
|
|
1353 return this.node___.properties___[name] = val;
|
|
1354 };
|
|
1355 TameBackedNode.prototype.handleDelete___ = function (name) {
|
|
1356 name = String(name);
|
|
1357 if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); }
|
|
1358 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1359 var handlerName = name + '_deleter___';
|
|
1360 if (this[handlerName]) {
|
|
1361 return this[handlerName]();
|
|
1362 }
|
|
1363 handlerName = handlerName.toLowerCase();
|
|
1364 if (this[handlerName]) {
|
|
1365 return this[handlerName]();
|
|
1366 }
|
|
1367 if (this.node___.properties___) {
|
|
1368 return (
|
|
1369 delete this.node___.properties___[name]
|
|
1370 && delete this[name + '_canEnum___']);
|
|
1371 } else {
|
|
1372 return true;
|
|
1373 }
|
|
1374 };
|
|
1375 /**
|
|
1376 * @param {boolean} ownFlag ignored
|
|
1377 */
|
|
1378 TameBackedNode.prototype.handleEnum___ = function (ownFlag) {
|
|
1379 // TODO(metaweta): Add code to list all the other handled stuff we know
|
|
1380 // about.
|
|
1381 if (this.node___.properties___) {
|
|
1382 return cajita.allKeys(this.node___.properties___);
|
|
1383 }
|
|
1384 return [];
|
|
1385 };
|
|
1386 TameBackedNode.prototype.hasChildNodes = function () {
|
|
1387 return !!this.node___.hasChildNodes();
|
|
1388 };
|
|
1389 // http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget :
|
|
1390 // "The EventTarget interface is implemented by all Nodes"
|
|
1391 TameBackedNode.prototype.dispatchEvent = function dispatchEvent(evt) {
|
|
1392 cajita.guard(tameEventTrademark, evt);
|
|
1393 bridal.dispatchEvent(this.node___, evt.event___);
|
|
1394 };
|
|
1395 ___.ctor(TameBackedNode, TameNode, 'TameBackedNode');
|
|
1396 ___.all2(___.grantTypedGeneric, TameBackedNode.prototype, tameNodeMembers);
|
|
1397 if (document.documentElement.contains) { // typeof is 'object' on IE
|
|
1398 TameBackedNode.prototype.contains = function (other) {
|
|
1399 cajita.guard(tameNodeTrademark, other);
|
|
1400 var otherNode = other.node___;
|
|
1401 return this.node___.contains(otherNode);
|
|
1402 };
|
|
1403 }
|
|
1404 if ('function' ===
|
|
1405 typeof document.documentElement.compareDocumentPosition) {
|
|
1406 /**
|
|
1407 * Speced in <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition">DOM-Level-3</a>.
|
|
1408 */
|
|
1409 TameBackedNode.prototype.compareDocumentPosition = function (other) {
|
|
1410 cajita.guard(tameNodeTrademark, other);
|
|
1411 var otherNode = other.node___;
|
|
1412 if (!otherNode) { return 0; }
|
|
1413 var bitmask = +this.node___.compareDocumentPosition(otherNode);
|
|
1414 // To avoid leaking information about the relative positioning of
|
|
1415 // different roots, if neither contains the other, then we mask out
|
|
1416 // the preceding/following bits.
|
|
1417 // 0x18 is (CONTAINS | CONTAINED)
|
|
1418 // 0x1f is all the bits documented at
|
|
1419 // http://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition
|
|
1420 // except IMPLEMENTATION_SPECIFIC
|
|
1421 // 0x01 is DISCONNECTED
|
|
1422 /*
|
|
1423 if (!(bitmask & 0x18)) {
|
|
1424 // TODO: If they are not under the same virtual doc root, return
|
|
1425 // DOCUMENT_POSITION_DISCONNECTED instead of leaking information
|
|
1426 // about PRECEDING | FOLLOWING.
|
|
1427 }
|
|
1428 */
|
|
1429 // Firefox3 returns spurious PRECEDING and FOLLOWING bits for
|
|
1430 // disconnected trees.
|
|
1431 // https://bugzilla.mozilla.org/show_bug.cgi?id=486002
|
|
1432 if (bitmask & 1) {
|
|
1433 bitmask &= ~6;
|
|
1434 }
|
|
1435 return bitmask & 0x1f;
|
|
1436 };
|
|
1437 if (!___.hasOwnProp(TameBackedNode.prototype, 'contains')) {
|
|
1438 // http://www.quirksmode.org/blog/archives/2006/01/contains_for_mo.html
|
|
1439 TameBackedNode.prototype.contains = function (other) {
|
|
1440 var docPos = this.compareDocumentPosition(other);
|
|
1441 return !(!(docPos & 0x10) && docPos);
|
|
1442 };
|
|
1443 }
|
|
1444 }
|
|
1445 ___.all2(function (o, k) {
|
|
1446 if (___.hasOwnProp(o, k)) { ___.grantTypedGeneric(o, k); }
|
|
1447 }, TameBackedNode.prototype,
|
|
1448 ['contains', 'compareDocumentPosition']);
|
|
1449
|
|
1450 /**
|
|
1451 * A fake node that is not backed by a real DOM node.
|
|
1452 * @constructor
|
|
1453 */
|
|
1454 function TamePseudoNode(editable) {
|
|
1455 TameNode.call(this, editable);
|
|
1456 this.properties___ = {};
|
|
1457 }
|
|
1458 classUtils.extend(TamePseudoNode, TameNode);
|
|
1459 TamePseudoNode.prototype.appendChild =
|
|
1460 TamePseudoNode.prototype.insertBefore =
|
|
1461 TamePseudoNode.prototype.removeChild =
|
|
1462 TamePseudoNode.prototype.replaceChild = function (child) {
|
|
1463 cajita.log("Node not editable; no action performed.");
|
|
1464 };
|
|
1465 TamePseudoNode.prototype.getFirstChild = function () {
|
|
1466 var children = this.getChildNodes();
|
|
1467 return children.length ? children[0] : null;
|
|
1468 };
|
|
1469 TamePseudoNode.prototype.getLastChild = function () {
|
|
1470 var children = this.getChildNodes();
|
|
1471 return children.length ? children[children.length - 1] : null;
|
|
1472 };
|
|
1473 TamePseudoNode.prototype.getNextSibling = function () {
|
|
1474 var parentNode = this.getParentNode();
|
|
1475 if (!parentNode) { return null; }
|
|
1476 var siblings = parentNode.getChildNodes();
|
|
1477 for (var i = siblings.length - 1; --i >= 0;) {
|
|
1478 if (siblings[i] === this) { return siblings[i + 1]; }
|
|
1479 }
|
|
1480 return null;
|
|
1481 };
|
|
1482 TamePseudoNode.prototype.getPreviousSibling = function () {
|
|
1483 var parentNode = this.getParentNode();
|
|
1484 if (!parentNode) { return null; }
|
|
1485 var siblings = parentNode.getChildNodes();
|
|
1486 for (var i = siblings.length; --i >= 1;) {
|
|
1487 if (siblings[i] === this) { return siblings[i - 1]; }
|
|
1488 }
|
|
1489 return null;
|
|
1490 };
|
|
1491 TamePseudoNode.prototype.handleRead___ = function (name) {
|
|
1492 name = String(name);
|
|
1493 if (endsWith__.test(name)) { return void 0; }
|
|
1494 var handlerName = name + '_getter___';
|
|
1495 if (this[handlerName]) {
|
|
1496 return this[handlerName]();
|
|
1497 }
|
|
1498 handlerName = handlerName.toLowerCase();
|
|
1499 if (this[handlerName]) {
|
|
1500 return this[handlerName]();
|
|
1501 }
|
|
1502 if (___.hasOwnProp(this.properties___, name)) {
|
|
1503 return this.properties___[name];
|
|
1504 } else {
|
|
1505 return void 0;
|
|
1506 }
|
|
1507 };
|
|
1508 TamePseudoNode.prototype.handleCall___ = function (name, args) {
|
|
1509 name = String(name);
|
|
1510 if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); }
|
|
1511 var handlerName = name + '_handler___';
|
|
1512 if (this[handlerName]) {
|
|
1513 return this[handlerName].call(this, args);
|
|
1514 }
|
|
1515 handlerName = handlerName.toLowerCase();
|
|
1516 if (this[handlerName]) {
|
|
1517 return this[handlerName].call(this, args);
|
|
1518 }
|
|
1519 if (___.hasOwnProp(this.properties___, name)) {
|
|
1520 return this.properties___[name].call(this, args);
|
|
1521 } else {
|
|
1522 throw new TypeError(name + ' is not a function.');
|
|
1523 }
|
|
1524 };
|
|
1525 TamePseudoNode.prototype.handleSet___ = function (name, val) {
|
|
1526 name = String(name);
|
|
1527 if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); }
|
|
1528 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1529 var handlerName = name + '_setter___';
|
|
1530 if (this[handlerName]) {
|
|
1531 return this[handlerName](val);
|
|
1532 }
|
|
1533 handlerName = handlerName.toLowerCase();
|
|
1534 if (this[handlerName]) {
|
|
1535 return this[handlerName](val);
|
|
1536 }
|
|
1537 if (!this.properties___) {
|
|
1538 this.properties___ = {};
|
|
1539 }
|
|
1540 this[name + '_canEnum___'] = true;
|
|
1541 return this.properties___[name] = val;
|
|
1542 };
|
|
1543 TamePseudoNode.prototype.handleDelete___ = function (name) {
|
|
1544 name = String(name);
|
|
1545 if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); }
|
|
1546 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1547 var handlerName = name + '_deleter___';
|
|
1548 if (this[handlerName]) {
|
|
1549 return this[handlerName]();
|
|
1550 }
|
|
1551 handlerName = handlerName.toLowerCase();
|
|
1552 if (this[handlerName]) {
|
|
1553 return this[handlerName]();
|
|
1554 }
|
|
1555 if (this.properties___) {
|
|
1556 return (
|
|
1557 delete this.properties___[name]
|
|
1558 && delete this[name + '_canEnum___']);
|
|
1559 } else {
|
|
1560 return true;
|
|
1561 }
|
|
1562 };
|
|
1563 TamePseudoNode.prototype.handleEnum___ = function (ownFlag) {
|
|
1564 // TODO(metaweta): Add code to list all the other handled stuff we know
|
|
1565 // about.
|
|
1566 if (this.properties___) {
|
|
1567 return cajita.allKeys(this.properties___);
|
|
1568 }
|
|
1569 return [];
|
|
1570 };
|
|
1571 TamePseudoNode.prototype.hasChildNodes = function () {
|
|
1572 return this.getFirstChild() != null;
|
|
1573 };
|
|
1574 ___.ctor(TamePseudoNode, TameNode, 'TamePseudoNode');
|
|
1575 ___.all2(___.grantTypedGeneric, TamePseudoNode.prototype, tameNodeMembers);
|
|
1576
|
|
1577
|
|
1578 function TamePseudoElement(
|
|
1579 tagName, tameDoc, childNodesGetter, parentNodeGetter, innerHTMLGetter,
|
|
1580 geometryDelegate, editable) {
|
|
1581 TamePseudoNode.call(this, editable);
|
|
1582 this.tagName___ = tagName;
|
|
1583 this.tameDoc___ = tameDoc;
|
|
1584 this.childNodesGetter___ = childNodesGetter;
|
|
1585 this.parentNodeGetter___ = parentNodeGetter;
|
|
1586 this.innerHTMLGetter___ = innerHTMLGetter;
|
|
1587 this.geometryDelegate___ = geometryDelegate;
|
|
1588 classUtils.exportFields(this, ['tagName', 'innerHTML']);
|
|
1589 }
|
|
1590 classUtils.extend(TamePseudoElement, TamePseudoNode);
|
|
1591 // TODO(mikesamuel): make nodeClasses work.
|
|
1592 TamePseudoElement.prototype.getNodeType = function () { return 1; };
|
|
1593 TamePseudoElement.prototype.getNodeName
|
|
1594 = function () { return this.tagName___; };
|
|
1595 TamePseudoElement.prototype.getTagName
|
|
1596 = function () { return this.tagName___; };
|
|
1597 TamePseudoElement.prototype.getNodeValue = function () { return null; };
|
|
1598 TamePseudoElement.prototype.getAttribute
|
|
1599 = function (attribName) { return null; };
|
|
1600 TamePseudoElement.prototype.setAttribute
|
|
1601 = function (attribName, value) { };
|
|
1602 TamePseudoElement.prototype.hasAttribute
|
|
1603 = function (attribName) { return false; };
|
|
1604 TamePseudoElement.prototype.removeAttribute
|
|
1605 = function (attribName) { };
|
|
1606 TamePseudoElement.prototype.getOwnerDocument
|
|
1607 = function () { return this.tameDoc___; };
|
|
1608 TamePseudoElement.prototype.getChildNodes
|
|
1609 = function () { return this.childNodesGetter___(); };
|
|
1610 TamePseudoElement.prototype.getAttributes
|
|
1611 = function () { return tameNodeList([], false); };
|
|
1612 TamePseudoElement.prototype.getParentNode
|
|
1613 = function () { return this.parentNodeGetter___(); };
|
|
1614 TamePseudoElement.prototype.getInnerHTML
|
|
1615 = function () { return this.innerHTMLGetter___(); };
|
|
1616 TamePseudoElement.prototype.getElementsByTagName = function (tagName) {
|
|
1617 tagName = String(tagName).toLowerCase();
|
|
1618 if (tagName === this.tagName___) {
|
|
1619 // Works since html, head, body, and title can't contain themselves.
|
|
1620 return fakeNodeList([]);
|
|
1621 }
|
|
1622 return this.getOwnerDocument().getElementsByTagName(tagName);
|
|
1623 };
|
|
1624 TamePseudoElement.prototype.getElementsByClassName = function (className) {
|
|
1625 return this.getOwnerDocument().getElementsByClassName(className);
|
|
1626 };
|
|
1627 TamePseudoElement.prototype.getBoundingClientRect = function () {
|
|
1628 return this.geometryDelegate___.getBoundingClientRect();
|
|
1629 };
|
|
1630 TamePseudoElement.prototype.getGeometryDelegate___ = function () {
|
|
1631 return this.geometryDelegate___;
|
|
1632 };
|
|
1633 TamePseudoElement.prototype.toString = function () {
|
|
1634 return '<' + this.tagName___ + '>';
|
|
1635 };
|
|
1636 ___.ctor(TamePseudoElement, TamePseudoNode, 'TamePseudoElement');
|
|
1637 ___.all2(___.grantTypedGeneric, TamePseudoElement.prototype,
|
|
1638 ['getTagName', 'getAttribute', 'setAttribute',
|
|
1639 'hasAttribute', 'removeAttribute',
|
|
1640 'getBoundingClientRect', 'getElementsByTagName']);
|
|
1641
|
|
1642 function TameOpaqueNode(node, editable) {
|
|
1643 TameBackedNode.call(this, node, editable, editable);
|
|
1644 }
|
|
1645 classUtils.extend(TameOpaqueNode, TameBackedNode);
|
|
1646 TameOpaqueNode.prototype.getNodeValue
|
|
1647 = TameBackedNode.prototype.getNodeValue;
|
|
1648 TameOpaqueNode.prototype.getNodeType
|
|
1649 = TameBackedNode.prototype.getNodeType;
|
|
1650 TameOpaqueNode.prototype.getNodeName
|
|
1651 = TameBackedNode.prototype.getNodeName;
|
|
1652 TameOpaqueNode.prototype.getNextSibling
|
|
1653 = TameBackedNode.prototype.getNextSibling;
|
|
1654 TameOpaqueNode.prototype.getPreviousSibling
|
|
1655 = TameBackedNode.prototype.getPreviousSibling;
|
|
1656 TameOpaqueNode.prototype.getFirstChild
|
|
1657 = TameBackedNode.prototype.getFirstChild;
|
|
1658 TameOpaqueNode.prototype.getLastChild
|
|
1659 = TameBackedNode.prototype.getLastChild;
|
|
1660 TameOpaqueNode.prototype.getParentNode
|
|
1661 = TameBackedNode.prototype.getParentNode;
|
|
1662 TameOpaqueNode.prototype.getChildNodes
|
|
1663 = TameBackedNode.prototype.getChildNodes;
|
|
1664 TameOpaqueNode.prototype.getAttributes
|
|
1665 = function () { return tameNodeList([], false); };
|
|
1666 for (var i = tameNodeMembers.length; --i >= 0;) {
|
|
1667 var k = tameNodeMembers[i];
|
|
1668 if (!TameOpaqueNode.prototype.hasOwnProperty(k)) {
|
|
1669 TameOpaqueNode.prototype[k] = ___.frozenFunc(function () {
|
|
1670 throw new Error('Node is opaque');
|
|
1671 });
|
|
1672 }
|
|
1673 }
|
|
1674 ___.all2(___.grantTypedGeneric, TameOpaqueNode.prototype, tameNodeMembers);
|
|
1675
|
|
1676 function TameAttrNode(node, editable) {
|
|
1677 assert(node.nodeType === 2);
|
|
1678 TameBackedNode.call(this, node, editable, editable);
|
|
1679 classUtils.exportFields(
|
|
1680 this, ['name', 'nodeValue', 'value', 'specified']);
|
|
1681 }
|
|
1682 classUtils.extend(TameAttrNode, TameBackedNode);
|
|
1683 nodeClasses.Attr = TameAttrNode;
|
|
1684 TameAttrNode.prototype.setNodeValue = function (value) {
|
|
1685 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1686 this.node___.nodeValue = String(value || '');
|
|
1687 return value;
|
|
1688 };
|
|
1689 TameAttrNode.prototype.getName = TameAttrNode.prototype.getNodeName;
|
|
1690 TameAttrNode.prototype.getValue = TameAttrNode.prototype.getNodeValue;
|
|
1691 TameAttrNode.prototype.setValue = TameAttrNode.prototype.setNodeValue;
|
|
1692 TameAttrNode.prototype.getSpecified = function () {
|
|
1693 return this.node___.specified;
|
|
1694 };
|
|
1695 TameAttrNode.prototype.toString = function () {
|
|
1696 return '#attr';
|
|
1697 };
|
|
1698 ___.ctor(TameAttrNode, TameBackedNode, 'TameAttrNode');
|
|
1699
|
|
1700 function TameTextNode(node, editable) {
|
|
1701 assert(node.nodeType === 3);
|
|
1702
|
|
1703 // The below should not be strictly necessary since childrenEditable for
|
|
1704 // TameScriptElements is always false, but it protects against tameNode
|
|
1705 // being called naively on a text node from container code.
|
|
1706 var pn = node.parentNode;
|
|
1707 if (editable && pn) {
|
|
1708 if (1 === pn.nodeType
|
|
1709 && (html4.ELEMENTS[pn.tagName.toLowerCase()]
|
|
1710 & html4.eflags.UNSAFE)) {
|
|
1711 // Do not allow mutation of text inside script elements.
|
|
1712 // See the testScriptLoading testcase for examples of exploits.
|
|
1713 editable = false;
|
|
1714 }
|
|
1715 }
|
|
1716
|
|
1717 TameBackedNode.call(this, node, editable, editable);
|
|
1718 classUtils.exportFields(this, ['nodeValue', 'data']);
|
|
1719 }
|
|
1720 classUtils.extend(TameTextNode, TameBackedNode);
|
|
1721 nodeClasses.Text = TameTextNode;
|
|
1722 TameTextNode.prototype.setNodeValue = function (value) {
|
|
1723 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1724 this.node___.nodeValue = String(value || '');
|
|
1725 return value;
|
|
1726 };
|
|
1727 TameTextNode.prototype.getData = TameTextNode.prototype.getNodeValue;
|
|
1728 TameTextNode.prototype.setData = TameTextNode.prototype.setNodeValue;
|
|
1729 TameTextNode.prototype.toString = function () {
|
|
1730 return '#text';
|
|
1731 };
|
|
1732 ___.ctor(TameTextNode, TameBackedNode, 'TameTextNode');
|
|
1733 ___.all2(___.grantTypedGeneric, TameTextNode.prototype,
|
|
1734 ['setNodeValue', 'getData', 'setData']);
|
|
1735
|
|
1736 function TameCommentNode(node, editable) {
|
|
1737 assert(node.nodeType === 8);
|
|
1738 TameBackedNode.call(this, node, editable, editable);
|
|
1739 }
|
|
1740 classUtils.extend(TameCommentNode, TameBackedNode);
|
|
1741 nodeClasses.CommentNode = TameCommentNode;
|
|
1742 TameCommentNode.prototype.toString = function () {
|
|
1743 return '#comment';
|
|
1744 };
|
|
1745 ___.ctor(TameCommentNode, TameBackedNode, 'TameCommentNode');
|
|
1746
|
|
1747 function getAttributeType(tagName, attribName) {
|
|
1748 var attribKey;
|
|
1749 attribKey = tagName + ':' + attribName;
|
|
1750 if (html4.ATTRIBS.hasOwnProperty(attribKey)) {
|
|
1751 return html4.ATTRIBS[attribKey];
|
|
1752 }
|
|
1753 attribKey = '*:' + attribName;
|
|
1754 if (html4.ATTRIBS.hasOwnProperty(attribKey)) {
|
|
1755 return html4.ATTRIBS[attribKey];
|
|
1756 }
|
|
1757 return void 0;
|
|
1758 }
|
|
1759
|
|
1760 /**
|
|
1761 * Plays the role of an Attr node for TameElement objects.
|
|
1762 */
|
|
1763 function TameBackedAttributeNode(elem, name){
|
|
1764 TameNode.call(this, false);
|
|
1765 classUtils.exportFields(this,
|
|
1766 ['name', 'specified', 'value', 'ownerElement']);
|
|
1767 this.name___ = name;
|
|
1768 this.ownerElement___ = elem;
|
|
1769 }
|
|
1770 classUtils.extend(TameBackedAttributeNode, TameNode);
|
|
1771 ___.ctor(TameBackedAttributeNode, TameNode, 'TameBackedAttributeNode');
|
|
1772 TameBackedAttributeNode.prototype.getNodeName =
|
|
1773 TameBackedAttributeNode.prototype.getName =
|
|
1774 function () { return this.name___; };
|
|
1775 TameBackedAttributeNode.prototype.getSpecified =
|
|
1776 function () { return this.ownerElement___.hasAttribute(this.name___); };
|
|
1777 TameBackedAttributeNode.prototype.getNodeValue =
|
|
1778 TameBackedAttributeNode.prototype.getValue =
|
|
1779 function () { return this.ownerElement___.getAttribute(this.name___); };
|
|
1780 TameBackedAttributeNode.prototype.getOwnerElement =
|
|
1781 function () { return this.ownerElement___; };
|
|
1782 TameBackedAttributeNode.prototype.getNodeType = function () { return 2; };
|
|
1783 TameBackedAttributeNode.prototype.cloneNode = function () {
|
|
1784 return new TameBackedAttributeNode(this.ownerElement___, this.name___);
|
|
1785 };
|
|
1786 TameBackedAttributeNode.prototype.appendChild =
|
|
1787 TameBackedAttributeNode.prototype.insertBefore =
|
|
1788 TameBackedAttributeNode.prototype.removeChild =
|
|
1789 TameBackedAttributeNode.prototype.replaceChild =
|
|
1790 TameBackedAttributeNode.prototype.getFirstChild =
|
|
1791 TameBackedAttributeNode.prototype.getLastChild =
|
|
1792 TameBackedAttributeNode.prototype.getNextSibling =
|
|
1793 TameBackedAttributeNode.prototype.getPreviousSibling =
|
|
1794 TameBackedAttributeNode.prototype.getParentNode =
|
|
1795 TameBackedAttributeNode.prototype.getElementsByTagName =
|
|
1796 TameBackedAttributeNode.prototype.getElementsByClassName =
|
|
1797 TameBackedAttributeNode.prototype.getChildNodes =
|
|
1798 TameBackedAttributeNode.prototype.getAttributes = function () {
|
|
1799 throw new Error ("Not implemented.");
|
|
1800 };
|
|
1801
|
|
1802 function TameElement(node, editable, childrenEditable) {
|
|
1803 assert(node.nodeType === 1);
|
|
1804 TameBackedNode.call(this, node, editable, childrenEditable);
|
|
1805 classUtils.exportFields(
|
|
1806 this,
|
|
1807 ['className', 'id', 'innerHTML', 'tagName', 'style',
|
|
1808 'offsetParent', 'title', 'dir']);
|
|
1809 }
|
|
1810 classUtils.extend(TameElement, TameBackedNode);
|
|
1811 nodeClasses.Element = nodeClasses.HTMLElement = TameElement;
|
|
1812 TameElement.prototype.getId = function () {
|
|
1813 return this.getAttribute('id') || '';
|
|
1814 };
|
|
1815 TameElement.prototype.setId = function (newId) {
|
|
1816 return this.setAttribute('id', newId);
|
|
1817 };
|
|
1818 TameElement.prototype.getAttribute = function (attribName) {
|
|
1819 attribName = String(attribName).toLowerCase();
|
|
1820 var tagName = this.node___.tagName.toLowerCase();
|
|
1821 var atype = getAttributeType(tagName, attribName);
|
|
1822 if (atype === void 0) {
|
|
1823 // Unrecognized attribute; use virtual map
|
|
1824 if (this.node___.attributes___) {
|
|
1825 return this.node___.attributes___[attribName] || null;
|
|
1826 }
|
|
1827 return null;
|
|
1828 }
|
|
1829 var value = bridal.getAttribute(this.node___, attribName);
|
|
1830 if ('string' !== typeof value) { return value; }
|
|
1831 switch (atype) {
|
|
1832 case html4.atype.ID:
|
|
1833 case html4.atype.IDREF:
|
|
1834 case html4.atype.IDREFS:
|
|
1835 if (!value) { return null; }
|
|
1836 var n = idSuffix.length;
|
|
1837 var len = value.length;
|
|
1838 var end = len - n;
|
|
1839 if (end > 0 && idSuffix === value.substring(end, len)) {
|
|
1840 return value.substring(0, end);
|
|
1841 }
|
|
1842 return null;
|
|
1843 default:
|
|
1844 if ('' === value) {
|
|
1845 // IE creates attribute nodes for any attribute in the HTML schema
|
|
1846 // so even when they are deleted, there will be a value, usually
|
|
1847 // the empty string.
|
|
1848 var attr = bridal.getAttributeNode(this.node___, attribName);
|
|
1849 if (attr && !attr.specified) { return null; }
|
|
1850 }
|
|
1851 return value;
|
|
1852 }
|
|
1853 };
|
|
1854 TameElement.prototype.getAttributeNode = function (name) {
|
|
1855 return new TameBackedAttributeNode(this, name);
|
|
1856 };
|
|
1857 TameElement.prototype.hasAttribute = function (attribName) {
|
|
1858 attribName = String(attribName).toLowerCase();
|
|
1859 var tagName = this.node___.tagName.toLowerCase();
|
|
1860 var atype = getAttributeType(tagName, attribName);
|
|
1861 if (atype === void 0) {
|
|
1862 // Unrecognized attribute; use virtual map
|
|
1863 return !!(
|
|
1864 this.node___.attributes___ &&
|
|
1865 ___.hasOwnProp(this.node___.attributes___, attribName));
|
|
1866 } else {
|
|
1867 return bridal.hasAttribute(this.node___, attribName);
|
|
1868 }
|
|
1869 };
|
|
1870 TameElement.prototype.setAttribute = function (attribName, value) {
|
|
1871 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1872 attribName = String(attribName).toLowerCase();
|
|
1873 var tagName = this.node___.tagName.toLowerCase();
|
|
1874 var atype = getAttributeType(tagName, attribName);
|
|
1875 if (atype === void 0) {
|
|
1876 // Unrecognized attribute; use virtual map
|
|
1877 if (!this.node___.attributes___) { this.node___.attributes___ = {}; }
|
|
1878 this.node___.attributes___[attribName] = String(value);
|
|
1879 } else {
|
|
1880 var sanitizedValue = rewriteAttribute(
|
|
1881 tagName, attribName, atype, value);
|
|
1882 if (sanitizedValue !== null) {
|
|
1883 bridal.setAttribute(this.node___, attribName, sanitizedValue);
|
|
1884 }
|
|
1885 }
|
|
1886 return value;
|
|
1887 };
|
|
1888 TameElement.prototype.removeAttribute = function (attribName) {
|
|
1889 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1890 attribName = String(attribName).toLowerCase();
|
|
1891 var tagName = this.node___.tagName.toLowerCase();
|
|
1892 var atype = getAttributeType(tagName, attribName);
|
|
1893 if (atype === void 0) {
|
|
1894 // Unrecognized attribute; use virtual map
|
|
1895 if (this.node___.attributes___) {
|
|
1896 delete this.node___.attributes___[attribName];
|
|
1897 }
|
|
1898 } else {
|
|
1899 this.node___.removeAttribute(attribName);
|
|
1900 }
|
|
1901 };
|
|
1902 TameElement.prototype.getBoundingClientRect = function () {
|
|
1903 var elRect = bridal.getBoundingClientRect(this.node___);
|
|
1904 var vbody = bridal.getBoundingClientRect(this.getOwnerDocument().body___);
|
|
1905 var vbodyLeft = vbody.left, vbodyTop = vbody.top;
|
|
1906 return ({
|
|
1907 top: elRect.top - vbodyTop,
|
|
1908 left: elRect.left - vbodyLeft,
|
|
1909 right: elRect.right - vbodyLeft,
|
|
1910 bottom: elRect.bottom - vbodyTop
|
|
1911 });
|
|
1912 };
|
|
1913 TameElement.prototype.getClassName = function () {
|
|
1914 return this.getAttribute('class') || '';
|
|
1915 };
|
|
1916 TameElement.prototype.setClassName = function (classes) {
|
|
1917 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1918 return this.setAttribute('class', String(classes));
|
|
1919 };
|
|
1920 TameElement.prototype.getTitle = function () {
|
|
1921 return this.getAttribute('title') || '';
|
|
1922 };
|
|
1923 TameElement.prototype.setTitle = function (classes) {
|
|
1924 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1925 return this.setAttribute('title', String(classes));
|
|
1926 };
|
|
1927 TameElement.prototype.getDir = function () {
|
|
1928 return this.getAttribute('dir') || '';
|
|
1929 };
|
|
1930 TameElement.prototype.setDir = function (classes) {
|
|
1931 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1932 return this.setAttribute('dir', String(classes));
|
|
1933 };
|
|
1934 TameElement.prototype.getTagName = TameBackedNode.prototype.getNodeName;
|
|
1935 TameElement.prototype.getInnerHTML = function () {
|
|
1936 var tagName = this.node___.tagName.toLowerCase();
|
|
1937 if (!html4.ELEMENTS.hasOwnProperty(tagName)) {
|
|
1938 return ''; // unknown node
|
|
1939 }
|
|
1940 var flags = html4.ELEMENTS[tagName];
|
|
1941 var innerHtml = this.node___.innerHTML;
|
|
1942 if (flags & html4.eflags.CDATA) {
|
|
1943 innerHtml = html.escapeAttrib(innerHtml);
|
|
1944 } else if (flags & html4.eflags.RCDATA) {
|
|
1945 // Make sure we return PCDATA.
|
|
1946 // For RCDATA we only need to escape & if they're not part of an entity.
|
|
1947 innerHtml = html.normalizeRCData(innerHtml);
|
|
1948 } else {
|
|
1949 // If we blessed the resulting HTML, then this would round trip better
|
|
1950 // but it would still not survive appending, and it would propagate
|
|
1951 // event handlers where the setter of innerHTML does not expect it to.
|
|
1952 innerHtml = tameInnerHtml(innerHtml);
|
|
1953 }
|
|
1954 return innerHtml;
|
|
1955 };
|
|
1956 TameElement.prototype.setInnerHTML = function (htmlFragment) {
|
|
1957 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1958 var tagName = this.node___.tagName.toLowerCase();
|
|
1959 if (!html4.ELEMENTS.hasOwnProperty(tagName)) { throw new Error(); }
|
|
1960 var flags = html4.ELEMENTS[tagName];
|
|
1961 if (flags & html4.eflags.UNSAFE) { throw new Error(); }
|
|
1962 var sanitizedHtml;
|
|
1963 if (flags & html4.eflags.RCDATA) {
|
|
1964 sanitizedHtml = html.normalizeRCData(String(htmlFragment || ''));
|
|
1965 } else {
|
|
1966 sanitizedHtml = (htmlFragment instanceof Html
|
|
1967 ? safeHtml(htmlFragment)
|
|
1968 : sanitizeHtml(String(htmlFragment || '')));
|
|
1969 }
|
|
1970 this.node___.innerHTML = sanitizedHtml;
|
|
1971 return htmlFragment;
|
|
1972 };
|
|
1973 TameElement.prototype.setStyle = function (style) {
|
|
1974 this.setAttribute('style', style);
|
|
1975 return this.getStyle();
|
|
1976 };
|
|
1977 TameElement.prototype.getStyle = function () {
|
|
1978 return new TameStyle(this.node___.style, this.editable___);
|
|
1979 };
|
|
1980 TameElement.prototype.updateStyle = function (style) {
|
|
1981 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
1982 var cssPropertiesAndValues = cssSealerUnsealerPair.unseal(style);
|
|
1983 if (!cssPropertiesAndValues) { throw new Error(); }
|
|
1984
|
|
1985 var styleNode = this.node___.style;
|
|
1986 for (var i = 0; i < cssPropertiesAndValues.length; i += 2) {
|
|
1987 var propName = cssPropertiesAndValues[i];
|
|
1988 var propValue = cssPropertiesAndValues[i + 1];
|
|
1989 // If the propertyName differs between DOM and CSS, there will
|
|
1990 // be a semicolon between the two.
|
|
1991 // E.g., 'background-color;backgroundColor'
|
|
1992 // See CssTemplate.toPropertyValueList.
|
|
1993 var semi = propName.indexOf(';');
|
|
1994 if (semi >= 0) { propName = propName.substring(semi + 1); }
|
|
1995 styleNode[propName] = propValue;
|
|
1996 }
|
|
1997 };
|
|
1998
|
|
1999 TameElement.prototype.getOffsetParent = function () {
|
|
2000 return tameRelatedNode(this.node___.offsetParent, this.editable___);
|
|
2001 };
|
|
2002 TameElement.prototype.getGeometryDelegate___ = function () {
|
|
2003 return this.node___;
|
|
2004 };
|
|
2005 TameElement.prototype.toString = function () {
|
|
2006 return '<' + this.node___.tagName + '>';
|
|
2007 };
|
|
2008 TameElement.prototype.addEventListener = tameAddEventListener;
|
|
2009 TameElement.prototype.removeEventListener = tameRemoveEventListener;
|
|
2010 ___.ctor(TameElement, TameBackedNode, 'TameElement');
|
|
2011 ___.all2(
|
|
2012 ___.grantTypedGeneric, TameElement.prototype,
|
|
2013 ['addEventListener', 'removeEventListener',
|
|
2014 'getAttribute', 'setAttribute',
|
|
2015 'removeAttribute', 'hasAttribute',
|
|
2016 'getAttributeNode',
|
|
2017 'getBoundingClientRect',
|
|
2018 'getClassName', 'setClassName', 'getId', 'setId',
|
|
2019 'getInnerHTML', 'setInnerHTML', 'updateStyle', 'getStyle', 'setStyle',
|
|
2020 'getTagName']);
|
|
2021
|
|
2022 cajita.forOwnKeys({
|
|
2023 clientWidth: {
|
|
2024 get: function () { return this.getGeometryDelegate___().clientWidth; }
|
|
2025 },
|
|
2026 clientHeight: {
|
|
2027 get: function () { return this.getGeometryDelegate___().clientHeight; }
|
|
2028 },
|
|
2029 offsetLeft: {
|
|
2030 get: function () { return this.getGeometryDelegate___().offsetLeft; }
|
|
2031 },
|
|
2032 offsetTop: {
|
|
2033 get: function () { return this.getGeometryDelegate___().offsetTop; }
|
|
2034 },
|
|
2035 offsetWidth: {
|
|
2036 get: function () { return this.getGeometryDelegate___().offsetWidth; }
|
|
2037 },
|
|
2038 offsetHeight: {
|
|
2039 get: function () { return this.getGeometryDelegate___().offsetHeight; }
|
|
2040 },
|
|
2041 scrollLeft: {
|
|
2042 get: function () { return this.getGeometryDelegate___().scrollLeft; },
|
|
2043 set: function (x) {
|
|
2044 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2045 this.getGeometryDelegate___().scrollLeft = +x;
|
|
2046 return x;
|
|
2047 }
|
|
2048 },
|
|
2049 scrollTop: {
|
|
2050 get: function () { return this.getGeometryDelegate___().scrollTop; },
|
|
2051 set: function (y) {
|
|
2052 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2053 this.getGeometryDelegate___().scrollTop = +y;
|
|
2054 return y;
|
|
2055 }
|
|
2056 },
|
|
2057 scrollWidth: {
|
|
2058 get: function () { return this.getGeometryDelegate___().scrollWidth; }
|
|
2059 },
|
|
2060 scrollHeight: {
|
|
2061 get: function () { return this.getGeometryDelegate___().scrollHeight; }
|
|
2062 }
|
|
2063 }, ___.func(function (propertyName, def) {
|
|
2064 var setter = def.set || propertyOnlyHasGetter;
|
|
2065 ___.useGetHandler(TameElement.prototype, propertyName, def.get);
|
|
2066 ___.useSetHandler(TameElement.prototype, propertyName, setter);
|
|
2067 ___.useGetHandler(TamePseudoElement.prototype, propertyName, def.get);
|
|
2068 ___.useSetHandler(TamePseudoElement.prototype, propertyName, setter);
|
|
2069 }));
|
|
2070
|
|
2071 // Register set handlers for onclick, onmouseover, etc.
|
|
2072 (function () {
|
|
2073 var attrNameRe = /:(.*)/;
|
|
2074 for (var html4Attrib in html4.ATTRIBS) {
|
|
2075 if (html4.atype.SCRIPT === html4.ATTRIBS[html4Attrib]) {
|
|
2076 (function (attribName) {
|
|
2077 ___.useSetHandler(
|
|
2078 TameElement.prototype,
|
|
2079 attribName,
|
|
2080 function eventHandlerSetter(listener) {
|
|
2081 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2082 if (!listener) { // Clear the current handler
|
|
2083 this.node___[attribName] = null;
|
|
2084 } else {
|
|
2085 // This handler cannot be copied from one node to another
|
|
2086 // which is why getters are not yet supported.
|
|
2087 this.node___[attribName] = makeEventHandlerWrapper(
|
|
2088 this.node___, listener);
|
|
2089 }
|
|
2090 return listener;
|
|
2091 });
|
|
2092 })(html4Attrib.match(attrNameRe)[1]);
|
|
2093 }
|
|
2094 }
|
|
2095 })();
|
|
2096
|
|
2097 function TameAElement(node, editable) {
|
|
2098 TameElement.call(this, node, editable, editable);
|
|
2099 classUtils.exportFields(this, ['href']);
|
|
2100 }
|
|
2101 classUtils.extend(TameAElement, TameElement);
|
|
2102 nodeClasses.HTMLAnchorElement = TameAElement;
|
|
2103 TameAElement.prototype.focus = function () {
|
|
2104 this.node___.focus();
|
|
2105 };
|
|
2106 TameAElement.prototype.getHref = function () {
|
|
2107 return this.node___.href;
|
|
2108 };
|
|
2109 TameAElement.prototype.setHref = function (href) {
|
|
2110 this.setAttribute('href', href);
|
|
2111 return href;
|
|
2112 };
|
|
2113 ___.ctor(TameAElement, TameElement, 'TameAElement');
|
|
2114 ___.all2(___.grantTypedGeneric, TameAElement.prototype,
|
|
2115 ['getHref', 'setHref', 'focus']);
|
|
2116
|
|
2117 // http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-40002357
|
|
2118 function TameFormElement(node, editable) {
|
|
2119 TameElement.call(this, node, editable, editable);
|
|
2120 this.length = node.length;
|
|
2121 classUtils.exportFields(
|
|
2122 this,
|
|
2123 ['action', 'elements', 'enctype', 'method', 'target']);
|
|
2124 }
|
|
2125 classUtils.extend(TameFormElement, TameElement);
|
|
2126 nodeClasses.HTMLFormElement = TameFormElement;
|
|
2127 TameFormElement.prototype.submit = function () {
|
|
2128 return this.node___.submit();
|
|
2129 };
|
|
2130 TameFormElement.prototype.reset = function () {
|
|
2131 return this.node___.reset();
|
|
2132 };
|
|
2133 TameFormElement.prototype.getAction = function () {
|
|
2134 return this.getAttribute('action') || '';
|
|
2135 };
|
|
2136 TameFormElement.prototype.setAction = function (newVal) {
|
|
2137 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2138 return this.setAttribute('action', String(newVal));
|
|
2139 };
|
|
2140 TameFormElement.prototype.getElements = function () {
|
|
2141 return tameNodeList(this.node___.elements, this.editable___, 'name');
|
|
2142 };
|
|
2143 TameFormElement.prototype.getEnctype = function () {
|
|
2144 return this.getAttribute('enctype') || '';
|
|
2145 };
|
|
2146 TameFormElement.prototype.setEnctype = function (newVal) {
|
|
2147 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2148 return this.setAttribute('enctype', String(newVal));
|
|
2149 };
|
|
2150 TameFormElement.prototype.getMethod = function () {
|
|
2151 return this.getAttribute('method') || '';
|
|
2152 };
|
|
2153 TameFormElement.prototype.setMethod = function (newVal) {
|
|
2154 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2155 return this.setAttribute('method', String(newVal));
|
|
2156 };
|
|
2157 TameFormElement.prototype.getTarget = function () {
|
|
2158 return this.getAttribute('target') || '';
|
|
2159 };
|
|
2160 TameFormElement.prototype.setTarget = function (newVal) {
|
|
2161 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2162 return this.setAttribute('target', String(newVal));
|
|
2163 };
|
|
2164 TameFormElement.prototype.reset = function () {
|
|
2165 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2166 this.node___.reset();
|
|
2167 };
|
|
2168 TameFormElement.prototype.submit = function () {
|
|
2169 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2170 this.node___.submit();
|
|
2171 };
|
|
2172 ___.ctor(TameFormElement, TameElement, 'TameFormElement');
|
|
2173 ___.all2(___.grantTypedGeneric, TameFormElement.prototype,
|
|
2174 ['getElements', 'reset', 'submit']);
|
|
2175
|
|
2176
|
|
2177 function TameInputElement(node, editable) {
|
|
2178 TameElement.call(this, node, editable, editable);
|
|
2179 classUtils.exportFields(
|
|
2180 this,
|
|
2181 ['form', 'value', 'defaultValue',
|
|
2182 'checked', 'disabled', 'readOnly',
|
|
2183 'options', 'selected', 'selectedIndex',
|
|
2184 'name', 'accessKey', 'tabIndex', 'text',
|
|
2185 'defaultChecked', 'defaultSelected', 'maxLength',
|
|
2186 'size', 'type', 'index', 'label',
|
|
2187 'multiple', 'cols', 'rows']);
|
|
2188 }
|
|
2189 classUtils.extend(TameInputElement, TameElement);
|
|
2190 nodeClasses.HTMLInputElement = TameInputElement;
|
|
2191 TameInputElement.prototype.getChecked = function () {
|
|
2192 return this.node___.checked;
|
|
2193 };
|
|
2194 TameInputElement.prototype.setChecked = function (checked) {
|
|
2195 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2196 return (this.node___.checked = !!checked);
|
|
2197 };
|
|
2198 TameInputElement.prototype.getValue = function () {
|
|
2199 // For <option> elements, Firefox returns a value even when no value
|
|
2200 // attribute is present, using the contained text, but IE does not.
|
|
2201 var value = this.node___.value;
|
|
2202 return value === null || value === void 0 ? null : String(value);
|
|
2203 };
|
|
2204 TameInputElement.prototype.setValue = function (newValue) {
|
|
2205 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2206 this.node___.value = (
|
|
2207 newValue === null || newValue === void 0 ? '' : '' + newValue);
|
|
2208 return newValue;
|
|
2209 };
|
|
2210 TameInputElement.prototype.getDefaultValue = function () {
|
|
2211 var value = this.node___.defaultValue;
|
|
2212 return value === null || value === void 0 ? null : String(value);
|
|
2213 };
|
|
2214 TameInputElement.prototype.setDefaultValue = function (newValue) {
|
|
2215 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2216 this.node___.defaultValue = (
|
|
2217 newValue === null || newValue === void 0 ? '' : '' + newValue);
|
|
2218 return newValue;
|
|
2219 };
|
|
2220 TameInputElement.prototype.focus = function () {
|
|
2221 this.node___.focus();
|
|
2222 };
|
|
2223 TameInputElement.prototype.blur = function () {
|
|
2224 this.node___.blur();
|
|
2225 };
|
|
2226 TameInputElement.prototype.select = function () {
|
|
2227 this.node___.select();
|
|
2228 };
|
|
2229 TameInputElement.prototype.getForm = function () {
|
|
2230 return tameRelatedNode(this.node___.form, this.editable___);
|
|
2231 };
|
|
2232 TameInputElement.prototype.getDisabled = function () {
|
|
2233 return this.node___.disabled;
|
|
2234 };
|
|
2235 TameInputElement.prototype.setDisabled = function (newValue) {
|
|
2236 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2237 this.node___.disabled = newValue;
|
|
2238 return newValue;
|
|
2239 };
|
|
2240 TameInputElement.prototype.getReadOnly = function () {
|
|
2241 return this.node___.readOnly;
|
|
2242 };
|
|
2243 TameInputElement.prototype.setReadOnly = function (newValue) {
|
|
2244 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2245 this.node___.readOnly = newValue;
|
|
2246 return newValue;
|
|
2247 };
|
|
2248 TameInputElement.prototype.getOptions = function () {
|
|
2249 return tameNodeList(this.node___.options, this.editable___, 'name');
|
|
2250 };
|
|
2251 TameInputElement.prototype.getDefaultSelected = function () {
|
|
2252 return this.node___.defaultSelected;
|
|
2253 };
|
|
2254 TameInputElement.prototype.setDefaultSelected = function (newValue) {
|
|
2255 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2256 this.node___.defaultSelected = !!newValue;
|
|
2257 return newValue;
|
|
2258 };
|
|
2259 TameInputElement.prototype.getSelected = function () {
|
|
2260 return this.node___.selected;
|
|
2261 };
|
|
2262 TameInputElement.prototype.setSelected = function (newValue) {
|
|
2263 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2264 this.node___.selected = newValue;
|
|
2265 return newValue;
|
|
2266 };
|
|
2267 TameInputElement.prototype.getSelectedIndex = function () {
|
|
2268 return this.node___.selectedIndex;
|
|
2269 };
|
|
2270 TameInputElement.prototype.setSelectedIndex = function (newValue) {
|
|
2271 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2272 this.node___.selectedIndex = (newValue | 0);
|
|
2273 return newValue;
|
|
2274 };
|
|
2275 TameInputElement.prototype.getName = function () {
|
|
2276 return this.node___.name;
|
|
2277 };
|
|
2278 TameInputElement.prototype.setName = function (newValue) {
|
|
2279 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2280 this.node___.name = newValue;
|
|
2281 return newValue;
|
|
2282 };
|
|
2283 TameInputElement.prototype.getAccessKey = function () {
|
|
2284 return this.node___.accessKey;
|
|
2285 };
|
|
2286 TameInputElement.prototype.setAccessKey = function (newValue) {
|
|
2287 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2288 this.node___.accessKey = newValue;
|
|
2289 return newValue;
|
|
2290 };
|
|
2291 TameInputElement.prototype.getTabIndex = function () {
|
|
2292 return this.node___.tabIndex;
|
|
2293 };
|
|
2294 TameInputElement.prototype.getText = function () {
|
|
2295 return String(this.node___.text);
|
|
2296 };
|
|
2297 TameInputElement.prototype.setTabIndex = function (newValue) {
|
|
2298 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2299 this.node___.tabIndex = newValue;
|
|
2300 return newValue;
|
|
2301 };
|
|
2302 TameInputElement.prototype.getDefaultChecked = function () {
|
|
2303 return this.node___.defaultChecked;
|
|
2304 };
|
|
2305 TameInputElement.prototype.setDefaultChecked = function (newValue) {
|
|
2306 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2307 this.node___.defaultChecked = newValue;
|
|
2308 return newValue;
|
|
2309 };
|
|
2310 TameInputElement.prototype.getMaxLength = function () {
|
|
2311 return this.node___.maxLength;
|
|
2312 };
|
|
2313 TameInputElement.prototype.setMaxLength = function (newValue) {
|
|
2314 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2315 this.node___.maxLength = newValue;
|
|
2316 return newValue;
|
|
2317 };
|
|
2318 TameInputElement.prototype.getSize = function () {
|
|
2319 return this.node___.size;
|
|
2320 };
|
|
2321 TameInputElement.prototype.setSize = function (newValue) {
|
|
2322 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2323 this.node___.size = newValue;
|
|
2324 return newValue;
|
|
2325 };
|
|
2326 TameInputElement.prototype.getType = function () {
|
|
2327 return String(this.node___.type);
|
|
2328 };
|
|
2329 TameInputElement.prototype.setType = function (newValue) {
|
|
2330 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2331 this.node___.type = newValue;
|
|
2332 return newValue;
|
|
2333 };
|
|
2334 TameInputElement.prototype.getIndex = function () {
|
|
2335 return this.node___.index;
|
|
2336 };
|
|
2337 TameInputElement.prototype.setIndex = function (newValue) {
|
|
2338 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2339 this.node___.index = newValue;
|
|
2340 return newValue;
|
|
2341 };
|
|
2342 TameInputElement.prototype.getLabel = function () {
|
|
2343 return this.node___.label;
|
|
2344 };
|
|
2345 TameInputElement.prototype.setLabel = function (newValue) {
|
|
2346 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2347 this.node___.label = newValue;
|
|
2348 return newValue;
|
|
2349 };
|
|
2350 TameInputElement.prototype.getMultiple = function () {
|
|
2351 return this.node___.multiple;
|
|
2352 };
|
|
2353 TameInputElement.prototype.setMultiple = function (newValue) {
|
|
2354 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2355 this.node___.multiple = newValue;
|
|
2356 return newValue;
|
|
2357 };
|
|
2358 TameInputElement.prototype.getCols = function () {
|
|
2359 return this.node___.cols;
|
|
2360 };
|
|
2361 TameInputElement.prototype.setCols = function (newValue) {
|
|
2362 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2363 this.node___.cols = newValue;
|
|
2364 return newValue;
|
|
2365 };
|
|
2366 TameInputElement.prototype.getRows = function () {
|
|
2367 return this.node___.rows;
|
|
2368 };
|
|
2369 TameInputElement.prototype.setRows = function (newValue) {
|
|
2370 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2371 this.node___.rows = newValue;
|
|
2372 return newValue;
|
|
2373 };
|
|
2374 ___.ctor(TameInputElement, TameElement, 'TameInputElement');
|
|
2375 ___.all2(___.grantTypedGeneric, TameInputElement.prototype,
|
|
2376 ['getValue', 'setValue', 'focus', 'getForm', 'getType', 'select']);
|
|
2377
|
|
2378
|
|
2379 function TameImageElement(node, editable) {
|
|
2380 TameElement.call(this, node, editable, editable);
|
|
2381 classUtils.exportFields(this, ['src', 'alt']);
|
|
2382 }
|
|
2383 classUtils.extend(TameImageElement, TameElement);
|
|
2384 nodeClasses.HTMLImageElement = TameImageElement;
|
|
2385 TameImageElement.prototype.getSrc = function () {
|
|
2386 return this.node___.src;
|
|
2387 };
|
|
2388 TameImageElement.prototype.setSrc = function (src) {
|
|
2389 this.setAttribute('src', src);
|
|
2390 return src;
|
|
2391 };
|
|
2392 TameImageElement.prototype.getAlt = function () {
|
|
2393 return this.node___.alt;
|
|
2394 };
|
|
2395 TameImageElement.prototype.setAlt = function (alt) {
|
|
2396 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2397 this.node___.alt = String(alt);
|
|
2398 return alt;
|
|
2399 };
|
|
2400 ___.ctor(TameImageElement, TameElement, 'TameImageElement');
|
|
2401 ___.all2(___.grantTypedGeneric, TameImageElement.prototype,
|
|
2402 ['getSrc', 'setSrc', 'getAlt', 'setAlt']);
|
|
2403
|
|
2404 /**
|
|
2405 * A script element wrapper that allows setting of a src that has been
|
|
2406 * rewritten by a URL policy, but not modifying of textual content.
|
|
2407 */
|
|
2408 function TameScriptElement(node, editable) {
|
|
2409 // Make the child list immutable so that text content can't be added
|
|
2410 // or removed.
|
|
2411 TameElement.call(this, node, editable, false);
|
|
2412 classUtils.exportFields(this, ['src']);
|
|
2413 }
|
|
2414 classUtils.extend(TameScriptElement, TameElement);
|
|
2415 nodeClasses.HTMLScriptElement = TameScriptElement;
|
|
2416 TameScriptElement.prototype.getSrc = function () {
|
|
2417 return this.node___.src;
|
|
2418 };
|
|
2419 TameScriptElement.prototype.setSrc = function (src) {
|
|
2420 this.setAttribute('src', src);
|
|
2421 return src;
|
|
2422 };
|
|
2423 ___.ctor(TameScriptElement, TameElement, 'TameScriptElement');
|
|
2424
|
|
2425
|
|
2426 function TameTableCompElement(node, editable) {
|
|
2427 TameElement.call(this, node, editable, editable);
|
|
2428 classUtils.exportFields(
|
|
2429 this,
|
|
2430 ['colSpan','cells','rowSpan','rows','rowIndex','align',
|
|
2431 'vAlign','nowrap']);
|
|
2432 }
|
|
2433 classUtils.extend(TameTableCompElement, TameElement);
|
|
2434 TameTableCompElement.prototype.getColSpan = function () {
|
|
2435 return this.node___.colSpan;
|
|
2436 };
|
|
2437 TameTableCompElement.prototype.setColSpan = function (newValue) {
|
|
2438 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2439 this.node___.colSpan = newValue;
|
|
2440 return newValue;
|
|
2441 };
|
|
2442 TameTableCompElement.prototype.getCells = function () {
|
|
2443 return tameNodeList(this.node___.cells, this.editable___);
|
|
2444 };
|
|
2445 TameTableCompElement.prototype.getRowSpan = function () {
|
|
2446 return this.node___.rowSpan;
|
|
2447 };
|
|
2448 TameTableCompElement.prototype.setRowSpan = function (newValue) {
|
|
2449 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2450 this.node___.rowSpan = newValue;
|
|
2451 return newValue;
|
|
2452 };
|
|
2453 TameTableCompElement.prototype.getRows = function () {
|
|
2454 return tameNodeList(this.node___.rows, this.editable___);
|
|
2455 };
|
|
2456 TameTableCompElement.prototype.getRowIndex = function () {
|
|
2457 return this.node___.rowIndex;
|
|
2458 };
|
|
2459 TameTableCompElement.prototype.getAlign = function () {
|
|
2460 return this.node___.align;
|
|
2461 };
|
|
2462 TameTableCompElement.prototype.setAlign = function (newValue) {
|
|
2463 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2464 this.node___.align = newValue;
|
|
2465 return newValue;
|
|
2466 };
|
|
2467 TameTableCompElement.prototype.getVAlign = function () {
|
|
2468 return this.node___.vAlign;
|
|
2469 };
|
|
2470 TameTableCompElement.prototype.setVAlign = function (newValue) {
|
|
2471 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2472 this.node___.vAlign = newValue;
|
|
2473 return newValue;
|
|
2474 };
|
|
2475 TameTableCompElement.prototype.getNowrap = function () {
|
|
2476 return this.node___.nowrap;
|
|
2477 };
|
|
2478 TameTableCompElement.prototype.setNowrap = function (newValue) {
|
|
2479 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2480 this.node___.nowrap = newValue;
|
|
2481 return newValue;
|
|
2482 };
|
|
2483 ___.ctor(TameTableCompElement, TameElement, 'TameTableCompElement');
|
|
2484
|
|
2485
|
|
2486 function TameTableElement(node, editable) {
|
|
2487 TameTableCompElement.call(this, node, editable);
|
|
2488 classUtils.exportFields(this, ['tBodies','tHead','tFoot']);
|
|
2489 }
|
|
2490 classUtils.extend(TameTableElement, TameTableCompElement);
|
|
2491 nodeClasses.HTMLTableElement = TameTableElement;
|
|
2492 TameTableElement.prototype.getTBodies = function () {
|
|
2493 return tameNodeList(this.node___.tBodies, this.editable___);
|
|
2494 };
|
|
2495 TameTableElement.prototype.getTHead = function () {
|
|
2496 return tameNode(this.node___.tHead, this.editable___);
|
|
2497 };
|
|
2498 TameTableElement.prototype.getTFoot = function () {
|
|
2499 return tameNode(this.node___.tFoot, this.editable___);
|
|
2500 };
|
|
2501 TameTableElement.prototype.createTHead = function () {
|
|
2502 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2503 return tameNode(this.node___.createTHead(), this.editable___);
|
|
2504 };
|
|
2505 TameTableElement.prototype.deleteTHead = function () {
|
|
2506 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2507 this.node___.deleteTHead();
|
|
2508 };
|
|
2509 TameTableElement.prototype.createTFoot = function () {
|
|
2510 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2511 return tameNode(this.node___.createTFoot(), this.editable___);
|
|
2512 };
|
|
2513 TameTableElement.prototype.deleteTFoot = function () {
|
|
2514 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2515 this.node___.deleteTFoot();
|
|
2516 };
|
|
2517 ___.ctor(TameTableElement, TameTableCompElement, 'TameTableElement');
|
|
2518 ___.all2(___.grantTypedGeneric, TameTableElement.prototype,
|
|
2519 ['createTHead', 'deleteTHead','createTFoot', 'deleteTFoot']);
|
|
2520
|
|
2521 function tameEvent(event) {
|
|
2522 if (event.tamed___) { return event.tamed___; }
|
|
2523 return event.tamed___ = new TameEvent(event);
|
|
2524 }
|
|
2525
|
|
2526 function TameEvent(event) {
|
|
2527 this.event___ = event;
|
|
2528 ___.stamp(tameEventTrademark, this, true);
|
|
2529 classUtils.exportFields(
|
|
2530 this,
|
|
2531 ['type', 'target', 'pageX', 'pageY', 'altKey',
|
|
2532 'ctrlKey', 'metaKey', 'shiftKey', 'button',
|
|
2533 'screenX', 'screenY',
|
|
2534 'currentTarget', 'relatedTarget',
|
|
2535 'fromElement', 'toElement',
|
|
2536 'srcElement',
|
|
2537 'clientX', 'clientY', 'keyCode', 'which']);
|
|
2538 }
|
|
2539 nodeClasses.Event = TameEvent;
|
|
2540 TameEvent.prototype.getType = function () {
|
|
2541 return bridal.untameEventType(String(this.event___.type));
|
|
2542 };
|
|
2543 TameEvent.prototype.getTarget = function () {
|
|
2544 var event = this.event___;
|
|
2545 return tameRelatedNode(event.target || event.srcElement, true);
|
|
2546 };
|
|
2547 TameEvent.prototype.getSrcElement = function () {
|
|
2548 return tameRelatedNode(this.event___.srcElement, true);
|
|
2549 };
|
|
2550 TameEvent.prototype.getCurrentTarget = function () {
|
|
2551 var e = this.event___;
|
|
2552 return tameRelatedNode(e.currentTarget, true);
|
|
2553 };
|
|
2554 TameEvent.prototype.getRelatedTarget = function () {
|
|
2555 var e = this.event___;
|
|
2556 var t = e.relatedTarget;
|
|
2557 if (!t) {
|
|
2558 if (e.type === 'mouseout') {
|
|
2559 t = e.toElement;
|
|
2560 } else if (e.type === 'mouseover') {
|
|
2561 t = e.fromElement;
|
|
2562 }
|
|
2563 }
|
|
2564 return tameRelatedNode(t, true);
|
|
2565 };
|
|
2566 TameEvent.prototype.getFromElement = function () {
|
|
2567 return tameRelatedNode(this.event___.fromElement, true);
|
|
2568 };
|
|
2569 TameEvent.prototype.getToElement = function () {
|
|
2570 return tameRelatedNode(this.event___.toElement, true);
|
|
2571 };
|
|
2572 TameEvent.prototype.getPageX = function () {
|
|
2573 return Number(this.event___.pageX);
|
|
2574 };
|
|
2575 TameEvent.prototype.getPageY = function () {
|
|
2576 return Number(this.event___.pageY);
|
|
2577 };
|
|
2578 TameEvent.prototype.stopPropagation = function () {
|
|
2579 // TODO(mikesamuel): make sure event doesn't propagate to dispatched
|
|
2580 // events for this gadget only.
|
|
2581 // But don't allow it to stop propagation to the container.
|
|
2582 if (this.event___.stopPropagation) {
|
|
2583 this.event___.stopPropagation();
|
|
2584 } else {
|
|
2585 this.event___.cancelBubble = true;
|
|
2586 }
|
|
2587 };
|
|
2588 TameEvent.prototype.preventDefault = function () {
|
|
2589 // TODO(mikesamuel): make sure event doesn't propagate to dispatched
|
|
2590 // events for this gadget only.
|
|
2591 // But don't allow it to stop propagation to the container.
|
|
2592 if (this.event___.preventDefault) {
|
|
2593 this.event___.preventDefault();
|
|
2594 } else {
|
|
2595 this.event___.returnValue = false;
|
|
2596 }
|
|
2597 };
|
|
2598 TameEvent.prototype.getAltKey = function () {
|
|
2599 return Boolean(this.event___.altKey);
|
|
2600 };
|
|
2601 TameEvent.prototype.getCtrlKey = function () {
|
|
2602 return Boolean(this.event___.ctrlKey);
|
|
2603 };
|
|
2604 TameEvent.prototype.getMetaKey = function () {
|
|
2605 return Boolean(this.event___.metaKey);
|
|
2606 };
|
|
2607 TameEvent.prototype.getShiftKey = function () {
|
|
2608 return Boolean(this.event___.shiftKey);
|
|
2609 };
|
|
2610 TameEvent.prototype.getButton = function () {
|
|
2611 var e = this.event___;
|
|
2612 return e.button && Number(e.button);
|
|
2613 };
|
|
2614 TameEvent.prototype.getClientX = function () {
|
|
2615 return Number(this.event___.clientX);
|
|
2616 };
|
|
2617 TameEvent.prototype.getClientY = function () {
|
|
2618 return Number(this.event___.clientY);
|
|
2619 };
|
|
2620 TameEvent.prototype.getScreenX = function () {
|
|
2621 return Number(this.event___.screenX);
|
|
2622 };
|
|
2623 TameEvent.prototype.getScreenY = function () {
|
|
2624 return Number(this.event___.screenY);
|
|
2625 };
|
|
2626 TameEvent.prototype.getWhich = function () {
|
|
2627 var w = this.event___.which;
|
|
2628 return w && Number(w);
|
|
2629 };
|
|
2630 TameEvent.prototype.getKeyCode = function () {
|
|
2631 var kc = this.event___.keyCode;
|
|
2632 return kc && Number(kc);
|
|
2633 };
|
|
2634 TameEvent.prototype.toString = function () { return '[Fake Event]'; };
|
|
2635 ___.ctor(TameEvent, void 0, 'TameEvent');
|
|
2636 ___.all2(___.grantTypedGeneric, TameEvent.prototype,
|
|
2637 ['getType', 'getTarget', 'getPageX', 'getPageY', 'stopPropagation',
|
|
2638 'getAltKey', 'getCtrlKey', 'getMetaKey', 'getShiftKey',
|
|
2639 'getButton', 'getClientX', 'getClientY',
|
|
2640 'getScreenX', 'getScreenY',
|
|
2641 'getRelatedTarget',
|
|
2642 'getFromElement', 'getToElement',
|
|
2643 'getSrcElement',
|
|
2644 'preventDefault',
|
|
2645 'getKeyCode', 'getWhich']);
|
|
2646
|
|
2647 function TameCustomHTMLEvent(event) {
|
|
2648 TameEvent.call(this, event);
|
|
2649 this.properties___ = {};
|
|
2650 }
|
|
2651 classUtils.extend(TameCustomHTMLEvent, TameEvent);
|
|
2652 TameCustomHTMLEvent.prototype.initEvent
|
|
2653 = function (type, bubbles, cancelable) {
|
|
2654 bridal.initEvent(this.event___, type, bubbles, cancelable);
|
|
2655 };
|
|
2656 TameCustomHTMLEvent.prototype.handleRead___ = function (name) {
|
|
2657 name = String(name);
|
|
2658 if (endsWith__.test(name)) { return void 0; }
|
|
2659 var handlerName = name + '_getter___';
|
|
2660 if (this[handlerName]) {
|
|
2661 return this[handlerName]();
|
|
2662 }
|
|
2663 handlerName = handlerName.toLowerCase();
|
|
2664 if (this[handlerName]) {
|
|
2665 return this[handlerName]();
|
|
2666 }
|
|
2667 if (___.hasOwnProp(this.event___.properties___, name)) {
|
|
2668 return this.event___.properties___[name];
|
|
2669 } else {
|
|
2670 return void 0;
|
|
2671 }
|
|
2672 };
|
|
2673 TameCustomHTMLEvent.prototype.handleCall___ = function (name, args) {
|
|
2674 name = String(name);
|
|
2675 if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); }
|
|
2676 var handlerName = name + '_handler___';
|
|
2677 if (this[handlerName]) {
|
|
2678 return this[handlerName].call(this, args);
|
|
2679 }
|
|
2680 handlerName = handlerName.toLowerCase();
|
|
2681 if (this[handlerName]) {
|
|
2682 return this[handlerName].call(this, args);
|
|
2683 }
|
|
2684 if (___.hasOwnProp(this.event___.properties___, name)) {
|
|
2685 return this.event___.properties___[name].call(this, args);
|
|
2686 } else {
|
|
2687 throw new TypeError(name + ' is not a function.');
|
|
2688 }
|
|
2689 };
|
|
2690 TameCustomHTMLEvent.prototype.handleSet___ = function (name, val) {
|
|
2691 name = String(name);
|
|
2692 if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); }
|
|
2693 var handlerName = name + '_setter___';
|
|
2694 if (this[handlerName]) {
|
|
2695 return this[handlerName](val);
|
|
2696 }
|
|
2697 handlerName = handlerName.toLowerCase();
|
|
2698 if (this[handlerName]) {
|
|
2699 return this[handlerName](val);
|
|
2700 }
|
|
2701 if (!this.event___.properties___) {
|
|
2702 this.event___.properties___ = {};
|
|
2703 }
|
|
2704 this[name + '_canEnum___'] = true;
|
|
2705 return this.event___.properties___[name] = val;
|
|
2706 };
|
|
2707 TameCustomHTMLEvent.prototype.handleDelete___ = function (name) {
|
|
2708 name = String(name);
|
|
2709 if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); }
|
|
2710 var handlerName = name + '_deleter___';
|
|
2711 if (this[handlerName]) {
|
|
2712 return this[handlerName]();
|
|
2713 }
|
|
2714 handlerName = handlerName.toLowerCase();
|
|
2715 if (this[handlerName]) {
|
|
2716 return this[handlerName]();
|
|
2717 }
|
|
2718 if (this.event___.properties___) {
|
|
2719 return (
|
|
2720 delete this.event___.properties___[name]
|
|
2721 && delete this[name + '_canEnum___']);
|
|
2722 } else {
|
|
2723 return true;
|
|
2724 }
|
|
2725 };
|
|
2726 TameCustomHTMLEvent.prototype.handleEnum___ = function (ownFlag) {
|
|
2727 // TODO(metaweta): Add code to list all the other handled stuff we know
|
|
2728 // about.
|
|
2729 if (this.event___.properties___) {
|
|
2730 return cajita.allKeys(this.event___.properties___);
|
|
2731 }
|
|
2732 return [];
|
|
2733 };
|
|
2734 TameCustomHTMLEvent.prototype.toString = function () {
|
|
2735 return '[Fake CustomEvent]';
|
|
2736 };
|
|
2737 ___.grantTypedGeneric(TameCustomHTMLEvent.prototype, 'initEvent');
|
|
2738 ___.ctor(TameCustomHTMLEvent, TameEvent, 'TameCustomHTMLEvent');
|
|
2739
|
|
2740 /**
|
|
2741 * Return a fake node list containing tamed nodes.
|
|
2742 * @param {Array.<TameNode>} array of tamed nodes.
|
|
2743 * @return an array that duck types to a node list.
|
|
2744 */
|
|
2745 function fakeNodeList(array) {
|
|
2746 array.item = ___.frozenFunc(function(i) { return array[i]; });
|
|
2747 return cajita.freeze(array);
|
|
2748 }
|
|
2749
|
|
2750 function TameHTMLDocument(doc, body, domain, editable) {
|
|
2751 TamePseudoNode.call(this, editable);
|
|
2752 this.doc___ = doc;
|
|
2753 this.body___ = body;
|
|
2754 this.domain___ = domain;
|
|
2755 this.onLoadListeners___ = [];
|
|
2756 var tameDoc = this;
|
|
2757
|
|
2758 var tameBody = tameNode(body, editable);
|
|
2759 this.tameBody___ = tameBody;
|
|
2760 // TODO(mikesamuel): create a proper class for BODY, HEAD, and HTML along
|
|
2761 // with all the other specialized node types.
|
|
2762 var tameBodyElement = new TamePseudoElement(
|
|
2763 'BODY',
|
|
2764 this,
|
|
2765 function () { return tameNodeList(body.childNodes, editable); },
|
|
2766 function () { return tameHtmlElement; },
|
|
2767 function () { return tameInnerHtml(body.innerHTML); },
|
|
2768 tameBody,
|
|
2769 editable);
|
|
2770 cajita.forOwnKeys(
|
|
2771 { appendChild: 0, removeChild: 0, insertBefore: 0, replaceChild: 0 },
|
|
2772 ___.frozenFunc(function (k) {
|
|
2773 tameBodyElement[k] = tameBody[k].bind(tameBody);
|
|
2774 ___.grantFunc(tameBodyElement, k);
|
|
2775 }));
|
|
2776
|
|
2777 var title = doc.createTextNode(body.getAttribute('title') || '');
|
|
2778 var tameTitleElement = new TamePseudoElement(
|
|
2779 'TITLE',
|
|
2780 this,
|
|
2781 function () { return [tameNode(title, false)]; },
|
|
2782 function () { return tameHeadElement; },
|
|
2783 function () { return html.escapeAttrib(title.nodeValue); },
|
|
2784 null,
|
|
2785 editable);
|
|
2786 var tameHeadElement = new TamePseudoElement(
|
|
2787 'HEAD',
|
|
2788 this,
|
|
2789 function () { return [tameTitleElement]; },
|
|
2790 function () { return tameHtmlElement; },
|
|
2791 function () {
|
|
2792 return '<title>' + tameTitleElement.getInnerHTML() + '</title>';
|
|
2793 },
|
|
2794 null,
|
|
2795 editable);
|
|
2796 var tameHtmlElement = new TamePseudoElement(
|
|
2797 'HTML',
|
|
2798 this,
|
|
2799 function () { return [tameHeadElement, tameBodyElement]; },
|
|
2800 function () { return tameDoc; },
|
|
2801 function () {
|
|
2802 return ('<head>' + tameHeadElement.getInnerHTML + '<\/head><body>'
|
|
2803 + tameBodyElement.getInnerHTML() + '<\/body>');
|
|
2804 },
|
|
2805 tameBody,
|
|
2806 editable);
|
|
2807 if (body.contains) { // typeof is 'object' on IE
|
|
2808 tameHtmlElement.contains = function (other) {
|
|
2809 cajita.guard(tameNodeTrademark, other);
|
|
2810 var otherNode = other.node___;
|
|
2811 return body.contains(otherNode);
|
|
2812 };
|
|
2813 ___.grantFunc(tameHtmlElement, 'contains');
|
|
2814 }
|
|
2815 if ('function' === typeof body.compareDocumentPosition) {
|
|
2816 /**
|
|
2817 * Speced in <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition">DOM-Level-3</a>.
|
|
2818 */
|
|
2819 tameHtmlElement.compareDocumentPosition = function (other) {
|
|
2820 cajita.guard(tameNodeTrademark, other);
|
|
2821 var otherNode = other.node___;
|
|
2822 if (!otherNode) { return 0; }
|
|
2823 var bitmask = +body.compareDocumentPosition(otherNode);
|
|
2824 // To avoid leaking information about the relative positioning of
|
|
2825 // different roots, if neither contains the other, then we mask out
|
|
2826 // the preceding/following bits.
|
|
2827 // 0x18 is (CONTAINS | CONTAINED).
|
|
2828 // 0x1f is all the bits documented at
|
|
2829 // http://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition
|
|
2830 // except IMPLEMENTATION_SPECIFIC.
|
|
2831 // 0x01 is DISCONNECTED.
|
|
2832 /*
|
|
2833 if (!(bitmask & 0x18)) {
|
|
2834 // TODO: If they are not under the same virtual doc root, return
|
|
2835 // DOCUMENT_POSITION_DISCONNECTED instead of leaking information
|
|
2836 // about PRECEEDED | FOLLOWING.
|
|
2837 }
|
|
2838 */
|
|
2839 return bitmask & 0x1f;
|
|
2840 };
|
|
2841 if (!___.hasOwnProp(tameHtmlElement, 'contains')) {
|
|
2842 // http://www.quirksmode.org/blog/archives/2006/01/contains_for_mo.html
|
|
2843 tameHtmlElement.contains = (function (other) {
|
|
2844 var docPos = this.compareDocumentPosition(other);
|
|
2845 return !(!(docPos & 0x10) && docPos);
|
|
2846 }).bind(tameHtmlElement);
|
|
2847 ___.grantFunc(tameHtmlElement, 'contains');
|
|
2848 }
|
|
2849 ___.grantFunc(tameHtmlElement, 'compareDocumentPosition');
|
|
2850 }
|
|
2851 this.documentElement___ = tameHtmlElement;
|
|
2852 classUtils.exportFields(
|
|
2853 this, ['documentElement', 'body', 'title', 'domain']);
|
|
2854 }
|
|
2855 classUtils.extend(TameHTMLDocument, TamePseudoNode);
|
|
2856 nodeClasses.HTMLDocument = TameHTMLDocument;
|
|
2857 TameHTMLDocument.prototype.getNodeType = function () { return 9; };
|
|
2858 TameHTMLDocument.prototype.getNodeName
|
|
2859 = function () { return '#document'; };
|
|
2860 TameHTMLDocument.prototype.getNodeValue = function () { return null; };
|
|
2861 TameHTMLDocument.prototype.getChildNodes
|
|
2862 = function () { return [this.documentElement___]; };
|
|
2863 TameHTMLDocument.prototype.getAttributes = function () { return []; };
|
|
2864 TameHTMLDocument.prototype.getParentNode = function () { return null; };
|
|
2865 TameHTMLDocument.prototype.getElementsByTagName = function (tagName) {
|
|
2866 tagName = String(tagName).toLowerCase();
|
|
2867 switch (tagName) {
|
|
2868 case 'body': return fakeNodeList([ this.getBody() ]);
|
|
2869 case 'head': return fakeNodeList([ this.getHead() ]);
|
|
2870 case 'title': return fakeNodeList([ this.getTitle() ]);
|
|
2871 case 'html': return fakeNodeList([ this.getDocumentElement() ]);
|
|
2872 default:
|
|
2873 return tameGetElementsByTagName(
|
|
2874 this.body___, tagName, this.editable___);
|
|
2875 }
|
|
2876 };
|
|
2877 TameHTMLDocument.prototype.getDocumentElement = function () {
|
|
2878 return this.documentElement___;
|
|
2879 };
|
|
2880 TameHTMLDocument.prototype.getBody = function () {
|
|
2881 return this.documentElement___.getLastChild();
|
|
2882 };
|
|
2883 TameHTMLDocument.prototype.getHead = function () {
|
|
2884 return this.documentElement___.getFirstChild();
|
|
2885 };
|
|
2886 TameHTMLDocument.prototype.getTitle = function () {
|
|
2887 return this.getHead().getFirstChild();
|
|
2888 };
|
|
2889 TameHTMLDocument.prototype.getDomain = function () {
|
|
2890 return this.domain___;
|
|
2891 };
|
|
2892 TameHTMLDocument.prototype.getElementsByClassName = function (className) {
|
|
2893 return tameGetElementsByClassName(
|
|
2894 this.body___, className, this.editable___);
|
|
2895 };
|
|
2896 TameHTMLDocument.prototype.addEventListener =
|
|
2897 function (name, listener, useCapture) {
|
|
2898 return this.tameBody___.addEventListener(name, listener, useCapture);
|
|
2899 };
|
|
2900 TameHTMLDocument.prototype.removeEventListener =
|
|
2901 function (name, listener, useCapture) {
|
|
2902 return this.tameBody___.removeEventListener(
|
|
2903 name, listener, useCapture);
|
|
2904 };
|
|
2905 TameHTMLDocument.prototype.createComment = function (text) {
|
|
2906 return new TameCommentNode(this.doc___.createComment(" "), true);
|
|
2907 };
|
|
2908 TameHTMLDocument.prototype.createDocumentFragment = function () {
|
|
2909 return new TameBackedNode(this.doc___.createDocumentFragment(),
|
|
2910 this.editable___);
|
|
2911 };
|
|
2912 TameHTMLDocument.prototype.createElement = function (tagName) {
|
|
2913 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2914 tagName = String(tagName).toLowerCase();
|
|
2915 if (!html4.ELEMENTS.hasOwnProperty(tagName)) {
|
|
2916 throw new Error(UNKNOWN_TAGNAME + "[" + tagName + "]");
|
|
2917 }
|
|
2918 var flags = html4.ELEMENTS[tagName];
|
|
2919 // Script exemption allows dynamic loading of proxied scripts.
|
|
2920 if ((flags & html4.eflags.UNSAFE) && !(flags & html4.eflags.SCRIPT)) {
|
|
2921 cajita.log(UNSAFE_TAGNAME + "[" + tagName + "]: no action performed");
|
|
2922 return null;
|
|
2923 }
|
|
2924 var newEl = this.doc___.createElement(tagName);
|
|
2925 if (elementPolicies.hasOwnProperty(tagName)) {
|
|
2926 var attribs = elementPolicies[tagName]([]);
|
|
2927 if (attribs) {
|
|
2928 for (var i = 0; i < attribs.length; i += 2) {
|
|
2929 bridal.setAttribute(newEl, attribs[i], attribs[i + 1]);
|
|
2930 }
|
|
2931 }
|
|
2932 }
|
|
2933 return tameNode(newEl, true);
|
|
2934 };
|
|
2935 TameHTMLDocument.prototype.createTextNode = function (text) {
|
|
2936 if (!this.editable___) { throw new Error(NOT_EDITABLE); }
|
|
2937 return tameNode(this.doc___.createTextNode(
|
|
2938 text !== null && text !== void 0 ? '' + text : ''), true);
|
|
2939 };
|
|
2940 TameHTMLDocument.prototype.getElementById = function (id) {
|
|
2941 id += idSuffix;
|
|
2942 var node = this.doc___.getElementById(id);
|
|
2943 return tameNode(node, this.editable___);
|
|
2944 };
|
|
2945 TameHTMLDocument.prototype.toString = function () {
|
|
2946 return '[Fake Document]';
|
|
2947 };
|
|
2948 TameHTMLDocument.prototype.write = function (text) {
|
|
2949 // TODO(mikesamuel): Needs implementation
|
|
2950 cajita.log('Called document.write() with: ' + text);
|
|
2951 };
|
|
2952 // http://www.w3.org/TR/DOM-Level-2-Events/events.html
|
|
2953 // #Events-DocumentEvent-createEvent
|
|
2954 TameHTMLDocument.prototype.createEvent = function (type) {
|
|
2955 type = String(type);
|
|
2956 if (type !== 'HTMLEvents') {
|
|
2957 // See https://developer.mozilla.org/en/DOM/document.createEvent#Notes
|
|
2958 // for a long list of event ypes.
|
|
2959 // See http://www.w3.org/TR/DOM-Level-2-Events/events.html
|
|
2960 // #Events-eventgroupings
|
|
2961 // for the DOM2 list.
|
|
2962 throw new Error('Unrecognized event type ' + type);
|
|
2963 }
|
|
2964 var document = this.doc___;
|
|
2965 var rawEvent;
|
|
2966 if (document.createEvent) {
|
|
2967 rawEvent = document.createEvent(type);
|
|
2968 } else {
|
|
2969 rawEvent = document.createEventObject();
|
|
2970 rawEvent.eventType = 'ondataavailable';
|
|
2971 }
|
|
2972 var tamedEvent = new TameCustomHTMLEvent(rawEvent);
|
|
2973 rawEvent.tamed___ = tamedEvent;
|
|
2974 return tamedEvent;
|
|
2975 };
|
|
2976 TameHTMLDocument.prototype.getOwnerDocument = function () {
|
|
2977 return null;
|
|
2978 };
|
|
2979 // Called by the html-emitter when the virtual document has been loaded.
|
|
2980 TameHTMLDocument.prototype.signalLoaded___ = function () {
|
|
2981 var listeners = this.onLoadListeners___;
|
|
2982 this.onLoadListeners___ = [];
|
|
2983 for (var i = 0, n = listeners.length; i < n; ++i) {
|
|
2984 (function (listener) {
|
|
2985 var listenerFn = ___.asFunc(listener);
|
|
2986 setTimeout(function () { listenerFn.call(cajita.USELESS); }, 0);
|
|
2987 })(listeners[i]);
|
|
2988 }
|
|
2989 };
|
|
2990
|
|
2991 ___.ctor(TameHTMLDocument, TamePseudoNode, 'TameHTMLDocument');
|
|
2992 ___.all2(___.grantTypedGeneric, TameHTMLDocument.prototype,
|
|
2993 ['addEventListener', 'removeEventListener',
|
|
2994 'createComment', 'createDocumentFragment',
|
|
2995 'createElement', 'createEvent', 'createTextNode',
|
|
2996 'getElementById', 'getElementsByClassName',
|
|
2997 'getElementsByTagName',
|
|
2998 'write']);
|
|
2999
|
|
3000
|
|
3001 imports.tameNode___ = tameNode;
|
|
3002 imports.tameEvent___ = tameEvent;
|
|
3003 imports.blessHtml___ = blessHtml;
|
|
3004 imports.blessCss___ = function (var_args) {
|
|
3005 var arr = [];
|
|
3006 for (var i = 0, n = arguments.length; i < n; ++i) {
|
|
3007 arr[i] = arguments[i];
|
|
3008 }
|
|
3009 return cssSealerUnsealerPair.seal(arr);
|
|
3010 };
|
|
3011 imports.htmlAttr___ = function (s) {
|
|
3012 return html.escapeAttrib(String(s || ''));
|
|
3013 };
|
|
3014 imports.html___ = safeHtml;
|
|
3015 imports.rewriteUri___ = function (uri, mimeType) {
|
|
3016 var s = rewriteAttribute(null, null, html4.atype.URI, uri);
|
|
3017 if (!s) { throw new Error(); }
|
|
3018 return s;
|
|
3019 };
|
|
3020 imports.suffix___ = function (nmtokens) {
|
|
3021 var p = String(nmtokens).replace(/^\s+|\s+$/g, '').split(/\s+/g);
|
|
3022 var out = [];
|
|
3023 for (var i = 0; i < p.length; ++i) {
|
|
3024 var nmtoken = rewriteAttribute(null, null, html4.atype.ID, p[i]);
|
|
3025 if (!nmtoken) { throw new Error(nmtokens); }
|
|
3026 out.push(nmtoken);
|
|
3027 }
|
|
3028 return out.join(' ');
|
|
3029 };
|
|
3030 imports.ident___ = function (nmtokens) {
|
|
3031 var p = String(nmtokens).replace(/^\s+|\s+$/g, '').split(/\s+/g);
|
|
3032 var out = [];
|
|
3033 for (var i = 0; i < p.length; ++i) {
|
|
3034 var nmtoken = rewriteAttribute(null, null, html4.atype.CLASSES, p[i]);
|
|
3035 if (!nmtoken) { throw new Error(nmtokens); }
|
|
3036 out.push(nmtoken);
|
|
3037 }
|
|
3038 return out.join(' ');
|
|
3039 };
|
|
3040 // Maps a lower-cased style property name, e.g. background-image,
|
|
3041 // to a style object property name, e.g. backgroundImage, so that
|
|
3042 // it can be used as a style object property name as in
|
|
3043 // myHtmlElement.style['backgroundImage'].
|
|
3044 var canonicalStylePropertyNames = {};
|
|
3045 // Maps style property names, e.g. cssFloat, to property names, e.g. float.
|
|
3046 var cssPropertyNames = [];
|
|
3047
|
|
3048 cajita.forOwnKeys(css.properties,
|
|
3049 ___.frozenFunc(function (cssPropertyName, pattern) {
|
|
3050 var baseStylePropertyName = cssPropertyName.replace(
|
|
3051 /-([a-z])/g, function (_, letter) { return letter.toUpperCase(); });
|
|
3052 var canonStylePropertyName = baseStylePropertyName;
|
|
3053 cssPropertyNames[baseStylePropertyName]
|
|
3054 = cssPropertyNames[canonStylePropertyName]
|
|
3055 = cssPropertyName;
|
|
3056 if (css.alternates.hasOwnProperty(canonStylePropertyName)) {
|
|
3057 var alts = css.alternates[canonStylePropertyName];
|
|
3058 for (var i = alts.length; --i >= 0;) {
|
|
3059 cssPropertyNames[alts[i]] = cssPropertyName;
|
|
3060 // Handle oddities like cssFloat/styleFloat.
|
|
3061 if (alts[i] in document.documentElement.style
|
|
3062 && !(canonStylePropertyName in document.documentElement.style)) {
|
|
3063 canonStylePropertyName = alts[i];
|
|
3064 }
|
|
3065 }
|
|
3066 }
|
|
3067 canonicalStylePropertyNames[cssPropertyName] = canonStylePropertyName;
|
|
3068 }));
|
|
3069
|
|
3070 function TameStyle(style, editable) {
|
|
3071 this.style___ = style;
|
|
3072 this.editable___ = editable;
|
|
3073 ___.grantCall(this, 'getPropertyValue');
|
|
3074 }
|
|
3075 nodeClasses.Style = TameStyle;
|
|
3076 TameStyle.prototype.allowProperty___ = function (cssPropertyName) {
|
|
3077 return css.properties.hasOwnProperty(cssPropertyName);
|
|
3078 };
|
|
3079 TameStyle.prototype.handleRead___ = function (stylePropertyName) {
|
|
3080 if (!this.style___
|
|
3081 || !cssPropertyNames.hasOwnProperty(stylePropertyName)) {
|
|
3082 return void 0;
|
|
3083 }
|
|
3084 var cssPropertyName = cssPropertyNames[stylePropertyName];
|
|
3085 if (!this.allowProperty___(cssPropertyName)) { return void 0; }
|
|
3086 var canonName = canonicalStylePropertyNames[cssPropertyName];
|
|
3087 return String(this.style___[canonName] || '');
|
|
3088 };
|
|
3089 TameStyle.prototype.getPropertyValue = function (cssPropertyName) {
|
|
3090 cssPropertyName = String(cssPropertyName || '').toLowerCase();
|
|
3091 if (!this.allowProperty___(cssPropertyName)) { return ''; }
|
|
3092 var canonName = canonicalStylePropertyNames[cssPropertyName];
|
|
3093 return String(this.style___[canonName] || '');
|
|
3094 };
|
|
3095 TameStyle.prototype.handleSet___ = function (stylePropertyName, value) {
|
|
3096 if (!this.editable___) { throw new Error('style not editable'); }
|
|
3097 if (!cssPropertyNames.hasOwnProperty(stylePropertyName)) {
|
|
3098 throw new Error('Unknown CSS property name ' + stylePropertyName);
|
|
3099 }
|
|
3100 var cssPropertyName = cssPropertyNames[stylePropertyName];
|
|
3101 if (!this.allowProperty___(cssPropertyName)) { return void 0; }
|
|
3102 var pattern = css.properties[cssPropertyName];
|
|
3103 if (!pattern) { throw new Error('style not editable'); }
|
|
3104 var val = '' + (value || '');
|
|
3105 // CssPropertyPatterns.java only allows styles of the form
|
|
3106 // url("..."). See the BUILTINS definition for the "uri" symbol.
|
|
3107 val = val.replace(
|
|
3108 /\burl\s*\(\s*\"([^\"]*)\"\s*\)/gi,
|
|
3109 function (_, url) {
|
|
3110 var decodedUrl = decodeCssString(url);
|
|
3111 var rewrittenUrl = uriCallback
|
|
3112 ? uriCallback.rewrite(decodedUrl, 'image/*')
|
|
3113 : null;
|
|
3114 if (!rewrittenUrl) {
|
|
3115 rewrittenUrl = 'about:blank';
|
|
3116 }
|
|
3117 return 'url("'
|
|
3118 + rewrittenUrl.replace(
|
|
3119 /[\"\'\{\}\(\):\\]/g,
|
|
3120 function (ch) {
|
|
3121 return '\\' + ch.charCodeAt(0).toString(16) + ' ';
|
|
3122 })
|
|
3123 + '")';
|
|
3124 });
|
|
3125 if (val && !pattern.test(val + ' ')) {
|
|
3126 throw new Error('bad value `' + val + '` for CSS property '
|
|
3127 + stylePropertyName);
|
|
3128 }
|
|
3129 var canonName = canonicalStylePropertyNames[cssPropertyName];
|
|
3130 this.style___[canonName] = val;
|
|
3131 return value;
|
|
3132 };
|
|
3133 TameStyle.prototype.toString = function () { return '[Fake Style]'; };
|
|
3134
|
|
3135 function TameComputedStyle(style) {
|
|
3136 TameStyle.call(this, style, false);
|
|
3137 }
|
|
3138 classUtils.extend(TameComputedStyle, TameStyle);
|
|
3139 TameComputedStyle.prototype.allowProperty___ = function (cssPropertyName) {
|
|
3140 return css.COMPUTED_STYLE_WHITELIST.hasOwnProperty(cssPropertyName);
|
|
3141 };
|
|
3142 TameComputedStyle.prototype.toString = function () {
|
|
3143 return '[Fake Computed Style]';
|
|
3144 };
|
|
3145
|
|
3146 nodeClasses.XMLHttpRequest = domitaModules.TameXMLHttpRequest(
|
|
3147 domitaModules.XMLHttpRequestCtor(
|
|
3148 window.XMLHttpRequest,
|
|
3149 window.ActiveXObject),
|
|
3150 uriCallback);
|
|
3151
|
|
3152 /**
|
|
3153 * given a number, outputs the equivalent css text.
|
|
3154 * @param {number} num
|
|
3155 * @return {string} an CSS representation of a number suitable for both html
|
|
3156 * attribs and plain text.
|
|
3157 */
|
|
3158 imports.cssNumber___ = function (num) {
|
|
3159 if ('number' === typeof num && isFinite(num) && !isNaN(num)) {
|
|
3160 return '' + num;
|
|
3161 }
|
|
3162 throw new Error(num);
|
|
3163 };
|
|
3164 /**
|
|
3165 * given a number as 24 bits of RRGGBB, outputs a properly formatted CSS
|
|
3166 * color.
|
|
3167 * @param {number} num
|
|
3168 * @return {string} a CSS representation of num suitable for both html
|
|
3169 * attribs and plain text.
|
|
3170 */
|
|
3171 imports.cssColor___ = function (color) {
|
|
3172 // TODO: maybe whitelist the color names defined for CSS if the arg is a
|
|
3173 // string.
|
|
3174 if ('number' !== typeof color || (color != (color | 0))) {
|
|
3175 throw new Error(color);
|
|
3176 }
|
|
3177 var hex = '0123456789abcdef';
|
|
3178 return '#' + hex.charAt((color >> 20) & 0xf)
|
|
3179 + hex.charAt((color >> 16) & 0xf)
|
|
3180 + hex.charAt((color >> 12) & 0xf)
|
|
3181 + hex.charAt((color >> 8) & 0xf)
|
|
3182 + hex.charAt((color >> 4) & 0xf)
|
|
3183 + hex.charAt(color & 0xf);
|
|
3184 };
|
|
3185 imports.cssUri___ = function (uri, mimeType) {
|
|
3186 var s = rewriteAttribute(null, null, html4.atype.URI, uri);
|
|
3187 if (!s) { throw new Error(); }
|
|
3188 return s;
|
|
3189 };
|
|
3190
|
|
3191 /**
|
|
3192 * Create a CSS stylesheet with the given text and append it to the DOM.
|
|
3193 * @param {string} cssText a well-formed stylesheet production.
|
|
3194 */
|
|
3195 imports.emitCss___ = function (cssText) {
|
|
3196 this.getCssContainer___().appendChild(
|
|
3197 bridal.createStylesheet(document, cssText));
|
|
3198 };
|
|
3199 /** The node to which gadget stylesheets should be added. */
|
|
3200 imports.getCssContainer___ = function () {
|
|
3201 return document.getElementsByTagName('head')[0];
|
|
3202 };
|
|
3203
|
|
3204 if (!/^-/.test(idSuffix)) {
|
|
3205 throw new Error('id suffix "' + idSuffix + '" must start with "-"');
|
|
3206 }
|
|
3207 var idClass = idSuffix.substring(1);
|
|
3208 /** A per-gadget class used to separate style rules. */
|
|
3209 imports.getIdClass___ = function () {
|
|
3210 return idClass;
|
|
3211 };
|
|
3212
|
|
3213 // TODO(mikesamuel): remove these, and only expose them via window once
|
|
3214 // Valija works
|
|
3215 imports.setTimeout = tameSetTimeout;
|
|
3216 imports.setInterval = tameSetInterval;
|
|
3217 imports.clearTimeout = tameClearTimeout;
|
|
3218 imports.clearInterval = tameClearInterval;
|
|
3219
|
|
3220 var tameDocument = new TameHTMLDocument(
|
|
3221 document,
|
|
3222 pseudoBodyNode,
|
|
3223 String(optPseudoWindowLocation.hostname || 'nosuchhost.fake'),
|
|
3224 true);
|
|
3225 imports.document = tameDocument;
|
|
3226
|
|
3227 // TODO(mikesamuel): figure out a mechanism by which the container can
|
|
3228 // specify the gadget's apparent URL.
|
|
3229 // See http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html#location0
|
|
3230 var tameLocation = ___.primFreeze({
|
|
3231 toString: ___.frozenFunc(function () { return tameLocation.href; }),
|
|
3232 href: String(optPseudoWindowLocation.href || 'http://nosuchhost.fake/'),
|
|
3233 hash: String(optPseudoWindowLocation.hash || ''),
|
|
3234 host: String(optPseudoWindowLocation.host || 'nosuchhost.fake'),
|
|
3235 hostname: String(optPseudoWindowLocation.hostname || 'nosuchhost.fake'),
|
|
3236 pathname: String(optPseudoWindowLocation.pathname || '/'),
|
|
3237 port: String(optPseudoWindowLocation.port || ''),
|
|
3238 protocol: String(optPseudoWindowLocation.protocol || 'http:'),
|
|
3239 search: String(optPseudoWindowLocation.search || '')
|
|
3240 });
|
|
3241
|
|
3242 // See spec at http://www.whatwg.org/specs/web-apps/current-work/multipage/browsers.html#navigator
|
|
3243 var tameNavigator = ___.primFreeze({
|
|
3244 appCodeName: 'Caja',
|
|
3245 appName: 'Sandbox',
|
|
3246 appVersion: '1.0', // Should we expose the user's Locale here?
|
|
3247 language: '', // Should we expose the user's Locale here?
|
|
3248 platform: 'Caja',
|
|
3249 oscpu: 'Caja',
|
|
3250 vendor: '',
|
|
3251 vendorSub: '',
|
|
3252 product: 'Caja',
|
|
3253 productSub: '',
|
|
3254 userAgent: 'Caja/1.0'
|
|
3255 });
|
|
3256
|
|
3257 /**
|
|
3258 * Set of allowed pseudo elements as described at
|
|
3259 * http://www.w3.org/TR/CSS2/selector.html#q20
|
|
3260 */
|
|
3261 var PSEUDO_ELEMENT_WHITELIST = {
|
|
3262 // after and before disallowed since they can leak information about
|
|
3263 // arbitrary ancestor nodes.
|
|
3264 'first-letter': true,
|
|
3265 'first-line': true
|
|
3266 };
|
|
3267
|
|
3268 /**
|
|
3269 * See http://www.whatwg.org/specs/web-apps/current-work/multipage/browsers.html#window for the full API.
|
|
3270 */
|
|
3271 function TameWindow() {
|
|
3272 this.properties___ = {};
|
|
3273 }
|
|
3274
|
|
3275 /**
|
|
3276 * An <a href=
|
|
3277 * href=http://www.w3.org/TR/DOM-Level-2-Views/views.html#Views-AbstractView
|
|
3278 * >AbstractView</a> implementation that exposes styling, positioning, and
|
|
3279 * sizing information about the current document's pseudo-body.
|
|
3280 * <p>
|
|
3281 * The AbstractView spec specifies very little in its IDL description, but
|
|
3282 * mozilla defines it thusly:<blockquote>
|
|
3283 * document.defaultView is generally a reference to the window object
|
|
3284 * for the document, however that is not defined in the specification
|
|
3285 * and can't be relied upon for all host environments, particularly as
|
|
3286 * not all browsers implement it.
|
|
3287 * </blockquote>
|
|
3288 * <p>
|
|
3289 * We can't provide access to the tamed window directly from document
|
|
3290 * since it is the global scope of valija code, and so access to another
|
|
3291 * module's tamed window provides an unbounded amount of authority.
|
|
3292 * <p>
|
|
3293 * Instead, we expose styling, positioning, and sizing properties
|
|
3294 * via this class. All of this authority is already available from the
|
|
3295 * document.
|
|
3296 */
|
|
3297 function TameDefaultView() {
|
|
3298 // TODO(mikesamuel): Implement in terms of
|
|
3299 // http://www.w3.org/TR/cssom-view/#the-windowview-interface
|
|
3300 // TODO: expose a read-only version of the document
|
|
3301 this.document = tameDocument;
|
|
3302 // Exposing an editable default view that pointed to a read-only
|
|
3303 // tameDocument via document.defaultView would allow escalation of
|
|
3304 // authority.
|
|
3305 assert(tameDocument.editable___);
|
|
3306 ___.grantRead(this, 'document');
|
|
3307 }
|
|
3308
|
|
3309 cajita.forOwnKeys({
|
|
3310 document: tameDocument,
|
|
3311 location: tameLocation,
|
|
3312 navigator: tameNavigator,
|
|
3313 setTimeout: tameSetTimeout,
|
|
3314 setInterval: tameSetInterval,
|
|
3315 clearTimeout: tameClearTimeout,
|
|
3316 clearInterval: tameClearInterval,
|
|
3317 addEventListener: ___.frozenFunc(function (name, listener, useCapture) {
|
|
3318 if (name === 'load') {
|
|
3319 ___.asFunc(listener);
|
|
3320 tameDocument.onLoadListeners___.push(listener);
|
|
3321 }
|
|
3322 }),
|
|
3323 removeEventListener: ___.frozenFunc(function (name, listener, useCapture) {
|
|
3324 var listeners = tameDocument.onLoadListeners___;
|
|
3325 var k = 0;
|
|
3326 for (var i = 0, n = listeners.length; i < n; ++i) {
|
|
3327 listeners[i - k] = listeners[i];
|
|
3328 if (listeners[i] === listener) {
|
|
3329 ++k;
|
|
3330 }
|
|
3331 }
|
|
3332 listeners.length -= k;
|
|
3333 }),
|
|
3334 dispathEvent: ___.frozenFunc(function (evt) {
|
|
3335 // TODO(ihab.awad): Implement
|
|
3336 })
|
|
3337 }, ___.func(function (propertyName, value) {
|
|
3338 TameWindow.prototype[propertyName] = value;
|
|
3339 ___.grantRead(TameWindow.prototype, propertyName);
|
|
3340 }));
|
|
3341 cajita.forOwnKeys({
|
|
3342 scrollBy: ___.frozenFunc(
|
|
3343 function (dx, dy) {
|
|
3344 // The window is always auto scrollable, so make the apparent window
|
|
3345 // body scrollable if the gadget tries to scroll it.
|
|
3346 if (dx || dy) { makeScrollable(tameDocument.body___); }
|
|
3347 tameScrollBy(tameDocument.body___, dx, dy);
|
|
3348 }),
|
|
3349 scrollTo: ___.frozenFunc(
|
|
3350 function (x, y) {
|
|
3351 // The window is always auto scrollable, so make the apparent window
|
|
3352 // body scrollable if the gadget tries to scroll it.
|
|
3353 makeScrollable(tameDocument.body___);
|
|
3354 tameScrollTo(tameDocument.body___, x, y);
|
|
3355 }),
|
|
3356 resizeTo: ___.frozenFunc(
|
|
3357 function (w, h) {
|
|
3358 tameResizeTo(tameDocument.body___, w, h);
|
|
3359 }),
|
|
3360 resizeBy: ___.frozenFunc(
|
|
3361 function (dw, dh) {
|
|
3362 tameResizeBy(tameDocument.body___, dw, dh);
|
|
3363 }),
|
|
3364 /** A partial implementation of getComputedStyle. */
|
|
3365 getComputedStyle: ___.frozenFunc(
|
|
3366 // Pseudo elements are suffixes like :first-line which constrain to
|
|
3367 // a portion of the element's content as defined at
|
|
3368 // http://www.w3.org/TR/CSS2/selector.html#q20
|
|
3369 function (tameElement, pseudoElement) {
|
|
3370 cajita.guard(tameNodeTrademark, tameElement);
|
|
3371 // Coerce all nullish values to undefined, since that is the value
|
|
3372 // for unspecified parameters.
|
|
3373 // Per bug 973: pseudoElement should be null according to the
|
|
3374 // spec, but mozilla docs contradict this.
|
|
3375 // From https://developer.mozilla.org/En/DOM:window.getComputedStyle
|
|
3376 // pseudoElt is a string specifying the pseudo-element to match.
|
|
3377 // Should be an empty string for regular elements.
|
|
3378 pseudoElement = (pseudoElement === null || pseudoElement === void 0
|
|
3379 || '' === pseudoElement)
|
|
3380 ? void 0 : String(pseudoElement).toLowerCase();
|
|
3381 if (pseudoElement !== void 0
|
|
3382 && !PSEUDO_ELEMENT_WHITELIST.hasOwnProperty(pseudoElement)) {
|
|
3383 throw new Error('Bad pseudo class ' + pseudoElement);
|
|
3384 }
|
|
3385 // No need to check editable since computed styles are readonly.
|
|
3386
|
|
3387 var rawNode = tameElement.node___;
|
|
3388 if (rawNode.currentStyle && pseudoElement === void 0) {
|
|
3389 return new TameComputedStyle(rawNode.currentStyle);
|
|
3390 } else if (window.getComputedStyle) {
|
|
3391 var rawStyleNode = window.getComputedStyle(
|
|
3392 tameElement.node___, pseudoElement);
|
|
3393 return new TameComputedStyle(rawStyleNode);
|
|
3394 } else {
|
|
3395 throw new Error(
|
|
3396 'Computed style not available for pseudo element '
|
|
3397 + pseudoElement);
|
|
3398 }
|
|
3399 })
|
|
3400
|
|
3401 // NOT PROVIDED
|
|
3402 // event: a global on IE. We always define it in scopes that can handle
|
|
3403 // events.
|
|
3404 // opera: defined only on Opera.
|
|
3405 }, ___.func(function (propertyName, value) {
|
|
3406 TameWindow.prototype[propertyName] = value;
|
|
3407 ___.grantRead(TameWindow.prototype, propertyName);
|
|
3408 TameDefaultView.prototype[propertyName] = value;
|
|
3409 ___.grantRead(TameDefaultView.prototype, propertyName);
|
|
3410 }));
|
|
3411 TameWindow.prototype.handleRead___ = function (name) {
|
|
3412 name = String(name);
|
|
3413 if (endsWith__.test(name)) { return void 0; }
|
|
3414 var handlerName = name + '_getter___';
|
|
3415 if (this[handlerName]) {
|
|
3416 return this[handlerName]();
|
|
3417 }
|
|
3418 handlerName = handlerName.toLowerCase();
|
|
3419 if (this[handlerName]) {
|
|
3420 return this[handlerName]();
|
|
3421 }
|
|
3422 if (___.hasOwnProp(this, name)) {
|
|
3423 return this[name];
|
|
3424 } else {
|
|
3425 return void 0;
|
|
3426 }
|
|
3427 };
|
|
3428 TameWindow.prototype.handleSet___ = function (name, val) {
|
|
3429 name = String(name);
|
|
3430 if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); }
|
|
3431 var handlerName = name + '_setter___';
|
|
3432 if (this[handlerName]) {
|
|
3433 return this[handlerName](val);
|
|
3434 }
|
|
3435 handlerName = handlerName.toLowerCase();
|
|
3436 if (this[handlerName]) {
|
|
3437 return this[handlerName](val);
|
|
3438 }
|
|
3439 this[name + '_canEnum___'] = true;
|
|
3440 this[name + '_canRead___'] = true;
|
|
3441 return this[name] = val;
|
|
3442 };
|
|
3443 TameWindow.prototype.handleDelete___ = function (name) {
|
|
3444 name = String(name);
|
|
3445 if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); }
|
|
3446 var handlerName = name + '_deleter___';
|
|
3447 if (this[handlerName]) {
|
|
3448 return this[handlerName]();
|
|
3449 }
|
|
3450 handlerName = handlerName.toLowerCase();
|
|
3451 if (this[handlerName]) {
|
|
3452 return this[handlerName]();
|
|
3453 }
|
|
3454 return (
|
|
3455 delete this[name]
|
|
3456 && delete this[name + '_canEnum___']
|
|
3457 && delete this[name + '_canRead___']);
|
|
3458 };
|
|
3459 TameWindow.prototype.handleEnum___ = function (ownFlag) {
|
|
3460 // TODO(metaweta): Add code to list all the other handled stuff we know
|
|
3461 // about.
|
|
3462 return cajita.allKeys(this);
|
|
3463 };
|
|
3464
|
|
3465 var tameWindow = new TameWindow();
|
|
3466 var tameDefaultView = new TameDefaultView(tameDocument.editable___);
|
|
3467
|
|
3468 function propertyOnlyHasGetter(_) {
|
|
3469 throw new TypeError('setting a property that only has a getter');
|
|
3470 }
|
|
3471 cajita.forOwnKeys({
|
|
3472 // We define all the window positional properties relative to
|
|
3473 // the fake body element to maintain the illusion that the fake
|
|
3474 // document is completely defined by the nodes under the fake body.
|
|
3475 clientLeft: {
|
|
3476 get: function () { return tameDocument.body___.clientLeft; }
|
|
3477 },
|
|
3478 clientTop: {
|
|
3479 get: function () { return tameDocument.body___.clientTop; }
|
|
3480 },
|
|
3481 clientHeight: {
|
|
3482 get: function () { return tameDocument.body___.clientHeight; }
|
|
3483 },
|
|
3484 clientWidth: {
|
|
3485 get: function () { return tameDocument.body___.clientWidth; }
|
|
3486 },
|
|
3487 offsetLeft: {
|
|
3488 get: function () { return tameDocument.body___.offsetLeft; }
|
|
3489 },
|
|
3490 offsetTop: {
|
|
3491 get: function () { return tameDocument.body___.offsetTop; }
|
|
3492 },
|
|
3493 offsetHeight: {
|
|
3494 get: function () { return tameDocument.body___.offsetHeight; }
|
|
3495 },
|
|
3496 offsetWidth: {
|
|
3497 get: function () { return tameDocument.body___.offsetWidth; }
|
|
3498 },
|
|
3499 // page{X,Y}Offset appear only as members of window, not on all elements
|
|
3500 // but http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
|
|
3501 // says that they are identical to the scrollTop/Left on all browsers but
|
|
3502 // old versions of Safari.
|
|
3503 pageXOffset: {
|
|
3504 get: function () { return tameDocument.body___.scrollLeft; }
|
|
3505 },
|
|
3506 pageYOffset: {
|
|
3507 get: function () { return tameDocument.body___.scrollTop; }
|
|
3508 },
|
|
3509 scrollLeft: {
|
|
3510 get: function () { return tameDocument.body___.scrollLeft; },
|
|
3511 set: function (x) { tameDocument.body___.scrollLeft = +x; return x; }
|
|
3512 },
|
|
3513 scrollTop: {
|
|
3514 get: function () { return tameDocument.body___.scrollTop; },
|
|
3515 set: function (y) { tameDocument.body___.scrollTop = +y; return y; }
|
|
3516 },
|
|
3517 scrollHeight: {
|
|
3518 get: function () { return tameDocument.body___.scrollHeight; }
|
|
3519 },
|
|
3520 scrollWidth: {
|
|
3521 get: function () { return tameDocument.body___.scrollWidth; }
|
|
3522 }
|
|
3523 }, ___.func(function (propertyName, def) {
|
|
3524 var setter = def.set || propertyOnlyHasGetter;
|
|
3525 // TODO(mikesamuel): define on prototype.
|
|
3526 ___.useGetHandler(tameWindow, propertyName, def.get);
|
|
3527 ___.useSetHandler(tameWindow, propertyName, setter);
|
|
3528 ___.useGetHandler(tameDefaultView, propertyName, def.get);
|
|
3529 ___.useSetHandler(tameDefaultView, propertyName, setter);
|
|
3530 var tameBody = tameDocument.getBody();
|
|
3531 ___.useGetHandler(tameBody, propertyName, def.get);
|
|
3532 ___.useSetHandler(tameBody, propertyName, setter);
|
|
3533 var tameDocEl = tameDocument.getDocumentElement();
|
|
3534 ___.useGetHandler(tameDocEl, propertyName, def.get);
|
|
3535 ___.useSetHandler(tameDocEl, propertyName, setter);
|
|
3536 }));
|
|
3537
|
|
3538 cajita.forOwnKeys({
|
|
3539 innerHeight: function () { return tameDocument.body___.clientHeight; },
|
|
3540 innerWidth: function () { return tameDocument.body___.clientWidth; },
|
|
3541 outerHeight: function () { return tameDocument.body___.clientHeight; },
|
|
3542 outerWidth: function () { return tameDocument.body___.clientWidth; }
|
|
3543 }, ___.func(function (propertyName, handler) {
|
|
3544 // TODO(mikesamuel): define on prototype.
|
|
3545 ___.useGetHandler(tameWindow, propertyName, handler);
|
|
3546 ___.useGetHandler(tameDefaultView, propertyName, handler);
|
|
3547 }));
|
|
3548
|
|
3549 // Attach reflexive properties to 'window' object
|
|
3550 var windowProps = ['top', 'self', 'opener', 'parent', 'window'];
|
|
3551 var wpLen = windowProps.length;
|
|
3552 for (var i = 0; i < wpLen; ++i) {
|
|
3553 var prop = windowProps[i];
|
|
3554 tameWindow[prop] = tameWindow;
|
|
3555 ___.grantRead(tameWindow, prop);
|
|
3556 }
|
|
3557
|
|
3558 if (tameDocument.editable___) {
|
|
3559 tameDocument.defaultView = tameDefaultView;
|
|
3560 ___.grantRead(tameDocument, 'defaultView');
|
|
3561 }
|
|
3562
|
|
3563 // Iterate over all node classes, assigning them to the Window object
|
|
3564 // under their DOM Level 2 standard name.
|
|
3565 cajita.forOwnKeys(nodeClasses, ___.func(function(name, ctor) {
|
|
3566 ___.primFreeze(ctor);
|
|
3567 tameWindow[name] = ctor;
|
|
3568 ___.grantRead(tameWindow, name);
|
|
3569 }));
|
|
3570
|
|
3571 // TODO(ihab.awad): Build a more sophisticated virtual class hierarchy by
|
|
3572 // creating a table of actual subclasses and instantiating tame nodes by
|
|
3573 // table lookups. This will allow the client code to see a truly consistent
|
|
3574 // DOM class hierarchy.
|
|
3575 var defaultNodeClasses = [
|
|
3576 'HTMLAppletElement',
|
|
3577 'HTMLAreaElement',
|
|
3578 'HTMLBaseElement',
|
|
3579 'HTMLBaseFontElement',
|
|
3580 'HTMLBodyElement',
|
|
3581 'HTMLBRElement',
|
|
3582 'HTMLButtonElement',
|
|
3583 'HTMLDirectoryElement',
|
|
3584 'HTMLDivElement',
|
|
3585 'HTMLDListElement',
|
|
3586 'HTMLFieldSetElement',
|
|
3587 'HTMLFontElement',
|
|
3588 'HTMLFrameElement',
|
|
3589 'HTMLFrameSetElement',
|
|
3590 'HTMLHeadElement',
|
|
3591 'HTMLHeadingElement',
|
|
3592 'HTMLHRElement',
|
|
3593 'HTMLHtmlElement',
|
|
3594 'HTMLIFrameElement',
|
|
3595 'HTMLIsIndexElement',
|
|
3596 'HTMLLabelElement',
|
|
3597 'HTMLLegendElement',
|
|
3598 'HTMLLIElement',
|
|
3599 'HTMLLinkElement',
|
|
3600 'HTMLMapElement',
|
|
3601 'HTMLMenuElement',
|
|
3602 'HTMLMetaElement',
|
|
3603 'HTMLModElement',
|
|
3604 'HTMLObjectElement',
|
|
3605 'HTMLOListElement',
|
|
3606 'HTMLOptGroupElement',
|
|
3607 'HTMLOptionElement',
|
|
3608 'HTMLParagraphElement',
|
|
3609 'HTMLParamElement',
|
|
3610 'HTMLPreElement',
|
|
3611 'HTMLQuoteElement',
|
|
3612 'HTMLScriptElement',
|
|
3613 'HTMLSelectElement',
|
|
3614 'HTMLStyleElement',
|
|
3615 'HTMLTableCaptionElement',
|
|
3616 'HTMLTableCellElement',
|
|
3617 'HTMLTableColElement',
|
|
3618 'HTMLTableElement',
|
|
3619 'HTMLTableRowElement',
|
|
3620 'HTMLTableSectionElement',
|
|
3621 'HTMLTextAreaElement',
|
|
3622 'HTMLTitleElement',
|
|
3623 'HTMLUListElement'
|
|
3624 ];
|
|
3625
|
|
3626 var defaultNodeClassCtor = ___.primFreeze(TameElement);
|
|
3627 for (var i = 0; i < defaultNodeClasses.length; i++) {
|
|
3628 tameWindow[defaultNodeClasses[i]] = defaultNodeClassCtor;
|
|
3629 ___.grantRead(tameWindow, defaultNodeClasses[i]);
|
|
3630 }
|
|
3631
|
|
3632 var outers = imports.outers;
|
|
3633 if (___.isJSONContainer(outers)) {
|
|
3634 // For Valija, attach use the window object as outers.
|
|
3635 cajita.forOwnKeys(outers, ___.func(function(k, v) {
|
|
3636 if (!(k in tameWindow)) {
|
|
3637 tameWindow[k] = v;
|
|
3638 ___.grantRead(tameWindow, k);
|
|
3639 }
|
|
3640 }));
|
|
3641 imports.outers = tameWindow;
|
|
3642 } else {
|
|
3643 imports.window = tameWindow;
|
|
3644 }
|
|
3645 }
|
|
3646
|
|
3647 return attachDocumentStub;
|
|
3648 })();
|
|
3649
|
|
3650 /**
|
|
3651 * Function called from rewritten event handlers to dispatch an event safely.
|
|
3652 */
|
|
3653 function plugin_dispatchEvent___(thisNode, event, pluginId, handler) {
|
|
3654 event = (event || window.event);
|
|
3655 var sig = String(handler).match(/^function\b[^\)]*\)/);
|
|
3656 cajita.log(
|
|
3657 'Dispatch ' + (event && event.type) +
|
|
3658 'event thisNode=' + thisNode + ', ' +
|
|
3659 'event=' + event + ', ' +
|
|
3660 'pluginId=' + pluginId + ', ' +
|
|
3661 'handler=' + (sig ? sig[0] : handler));
|
|
3662 var imports = ___.getImports(pluginId);
|
|
3663 switch (typeof handler) {
|
|
3664 case 'string':
|
|
3665 handler = imports[handler];
|
|
3666 break;
|
|
3667 case 'function': case 'object':
|
|
3668 break;
|
|
3669 default:
|
|
3670 throw new Error(
|
|
3671 'Expected function as event handler, not ' + typeof handler);
|
|
3672 }
|
|
3673 if (___.startCallerStack) { ___.startCallerStack(); }
|
|
3674 imports.isProcessingEvent___ = true;
|
|
3675 try {
|
|
3676 return ___.callPub(
|
|
3677 handler, 'call',
|
|
3678 [___.USELESS,
|
|
3679 imports.tameEvent___(event),
|
|
3680 imports.tameNode___(thisNode, true)
|
|
3681 ]);
|
|
3682 } catch (ex) {
|
|
3683 if (ex && ex.cajitaStack___ && 'undefined' !== (typeof console)) {
|
|
3684 console.error('Event dispatch %s: %s',
|
|
3685 handler, ___.unsealCallerStack(ex.cajitaStack___).join('\n'));
|
|
3686 }
|
|
3687 throw ex;
|
|
3688 } finally {
|
|
3689 imports.isProcessingEvent___ = false;
|
|
3690 }
|
|
3691 }
|