Mercurial > caja-test
changeset 0:633c9cb05555
Origination.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Sun, 07 Jun 2009 19:29:10 -0700 |
parents | |
children | 00d50391d378 |
files | caja-test.html js/caja-test.js js/ext/bridal.js js/ext/cajita.js js/ext/domita.js js/ext/html-emitter.js js/ext/html-sanitizer.js js/ext/jquery.js js/ext/unicode.js js/my-caja-module.co.js js/my-caja-module.js pavement.py |
diffstat | 12 files changed, 12622 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/caja-test.html Sun Jun 07 19:29:10 2009 -0700 @@ -0,0 +1,11 @@ +<html> +<head> + <title>Caja Test</title> +</head> +<body> +Hi. +<script src="js/ext/cajita.js"></script> +<script src="js/caja-test.js"></script> +<script src="js/my-caja-module.co.js"></script> +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/caja-test.js Sun Jun 07 19:29:10 2009 -0700 @@ -0,0 +1,13 @@ +___.getNewModuleHandler().setImports( + {register: ___.func( + function(f) { + console.log(f); + }) + }); + +window.addEventListener( + "load", + function() { + }, + false +);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/ext/bridal.js Sun Jun 07 19:29:10 2009 -0700 @@ -0,0 +1,550 @@ +// Copyright (C) 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview + * A set of utility functions that implement browser feature testing to unify + * certain DOM behaviors, and a set of recommendations about when to use these + * functions as opposed to the native DOM functions. + * + * @author ihab.awad@gmail.com + * @author jasvir@gmail.com + * @provides bridal + * @requires ___, cajita, document, html, html4, navigator + */ + +var bridal = (function() { + + //////////////////////////////////////////////////////////////////////////// + // Private section + //////////////////////////////////////////////////////////////////////////// + + var isOpera = navigator.userAgent.indexOf('Opera') === 0; + var isIE = !isOpera && navigator.userAgent.indexOf('MSIE') !== -1; + var isWebkit = !isOpera && navigator.userAgent.indexOf('WebKit') !== -1; + + var features = { + attachEvent: !!(document.createElement('div').attachEvent), + setAttributeExtraParam: isIE, + /** + * Does the extended form of extendedCreateElement work? + * From http://msdn.microsoft.com/en-us/library/ms536389.aspx :<blockquote> + * You can also specify all the attributes inside the createElement + * method by using an HTML string for the method argument. + * The following example demonstrates how to dynamically create two + * radio buttons utilizing this technique. + * <pre> + * ... + * var newRadioButton = document.createElement( + * "<INPUT TYPE='RADIO' NAME='RADIOTEST' VALUE='First Choice'>") + * </pre> + * </blockquote> + */ + extendedCreateElement: ( + function () { + try { + var inp = document.createElement('<input name="x" type="radio">'); + return inp.name === 'x' && inp.type === 'radio'; + } catch (ex) { + return false; + } + })() + }; + + var CUSTOM_EVENT_TYPE_SUFFIX = '_custom___'; + function tameEventType(type, opt_isCustom) { + type = String(type); + if (endsWith__.test(type)) { + throw new Error('Invalid event type ' + type); + } + if (opt_isCustom + || html4.atype.SCRIPT !== html4.ATTRIBS['*:on' + type]) { + type = type + CUSTOM_EVENT_TYPE_SUFFIX; + } + return type; + } + + function eventHandlerTypeFilter(handler, tameType) { + // This does not need to check that handler is callable by untrusted code + // since the handler will invoke plugin_dispatchEvent which will do that + // check on the untrusted function reference. + return function (event) { + if (tameType === event.eventType___) { + return handler.call(this, event); + } + }; + } + + var endsWith__ = /__$/; + function constructClone(node, deep) { + var clone; + if (node.nodeType === 1) { + // From http://blog.pengoworks.com/index.cfm/2007/7/16/IE6--IE7-quirks-with-cloneNode-and-form-elements + // It turns out IE 6/7 doesn't properly clone some form elements + // when you use the cloneNode(true) and the form element is a + // checkbox, radio or select element. + // JQuery provides a clone method which attempts to fix this and an issue + // with event listeners. According to the source code for JQuery's clone + // method ( http://docs.jquery.com/Manipulation/clone#true ): + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // We do not need to deal with XHTML DOMs and so can skip the clean step + // that jQuery does. + var tagDesc = node.tagName; + // Copying form state is not strictly mentioned in DOM2's spec of + // cloneNode, but all implementations do it. The value copying + // can be interpreted as fixing implementations' failure to have + // the value attribute "reflect" the input's value as determined by the + // value property. + switch (node.tagName) { + case 'INPUT': + tagDesc = '<input name="' + html.escapeAttrib(node.name) + + '" type="' + html.escapeAttrib(node.type) + + '" value="' + html.escapeAttrib(node.defaultValue) + '"' + + (node.defaultChecked ? ' checked="checked">' : '>'); + break; + case 'OPTION': + tagDesc = '<option ' + + (node.defaultSelected ? ' selected="selected">' : '>'); + break; + case 'TEXTAREA': + tagDesc = '<textarea value="' + + html.escapeAttrib(node.defaultValue) + '">'; + break; + } + + clone = document.createElement(tagDesc); + + var attrs = node.attributes; + for (var i = 0, attr; (attr = attrs[i]); ++i) { + if (attr.specified && !endsWith__.test(attr.name)) { + clone.setAttribute(attr.nodeName, attr.nodeValue); + } + } + } else { + clone = node.cloneNode(false); + } + if (deep) { + // TODO(mikesamuel): should we whitelist nodes here, to e.g. prevent + // untrusted code from reloading an already loaded script by cloning + // a script node that somehow exists in a tree accessible to it? + for (var child = node.firstChild; child; child = child.nextSibling) { + var cloneChild = constructClone(child, deep); + clone.appendChild(cloneChild); + } + } + return clone; + } + + function fixupClone(node, clone) { + for (var child = node.firstChild, cloneChild = clone.firstChild; cloneChild; + child = child.nextSibling, cloneChild = cloneChild.nextSibling) { + fixupClone(child, cloneChild); + } + if (node.nodeType === 1) { + switch (node.tagName) { + case 'INPUT': + clone.value = node.value; + clone.checked = node.checked; + break; + case 'OPTION': + clone.selected = node.selected; + clone.value = node.value; + break; + case 'TEXTAREA': + clone.value = node.value; + break; + } + } + + // Do not copy listeners since DOM2 specifies that only attributes and + // children are copied, and that children should only be copied if the + // deep flag is set. + // The children are handled in constructClone. + var originalAttribs = node.attributes___; + if (originalAttribs) { + var attribs = {}; + clone.attributes___ = attribs; + cajita.forOwnKeys(originalAttribs, ___.func(function (k, v) { + switch (typeof v) { + case 'string': case 'number': case 'boolean': + attribs[k] = v; + break; + } + })); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Public section + //////////////////////////////////////////////////////////////////////////// + + function untameEventType(type) { + var suffix = CUSTOM_EVENT_TYPE_SUFFIX; + var tlen = type.length, slen = suffix.length; + var end = tlen - slen; + if (end >= 0 && suffix === type.substring(end)) { + type = type.substring(0, end); + } + return type; + } + + function initEvent(event, type, bubbles, cancelable) { + type = tameEventType(type, true); + bubbles = Boolean(bubbles); + cancelable = Boolean(cancelable); + + if (event.initEvent) { // Non-IE + event.initEvent(type, bubbles, cancelable); + } else if (bubbles && cancelable) { // IE + event.eventType___ = type; + } else { + // TODO(mikesamuel): can bubbling and cancelable on events be simulated + // via http://msdn.microsoft.com/en-us/library/ms533545(VS.85).aspx + throw new Error( + 'Browser does not support non-bubbling/uncanceleable events'); + } + } + + function dispatchEvent(element, event) { + // TODO(mikesamuel): when we change event dispatching to happen + // asynchronously, we should exempt custom events since those + // need to return a useful value, and there may be code bracketing + // them which could observe asynchronous dispatch. + + // "The return value of dispatchEvent indicates whether any of + // the listeners which handled the event called + // preventDefault. If preventDefault was called the value is + // false, else the value is true." + if (element.dispatchEvent) { + return Boolean(element.dispatchEvent(event)); + } else { + // Only dispatches custom events as when tameEventType(t) !== t. + element.fireEvent('ondataavailable', event); + return Boolean(event.returnValue); + } + } + + /** + * Add an event listener function to an element. + * + * <p>Replaces + * W3C <code>Element::addEventListener</code> and + * IE <code>Element::attachEvent</code>. + * + * @param {HTMLElement} element a native DOM element. + * @param {string} type a string identifying the event type. + * @param {boolean Element::function (event)} handler an event handler. + * @param {boolean} useCapture whether the user wishes to initiate capture. + * @return {boolean Element::function (event)} the handler added. May be + * a wrapper around the input. + */ + function addEventListener(element, type, handler, useCapture) { + type = String(type); + var tameType = tameEventType(type); + if (features.attachEvent) { + // TODO(ihab.awad): How do we emulate 'useCapture' here? + if (type !== tameType) { + var wrapper = eventHandlerTypeFilter(handler, tameType); + element.attachEvent('ondataavailable', wrapper); + return wrapper; + } else { + element.attachEvent('on' + type, handler); + return handler; + } + } else { + // FF2 fails if useCapture not passed or is not a boolean. + element.addEventListener(tameType, handler, useCapture); + return handler; + } + } + + /** + * Remove an event listener function from an element. + * + * <p>Replaces + * W3C <code>Element::removeEventListener</code> and + * IE <code>Element::detachEvent</code>. + * + * @param element a native DOM element. + * @param type a string identifying the event type. + * @param handler a function acting as an event handler. + * @param useCapture whether the user wishes to initiate capture. + */ + function removeEventListener(element, type, handler, useCapture) { + type = String(type); + var tameType = tameEventType(type); + if (features.attachEvent) { + // TODO(ihab.awad): How do we emulate 'useCapture' here? + if (tameType !== type) { + element.detachEvent('ondataavailable', handler); + } else { + element.detachEvent('on' + type, handler); + } + } else { + element.removeEventListener(tameType, handler, useCapture); + } + } + + /** + * Clones a node per {@code Node.clone()}. + * <p> + * Returns a duplicate of this node, i.e., serves as a generic copy + * constructor for nodes. The duplicate node has no parent; + * (parentNode is null.). + * <p> + * Cloning an Element copies all attributes and their values, + * including those generated by the XML processor to represent + * defaulted attributes, but this method does not copy any text it + * contains unless it is a deep clone, since the text is contained + * in a child Text node. Cloning an Attribute directly, as opposed + * to be cloned as part of an Element cloning operation, returns a + * specified attribute (specified is true). Cloning any other type + * of node simply returns a copy of this node. + * <p> + * Note that cloning an immutable subtree results in a mutable copy, + * but the children of an EntityReference clone are readonly. In + * addition, clones of unspecified Attr nodes are specified. And, + * cloning Document, DocumentType, Entity, and Notation nodes is + * implementation dependent. + * + * @param {boolean} deep If true, recursively clone the subtree + * under the specified node; if false, clone only the node itself + * (and its attributes, if it is an Element). + * + * @return {Node} The duplicate node. + */ + function cloneNode(node, deep) { + var clone; + if (!document.all) { // Not IE 6 or IE 7 + clone = node.cloneNode(deep); + } else { + clone = constructClone(node, deep); + } + fixupClone(node, clone); + return clone; + } + + /** + * Create a <code>style</code> element for a document containing some + * specified CSS text. Does not add the element to the document: the client + * may do this separately if desired. + * + * <p>Replaces directly creating the <code>style</code> element and + * populating its contents. + * + * @param document a DOM document. + * @param cssText a string containing a well-formed stylesheet production. + * @return a <code>style</code> element for the specified document. + */ + function createStylesheet(document, cssText) { + // Courtesy Stoyan Stefanov who documents the derivation of this at + // http://www.phpied.com/dynamic-script-and-style-elements-in-ie/ and + // http://yuiblog.com/blog/2007/06/07/style/ + var styleSheet = document.createElement('style'); + styleSheet.setAttribute('type', 'text/css'); + if (styleSheet.styleSheet) { // IE + styleSheet.styleSheet.cssText = cssText; + } else { // the world + styleSheet.appendChild(document.createTextNode(cssText)); + } + return styleSheet; + } + + /** + * Set an attribute on a DOM node. + * + * <p>Replaces DOM <code>Node::setAttribute</code>. + * + * @param {HTMLElement} element a DOM element. + * @param {string} name the name of an attribute. + * @param {string} value the value of an attribute. + */ + function setAttribute(element, name, value) { + switch (name) { + case 'style': + if ((typeof element.style.cssText) === 'string') { + // Setting the 'style' attribute does not work for IE, but + // setting cssText works on IE 6, Firefox, and IE 7. + element.style.cssText = value; + return value; + } + break; + case 'class': + element.className = value; + return value; + case 'for': + element.htmlFor = value; + return value; + } + if (features.setAttributeExtraParam) { + element.setAttribute(name, value, 0); + } else { + element.setAttribute(name, value); + } + return value; + } + + /** + * See <a href="http://www.w3.org/TR/cssom-view/#the-getclientrects" + * >ElementView.getBoundingClientRect()</a>. + * @return {Object} duck types as a TextRectangle with numeric fields + * {@code left}, {@code right}, {@code top}, and {@code bottom}. + */ + function getBoundingClientRect(el) { + var doc = el.ownerDocument; + // Use the native method if present. + if (el.getBoundingClientRect) { + var cRect = el.getBoundingClientRect(); + if (isIE) { + // IE has an unnecessary border, which can be mucked with by styles, so + // the amount of border is not predictable. + // Depending on whether the document is in quirks or standards mode, + // the border will be present on either the HTML or BODY elements. + var fixupLeft = doc.documentElement.clientLeft + doc.body.clientLeft; + cRect.left -= fixupLeft; + cRect.right -= fixupLeft; + var fixupTop = doc.documentElement.clientTop + doc.body.clientTop; + cRect.top -= fixupTop; + cRect.bottom -= fixupTop; + } + return ({ + top: +cRect.top, + left: +cRect.left, + right: +cRect.right, + bottom: +cRect.bottom + }); + } + + // Otherwise, try using the deprecated gecko method, or emulate it in + // horribly inefficient ways. + + // http://code.google.com/p/doctype/wiki/ArticleClientViewportElement + var viewport = (isIE && doc.compatMode === 'CSS1Compat') + ? doc.body : doc.documentElement; + + // Figure out the position relative to the viewport. + // From http://code.google.com/p/doctype/wiki/ArticlePageOffset + var pageX = 0, pageY = 0; + if (el === viewport) { + // The viewport is the origin. + } else if (doc.getBoxObjectFor) { // Handles Firefox < 3 + var elBoxObject = doc.getBoxObjectFor(el); + var viewPortBoxObject = doc.getBoxObjectFor(viewport); + pageX = elBoxObject.screenX - viewPortBoxObject.screenX; + pageY = elBoxObject.screenY - viewPortBoxObject.screenY; + } else { + // Walk the offsetParent chain adding up offsets. + for (var op = el; (op && op !== el); op = op.offsetParent) { + pageX += op.offsetLeft; + pageY += op.offsetTop; + if (op !== el) { + pageX += op.clientLeft || 0; + pageY += op.clientTop || 0; + } + if (isWebkit) { + // On webkit the offsets for position:fixed elements are off by the + // scroll offset. + var opPosition = doc.defaultView.getComputedStyle(op, 'position'); + if (opPosition === 'fixed') { + pageX += doc.body.scrollLeft; + pageY += doc.body.scrollTop; + } + break; + } + } + + // Opera & (safari absolute) incorrectly account for body offsetTop + if ((isWebkit + && doc.defaultView.getComputedStyle(el, 'position') === 'absolute') + || isOpera) { + pageY -= doc.body.offsetTop; + } + + // Accumulate the scroll positions for everything but the body element + for (var op = el; (op = op.offsetParent) && op !== doc.body;) { + pageX -= op.scrollLeft; + // see https://bugs.opera.com/show_bug.cgi?id=249965 + if (!isOpera || op.tagName !== 'TR') { + pageY -= op.scrollTop; + } + } + } + + // Figure out the viewport container so we can subtract the window's + // scroll offsets. + var scrollEl = !isWebkit && doc.compatMode === 'CSS1Compat' + ? doc.documentElement + : doc.body; + + var left = pageX - scrollEl.scrollLeft, top = pageY - scrollEl.scrollTop; + return ({ + top: top, + left: left, + right: left + el.clientWidth, + bottom: top + el.clientHeight + }); + } + + /** + * Returns the value of the named attribute on element. + * + * @param {HTMLElement} element a DOM element. + * @param {string} name the name of an attribute. + */ + function getAttribute(element, name) { + switch (name) { + case 'style': + if ((typeof element.style.cssText) === 'string') { + return element.style.cssText; + } + break; + case 'class': + return element.className; + case 'for': + return element.htmlFor; + } + return element.getAttribute(name); + } + + function getAttributeNode(element, name) { + return element.getAttributeNode(name); + } + + function hasAttribute(element, name) { + if (element.hasAttribute) { // Non IE + return element.hasAttribute(name); + } else { + var attr = getAttributeNode(element, name); + return attr !== null && attr.specified; + } + } + + return { + addEventListener: addEventListener, + removeEventListener: removeEventListener, + initEvent: initEvent, + dispatchEvent: dispatchEvent, + cloneNode: cloneNode, + createStylesheet: createStylesheet, + setAttribute: setAttribute, + getAttribute: getAttribute, + getAttributeNode: getAttributeNode, + hasAttribute: hasAttribute, + getBoundingClientRect: getBoundingClientRect, + untameEventType: untameEventType, + extendedCreateElementFeature: features.extendedCreateElement + }; +})();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/ext/cajita.js Sun Jun 07 19:29:10 2009 -0700 @@ -0,0 +1,3225 @@ +// Copyright (C) 2007 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview the Cajita runtime library. + * It is written in Javascript, not Cajita, and would be rejected by the Cajita + * translator. This module exports two globals:<ol> + * <li>"___" for use by the output of the Cajita translator and by some + * other untranslated Javascript code. + * <li>"cajita" providing some common services to the Cajita programmer. + * </ol> + * @author erights@gmail.com + * @requires console + * @provides ___, arraySlice, cajita, dateToISOString, funcBind + * @overrides Array, Boolean, Date, Function, Number, Object, RegExp, String + * @overrides Error, EvalError, RangeError, ReferenceError, SyntaxError, + * TypeError, URIError + */ + +// TODO(erights): All code text in comments should be enclosed in +// {@code ...}. + +// TODO(ihab.awad): Missing tests in CajitaTest.java for the functionality +// in this file. + + +// Add a tag to whitelisted builtin constructors so we can check the class +// cross-frame. Note that Function is not on the list. + +Array.typeTag___ = 'Array'; +Object.typeTag___ = 'Object'; +String.typeTag___ = 'String'; +Boolean.typeTag___ = 'Boolean'; +Number.typeTag___ = 'Number'; +Date.typeTag___ = 'Date'; +RegExp.typeTag___ = 'RegExp'; +Error.typeTag___ = 'Error'; +EvalError.typeTag___ = 'EvalError'; +RangeError.typeTag___ = 'RangeError'; +ReferenceError.typeTag___ = 'ReferenceError'; +SyntaxError.typeTag___ = 'SyntaxError'; +TypeError.typeTag___ = 'TypeError'; +URIError.typeTag___ = 'URIError'; + +Object.prototype.proto___ = null; + +//////////////////////////////////////////////////////////////////////// +// Cajita adds the following common Javascript extensions to ES3 +// TODO(erights): Move such extensions to a separate extensions.js. +//////////////////////////////////////////////////////////////////////// + +if (Date.prototype.toISOString === void 0) { + /** In anticipation of ES3.1 */ + Date.prototype.toISOString = function () { + function f(n) { + return n < 10 ? '0' + n : n; + } + return (this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'); + }; +} + +if (Date.prototype.toJSON === void 0) { + /** In anticipation of ES3.1 */ + Date.prototype.toJSON = Date.prototype.toISOString; +} + +/** In anticipation of ES4, and because it's useful. */ +if (Array.slice === void 0) { + Array.slice = function (self, start, end) { + return Array.prototype.slice.call(self, start || 0, end || self.length); + }; +} + + +/** + * In anticipation of ES3.1. + * <p> + * Bind this function to <tt>self</tt>, which will serve + * as the value of <tt>this</tt> during invocation. Curry on a + * partial set of arguments in <tt>var_args</tt>. Return the curried + * result as a new function object. + * <p> + * Note: Like the built-in Function.prototype.call and + * Function.prototype.apply, this one is not whitelisted. Instead, + * it is provided as it would be in future JavaScripts without + * special knowledge of Caja. This allows Caja code using bind() to + * work today uncajoled as well. It also suppresses the overriding + * of 'bind' by the 'in' test in setStatic(). + * <p> + * Note that this is distinct from the tamed form of bind() made + * available to Cajita code. + */ +if (Function.prototype.bind === void 0) { + Function.prototype.bind = function (self, var_args) { + var thisFunc = this; + var leftArgs = Array.slice(arguments, 1); + function funcBound(var_args) { + var args = leftArgs.concat(Array.slice(arguments, 0)); + return thisFunc.apply(self, args); + } + return funcBound; + }; +} + +// Use json_sans_eval.js if there is no native implementation. +if ('undefined' === typeof JSON) { + JSON = { parse: jsonParse }; +} + +// The following may or may not exist in the browser-supplied +// global scope; declare as a 'var' to avoid errors when we +// check for its existence later. +var escape; + +// cajita.js exports the following names to the Javascript global +// namespace. Cajita code can only use the "cajita" object. The "___" +// object is for use by code generated by the Cajita translator, and by +// Javascript code (such as a powerbox) in the embedding application. + +var cajita; +var ___; +var safeJSON; + +// Explicitly passing in the actual global object to avoid +// ReferenceErrors when referring to potentially nonexistent objects +// like HTMLDivElement. + +(function(global) { + + function ToInt32(alleged_int) { + return alleged_int >> 0; + } + + function ToUInt32(alleged_int) { + return alleged_int >>> 0; + } + + /** + * Returns the first index at which the specimen is found (by + * "identical()") or -1 if none, starting at offset i, if present. + * If i < 0, the offset is relative to the end of the array. + */ + function arrayIndexOf(specimen, i) { + var len = ToUInt32(this.length); + i = ToInt32(i); + if (i < 0) { + if ((i += len) < 0) { + i = 0; + } + } + for (; i < len; ++i) { + if (i in this && identical(this[i], specimen)) { + return i; + } + } + return -1; + } + Array.prototype.indexOf = arrayIndexOf; + + /** + * Returns the last index at which the specimen is found (by + * "identical()") or -1 if none, starting at offset i, if present. + * If i < 0, the offset is relative to the end of the array. + */ + function arrayLastIndexOf(specimen, i) { + var len = ToUInt32(this.length); + + if (isNaN(i)) { + i = len - 1; + } else { + i = ToInt32(i); + if (i < 0) { + i += len; + if (i < 0) { + return -1; + } + } else if (i >= len) { + i = len - 1; + } + } + + for (; i >= 0 ; --i) { + if (i in this && identical(this[i], specimen)) { + return i; + } + } + return -1; + } + Array.prototype.lastIndexOf = arrayLastIndexOf; + + //////////////////////////////////////////////////////////////////////// + // Some regular expressions checking for specific suffixes. + //////////////////////////////////////////////////////////////////////// + + var endsWith_canDelete___ = /_canDelete___$/; + var endsWith_canRead___ = /_canRead___$/; + var endsWith_canSet___ = /_canSet___$/; + var endsWith___ = /___$/; + var endsWith__ = /__$/; + + //////////////////////////////////////////////////////////////////////// + // Some very basic primordial methods + //////////////////////////////////////////////////////////////////////// + + /** + * A reliable typeof for use by Cajita code, and by uncajoled code + * (like parts of cajita.js) that require a reliable typeof. + * <p> + * Cajita specifies that <tt>typeof new RegExp("x")</tt> + * evaluate to <tt>'object'</tt>. Unfortunately, on some of Cajita's + * current target platforms (including at least Safari 3 and Rhino), + * it returns <tt>'function'</tt> instead. Since the distinction + * between functions and non-functions is crucial to Cajita, we + * translate the Cajita <tt>typeof</tt> operator into calls to this + * <tt>typeOf</tt> function. + */ + function typeOf(obj) { + var result = typeof obj; + if (result !== 'function') { return result; } + var ctor = obj.constructor; + if (typeof ctor === 'function' && + ctor.typeTag___ === 'RegExp' && + obj instanceof ctor) { + return 'object'; + } + return 'function'; + } + + var myOriginalHOP = Object.prototype.hasOwnProperty; + + /** + * <tt>hasOwnProp(obj, name)</tt> means what + * <tt>obj.hasOwnProperty(name)</tt> would normally mean in an + * unmodified Javascript system. + */ + function hasOwnProp(obj, name) { + var t = typeof obj; + if (t !== 'object' && t !== 'function') { + // If obj is a primitive, Object(obj) still has no own properties. + return false; + } + return myOriginalHOP.call(obj, name); + } + + /** + * Are x and y not observably distinguishable? + */ + function identical(x, y) { + if (x === y) { + // 0 === -0, but they are not identical + return x !== 0 || 1/x === 1/y; + } else { + // NaN !== NaN, but they are identical. + // NaNs are the only non-reflexive value, i.e., if x !== x, + // then x is a NaN. + return x !== x && y !== y; + } + } + + /** + * Inherited by non-frozen simple functions, which freezes them and + * installs an overriding fastpath CALL___ to themselves. + */ + function callFault(var_args) { + return asFunc(this).apply(USELESS, arguments); + } + Object.prototype.CALL___ = callFault; + + //////////////////////////////////////////////////////////////////////// + // Diagnostics and condition enforcement + //////////////////////////////////////////////////////////////////////// + + /** + * The initial default logging function does nothing. + * <p> + * Note: JavaScript has no macros, so even in the "does nothing" + * case, remember that the arguments are still evaluated. + */ + function defaultLogger(str, opt_stop) {} + var myLogFunc = frozenFunc(defaultLogger); + + /** + * Gets the currently registered logging function. + */ + function getLogFunc() { return myLogFunc; } + + /** + * Register newLogFunc as the current logging function, to be called + * by <tt>___.log(str)</tt> and <tt>___.fail(...)</tt>. + * <p> + * A logging function is assumed to have the signature + * <tt>(str, opt_stop)</tt>, where<ul> + * <li><tt>str</tt> is the diagnostic string to be logged, and + * <li><tt>opt_stop</tt>, if present and <tt>true</tt>, indicates + * that normal flow control is about to be terminated by a + * throw. This provides the logging function the opportunity to + * terminate normal control flow in its own way, such as by + * invoking an undefined method, in order to trigger a Firebug + * stacktrace. + * </ul> + */ + function setLogFunc(newLogFunc) { myLogFunc = newLogFunc; } + + /** + * Calls the currently registered logging function. + */ + function log(str) { myLogFunc(String(str)); } + + + /** + * Throw, and optionally log, an error whose message is the + * concatenation of the arguments. + * <p> + * The arguments are converted to strings (presumably by an + * implicit call to ".toString()") and appended together to make + * the message of the Error that's thrown. + */ + function fail(var_args) { + var message = Array.slice(arguments, 0).join(''); + myLogFunc(message, true); + throw new Error(message); + } + + /** + * Like an assert that can't be turned off. + * <p> + * Either returns true (on success) or throws (on failure). The + * arguments starting with <tt>var_args</tt> are converted to + * strings and appended together to make the message of the Error + * that's thrown. + * <p> + * TODO(erights) We may deprecate this in favor of <pre> + * test || fail(var_args...) + * </pre> or <pre> + * if (!test) { fail(var_args...); } + * </pre> + */ + function enforce(test, var_args) { + return test || fail.apply({}, Array.slice(arguments, 1)); + } + + /** + * Enforces <tt>typeOf(specimen) === typename</tt>, in which case + * specimen is returned. + * <p> + * If not, throws an informative TypeError + * <p> + * opt_name, if provided, should be a name or description of the + * specimen used only to generate friendlier error messages. + */ + function enforceType(specimen, typename, opt_name) { + if (typeOf(specimen) !== typename) { + fail('expected ', typename, ' instead of ', typeOf(specimen), + ': ', (opt_name || specimen)); + } + return specimen; + } + + /** + * Enforces that specimen is a non-negative integer within the range + * of exactly representable consecutive integers, in which case + * specimen is returned. + * <p> + * "Nat" is short for "Natural number". + */ + function enforceNat(specimen) { + enforceType(specimen, 'number'); + if (Math.floor(specimen) !== specimen) { + fail('Must be integral: ', specimen); + } + if (specimen < 0) { + fail('Must not be negative: ', specimen); + } + // Could pre-compute precision limit, but probably not faster + // enough to be worth it. + if (Math.floor(specimen - 1) !== specimen - 1) { + fail('Beyond precision limit: ', specimen); + } + if (Math.floor(specimen - 1) >= specimen) { + fail('Must not be infinite: ', specimen); + } + return specimen; + } + + //////////////////////////////////////////////////////////////////////// + // Privileged fault handlers + //////////////////////////////////////////////////////////////////////// + + function debugReference(obj) { + switch (typeOf(obj)) { + case 'object': { + if (obj === null) { return '<null>'; } + var constr = directConstructor(obj); + return '[' + ((constr && constr.name) || 'Object') + ']'; + } + default: { + return '(' + obj + ':' + typeOf(obj) + ')'; + } + } + } + + var myKeeper = { + + toString: function toString() { return '<Logging Keeper>'; }, + + handleRead: function handleRead(obj, name) { + //log('Not readable: (' + debugReference(obj) + ').' + name); + return void 0; + }, + + handleCall: function handleCall(obj, name, args) { + fail('Not callable: (', debugReference(obj), ').', name); + }, + + handleSet: function handleSet(obj, name, val) { + fail('Not settable: (', debugReference(obj), ').', name); + }, + + handleDelete: function handleDelete(obj, name) { + fail('Not deletable: (', debugReference(obj), ').', name); + } + }; + + Object.prototype.handleRead___ = function handleRead___(name) { + var handlerName = name + '_getter___'; + if (this[handlerName]) { + return this[handlerName](); + } + return myKeeper.handleRead(this, name); + }; + + Object.prototype.handleCall___ = function handleCall___(name, args) { + var handlerName = name + '_handler___'; + if (this[handlerName]) { + return this[handlerName].call(this, args); + } + return myKeeper.handleCall(this, name, args); + }; + + Object.prototype.handleSet___ = function handleSet___(name, val) { + var handlerName = name + '_setter___'; + if (this[handlerName]) { + return this[handlerName](val); + } + return myKeeper.handleSet(this, name, val); + }; + + Object.prototype.handleDelete___ = function handleDelete___(name) { + var handlerName = name + '_deleter___'; + if (this[handlerName]) { + return this[handlerName](); + } + return myKeeper.handleDelete(this, name); + }; + + //////////////////////////////////////////////////////////////////////// + // walking prototype chain, checking JSON containers + //////////////////////////////////////////////////////////////////////// + + /** + * Does str end with suffix? + */ + function endsWith(str, suffix) { + enforceType(str, 'string'); + enforceType(suffix, 'string'); + var d = str.length - suffix.length; + return d >= 0 && str.lastIndexOf(suffix) === d; + } + + /** + * Returns the 'constructor' property of obj's prototype. + * <p> + * SECURITY TODO(erights): Analyze the security implications + * of exposing this as a property of the cajita object. + * <p> + * By "obj's prototype", we mean the prototypical object that obj + * most directly inherits from, not the value of its 'prototype' + * property. We memoize the apparent prototype into 'proto___' to + * speed up future queries. + * <p> + * If obj is a function or not an object, return undefined. + */ + function directConstructor(obj) { + if (obj === null) { return void 0; } + if (obj === void 0) { return void 0; } + if (typeOf(obj) === 'function') { + // Since functions return undefined, + // directConstructor() doesn't provide access to the + // forbidden Function constructor. + return void 0; + } + obj = Object(obj); + var result = null; + if (myOriginalHOP.call(obj, 'proto___')) { + var proto = obj.proto___; + // At this point we know that (typeOf(proto) === 'object') + if (proto === null) { return void 0; } + result = proto.constructor; + // rest of: if (!isPrototypical(result)) + if (result.prototype !== proto || typeOf(result) !== 'function') { + result = directConstructor(proto); + } + } else { + if (!myOriginalHOP.call(obj, 'constructor')) { + // TODO(erights): Detect whether this is a valid constructor + // property in the sense that result is a proper answer. If + // not, at least give a sensible error, which will be hard to + // phrase. + result = obj.constructor; + } else { + var oldConstr = obj.constructor; + if (delete obj.constructor) { + result = obj.constructor; + obj.constructor = oldConstr; + } else if (isPrototypical(obj)) { + // A difficult case. In Safari, and perhaps according to + // ES3, the prototypical object created for the default + // value of a function's 'prototype' property has a + // non-deletable 'constructor' property. If this is what we + // have, then we assume it inherits directly from + // Object.prototype, so the result should be Object. + log('Guessing the directConstructor of : ' + obj); + result = Object; + } else { + fail('Discovery of direct constructors unsupported when the ', + 'constructor property is not deletable: ', + oldConstr); + } + } + if (typeOf(result) !== 'function' || !(obj instanceof result)) { + fail('Discovery of direct constructors for foreign begotten ', + 'objects not implemented on this platform.\n'); + } + if (result.prototype.constructor === result) { + // Memoize, so it'll be faster next time. + obj.proto___ = result.prototype; + } + } + return result; + } + + /** + * The function category of the whitelisted global constructors + * defined in ES is the string name of the constructor, allowing + * isInstanceOf() to work cross-frame. Otherwise, the function + * category of a function is just the function itself. + */ + function getFuncCategory(fun) { + enforceType(fun, 'function'); + if (fun.typeTag___) { + return fun.typeTag___; + } else { + return fun; + } + } + + /** + * Is <tt>obj</tt> a direct instance of a function whose category is + * the same as the category of <tt>ctor</tt>? + */ + function isDirectInstanceOf(obj, ctor) { + var constr = directConstructor(obj); + if (constr === void 0) { return false; } + return getFuncCategory(constr) === getFuncCategory(ctor); + } + + /** + * Is <tt>obj</tt> an instance of a function whose category is + * the same as the category of <tt>ctor</tt>? + */ + function isInstanceOf(obj, ctor) { + if (obj instanceof ctor) { return true; } + if (isDirectInstanceOf(obj, ctor)) { return true; } + // BUG TODO(erights): walk prototype chain. + // In the meantime, this will fail should it encounter a + // cross-frame instance of a "subclass" of ctor. + return false; + } + + /** + * A Record is an object whose direct constructor is Object. + * <p> + * These are the kinds of objects that can be expressed as + * an object literal ("<tt>{...}</tt>") in the JSON language. + */ + function isRecord(obj) { + return isDirectInstanceOf(obj, Object); + } + + /** + * An Array is an object whose direct constructor is Array. + * <p> + * These are the kinds of objects that can be expressed as + * an array literal ("<tt>[...]</tt>") in the JSON language. + */ + function isArray(obj) { + return isDirectInstanceOf(obj, Array); + } + + /** + * A JSON container is a non-prototypical object whose direct + * constructor is Object or Array. + * <p> + * These are the kinds of non-primitive objects that can be + * expressed in the JSON language. + */ + function isJSONContainer(obj) { + var constr = directConstructor(obj); + if (constr === void 0) { return false; } + var typeTag = constr.typeTag___; + if (typeTag !== 'Object' && typeTag !== 'Array') { return false; } + return !isPrototypical(obj); + } + + /** + * If obj is frozen, Cajita code cannot directly assign to + * own properties of obj, nor directly add or delete own properties to + * obj. + * <p> + * The status of being frozen is not inherited. If A inherits from + * B (i.e., if A's prototype is B), A and B each may or may not be + * frozen independently. (Though if B is prototypical, then it must + * be frozen.) + * <p> + * If <tt>typeof obj</tt> is neither 'object' nor 'function', then + * it's currently considered frozen. + */ + function isFrozen(obj) { + if (!obj) { return true; } + // TODO(erights): Object(<primitive>) wrappers should also be + // considered frozen. + if (obj.FROZEN___ === obj) { return true; } + var t = typeof obj; + return t !== 'object' && t !== 'function'; + } + + /** + * Mark obj as frozen so that Cajita code cannot directly assign to its + * own properties. + * <p> + * If obj is a function, also freeze obj.prototype. + * <p> + * This appears as <tt>___.primFreeze(obj)</tt> and is wrapped by + * <tt>cajita.freeze(obj)</tt>, which applies only to JSON containers. + * It does a shallow freeze, i.e., if record y inherits from record x, + * ___.primFreeze(y) will not freeze x. + */ + function primFreeze(obj) { + // Fail silently on undefined, since + // (function(){ + // var f = Foo; + // if (true) { function Foo() {} } + // })(); + // gets translated to (roughly) + // (function(){ + // var Foo; + // var f = ___.primFreeze(Foo); + // if (true) { Foo = function Foo() {}; } + // })(); + if (isFrozen(obj)) { return obj; } + + // badFlags are names of properties we need to turn off. + // We accumulate these first, so that we're not in the midst of a + // for/in loop on obj while we're deleting properties from obj. + var badFlags = []; + for (var k in obj) { + if (endsWith_canSet___.test(k) || endsWith_canDelete___.test(k)) { + if (obj[k]) { + badFlags.push(k); + } + } + } + for (var i = 0; i < badFlags.length; i++) { + var flag = badFlags[i]; + if (myOriginalHOP.call(obj, flag)) { + if (!(delete obj[flag])) { + fail('internal: failed delete: ', debugReference(obj), '.', flag); + } + } + if (obj[flag]) { + obj[flag] = false; + } + } + obj.FROZEN___ = obj; + if (typeOf(obj) === 'function') { + if (isFunc(obj)) { + grantCall(obj, 'call'); + grantCall(obj, 'apply'); + obj.CALL___ = obj; + } + // Do last to avoid possible infinite recursion. + if (obj.prototype) { primFreeze(obj.prototype); } + } + return obj; + } + + /** + * Like primFreeze(obj), but applicable only to JSON containers and + * (pointlessly but harmlessly) to functions. + */ + function freeze(obj) { + if (!isJSONContainer(obj)) { + if (typeOf(obj) === 'function') { + enforce(isFrozen(obj), 'Internal: non-frozen function: ' + obj); + return obj; + } + fail('cajita.freeze(obj) applies only to JSON Containers: ', + debugReference(obj)); + } + return primFreeze(obj); + } + + /** + * Makes a mutable copy of a JSON container. + * <p> + * Even if the original is frozen, the copy will still be mutable. + * It does a shallow copy, i.e., if record y inherits from record x, + * ___.copy(y) will also inherit from x. + */ + function copy(obj) { + if (!isJSONContainer(obj)) { + fail('cajita.copy(obj) applies only to JSON Containers: ', + debugReference(obj)); + } + var result = isArray(obj) ? [] : {}; + forOwnKeys(obj, frozenFunc(function(k, v) { + result[k] = v; + })); + return result; + } + + /** + * A snapshot of a JSON container is a frozen shallow copy of that + * container. + */ + function snapshot(obj) { + return primFreeze(copy(obj)); + } + + + //////////////////////////////////////////////////////////////////////// + // Accessing property attributes + //////////////////////////////////////////////////////////////////////// + + /** + * Tests whether the fast-path canRead flag is set. + */ + function canRead(obj, name) { + if (obj === void 0 || obj === null) { return false; } + return !!obj[name + '_canRead___']; + } + + /** + * Tests whether the fast-path canEnum flag is set. + */ + function canEnum(obj, name) { + if (obj === void 0 || obj === null) { return false; } + return !!obj[name + '_canEnum___']; + } + + /** + * Tests whether the fast-path canCall flag is set, or grantCall() has been + * called. + */ + function canCall(obj, name) { + if (obj === void 0 || obj === null) { return false; } + if (obj[name + '_canCall___']) { return true; } + if (obj[name + '_grantCall___']) { + fastpathCall(obj, name); + return true; + } + return false; + } + /** + * Tests whether the fast-path canSet flag is set, or grantSet() has been + * called, on this object itself as an own (non-inherited) attribute. + */ + function canSet(obj, name) { + if (obj === void 0 || obj === null) { return false; } + if (obj[name + '_canSet___'] === obj) { return true; } + if (obj[name + '_grantSet___'] === obj) { + fastpathSet(obj, name); + return true; + } + return false; + } + + /** + * Tests whether the fast-path canDelete flag is set, on this + * object itself as an own (non-inherited) attribute. + */ + function canDelete(obj, name) { + if (obj === void 0 || obj === null) { return false; } + return obj[name + '_canDelete___'] === obj; + } + + /** + * Sets the fast-path canRead flag. + * <p> + * These are called internally to memoize decisions arrived at by + * other means. + */ + function fastpathRead(obj, name) { + if (name === 'toString') { fail("internal: Can't fastpath .toString"); } + obj[name + '_canRead___'] = obj; + } + + function fastpathEnumOnly(obj, name) { + obj[name + '_canEnum___'] = obj; + } + + /** + * Simple functions should callable and readable, but methods + * should only be callable. + */ + function fastpathCall(obj, name) { + if (name === 'toString') { fail("internal: Can't fastpath .toString"); } + if (obj[name + '_canSet___']) { + obj[name + '_canSet___'] = false; + } + if (obj[name + '_grantSet___']) { + obj[name + '_grantSet___'] = false; + } + obj[name + '_canCall___'] = obj; + } + + /** + * fastpathSet implies fastpathEnumOnly and fastpathRead. It also + * disables the ability to call. + */ + function fastpathSet(obj, name) { + if (name === 'toString') { fail("internal: Can't fastpath .toString"); } + if (isFrozen(obj)) { + fail("Can't set .", name, ' on frozen (', debugReference(obj), ')'); + } + fastpathEnumOnly(obj, name); + fastpathRead(obj, name); + if (obj[name + '_canCall___']) { + obj[name + '_canCall___'] = false; + } + if (obj[name + '_grantCall___']) { + obj[name + '_grantCall___'] = false; + } + obj[name + '_canSet___'] = obj; + } + + /** + * fastpathDelete allows delete of a member on a constructed object via + * the private API. + * <p> + * TODO(erights): Having a fastpath flag for this probably doesn't + * make sense. + */ + function fastpathDelete(obj, name) { + if (name === 'toString') { fail("internal: Can't fastpath .toString"); } + if (isFrozen(obj)) { + fail("Can't delete .", name, ' on frozen (', debugReference(obj), ')'); + } + obj[name + '_canDelete___'] = obj; + } + + /** + * The various <tt>grant*</tt> functions are called externally by + * Javascript code to express whitelisting taming decisions. + */ + function grantRead(obj, name) { + fastpathRead(obj, name); + } + + // TODO(mikesamuel): none of the other grants grant enum, is "Only" operable? + function grantEnumOnly(obj, name) { + fastpathEnumOnly(obj, name); + } + + function grantCall(obj, name) { + fastpathCall(obj, name); + obj[name + '_grantCall___'] = obj; + } + + function grantSet(obj, name) { + fastpathSet(obj, name); + obj[name + '_grantSet___'] = obj; + } + + function grantDelete(obj, name) { + fastpathDelete(obj, name); + } + + //////////////////////////////////////////////////////////////////////// + // Classifying functions + //////////////////////////////////////////////////////////////////////// + + function isCtor(constr) { + return constr && !!constr.CONSTRUCTOR___; + } + function isFunc(fun) { + return fun && !!fun.FUNC___; + } + function isXo4aFunc(func) { + return func && !!func.XO4A___; + } + + /** + * Mark <tt>constr</tt> as a constructor. + * <p> + * A function is tamed and classified by calling one of + * <tt>ctor()</tt>, <tt>method()</tt>, or <tt>func()</tt>. Each + * of these checks that the function hasn't already been classified by + * any of the others. A function which has not been so classified is an + * <i>untamed function</i>. + * <p> + * If <tt>opt_Sup</tt> is provided, record that const.prototype + * inherits from opt_Sup.prototype. This bookkeeping helps + * directConstructor(). + * <p> + * <tt>opt_name</tt>, if provided, should be the name of the constructor + * function. Currently, this is used only to generate friendlier + * error messages. + */ + function ctor(constr, opt_Sup, opt_name) { + enforceType(constr, 'function', opt_name); + if (isFunc(constr)) { + fail("Simple functions can't be constructors: ", constr); + } + if (isXo4aFunc(constr)) { + fail("Exophoric functions can't be constructors: ", constr); + } + constr.CONSTRUCTOR___ = true; + if (opt_Sup) { + derive(constr, opt_Sup); + } + if (opt_name) { + constr.NAME___ = String(opt_name); + } + return constr; // translator freezes constructor later + } + + function derive(constr, sup) { + sup = asCtor(sup); + if (isFrozen(constr)) { + fail('Derived constructor already frozen: ', constr); + } + if (!isFrozen(constr.prototype)) { + // Some platforms, like Safari, actually conform to the part + // of the ES3 spec which states that the constructor property + // of implicitly created prototypical objects are not + // deletable. But this prevents the inheritance-walking + // algorithm (kludge) in directConstructor from working. Thus, + // we set proto___ here so that directConstructor can skip + // that impossible case. + constr.prototype.proto___ = sup.prototype; + } + } + + /** + * Enables first-class reification of exophoric functions as + * pseudo-functions -- frozen records with call, bind, and apply + * functions. + */ + function reifyIfXo4a(xfunc, opt_name) { + if (!isXo4aFunc(xfunc)) { + return asFirstClass(xfunc); + } + var result = { + call: frozenFunc(function callXo4a(self, var_args) { + if (self === null || self === void 0) { self = USELESS; } + return xfunc.apply(self, Array.slice(arguments, 1)); + }), + apply: frozenFunc(function applyXo4a(self, args) { + if (self === null || self === void 0) { self = USELESS; } + return xfunc.apply(self, args); + }), + bind: frozenFunc(function bindXo4a(self, var_args) { + var args = arguments; + if (self === null || self === void 0) { + self = USELESS; + args = [self].concat(Array.slice(args, 1)); + } + return frozenFunc(xfunc.bind.apply(xfunc, args)); + }), + length: xfunc.length, + toString: frozenFunc(function xo4aToString() { + return xfunc.toString(); + }) + }; + if (opt_name !== void 0) { + result.name = opt_name; + } + return primFreeze(result); + } + + /** + * Marks an anonymous function as exophoric: + * the function mentions <tt>this</tt>, + * but only accesses the public interface. + * <p> + * @param opt_name if provided, should be the message name associated + * with the method. Currently, this is used only to generate + * friendlier error messages. + */ + function xo4a(func, opt_name) { + enforceType(func, 'function', opt_name); + if (isCtor(func)) { + fail("Internal: Constructors can't be exophora: ", func); + } + if (isFunc(func)) { + fail("Internal: Simple functions can't be exophora: ", func); + } + func.XO4A___ = true; + return primFreeze(func); + } + + /** + * Mark fun as a simple function. + * <p> + * opt_name, if provided, should be the name of the + * function. Currently, this is used only to generate friendlier + * error messages. + */ + function func(fun, opt_name) { + enforceType(fun, 'function', opt_name); + if (isCtor(fun)) { + fail("Constructors can't be simple functions: ", fun); + } + if (isXo4aFunc(fun)) { + fail("Exophoric functions can't be simple functions: ", fun); + } + fun.FUNC___ = true; + if (opt_name) { + fun.NAME___ = String(opt_name); + } + return fun; // translator freezes fun later + } + + /** + * Mark fun as a simple function and freeze it. + */ + function frozenFunc(fun, opt_name) { + return primFreeze(func(fun, opt_name)); + } + + /** This "Only" form doesn't freeze */ + function asCtorOnly(constr) { + if (isCtor(constr) || isFunc(constr)) { + return constr; + } + + enforceType(constr, 'function'); + fail("Untamed functions can't be called as constructors: ", constr); + } + + /** Only constructors and simple functions can be called as constructors */ + function asCtor(constr) { + return primFreeze(asCtorOnly(constr)); + } + + /** + * Only simple functions can be called as simple functions. + * <p> + * It is now <tt>asFunc</tt>'s responsibility to + * <tt>primFreeze(fun)</tt>. + */ + function asFunc(fun) { + if (fun && fun.FUNC___) { + // fastpath shortcut + if (fun.FROZEN___ === fun) { + return fun; + } else { + return primFreeze(fun); + } + } + enforceType(fun, 'function'); + if (isCtor(fun)) { + if (fun === Number || fun === String || fun === Boolean) { + // TODO(erights): To avoid accidents, <tt>method</tt>, + // <tt>func</tt>, and <tt>ctor</tt> each ensure that + // these classifications are exclusive. A function can be + // classified as in at most one of these categories. However, + // some primordial type conversion functions like + // <tt>String</tt> need to be invocable both ways, so we + // should probably relax this constraint. + // <p> + // But before we do, we should reexamine other + // implications. For example, simple-functions, when called + // reflectively by <tt>call</tt> or <tt>apply</tt> (and + // therefore <tt>bind</tt>), ignore their first argument, + // whereas constructors can be called reflectively by + // <tt>call</tt> to do super-initialization on behalf of a + // derived constructor. + // <p> + // Curiously, ES3 also defines function behavior different + // from constructor behavior for <tt>Object</tt>, + // <tt>Date</tt>, <tt>RegExp</tt>, and <tt>Error</tt>. (Not + // sure about <tt>Array</tt>.) We should understand these as + // well before introducing a proper solution. + return primFreeze(fun); + } + fail("Constructors can't be called as simple functions: ", fun); + } + if (isXo4aFunc(fun)) { + fail("Exophoric functions can't be called as simple functions: ", fun); + } + fail("Untamed functions can't be called as simple functions: ", fun); + } + + /** + * Is <tt>funoid</tt> an applicator -- a non-function object with a + * callable <tt>apply</tt> method, such as a pseudo-function or + * disfunction? + * <p> + * If so, then it can be used as a function in some contexts. + */ + function isApplicator(funoid) { + if (typeof funoid !== 'object') { return false; } + if (funoid === null) { return false; } + return canCallPub(funoid, 'apply'); + } + + /** + * Coerces fun to a genuine simple-function. + * <p> + * If fun is an applicator, then return a simple-function that invokes + * fun's apply method. Otherwise, asFunc(). + */ + function toFunc(fun) { + if (isApplicator(fun)) { + return frozenFunc(function applier(var_args) { + return callPub(fun, 'apply', [USELESS, Array.slice(arguments, 0)]); + }); + } + return asFunc(fun); + } + + /** + * An object is prototypical iff its 'constructor' property points + * at a genuine function whose 'prototype' property points back at + * it. + * <p> + * Cajita code cannot access or create prototypical objects since + * the 'prototype' property of genuine functions is inaccessible, + * and since the transient function used by <tt>beget</tt> to create + * inheritance chains does not escape. + */ + function isPrototypical(obj) { + if (typeOf(obj) !== 'object') { return false; } + if (obj === null) { return false; } + var constr = obj.constructor; + if (typeOf(constr) !== 'function') { return false; } + return constr.prototype === obj; + } + + /** + * Throws an exception if the value is an unmarked function or a + * prototypical object. + */ + function asFirstClass(value) { + switch (typeOf(value)) { + case 'function': { + if (isFunc(value) || isCtor(value)) { + if (isFrozen(value)) { + return value; + } + // TODO(metaweta): make this a cajita-uncatchable exception + fail('Internal: non-frozen function encountered: ', value); + } else if (isXo4aFunc(value)) { + // TODO(metaweta): make this a cajita-uncatchable exception + // TODO(erights): non-user-hostile error message + fail('Internal: toxic exophora encountered: ', value); + } else { + // TODO(metaweta): make this a cajita-uncatchable exception + fail('Internal: toxic function encountered: ', value); + } + break; + } + case 'object': { + if (value !== null && isPrototypical(value)) { + // TODO(metaweta): make this a cajita-uncatchable exception + fail('Internal: prototypical object encountered: ', value); + } + return value; + } + default: { + return value; + } + } + } + + //////////////////////////////////////////////////////////////////////// + // Accessing properties + //////////////////////////////////////////////////////////////////////// + + /** + * Can a Cajita client of <tt>obj</tt> read its {@code name} property? + * <p> + * If the property is unmentionable (i.e. ends in an '__'), then no. + * If the property was defined by Cajita code, then yes. If it was + * whitelisted, then yes. Or if the property is an own property of + * <i>some</i> JSON container, then yes. + * <p> + * Why "some"? If record y inherits from record x, and 'foo' is an own + * property of x, then canReadPub(y, 'foo') must be true. + */ + function canReadPub(obj, name) { + if (typeof name === 'number') { return name in obj; } + name = String(name); + if (obj === null) { return false; } + if (obj === void 0) { return false; } + if (obj[name + '_canRead___']) { return true; } + if (endsWith__.test(name)) { return false; } + if (name === 'toString') { return false; } + if (!isJSONContainer(obj)) { return false; } + if (!myOriginalHOP.call(obj, name)) { return false; } + fastpathRead(obj, name); + return true; + } + + function hasOwnPropertyOf(obj, name) { + if (typeof name === 'number') { return hasOwnProp(obj, name); } + name = String(name); + if (obj && obj[name + '_canRead___'] === obj) { return true; } + return canReadPub(obj, name) && myOriginalHOP.call(obj, name); + } + + /** + * Implements Cajita's <tt><i>name</i> in <i>obj</i></tt> + */ + function inPub(name, obj) { + if (obj === null || obj === void 0) { + throw new TypeError('invalid "in" operand: ' + obj); + } + obj = Object(obj); + if (canReadPub(obj, name)) { return true; } + if (canCallPub(obj, name)) { return true; } + if ((name + '_getter___') in obj) { return true; } + if ((name + '_handler___') in obj) { return true; } + return false; + } + + /** + * Called by Caja code attempting to read a property. + * <p> + * If it can't then <tt>readPub</tt> returns <tt>undefined</tt> instead. + */ + function readPub(obj, name) { + if (typeof name === 'number') { + if (typeof obj === 'string') { + // In partial anticipation of ES3.1. + // TODO(erights): Once ES3.1 settles, revisit this and + // correctly implement the agreed semantics. + // Mike Samuel suggests also making it conditional on + // (+name) === (name & 0x7fffffff) + return obj.charAt(name); + } else { + return obj[name]; + } + } + name = String(name); + if (canReadPub(obj, name)) { return obj[name]; } + if (obj === null || obj === void 0) { + throw new TypeError("Can't read " + name + " on " + obj); + } + return obj.handleRead___(name); + } + + /** + * If <tt>obj</tt> is an object with a property <tt>name</tt> that + * should be objectively readable from Valija, return + * <tt>obj[name]</tt>, else <tt>pumpkin</tt>. + * <p> + * Provides a fastpath for Valija's <tt>read()</tt> function + * <tt>$v.r()</tt>. The reason for returning the passed in pumpkin + * rather than, for example, <tt>undefined</tt>, is so that the + * caller can pass in a known unique value and distinguish it, on + * return, from any possible valid value. + * <p> + * A property should be objectively readable iff<ul> + * <li>It is readable from Cajita, and + * <li><tt>obj</tt> is not a function, and + * <li>either<ul> + * <li><tt>name</tt> is an own property of <tt>obj</tt>, or + * <li><tt>obj</tt> inherits <tt>name</tt> from an ancestor that + * Cajita considers first-class. The one such possibility is + * when <tt>obj</tt> is a record inheriting <tt>name</tt> + * from another record. (A record is a non-prototypical + * object whose directConstructor is Object.) + * </ul> + * </ul> + */ + function readOwn(obj, name, pumpkin) { + if (typeof obj !== 'object' || !obj) { + if (typeOf(obj) !== 'object') { + return pumpkin; + } + } + if (typeof name === 'number') { + if (myOriginalHOP.call(obj, name)) { return obj[name]; } + return pumpkin; + } + name = String(name); + if (obj[name + '_canRead___'] === obj) { return obj[name]; } + if (!myOriginalHOP.call(obj, name)) { return pumpkin; } + // inline remaining relevant cases from canReadPub + if (endsWith__.test(name)) { return pumpkin; } + if (name === 'toString') { return pumpkin; } + if (!isJSONContainer(obj)) { return pumpkin; } + fastpathRead(obj, name); + return obj[name]; + } + + /** + * Ensure that all the permitsUsed starting at result are forever + * safe to allow without runtime checks. + */ + function enforceStaticPath(result, permitsUsed) { + forOwnKeys(permitsUsed, frozenFunc(function(name, subPermits) { + // Don't factor out since we don't enforce frozen if permitsUsed + // are empty. + // TODO(erights): Once we have ES3.1ish attribute control, it + // will suffice to enforce that each used property is frozen + // independent of the object as a whole. + enforce(isFrozen(result), 'Assumed frozen: ', result); + if (name === '()') { + // TODO(erights): Revisit this case + } else { + enforce(canReadPub(result, name), + 'Assumed readable: ', result, '.', name); + if (inPub('()', subPermits)) { + enforce(canCallPub(result, name), + 'Assumed callable: ', result, '.', name, '()'); + } + enforceStaticPath(readPub(result, name), subPermits); + } + })); + } + + /** + * Privileged code attempting to read an imported value from a module's + * <tt>IMPORTS___</tt>. This function is NOT available to Cajita code. + * <p> + * This delegates to <tt>readOwn()</tt>, and so will only read + * those properties from module_imports that are objectively visible + * from both Cajita and Valija. + */ + function readImport(module_imports, name, opt_permitsUsed) { + var pumpkin = {}; + var result = readOwn(module_imports, name, pumpkin); + if (result === pumpkin) { + log('Linkage warning: ' + name + ' not importable'); + return void 0; + } + if (opt_permitsUsed) { + enforceStaticPath(result, opt_permitsUsed); + } + return result; + } + + /** + * Can "innocent" code enumerate the named property on this object? + * <p> + * "Innocent" code is code which we assume to be ignorant of Caja, + * not to be actively hostile, but which may be buggy (and + * therefore accidentally harmful or exploitable). This + * corresponds to legacy code, such as libraries, that we decide + * to run untranslated, perhaps hidden or tamed, but which needs + * to co-exist smoothly with the Caja runtime. + * <p> + * An earlier version of canInnocentEnum() filtered out exactly those + * names ending with a double underbar. It now filters out exactly + * those names ending in a triple underbar. Cajita code can't see names + * ending in a double underbar, since existing platforms (like + * Firefox) use such names for purposes that should be hidden from + * Caja code. However, it is not up to Caja to shield innocent code + * from seeing such platform properties. All the magic names Cajita + * adds for its own internal bookkeeping end in triple underbar, so + * that is all we need to hide from innocent code. + */ + function canInnocentEnum(obj, name) { + name = String(name); + if (endsWith___.test(name)) { return false; } + return true; + } + + /** + * Would a Cajita for/in loop by a client of obj see this name? + * <p> + * For properties defined in Cajita, this is generally the same as + * canReadPub. Otherwise according to whitelisting. + */ + function canEnumPub(obj, name) { + if (obj === null) { return false; } + if (obj === void 0) { return false; } + name = String(name); + if (obj[name + '_canEnum___']) { return true; } + if (endsWith__.test(name)) { return false; } + if (!isJSONContainer(obj)) { return false; } + if (!myOriginalHOP.call(obj, name)) { return false; } + fastpathEnumOnly(obj, name); + if (name === 'toString') { return true; } + fastpathRead(obj, name); + return true; + } + + /** + * Like canEnumPub, but allows only non-inherited properties. + */ + function canEnumOwn(obj, name) { + name = String(name); + if (obj && obj[name + '_canEnum___'] === obj) { return true; } + return canEnumPub(obj, name) && myOriginalHOP.call(obj, name); + } + + /** + * Returns a new object whose only utility is its identity and (for + * diagnostic purposes only) its name. + */ + function Token(name) { + name = String(name); + return primFreeze({ + toString: frozenFunc(function tokenToString() { return name; }) + }); + } + frozenFunc(Token); + + /** + * Inside a <tt>cajita.forOwnKeys()</tt>, or <tt>cajita.forAllKeys()</tt>, the + * body function can terminate early, as if with a conventional + * <tt>break;</tt>, by doing a <pre>return cajita.BREAK;</pre> + */ + var BREAK = Token('BREAK'); + + /** + * A unique value that should never be made accessible to untrusted + * code, for distinguishing the absence of a result from any + * returnable result. + * <p> + * See makeNewModuleHandler's getLastOutcome(). + */ + var NO_RESULT = Token('NO_RESULT'); + + /** + * For each sensible key/value pair in obj, call fn with that + * pair. + * <p> + * If obj is an array, then enumerate indexes. Otherwise, enumerate + * the canEnumOwn() property names. + */ + function forOwnKeys(obj, fn) { + fn = toFunc(fn); + var keys = ownKeys(obj); + for (var i = 0; i < keys.length; i++) { + if (fn(keys[i], readPub(obj, keys[i])) === BREAK) { + return; + } + } + } + + /** + * For each sensible key/value pair in obj, call fn with that + * pair. + * <p> + * If obj is an array, then enumerate indexes. Otherwise, enumerate + * the canEnumPub() property names. + */ + function forAllKeys(obj, fn) { + fn = toFunc(fn); + var keys = allKeys(obj); + for (var i = 0; i < keys.length; i++) { + if (fn(keys[i], readPub(obj, keys[i])) === BREAK) { + return; + } + } + } + + /** + * Return an array of the publicly readable own keys of obj. + * <p> + * If obj is an array, then enumerate indexes. Otherwise, enumerate + * the canEnumOwn() property names. + */ + function ownKeys(obj) { + var result = []; + if (isArray(obj)) { + var len = obj.length; + for (var i = 0; i < len; i++) { + result.push(i); + } + } else { + for (var k in obj) { + if (canEnumOwn(obj, k)) { + result.push(k); + } + } + if (obj !== void 0 && obj !== null && obj.handleEnum___) { + result = result.concat(obj.handleEnum___(true)); + } + } + return result; + } + + /** + * Return an array of the publicly readable own and inherited keys of obj. + * <p> + * If obj is an array, then enumerate indexes. Otherwise, enumerate + * the canEnumPub() property names. + */ + function allKeys(obj) { + if (isArray(obj)) { + return ownKeys(obj); + } else { + var result = []; + for (var k in obj) { + if (canEnumPub(obj, k)) { + result.push(k); + } + } + if (obj !== void 0 && obj !== null && obj.handleEnum___) { + result = result.concat(obj.handleEnum___(false)); + } + return result; + } + } + + /** + * Can this be called as a public method? + * <p> + * For genuine methods, they are only callable if the canCall + * attribute is set. Otherwise, if this property is readable and + * holds a simple function, then it's also callable as a function, + * which we can memoize. + */ + function canCallPub(obj, name) { + if (obj === null) { return false; } + if (obj === void 0) { return false; } + name = String(name); + if (obj[name + '_canCall___']) { return true; } + if (obj[name + '_grantCall___']) { + fastpathCall(obj, name); + return true; + } + if (!canReadPub(obj, name)) { return false; } + if (endsWith__.test(name)) { return false; } + if (name === 'toString') { return false; } + var func = obj[name]; + if (!isFunc(func) && !isXo4aFunc(func)) { + return false; + } + fastpathCall(obj, name); + return true; + } + + /** + * A client of obj tries to call one of its methods. + */ + function callPub(obj, name, args) { + name = String(name); + if (obj === null || obj === void 0) { + throw new TypeError("Can't call " + name + " on " + obj); + } + if (obj[name + '_canCall___'] || canCallPub(obj, name)) { + return obj[name].apply(obj, args); + } + if (obj.handleCall___) { return obj.handleCall___(name, args); } + fail('not callable:', debugReference(obj), '.', name); + } + + /** + * Can a client of obj directly assign to its name property? + * <p> + * If this property is unmentionable (i.e., ends with a '__') or if this + * object is frozen, then no. + * Else if this is an own property defined by Cajita code, + * then yes. If the object is a JSON container, then + * yes. Otherwise according to whitelisting decisions. + */ + function canSetPub(obj, name) { + name = String(name); + if (canSet(obj, name)) { return true; } + if (endsWith__.test(name)) { return false; } + if (name === 'valueOf') { return false; } + if (name === 'toString') { return false; } + return !isFrozen(obj) && isJSONContainer(obj); + } + + /** A client of obj attempts to assign to one of its properties. */ + function setPub(obj, name, val) { + // asFirstClass() here would be a useful safety check, to prevent + // the further propogation of, for example, a leaked toxic + // function. However, its security benefit is questionable, and + // the check is expensive in this position. +// val = asFirstClass(val); + if (typeof name === 'number' && + // See issue 875 + obj instanceof Array && + obj.FROZEN___ !== obj) { + return obj[name] = val; + } + name = String(name); + if (obj === null || obj === void 0) { + throw new TypeError("Can't set " + name + " on " + obj); + } + if (obj[name + '_canSet___'] === obj) { + return obj[name] = val; + } else if (canSetPub(obj, name)) { + fastpathSet(obj, name); + return obj[name] = val; + } else { + return obj.handleSet___(name, val); + } + } + + /** + * Can the given constructor have the given static method added to it? + * @param {Function} ctor + * @param {string} staticMemberName an identifier in the public namespace. + */ + function canSetStatic(ctor, staticMemberName) { + staticMemberName = '' + staticMemberName; + if (typeOf(ctor) !== 'function') { + log('Cannot set static member of non function', ctor); + return false; + } + if (isFrozen(ctor)) { + log('Cannot set static member of frozen function', ctor); + return false; + } + // disallows prototype, call, apply, bind + if (staticMemberName in ctor) { + log('Cannot override static member ', staticMemberName); + return false; + } + // statics are public + if (endsWith__.test(staticMemberName) || staticMemberName === 'valueOf') { + log('Illegal static member name ', staticMemberName); + return false; + } + if (staticMemberName === 'toString') { + // no diagnostic as this is a normal fault-handling case. + return false; + } + return true; + } + + /** + * Sets a static members of a ctor, making sure that it can't be used to + * override call/apply/bind and other builtin members of function. + * @param {Function} ctor + * @param {string} staticMemberName an identifier in the public namespace. + * @param staticMemberValue the value of the static member. + */ + function setStatic(ctor, staticMemberName, staticMemberValue) { + staticMemberName = '' + staticMemberName; + if (canSetStatic(ctor, staticMemberName)) { + ctor[staticMemberName] = staticMemberValue; + fastpathEnumOnly(ctor, staticMemberName); + fastpathRead(ctor, staticMemberName); + } else { + ctor.handleSet___(staticMemberName, staticMemberValue); + } + } + + /** + * Can a client of obj delete the named property? + */ + function canDeletePub(obj, name) { + name = String(name); + if (isFrozen(obj)) { return false; } + if (endsWith__.test(name)) { return false; } + if (name === 'valueOf') { return false; } + if (name === 'toString') { return false; } + if (isJSONContainer(obj)) { return true; } + return false; + } + + /** + * A client of obj can only delete a property of obj if obj is a + * non-frozen JSON container. + */ + function deletePub(obj, name) { + name = String(name); + if (obj === null || obj === void 0) { + throw new TypeError("Can't delete " + name + " on " + obj); + } + if (canDeletePub(obj, name)) { + // See deleteFieldEntirely for reasons why we don't cache deletability. + return deleteFieldEntirely(obj, name); + } else { + return obj.handleDelete___(name); + } + } + + /** + * Deletes a field removing any cached permissions. + * @param {object} obj + * @param {string} name of field in obj to delete. + * @return {boolean} + * @throws {Error} if field not deletable or name not in field. + * @private + */ + function deleteFieldEntirely(obj, name) { + // Can't cache fastpath delete since deleting the field should remove + // all privileges for that field. + delete obj[name + '_canRead___']; + delete obj[name + '_canEnum___']; + delete obj[name + '_canCall___']; + delete obj[name + '_grantCall___']; + delete obj[name + '_grantSet___']; + delete obj[name + '_canSet___']; + delete obj[name + '_canDelete___']; + return (delete obj[name]) || (fail('not deleted: ', name), false); + } + + //////////////////////////////////////////////////////////////////////// + // Other + //////////////////////////////////////////////////////////////////////// + + /** + * This returns a frozen array copy of the original array or + * array-like object. + * <p> + * If a Cajita program makes use of <tt>arguments</tt> in any + * position other than <tt>arguments.callee</tt>, this is + * rewritten to use a frozen array copy of arguments instead. This + * way, if Cajita code passes its arguments to someone else, they + * are not giving the receiver the rights to access the passing + * function nor to modify the parameter variables of the passing + * function. + */ + function args(original) { + return primFreeze(Array.slice(original, 0)); + } + + /** + * When a <tt>this</tt> value must be provided but nothing is + * suitable, provide this useless object instead. + */ + var USELESS = Token('USELESS'); + + /** + * A call to cajita.manifest(data) is dynamically ignored, but if the + * data expression is valid static JSON text, its value is made + * statically available to the module loader. + */ + function manifest(ignored) {} + + /** Sealer for call stacks as from {@code (new Error).stack}. */ + var callStackSealer = makeSealerUnsealerPair(); + + /** + * Receives the exception caught by a user defined catch block. + * @param ex a value caught in a try block. + * @return a tamed exception. + */ + function tameException(ex) { + try { + switch (typeOf(ex)) { + case 'object': { + if (ex === null) { return null; } + if (isInstanceOf(ex, Error)) { + // See Ecma-262 S15.11 for the definitions of these properties. + var message = ex.message || ex.desc; + var stack = ex.stack; + var name = ex.constructor && ex.constructor.name; // S15.11.7.9 + // Convert to undefined if falsy, or a string otherwise. + message = !message ? void 0 : '' + message; + stack = !stack ? void 0 : callStackSealer.seal('' + stack); + name = !name ? void 0 : '' + name; + return primFreeze({ message: message, name: name, stack: stack }); + } + return '' + ex; + } + case 'string': + case 'number': + case 'boolean': { + // Immutable. + return ex; + } + case 'undefined': { + return (void 0); + } + case 'function': { + // According to Pratap Lakhsman's "JScript Deviations" S2.11 + // If the caught object is a function, calling it within the catch + // supplies the head of the scope chain as the "this value". The + // called function can add properties to this object. This implies + // that for code of this shape: + // var x; + // try { + // // ... + // } catch (E) { + // E(); + // return s; + // } + // The reference to 'x' within the catch is not necessarily to the + // local declaration of 'x'; this gives Catch the same performance + // problems as with. + + // We return a different, powerless function instead. + return ___.func(function () {}); + } + default: { + log('Unrecognized exception type ' + (typeOf(ex))); + return 'Unrecognized exception type ' + (typeOf(ex)); + } + } + } catch (_) { + // Can occur if coercion to string fails, or if ex has getters + // that fail. This function must never throw an exception + // because doing so would cause control to leave a catch block + // before the handler fires. + log('Exception during exception handling.'); + return 'Exception during exception handling.'; + } + } + + /** + * Makes a new empty object that directly inherits from <tt>proto</tt>. + */ + function primBeget(proto) { + if (proto === null) { fail("Cannot beget from null."); } + if (proto === (void 0)) { fail("Cannot beget from undefined."); } + function F() {} + F.prototype = proto; + var result = new F(); + result.proto___ = proto; + return result; + } + + //////////////////////////////////////////////////////////////////////// + // Taming mechanism + //////////////////////////////////////////////////////////////////////// + + /** + * Arrange to handle read-faults on <tt>obj[name]</tt> + * by calling <tt>getHandler()</tt> as a method on + * the faulted object. + * <p> + * In order for this fault-handler to get control, it's important + * that no one does a conflicting <tt>grantRead()</tt>. + * FIXME(ben): and fastpathRead()? + */ + function useGetHandler(obj, name, getHandler) { + obj[name + '_getter___'] = getHandler; + } + + /** + * Arrange to handle call-faults on <tt>obj[name](args...)</tt> by + * calling <tt>applyHandler(args)</tt> as a method on the faulted + * object. + * <p> + * Note that <tt>applyHandler</tt> is called with a single argument, + * which is the list of arguments in the original call. + * <p> + * In order for this fault-handler to get control, it's important + * that no one does a conflicting grantCall() or other grants which + * imply grantCall(). + * FIXME(ben): also fastpath? + */ + function useApplyHandler(obj, name, applyHandler) { + obj[name + '_handler___'] = applyHandler; + } + + /** + * Arrange to handle call-faults on <tt>obj[name](args...)</tt> by + * calling <tt>callHandler(args...)</tt> as a method on the faulted + * object. + * <p> + * Note that <tt>callHandler</tt> is called with the same arguments + * as the original call. + * <p> + * In order for this fault-handler to get control, it's important + * that no one does a conflicting grantCall() or other grants which + * imply grantCall(). + * FIXME(ben): also fastpath? + */ + function useCallHandler(obj, name, callHandler) { + useApplyHandler(obj, name, function callApplier(args) { + return callHandler.apply(this, args); + }); + } + + /** + * Arrange to handle set-faults on <tt>obj[name] = newValue</tt> by + * calling <tt>setHandler(newValue)</tt> as a method on the faulted + * object. + * <p> + * In order for this fault-handler to get control, it's important + * that no one does a conflicting grantSet(). + * FIXME(ben): also fastpath? + */ + function useSetHandler(obj, name, setHandler) { + obj[name + '_setter___'] = setHandler; + } + + /** + * Arrange to handle delete-faults on <tt>delete obj[name]</tt> by + * calling <tt>deleteHandler()</tt> as a method on the faulted object. + * <p> + * In order for this fault-handler to get control, it's important + * that no one does a conflicting grantDelete(). + * FIXME(ben): also fastpath? + */ + function useDeleteHandler(obj, name, deleteHandler) { + obj[name + '_deleter___'] = deleteHandler; + } + + /** + * Whilelist obj[name] as a simple frozen function that can be either + * called or read. + */ + function grantFunc(obj, name) { + frozenFunc(obj[name], name); + grantCall(obj, name); + grantRead(obj, name); + } + + /** + * Whitelist proto[name] as a generic exophoric function that can + * safely be called with its <tt>this</tt> bound to other objects. + * <p> + * Since exophoric functions are not first-class, reading + * proto[name] returns the corresponding pseudo-function -- a record + * with simple-functions for its call, bind, and apply. + */ + function grantGeneric(proto, name) { + var func = xo4a(proto[name], name); + grantCall(proto, name); + var pseudoFunc = reifyIfXo4a(func, name); + useGetHandler(proto, name, function xo4aGetter() { + return pseudoFunc; + }); + } + + /** + * Mark func as exophoric and use it as a virtual generic + * exophoric function. + * <p> + * Since exophoric functions are not first-class, reading + * proto[name] returns the corresponding pseudo-function -- a record + * with simple-functions for its call, bind, and apply. + */ + function handleGeneric(obj, name, func) { + xo4a(func); + useCallHandler(obj, name, func); + var pseudoFunc = reifyIfXo4a(func, name); + useGetHandler(obj, name, function genericGetter() { + return pseudoFunc; + }); + } + + /** + * Virtually replace proto[name] with a fault-handler + * wrapper that first verifies that <tt>this</tt> inherits from + * proto. + * <p> + * When a pre-existing Javascript method may do something unsafe + * when applied to a <tt>this</tt> of the wrong type, we need to + * provide a fault-handler instead to prevent such mis-application. + * <p> + * In order for this fault handler to get control, it's important + * that no one does an grantCall() or other grants which imply + * grantCall(). + * FIXME(ben): also fastpath? + */ + function grantTypedGeneric(proto, name) { + var original = proto[name]; + handleGeneric(proto, name, function guardedApplier(var_args) { + if (!inheritsFrom(this, proto)) { + fail("Can't call .", name, ' on a non ', + directConstructor(proto), ': ', this); + } + return original.apply(this, arguments); + }); + } + + /** + * Virtually replace proto[name] with a fault-handler + * wrapper that first verifies that <tt>this</tt> isn't frozen. + * <p> + * When a pre-existing Javascript method would mutate its object, + * we need to provide a fault handler instead to prevent such + * mutation from violating Cajita semantics. + * <p> + * In order for this fault handler to get control, it's important + * that no one does an grantCall() or other grants which imply + * grantCall(). + * FIXME(ben): also fastpath? + */ + function grantMutator(proto, name) { + var original = proto[name]; + handleGeneric(proto, name, function nonMutatingApplier(var_args) { + if (isFrozen(this)) { + fail("Can't .", name, ' a frozen object'); + } + return original.apply(this, arguments); + }); + } + + /** + * Verifies that regexp is something that can appear as a + * parameter to a Javascript method that would use it in a match. + * <p> + * If it is a RegExp, then this match might mutate it, which must + * not be allowed if regexp is frozen. Otherwise it must be a string. + */ + function enforceMatchable(regexp) { + if (isInstanceOf(regexp, RegExp)) { + if (isFrozen(regexp)) { + fail("Can't match with frozen RegExp: ", regexp); + } + } else { + enforceType(regexp, 'string'); + } + } + + /** + * A shorthand that happens to be useful here. + * <p> + * For all i in arg2s: func2(arg1,arg2s[i]). + */ + function all2(func2, arg1, arg2s) { + var len = arg2s.length; + for (var i = 0; i < len; i += 1) { + func2(arg1, arg2s[i]); + } + } + + //////////////////////////////////////////////////////////////////////// + // Taming decisions + //////////////////////////////////////////////////////////////////////// + + /// Math + + all2(grantRead, Math, [ + 'E', 'LN10', 'LN2', 'LOG2E', 'LOG10E', 'PI', 'SQRT1_2', 'SQRT2' + ]); + all2(grantFunc, Math, [ + 'abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', + 'log', 'max', 'min', 'pow', 'random', 'round', 'sin', 'sqrt', 'tan' + ]); + + /// toString + + function grantToString(proto) { + proto.TOSTRING___ = xo4a(proto.toString, 'toString'); + } + useGetHandler(Object.prototype, 'toString', + function toStringGetter() { + if (hasOwnProp(this, 'toString') && + typeOf(this.toString) === 'function' && + !hasOwnProp(this, 'TOSTRING___')) { + // This case is a kludge that doesn't work for undiagnosed reasons. +// this.TOSTRING___ = xo4a(this.toString, 'toString'); + // TODO(erights): This case is a different kludge that needs to + // be explained. + // TODO(erights): This is probably wrong in that it can leak xo4a. + return this.toString; + } + return reifyIfXo4a(this.TOSTRING___, 'toString'); + }); + useApplyHandler(Object.prototype, 'toString', + function toStringApplier(args) { + return this.toString.apply(this, args); + }); + useSetHandler(Object.prototype, 'toString', + function toStringSetter(meth) { + if (isFrozen(this)) { + return myKeeper.handleSet(this, 'toString', meth); + } + meth = asFirstClass(meth); + this.TOSTRING___ = meth; + this.toString = function delegatingToString(var_args) { + var args = Array.slice(arguments, 0); + if (typeOf(meth) === 'function') { + return meth.apply(this, args); + } + var methApply = readPub(meth, 'apply'); + if (typeOf(methApply) === 'function') { + return methApply.call(meth, this, args); + } + var result = Object.toString.call(this); + log('Not correctly printed: ' + result); + return result; + }; + return meth; + }); + useDeleteHandler(Object.prototype, 'toString', + function toStringDeleter() { + if (isFrozen(this)) { + return myKeeper.handleDelete(this, 'toString'); + } + return (delete this.toString) && (delete this.TOSTRING___); + }); + + /// Object + + ctor(Object, void 0, 'Object'); + grantToString(Object.prototype); + all2(grantGeneric, Object.prototype, [ + 'toLocaleString', 'valueOf', 'isPrototypeOf' + ]); + grantRead(Object.prototype, 'length'); + handleGeneric(Object.prototype, 'hasOwnProperty', + function hasOwnPropertyHandler(name) { + return hasOwnPropertyOf(this, name); + }); + handleGeneric(Object.prototype, 'propertyIsEnumerable', + function propertyIsEnumerableHandler(name) { + name = String(name); + return canEnumPub(this, name); + }); + + /// Function + + handleGeneric(Function.prototype, 'apply', + function applyHandler(self, realArgs) { + return toFunc(this).apply(self, realArgs); + }); + handleGeneric(Function.prototype, 'call', + function callHandler(self, var_args) { + return toFunc(this).apply(self, Array.slice(arguments, 1)); + }); + handleGeneric(Function.prototype, 'bind', + function bindHandler(self, var_args) { + var thisFunc = this; + var leftArgs = Array.slice(arguments, 1); + function boundHandler(var_args) { + var args = leftArgs.concat(Array.slice(arguments, 0)); + return callPub(thisFunc, 'apply', [self, args]); + } + return frozenFunc(boundHandler); + }); + + /// Array + + ctor(Array, Object, 'Array'); + grantFunc(Array, 'slice'); + grantToString(Array.prototype); + all2(grantTypedGeneric, Array.prototype, [ 'toLocaleString' ]); + all2(grantGeneric, Array.prototype, [ + 'concat', 'join', 'slice', 'indexOf', 'lastIndexOf' + ]); + all2(grantMutator, Array.prototype, [ + 'pop', 'push', 'reverse', 'shift', 'splice', 'unshift' + ]); + handleGeneric(Array.prototype, 'sort', + function sortHandler(comparator) { + if (isFrozen(this)) { + fail("Can't sort a frozen array."); + } + if (comparator) { + return Array.prototype.sort.call(this, toFunc(comparator)); + } else { + return Array.prototype.sort.call(this); + } + }); + + /// String + + ctor(String, Object, 'String'); + grantFunc(String, 'fromCharCode'); + grantToString(String.prototype); + all2(grantTypedGeneric, String.prototype, [ + 'toLocaleString', 'indexOf', 'lastIndexOf' + ]); + all2(grantGeneric, String.prototype, [ + 'charAt', 'charCodeAt', 'concat', + 'localeCompare', 'slice', 'substr', 'substring', + 'toLowerCase', 'toLocaleLowerCase', 'toUpperCase', 'toLocaleUpperCase' + ]); + + handleGeneric(String.prototype, 'match', + function matchHandler(regexp) { + enforceMatchable(regexp); + return this.match(regexp); + }); + handleGeneric(String.prototype, 'replace', + function replaceHandler(searcher, replacement) { + enforceMatchable(searcher); + if (isFunc(replacement)) { + replacement = asFunc(replacement); + } else if (isApplicator(replacement)) { + replacement = toFunc(replacement); + } else { + replacement = '' + replacement; + } + return this.replace(searcher, replacement); + }); + handleGeneric(String.prototype, 'search', + function searchHandler(regexp) { + enforceMatchable(regexp); + return this.search(regexp); + }); + handleGeneric(String.prototype, 'split', + function splitHandler(separator, limit) { + enforceMatchable(separator); + return this.split(separator, limit); + }); + + /// Boolean + + ctor(Boolean, Object, 'Boolean'); + grantToString(Boolean.prototype); + + /// Number + + ctor(Number, Object, 'Number'); + all2(grantRead, Number, [ + 'MAX_VALUE', 'MIN_VALUE', 'NaN', + 'NEGATIVE_INFINITY', 'POSITIVE_INFINITY' + ]); + grantToString(Number.prototype); + all2(grantTypedGeneric, Number.prototype, [ + 'toFixed', 'toExponential', 'toPrecision' + ]); + + /// Date + + ctor(Date, Object, 'Date'); + grantFunc(Date, 'parse'); + grantFunc(Date, 'UTC'); + grantToString(Date.prototype); + all2(grantTypedGeneric, Date.prototype, [ + 'toDateString','toTimeString', 'toUTCString', + 'toLocaleString', 'toLocaleDateString', 'toLocaleTimeString', + 'toISOString', + 'getDay', 'getUTCDay', 'getTimezoneOffset', + + 'getTime', 'getFullYear', 'getUTCFullYear', 'getMonth', 'getUTCMonth', + 'getDate', 'getUTCDate', 'getHours', 'getUTCHours', + 'getMinutes', 'getUTCMinutes', 'getSeconds', 'getUTCSeconds', + 'getMilliseconds', 'getUTCMilliseconds' + ]); + all2(grantMutator, Date.prototype, [ + 'setTime', 'setFullYear', 'setUTCFullYear', 'setMonth', 'setUTCMonth', + 'setDate', 'setUTCDate', 'setHours', 'setUTCHours', + 'setMinutes', 'setUTCMinutes', 'setSeconds', 'setUTCSeconds', + 'setMilliseconds', 'setUTCMilliseconds' + ]); + + /// RegExp + + ctor(RegExp, Object, 'RegExp'); + grantToString(RegExp.prototype); + handleGeneric(RegExp.prototype, 'exec', + function execHandler(specimen) { + if (isFrozen(this)) { + fail("Can't .exec a frozen RegExp"); + } + specimen = String(specimen); // See bug 528 + return this.exec(specimen); + }); + handleGeneric(RegExp.prototype, 'test', + function testHandler(specimen) { + if (isFrozen(this)) { + fail("Can't .test a frozen RegExp"); + } + specimen = String(specimen); // See bug 528 + return this.test(specimen); + }); + + all2(grantRead, RegExp.prototype, [ + 'source', 'global', 'ignoreCase', 'multiline', 'lastIndex' + ]); + + /// errors + + ctor(Error, Object, 'Error'); + grantToString(Error.prototype); + grantRead(Error.prototype, 'name'); + grantRead(Error.prototype, 'message'); + ctor(EvalError, Error, 'EvalError'); + ctor(RangeError, Error, 'RangeError'); + ctor(ReferenceError, Error, 'ReferenceError'); + ctor(SyntaxError, Error, 'SyntaxError'); + ctor(TypeError, Error, 'TypeError'); + ctor(URIError, Error, 'URIError'); + + + var sharedImports; + + //////////////////////////////////////////////////////////////////////// + // Module loading + //////////////////////////////////////////////////////////////////////// + + var myNewModuleHandler; + + /** + * Gets the current module handler. + */ + function getNewModuleHandler() { + return myNewModuleHandler; + } + + /** + * Registers a new-module-handler, to be called back when a new + * module is loaded. + * <p> + * This callback mechanism is provided so that translated Cajita + * modules can be loaded from a trusted site with the + * <script> tag, which runs its script as a statement, not + * an expression. The callback is of the form + * <tt>newModuleHandler.handle(newModule)</tt>. + */ + function setNewModuleHandler(newModuleHandler) { + myNewModuleHandler = newModuleHandler; + } + + /** + * A new-module-handler which returns the new module without + * instantiating it. + */ + var obtainNewModule = freeze({ + handle: frozenFunc(function handleOnly(newModule){ return newModule; }) + }); + + /** + * Makes and returns a fresh "normal" module handler whose imports + * are initialized to a copy of the sharedImports. + * <p> + * This handles a new module by calling it, passing it the imports + * object held in this handler. Successive modules handled by the + * same "normal" handler thereby see a simulation of successive + * updates to a shared global scope. + */ + function makeNormalNewModuleHandler() { + var imports = copy(sharedImports); + var lastOutcome = void 0; + return freeze({ + getImports: frozenFunc(function getImports() { return imports; }), + setImports: frozenFunc(function setImports(newImports) { + imports = newImports; + }), + + /** + * An outcome is a pair of a success flag and a value. + * <p> + * If the success flag is true, then the value is the normal + * result of calling the module function. If the success flag is + * false, then the value is the thrown error by which the module + * abruptly terminated. + * <p> + * An html page is cajoled to a module that runs to completion, + * but which reports as its outcome the outcome of its last + * script block. In order to reify that outcome and report it + * later, the html page initializes moduleResult___ to + * NO_RESULT, the last script block is cajoled to set + * moduleResult___ to something other than NO_RESULT on success + * but to call handleUncaughtException() on + * failure, and the html page returns moduleResult___ on + * completion. handleUncaughtException() records a failed + * outcome. This newModuleHandler's handle() method will not + * overwrite an already reported outcome with NO_RESULT, so the + * last script-block's outcome will be preserved. + */ + getLastOutcome: frozenFunc(function getLastOutcome() { + return lastOutcome; + }), + + /** + * If the last outcome is a success, returns its value; + * otherwise <tt>undefined</tt>. + */ + getLastValue: frozenFunc(function getLastValue() { + if (lastOutcome && lastOutcome[0]) { + return lastOutcome[1]; + } else { + return void 0; + } + }), + + /** + * Runs the newModule's module function. + * <p> + * Updates the last outcome to report the module function's + * reported outcome. Propogate this outcome by terminating in + * the same manner. + */ + handle: frozenFunc(function handle(newModule) { + lastOutcome = void 0; + try { + var result = newModule.instantiate(___, imports); + if (result !== NO_RESULT) { + lastOutcome = [true, result]; + } + } catch (ex) { + lastOutcome = [false, ex]; + } + if (lastOutcome) { + if (lastOutcome[0]) { + return lastOutcome[1]; + } else { + throw lastOutcome[1]; + } + } else { + return void 0; + } + }), + + /** + * This emulates HTML5 exception handling for scripts as discussed at + * http://code.google.com/p/google-caja/wiki/UncaughtExceptionHandling + * and see HtmlCompiler.java for the code that calls this. + * @param exception a raw exception. Since {@code throw} can raise any + * value, exception could be any value accessible to cajoled code, or + * any value thrown by an API imported by cajoled code. + * @param onerror the value of the raw reference "onerror" in top level + * cajoled code. This will likely be undefined much of the time, but + * could be anything. If it is a func, it can be called with + * three strings (message, source, lineNum) as the + * {@code window.onerror} event handler. + * @param {string} source a URI describing the source file from which the + * error originated. + * @param {string} lineNum the approximate line number in source at which + * the error originated. + */ + handleUncaughtException: function handleUncaughtException(exception, + onerror, + source, + lineNum) { + lastOutcome = [false, exception]; + + // Cause exception to be rethrown if it is uncatchable. + tameException(exception); + + var message = 'unknown'; + if ('object' === typeOf(exception) && exception !== null) { + message = String(exception.message || exception.desc || message); + } + + // If we wanted to provide a hook for containers to get uncaught + // exceptions, it would go here before onerror is invoked. + + // See the HTML5 discussion for the reasons behind this rule. + if (isApplicator(onerror)) { onerror = toFunc(onerror); } + var shouldReport = ( + isFunc(onerror) + ? onerror.CALL___(message, String(source), String(lineNum)) + : onerror !== null); + if (shouldReport !== false) { + cajita.log(source + ':' + lineNum + ': ' + message); + } + } + }); + } + + /** + * A module is an object literal containing metadata and an + * <code>instantiate</code> member, which is a plugin-maker function. + * <p> + * loadModule(module) marks module's <code>instantiate</code> member as a + * func, freezes the module, asks the current new-module-handler to handle it + * (thereby notifying the handler), and returns the new module. + */ + function loadModule(module) { + freeze(module); + frozenFunc(module.instantiate); + return callPub(myNewModuleHandler, 'handle', [module]); + } + + var registeredImports = []; + + /** + * Gets or assigns the id associated with this (assumed to be) + * imports object, registering it so that + * <tt>getImports(getId(imports)) === imports</tt>. + * <p> + * This system of registration and identification allows us to + * cajole html such as + * <pre><a onmouseover="alert(1)">Mouse here</a></pre> + * into html-writing JavaScript such as<pre> + * ___IMPORTS___.document.innerHTML = " + * <a onmouseover=\" + * (function(___IMPORTS___) { + * ___IMPORTS___.alert(1); + * })(___.getImports(" + ___.getId(___IMPORTS___) + ")) + * \">Mouse here</a> + * "; + * </pre> + * If this is executed by a plugin whose imports is assigned id 42, + * it generates html with the same meaning as<pre> + * <a onmouseover="___.getImports(42).alert(1)">Mouse here</a> + * </pre> + * <p> + * An imports is not registered and no id is assigned to it until the + * first call to <tt>getId</tt>. This way, an imports that is never + * registered, or that has been <tt>unregister</tt>ed since the last + * time it was registered, will still be garbage collectable. + */ + function getId(imports) { + enforceType(imports, 'object', 'imports'); + var id; + if ('id___' in imports) { + id = enforceType(imports.id___, 'number', 'id'); + } else { + id = imports.id___ = registeredImports.length; + } + registeredImports[id] = imports; + return id; + } + + /** + * Gets the imports object registered under this id. + * <p> + * If it has been <tt>unregistered</tt> since the last + * <tt>getId</tt> on it, then <tt>getImports</tt> will fail. + */ + function getImports(id) { + var result = registeredImports[enforceType(id, 'number', 'id')]; + if (result === void 0) { + fail('imports#', id, ' unregistered'); + } + return result; + } + + /** + * If you know that this <tt>imports</tt> no longers needs to be + * accessed by <tt>getImports</tt>, then you should + * <tt>unregister</tt> it so it can be garbage collected. + * <p> + * After unregister()ing, the id is not reassigned, and the imports + * remembers its id. If asked for another <tt>getId</tt>, it + * reregisters itself at its old id. + */ + function unregister(imports) { + enforceType(imports, 'object', 'imports'); + if ('id___' in imports) { + var id = enforceType(imports.id___, 'number', 'id'); + registeredImports[id] = void 0; + } + } + + + //////////////////////////////////////////////////////////////////////// + // Trademarking + //////////////////////////////////////////////////////////////////////// + + /** + * Return a trademark object. + */ + function Trademark(name) { + return Token(name); + } + frozenFunc(Trademark); + + /** + * Returns true if the object has a list of trademarks + * and the given trademark is in the list. + */ + function hasTrademark(trademark, obj) { + if (!hasOwnProp(obj, 'trademarks___')) { return false; } + var list = obj.trademarks___; + for (var i = 0; i < list.length; ++i) { + if (list[i] === trademark) { return true; } + } + return false; + } + + /** + * Throws an exception if the object does not have any trademarks or + * the given trademark is not in the list of trademarks. + */ + function guard(trademark, obj) { + if (!hasTrademark(trademark, obj)) { + fail('Object "' + obj + '" does not have the "' + + (trademark.toString() || '*unknown*') + '" trademark'); + } + } + + /** + * This function adds the given trademark to the given object's list of + * trademarks. + * If the trademark list doesn't exist yet, this function creates it. + * JSON containers and functions may be stamped at any time; constructed + * objects may only be stamped during construction unless the third + * parameter is truthy. + */ + function stamp(trademark, obj, opt_allow_constructed) { + if (typeOf(trademark) !== 'object') { + fail('The supplied trademark is not an object.'); + } + if (isFrozen(obj)) { fail('The supplied object ' + obj + ' is frozen.'); } + if (!isJSONContainer(obj) && + (typeOf(obj) !== 'function') && + !obj.underConstruction___ && + !opt_allow_constructed) { + fail('The supplied object ', obj, + ' has already been constructed and may not be stamped.'); + } + var list = obj.underConstruction___ ? + 'delayedTrademarks___' : 'trademarks___'; + if (!obj[list]) { obj[list] = []; } + obj[list].push(trademark); + return obj; + } + + function initializeMap(list) { + var result = {}; + for (var i = 0; i < list.length; i += 2) { + // Call asFirstClass() here to prevent, for example, a toxic + // function being used at the toString property of an object + // literal. + setPub(result, list[i], asFirstClass(list[i + 1])); + } + return result; + } + + //////////////////////////////////////////////////////////////////////// + // Sealing and Unsealing + //////////////////////////////////////////////////////////////////////// + + /** + * Returns a pair of functions such that the seal(x) wraps x in an object + * so that only unseal can get x back from the object. + * + * @return {object} of the form + * { seal: function seal(x) { return {}; }, + * unseal: function unseal(obj) { return x; } }. + */ + function makeSealerUnsealerPair() { + var flag = false; // Was a box successfully unsealed + var squirrel = null; // Receives the payload from an unsealed box. + function seal(payload) { + function box() { + flag = true; + squirrel = payload; + } + box.toString = frozenFunc(function toString() { return '(box)'; }); + return frozenFunc(box); + } + function unseal(box) { + // Start off in a known good state. + flag = false; + squirrel = null; + try { // Don't do anything outside try to foil forwarding functions. + box.CALL___(); + if (!flag) { throw new Error('Sealer/Unsealer mismatch'); } + return squirrel; + } finally { + // Restore to a known good state. + flag = false; + squirrel = null; + } + } + return freeze({ + seal: frozenFunc(seal), + unseal: frozenFunc(unseal) + }); + } + + //////////////////////////////////////////////////////////////////////// + // Needed for Valija + //////////////////////////////////////////////////////////////////////// + + /** + * <tt>cajita.construct(ctor, [args...])</tt> invokes a simple function as + * a constructor using 'new'. + */ + function construct(ctor, args) { + ctor = asCtor(ctor); + // This works around problems with (new Array()) and (new Date()) where + // the returned object is not really a Date or Array on SpiderMonkey and + // other interpreters. + switch (args.length) { + case 0: return new ctor(); + case 1: return new ctor(args[0]); + case 2: return new ctor(args[0], args[1]); + case 3: return new ctor(args[0], args[1], args[2]); + case 4: return new ctor(args[0], args[1], args[2], args[3]); + case 5: return new ctor(args[0], args[1], args[2], args[3], args[4]); + case 6: return new ctor(args[0], args[1], args[2], args[3], args[4], + args[5]); + case 7: return new ctor(args[0], args[1], args[2], args[3], args[4], + args[5], args[6]); + case 8: return new ctor(args[0], args[1], args[2], args[3], args[4], + args[5], args[6], args[7]); + case 9: return new ctor(args[0], args[1], args[2], args[3], args[4], + args[5], args[6], args[7], args[8]); + case 10: return new ctor(args[0], args[1], args[2], args[3], args[4], + args[5], args[6], args[7], args[8], args[9]); + case 11: return new ctor(args[0], args[1], args[2], args[3], args[4], + args[5], args[6], args[7], args[8], args[9], + args[10]); + case 12: return new ctor(args[0], args[1], args[2], args[3], args[4], + args[5], args[6], args[7], args[8], args[9], + args[10], args[11]); + default: + if (ctor.typeTag___ === 'Array') { + return ctor.apply(USELESS, args); + } + var tmp = function (args) { + return ctor.apply(this, args); + }; + tmp.prototype = ctor.prototype; + return new tmp(args); + } + } + + /** + * Create a unique identification of a given table identity that can + * be used to invisibly (to Cajita code) annotate a key object to + * index into a table. + * <p> + * magicCount and MAGIC_TOKEN together represent a + * unique-across-frames value safe against collisions, under the + * normal Caja threat model assumptions. magicCount and + * MAGIC_NAME together represent a probably unique across frames + * value, with which can generate strings in which collision is + * unlikely but possible. + * <p> + * The MAGIC_TOKEN is a unique unforgeable per-Cajita runtime + * value. magicCount is a per-Cajita counter, which increments each + * time a new one is needed. + */ + var magicCount = 0; + var MAGIC_NUM = Math.random(); + var MAGIC_TOKEN = Token('MAGIC_TOKEN_FOR:' + MAGIC_NUM); + // Using colons in the below causes it to fail on IE since getting a + // property whose name contains a semicolon on a DOM table element causes + // an exception. + var MAGIC_NAME = '_index;'+ MAGIC_NUM + ';'; + + /** + * Creates a new mutable associative table mapping from the + * identity of arbitrary keys (as defined by tt>identical()</tt>) to + * arbitrary values. + * <p> + * Once there is a conventional way for JavaScript implementations + * to provide weak-key tables, this should feature-test and use that + * where it is available, in which case the opt_useKeyLifetime flag + * can be ignored. When no weak-key table is primitively provided, + * this flag determines which of two possible approximations to + * use. In all three cases (actual weak key tables, + * opt_useKeyLifetime is falsy, and opt_useKeyLifetime is + * truthy), the table returned + * <ul> + * <li>should work across frames, + * <li>should have O(1) complexity measure within a frame where + * collision is impossible, + * <li>and should have O(1) complexity measure between frames with + * high probability. + * <li>the table should not retain its keys. In other words, if a + * given table T is non-garbage but a given value K is otherwise + * garbage, the presence of K as a key in table T will + * not, by itself, prevent K from being garbage collected. (Note + * that this is not quite as aggressive as the contract provided + * by ephemerons.) + * </ul> + * Given that a K=>V association has been stored in table T, the + * three cases differ according to how long they retain V: + * <ul> + * <li>A genuine weak-key table retains V only while both T and K + * are not garbage. + * <li>If opt_useKeyLifetime is falsy, retain V while T is not + * garbage. + * <li>If opt_useKeyLifetime is truthy, retain V while K is not + * garbage. In this case, K must be an object rather than a + * primitive value. + * </ul> + * <p> + * To support Domita, the keys might be host objects. + */ + function newTable(opt_useKeyLifetime) { + magicCount++; + var myMagicIndexName = MAGIC_NAME + magicCount + '___'; + + function setOnKey(key, value) { + if (key !== Object(key)) { + fail("Can't use key lifetime on primitive keys: ", key); + } + var list = key[myMagicIndexName]; + if (!list) { + key[myMagicIndexName] = [MAGIC_TOKEN, value]; + } else { + var i = 0; + for (; i < list.length; i += 2) { + if (list[i] === MAGIC_TOKEN) { break; } + } + list[i] = MAGIC_TOKEN; + list[i + 1] = value; + } + } + + function getOnKey(key) { + if (key !== Object(key)) { + fail("Can't use key lifetime on primitive keys: ", key); + } + var list = key[myMagicIndexName]; + if (!list) { + return void 0; + } else { + var i = 0; + for (; i < list.length; i += 2) { + if (list[i] === MAGIC_TOKEN) { return list[i + 1]; } + } + return void 0; + } + } + + if (opt_useKeyLifetime) { + return primFreeze({ + set: frozenFunc(setOnKey), + get: frozenFunc(getOnKey) + }); + } + + var myValues = []; + + function setOnTable(key, value) { + switch (typeof key) { + case 'object': + case 'function': { + if (null === key) { myValues.prim_null = value; return; } + var index = getOnKey(key); + if (index === void 0) { + index = myValues.length; + setOnKey(key, index); + } + myValues[index] = value; + return; + } + case 'string': { myValues['str_' + key] = value; return; } + default: { myValues['prim_' + key] = value; return; } + } + } + + /** + * If the key is absent, returns <tt>undefined</tt>. + * <p> + * Users of this table cannot distinguish an <tt>undefined</tt> + * value from an absent key. + */ + function getOnTable(key) { + switch (typeof key) { + case 'object': + case 'function': { + if (null === key) { return myValues.prim_null; } + var index = getOnKey(key); + if (void 0 === index) { return void 0; } + return myValues[index]; + } + case 'string': { return myValues['str_' + key]; } + default: { return myValues['prim_' + key]; } + } + } + + return primFreeze({ + set: frozenFunc(setOnTable), + get: frozenFunc(getOnTable) + }); + } + + + /** + * Is <tt>allegedParent</tt> on <obj>'s prototype chain? + * <p> + * Although in raw JavaScript <tt>'foo' instanceof String</tt> is + * false, to reduce the differences between primitives and their + * wrappers, <tt>inheritsFrom('foo', String.prototype)</tt> is true. + */ + function inheritsFrom(obj, allegedParent) { + if (null === obj) { return false; } + if (void 0 === obj) { return false; } + if (typeOf(obj) === 'function') { return false; } + if (typeOf(allegedParent) !== 'object') { return false; } + if (null === allegedParent) { return false; } + function F() {} + F.prototype = allegedParent; + return Object(obj) instanceof F; + } + + /** + * Return func.prototype's directConstructor. + * <p> + * When following the "classical" inheritance pattern (simulating + * class-style inheritance as a pattern of prototypical + * inheritance), func may represent (the constructor of) a class; in + * which case getSuperCtor() returns (the constructor of) its + * immediate superclass. + */ + function getSuperCtor(func) { + enforceType(func, 'function'); + if (isCtor(func) || isFunc(func)) { + var result = directConstructor(func.prototype); + if (isCtor(result) || isFunc(result)) { + return result; + } + } + return void 0; + } + + var attribute = new RegExp( + '^([\\s\\S]*)_(?:canRead|canCall|getter|handler)___$'); + + /** + * Returns a list of all cajita-readable own properties, whether or + * not they are cajita-enumerable. + */ + function getOwnPropertyNames(obj) { + var result = []; + var seen = {}; + // TODO(erights): revisit once we do es3.1ish attribute control. + var implicit = isJSONContainer(obj); + for (var k in obj) { + if (hasOwnProp(obj, k)) { + if (implicit && !endsWith__.test(k)) { + if (!myOriginalHOP.call(seen, k)) { + seen[k] = true; + result.push(k); + } + } else { + var match = attribute.exec(k); + if (match !== null) { + var base = match[1]; + if (!myOriginalHOP.call(seen, base)) { + seen[base] = true; + result.push(base); + } + } + } + } + } + return result; + } + + /** + * Return the names of the accessible own properties of + * func.prototype. + * <p> + * Since prototypical objects are not themselves accessible in + * Cajita, this means in effect: the properties contributed by + * func.prototype that would be accessible on objects that inherit + * from func.prototype. + */ + function getProtoPropertyNames(func) { + enforceType(func, 'function'); + return getOwnPropertyNames(func.prototype); + } + + /** + * Return the value associated with func.prototype[name]. + * <p> + * Since prototypical objects are not themselves accessible in + * Cajita, this means in effect: If x inherits name from + * func.prototype, what would the value of x[name] be? If the value + * associated with func.prototype[name] is an exophoric function + * (resulting from taming a generic method), then return the + * corresponding pseudo-function. See reifyIfXo4a(). + */ + function getProtoPropertyValue(func, name) { + return asFirstClass(readPub(func.prototype, name)); + } + + /** + * Like primBeget(), but applicable only to records. + */ + function beget(parent) { + if (!isRecord(parent)) { + fail('Can only beget() records: ', parent); + } + return primBeget(parent); + } + + + //////////////////////////////////////////////////////////////////////// + // Exports + //////////////////////////////////////////////////////////////////////// + cajita = { + // Diagnostics and condition enforcement + log: log, + fail: fail, + enforce: enforce, + enforceType: enforceType, + enforceNat: enforceNat, + + // walking prototype chain, checking JSON containers + directConstructor: directConstructor, + getFuncCategory: getFuncCategory, + isDirectInstanceOf: isDirectInstanceOf, + isInstanceOf: isInstanceOf, + isRecord: isRecord, isArray: isArray, + isJSONContainer: isJSONContainer, + freeze: freeze, isFrozen: isFrozen, + copy: copy, snapshot: snapshot, + + // Accessing properties + canReadPub: canReadPub, readPub: readPub, + hasOwnPropertyOf: hasOwnPropertyOf, + readOwn: readOwn, + canEnumPub: canEnumPub, + canEnumOwn: canEnumOwn, + canInnocentEnum: canInnocentEnum, + BREAK: BREAK, + allKeys: allKeys, forAllKeys: forAllKeys, + ownKeys: ownKeys, forOwnKeys: forOwnKeys, + canCallPub: canCallPub, callPub: callPub, + canSetPub: canSetPub, setPub: setPub, + canDeletePub: canDeletePub, deletePub: deletePub, + + // Trademarking + Trademark: Trademark, + hasTrademark: hasTrademark, + guard: guard, + + // Sealing & Unsealing + makeSealerUnsealerPair: makeSealerUnsealerPair, + + // Other + USELESS: USELESS, + manifest: manifest, + + // Needed for Valija + construct: construct, + newTable: newTable, + inheritsFrom: inheritsFrom, + getSuperCtor: getSuperCtor, + getOwnPropertyNames: getOwnPropertyNames, + getProtoPropertyNames: getProtoPropertyNames, + getProtoPropertyValue: getProtoPropertyValue, + beget: beget + }; + + forOwnKeys(cajita, frozenFunc(function(k, v) { + switch (typeOf(v)) { + case 'object': { + if (v !== null) { primFreeze(v); } + break; + } + case 'function': { + frozenFunc(v); + break; + } + } + })); + + var nativeJSON = global.JSON; + safeJSON = primFreeze({ + parse: frozenFunc(function (text, opt_reviver) { + var attenuatedReviver; + // In attenuatedReviver, key will be a string, and "this" will be an + // object constructed by the JSON parser or attached to the JSON parser + // during a previous call to the reviver. + + text = String(text); + if (opt_reviver) { + opt_reviver = toFunc(opt_reviver); + throw new Error('JSON.parse with a reviver unimplemented'); + // TODO(mikesamuel): implement me + } else { + return nativeJSON.parse(text, function (key, value) { + return canSetPub(this, key) ? value : void 0; + }); + } + }), + stringify: frozenFunc(function (obj, opt_replacer, opt_space) { + switch (typeof opt_space) { + case 'object': case 'function': + throw new TypeError('space must be a number or string'); + } + if (opt_replacer) { + opt_replacer = toFunc(opt_replacer); + throw new Error('JSON.stringify with a replacer unimplemented'); + // TODO(mikesamuel): implement me + } else { + return nativeJSON.stringify(obj, function (key, value) { + return (canReadPub(this, key)) ? value : void 0; + }, opt_space); + } + }) + }); + + sharedImports = { + cajita: cajita, + + 'null': null, + 'false': false, + 'true': true, + 'NaN': NaN, + 'Infinity': Infinity, + 'undefined': void 0, + parseInt: frozenFunc(parseInt), + parseFloat: frozenFunc(parseFloat), + isNaN: frozenFunc(isNaN), + isFinite: frozenFunc(isFinite), + decodeURI: frozenFunc(decodeURI), + decodeURIComponent: frozenFunc(decodeURIComponent), + encodeURI: frozenFunc(encodeURI), + encodeURIComponent: frozenFunc(encodeURIComponent), + escape: escape ? frozenFunc(escape) : (void 0), + Math: Math, + JSON: safeJSON, + + Object: Object, + Array: Array, + String: String, + Boolean: Boolean, + Number: Number, + Date: Date, + RegExp: RegExp, + + Error: Error, + EvalError: EvalError, + RangeError: RangeError, + ReferenceError: ReferenceError, + SyntaxError: SyntaxError, + TypeError: TypeError, + URIError: URIError + }; + + forOwnKeys(sharedImports, frozenFunc(function(k, v) { + switch (typeOf(v)) { + case 'object': { + if (v !== null) { primFreeze(v); } + break; + } + case 'function': { + primFreeze(v); + break; + } + } + })); + primFreeze(sharedImports); + + ___ = { + // Diagnostics and condition enforcement + getLogFunc: getLogFunc, + setLogFunc: setLogFunc, + + primFreeze: primFreeze, + + // Accessing property attributes. + canRead: canRead, grantRead: grantRead, + canEnum: canEnum, grantEnumOnly: grantEnumOnly, + // TODO(mikesamuel): should grantCall be exported? + canCall: canCall, grantCall: grantCall, + canSet: canSet, grantSet: grantSet, + canDelete: canDelete, grantDelete: grantDelete, + + // Module linkage + readImport: readImport, + + // Classifying functions + isCtor: isCtor, + isFunc: isFunc, + isXo4aFunc: isXo4aFunc, + ctor: ctor, + func: func, frozenFunc: frozenFunc, + asFunc: asFunc, toFunc: toFunc, + xo4a: xo4a, + initializeMap: initializeMap, + + // Accessing properties + inPub: inPub, + canSetStatic: canSetStatic, setStatic: setStatic, + + // Other + typeOf: typeOf, + hasOwnProp: hasOwnProp, + identical: identical, + args: args, + tameException: tameException, + primBeget: primBeget, + callStackUnsealer: callStackSealer.unseal, + RegExp: RegExp, // Available to rewrite rule w/o risk of masking + stamp: stamp, + asFirstClass: asFirstClass, + + // Taming mechanism + useGetHandler: useGetHandler, + useApplyHandler: useApplyHandler, + useCallHandler: useCallHandler, + useSetHandler: useSetHandler, + useDeleteHandler: useDeleteHandler, + + grantFunc: grantFunc, + grantGeneric: grantGeneric, + handleGeneric: handleGeneric, + grantTypedGeneric: grantTypedGeneric, + grantMutator: grantMutator, + + enforceMatchable: enforceMatchable, + all2: all2, + + // Taming decisions + sharedImports: sharedImports, + + // Module loading + getNewModuleHandler: getNewModuleHandler, + setNewModuleHandler: setNewModuleHandler, + obtainNewModule: obtainNewModule, + makeNormalNewModuleHandler: makeNormalNewModuleHandler, + loadModule: loadModule, + NO_RESULT: NO_RESULT, + + getId: getId, + getImports: getImports, + unregister: unregister + }; + + forOwnKeys(cajita, frozenFunc(function(k, v) { + if (k in ___) { + fail('internal: initialization conflict: ', k); + } + if (typeOf(v) === 'function') { + grantFunc(cajita, k); + } + ___[k] = v; + })); + setNewModuleHandler(makeNormalNewModuleHandler()); +})(this);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/ext/domita.js Sun Jun 07 19:29:10 2009 -0700 @@ -0,0 +1,3691 @@ +// Copyright (C) 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview + * A partially tamed browser object model based on + * <a href="http://www.w3.org/TR/DOM-Level-2-HTML/Overview.html" + * >DOM-Level-2-HTML</a> and specifically, the + * <a href="http://www.w3.org/TR/DOM-Level-2-HTML/ecma-script-binding.html" + * >ECMAScript Language Bindings</a>. + * + * Caveats:<ul> + * <li>This is not a full implementation. + * <li>Security Review is pending. + * <li><code>===</code> and <code>!==</code> on node lists will not + * behave the same as with untamed node lists. Specifically, it is + * not always true that {@code nodeA.childNodes === nodeA.childNodes}. + * <li>Properties backed by setters/getters like {@code HTMLElement.innerHTML} + * will not appear to uncajoled code as DOM nodes do, since they are + * implemented using cajita property handlers. + * </ul> + * + * <p> + * TODO(ihab.awad): Our implementation of getAttribute (and friends) + * is such that standard DOM attributes which we disallow for security + * reasons (like 'form:enctype') are placed in the "virtual" + * attributes map (this.node___.attributes___). They appear to be + * settable and gettable, but their values are ignored and do not have + * the expected semantics per the DOM API. This is because we do not + * have a column in html4-defs.js stating that an attribute is valid + * but explicitly blacklisted. Alternatives would be to always throw + * upon access to these attributes; to make them always appear to be + * null; etc. Revisit this decision if needed. + * + * @author mikesamuel@gmail.com + * @requires console, document, window + * @requires clearInterval, clearTimeout, setInterval, setTimeout + * @requires ___, bridal, cajita, css, html, html4, unicode + * @provides attachDocumentStub, plugin_dispatchEvent___ + * @overrides domitaModules + */ + +var domitaModules; +if (!domitaModules) { domitaModules = {}; } + +domitaModules.classUtils = function() { + + /** + * Add setter and getter hooks so that the caja {@code node.innerHTML = '...'} + * works as expected. + */ + function exportFields(object, fields) { + for (var i = fields.length; --i >= 0;) { + var field = fields[i]; + var fieldUCamel = field.charAt(0).toUpperCase() + field.substring(1); + var getterName = 'get' + fieldUCamel; + var setterName = 'set' + fieldUCamel; + var count = 0; + if (object[getterName]) { + ++count; + ___.useGetHandler( + object, field, object[getterName]); + } + if (object[setterName]) { + ++count; + ___.useSetHandler( + object, field, object[setterName]); + } + if (!count) { + throw new Error('Failed to export field ' + field + ' on ' + object); + } + } + } + + /** + * Makes the first a subclass of the second. + */ + function extend(subClass, baseClass) { + var noop = function () {}; + noop.prototype = baseClass.prototype; + subClass.prototype = new noop(); + subClass.prototype.constructor = subClass; + } + + return { + exportFields: exportFields, + extend: extend + }; +}; + +/** XMLHttpRequest or an equivalent on IE 6. */ +domitaModules.XMLHttpRequestCtor = function (XMLHttpRequest, ActiveXObject) { + if (XMLHttpRequest) { + return XMLHttpRequest; + } else if (ActiveXObject) { + // The first time the ctor is called, find an ActiveX class supported by + // this version of IE. + var activeXClassId; + return function ActiveXObjectForIE() { + if (activeXClassId === void 0) { + activeXClassId = null; + /** Candidate Active X types. */ + var activeXClassIds = [ + 'MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0', + 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', + 'MICROSOFT.XMLHTTP.1.0', 'MICROSOFT.XMLHTTP.1', + 'MICROSOFT.XMLHTTP']; + for (var i = 0, n = activeXClassIds.length; i < n; i++) { + var candidate = activeXClassIds[i]; + try { + void new ActiveXObject(candidate); + activeXClassId = candidate; + break; + } catch (e) { + // do nothing; try next choice + } + } + activeXClassIds = null; + } + return new ActiveXObject(activeXClassId); + }; + } else { + throw new Error('ActiveXObject not available'); + } +}; + +domitaModules.TameXMLHttpRequest = function( + xmlHttpRequestMaker, + uriCallback) { + var classUtils = domitaModules.classUtils(); + + // See http://www.w3.org/TR/XMLHttpRequest/ + + // TODO(ihab.awad): Improve implementation (interleaving, memory leaks) + // per http://www.ilinsky.com/articles/XMLHttpRequest/ + + function TameXMLHttpRequest() { + this.xhr___ = new xmlHttpRequestMaker(); + classUtils.exportFields( + this, + ['onreadystatechange', 'readyState', 'responseText', 'responseXML', + 'status', 'statusText']); + } + TameXMLHttpRequest.prototype.setOnreadystatechange = function (handler) { + // TODO(ihab.awad): Do we need more attributes of the event than 'target'? + // May need to implement full "tame event" wrapper similar to DOM events. + var self = this; + this.xhr___.onreadystatechange = function(event) { + var evt = { target: self }; + return ___.callPub(handler, 'call', [void 0, evt]); + }; + // Store for later direct invocation if need be + this.handler___ = handler; + }; + TameXMLHttpRequest.prototype.getReadyState = function () { + // The ready state should be a number + return Number(this.xhr___.readyState); + }; + TameXMLHttpRequest.prototype.open = function ( + method, URL, opt_async, opt_userName, opt_password) { + method = String(method); + // The XHR interface does not tell us the MIME type in advance, so we + // must assume the broadest possible. + var safeUri = uriCallback.rewrite(String(URL), "*/*"); + // If the uriCallback rejects the URL, we throw an exception, but we do not + // put the URI in the exception so as not to put the caller at risk of some + // code in its stack sniffing the URI. + if (safeUri === void 0) { throw 'URI violates security policy'; } + switch (arguments.length) { + case 2: + this.async___ = true; + this.xhr___.open(method, safeUri); + break; + case 3: + this.async___ = opt_async; + this.xhr___.open(method, safeUri, Boolean(opt_async)); + break; + case 4: + this.async___ = opt_async; + this.xhr___.open( + method, safeUri, Boolean(opt_async), String(opt_userName)); + break; + case 5: + this.async___ = opt_async; + this.xhr___.open( + method, safeUri, Boolean(opt_async), String(opt_userName), + String(opt_password)); + break; + default: + throw 'XMLHttpRequest cannot accept ' + arguments.length + ' arguments'; + break; + } + }; + TameXMLHttpRequest.prototype.setRequestHeader = function (label, value) { + this.xhr___.setRequestHeader(String(label), String(value)); + }; + TameXMLHttpRequest.prototype.send = function(opt_data) { + if (arguments.length === 0) { + // TODO(ihab.awad): send()-ing an empty string because send() with no + // args does not work on FF3, others? + this.xhr___.send(''); + } else if (typeof opt_data === 'string') { + this.xhr___.send(opt_data); + } else /* if XML document */ { + // TODO(ihab.awad): Expect tamed XML document; unwrap and send + this.xhr___.send(''); + } + + // Firefox does not call the 'onreadystatechange' handler in + // the case of a synchronous XHR. We simulate this behavior by + // calling the handler explicitly. + if (this.xhr___.overrideMimeType) { + // This is Firefox + if (!this.async___ && this.handler___) { + var evt = { target: this }; + ___.callPub(this.handler___, 'call', [void 0, evt]); + } + } + }; + TameXMLHttpRequest.prototype.abort = function () { + this.xhr___.abort(); + }; + TameXMLHttpRequest.prototype.getAllResponseHeaders = function () { + var result = this.xhr___.getAllResponseHeaders(); + return (result === undefined || result === null) ? + result : String(result); + }; + TameXMLHttpRequest.prototype.getResponseHeader = function (headerName) { + var result = this.xhr___.getResponseHeader(String(headerName)); + return (result === undefined || result === null) ? + result : String(result); + }; + TameXMLHttpRequest.prototype.getResponseText = function () { + var result = this.xhr___.responseText; + return (result === undefined || result === null) ? + result : String(result); + }; + TameXMLHttpRequest.prototype.getResponseXML = function () { + // TODO(ihab.awad): Implement a taming layer for XML. Requires generalizing + // the HTML node hierarchy as well so we have a unified implementation. + return {}; + }; + TameXMLHttpRequest.prototype.getStatus = function () { + var result = this.xhr___.status; + return (result === undefined || result === null) ? + result : Number(result); + }; + TameXMLHttpRequest.prototype.getStatusText = function () { + var result = this.xhr___.statusText; + return (result === undefined || result === null) ? + result : String(result); + }; + TameXMLHttpRequest.prototype.toString = function () { + return 'Not a real XMLHttpRequest'; + }; + ___.ctor(TameXMLHttpRequest, void 0, 'TameXMLHttpRequest'); + ___.all2(___.grantTypedGeneric, TameXMLHttpRequest.prototype, + ['open', 'setRequestHeader', 'send', 'abort', + 'getAllResponseHeaders', 'getResponseHeader']); + + return TameXMLHttpRequest; +}; + +/** + * Add a tamed document implementation to a Gadget's global scope. + * + * @param {string} idSuffix a string suffix appended to all node IDs. + * @param {Object} uriCallback an object like <pre>{ + * rewrite: function (uri, mimeType) { return safeUri } + * }</pre>. + * The rewrite function should be idempotent to allow rewritten HTML + * to be reinjected. + * @param {Object} imports the gadget's global scope. + * @param {Node} pseudoBodyNode an HTML node to act as the "body" of the + * virtual document provided to Cajoled code. + * @param {Object} optPseudoWindowLocation a record containing the + * properties of the browser "window.location" object, which will + * be provided to the Cajoled code. + */ +var attachDocumentStub = (function () { + // Array Remove - By John Resig (MIT Licensed) + function arrayRemove(array, from, to) { + var rest = array.slice((to || from) + 1 || array.length); + array.length = from < 0 ? array.length + from : from; + return array.push.apply(array, rest); + } + + var tameNodeTrademark = cajita.Trademark('tameNode'); + var tameEventTrademark = cajita.Trademark('tameEvent'); + + // Define a wrapper type for known safe HTML, and a trademarker. + // This does not actually use the trademarking functions since trademarks + // cannot be applied to strings. + function Html(htmlFragment) { this.html___ = String(htmlFragment || ''); } + Html.prototype.valueOf = Html.prototype.toString + = function () { return this.html___; }; + function safeHtml(htmlFragment) { + return (htmlFragment instanceof Html) + ? htmlFragment.html___ + : html.escapeAttrib(String(htmlFragment || '')); + } + function blessHtml(htmlFragment) { + return (htmlFragment instanceof Html) + ? htmlFragment + : new Html(htmlFragment); + } + + var XML_SPACE = '\t\n\r '; + + var XML_NAME_PATTERN = new RegExp( + '^[' + unicode.LETTER + '_:][' + unicode.LETTER + unicode.DIGIT + '.\\-_:' + + unicode.COMBINING_CHAR + unicode.EXTENDER + ']*$'); + + var XML_NMTOKEN_PATTERN = new RegExp( + '^[' + unicode.LETTER + unicode.DIGIT + '.\\-_:' + + unicode.COMBINING_CHAR + unicode.EXTENDER + ']+$'); + + var XML_NMTOKENS_PATTERN = new RegExp( + '^(?:[' + XML_SPACE + ']*[' + unicode.LETTER + unicode.DIGIT + '.\\-_:' + + unicode.COMBINING_CHAR + unicode.EXTENDER + ']+)+[' + XML_SPACE + ']*$' + ); + + var JS_SPACE = '\t\n\r '; + // An identifier that does not end with __. + var JS_IDENT = '(?:[a-zA-Z_][a-zA-Z0-9$_]*[a-zA-Z0-9$]|[a-zA-Z])_?'; + var SIMPLE_HANDLER_PATTERN = new RegExp( + '^[' + JS_SPACE + ']*' + + '(return[' + JS_SPACE + ']+)?' // Group 1 is present if it returns. + + '(' + JS_IDENT + ')[' + JS_SPACE + ']*' // Group 2 is a function name. + // Which can be passed optionally this node, and optionally the event. + + '\\((?:this' + + '(?:[' + JS_SPACE + ']*,[' + JS_SPACE + ']*event)?' + + '[' + JS_SPACE + ']*)?\\)' + // And it can end with a semicolon. + + '[' + JS_SPACE + ']*(?:;?[' + JS_SPACE + ']*)$'); + + /** + * Coerces the string to a valid XML Name. + * @see http://www.w3.org/TR/2000/REC-xml-20001006#NT-Name + */ + function isXmlName(s) { + return XML_NAME_PATTERN.test(s); + } + + /** + * Coerces the string to valid XML Nmtokens + * @see http://www.w3.org/TR/2000/REC-xml-20001006#NT-Nmtokens + */ + function isXmlNmTokens(s) { + return XML_NMTOKENS_PATTERN.test(s); + } + + // Trim whitespace from the beginning and end of a CSS string. + + function trimCssSpaces(input) { + return input.replace(/^[ \t\r\n\f]+|[ \t\r\n\f]+$/g, ''); + } + + /** + * The plain text equivalent of a CSS string body. + * @param {string} s the body of a CSS string literal w/o quotes + * or CSS identifier. + * @return {string} plain text. + * {@updoc + * $ decodeCssString('') + * # '' + * $ decodeCssString('foo') + * # 'foo' + * $ decodeCssString('foo\\\nbar\\\r\nbaz\\\rboo\\\ffar') + * # 'foobarbazboofar' + * $ decodeCssString('foo\\000a bar\\000Abaz') + * # 'foo' + '\n' + 'bar' + '\u0ABA' + 'z' + * $ decodeCssString('foo\\\\bar\\\'baz') + * # "foo\\bar'baz" + * } + */ + function decodeCssString(s) { + // Decode a CSS String literal. + // From http://www.w3.org/TR/CSS21/grammar.html + // string1 \"([^\n\r\f\\"]|\\{nl}|{escape})*\" + // unicode \\{h}{1,6}(\r\n|[ \t\r\n\f])? + // escape {unicode}|\\[^\r\n\f0-9a-f] + // s [ \t\r\n\f]+ + // nl \n|\r\n|\r|\f + return s.replace( + /\\(?:(\r\n?|\n|\f)|([0-9a-f]{1,6})(?:\r\n?|[ \t\n\f])?|(.))/gi, + function (_, nl, hex, esc) { + return esc || (nl ? '' : String.fromCharCode(parseInt(hex, 16))); + }); + } + + /** + * Sanitize the 'style' attribute value of an HTML element. + * + * @param styleAttrValue the value of a 'style' attribute, which we + * assume has already been checked by the caller to be a plain String. + * + * @return a sanitized version of the attribute value. + */ + function sanitizeStyleAttrValue(styleAttrValue) { + var sanitizedDeclarations = []; + var declarations = styleAttrValue.split(/;/g); + + for (var i = 0; declarations && i < declarations.length; i++) { + var parts = declarations[i].split(':'); + var property = trimCssSpaces(parts[0]).toLowerCase(); + var value = trimCssSpaces(parts.slice(1).join(":")); + if (css.properties.hasOwnProperty(property) + && css.properties[property].test(value + ' ')) { + sanitizedDeclarations.push(property + ': ' + value); + } + } + + return sanitizedDeclarations.join(' ; '); + } + + function mimeTypeForAttr(tagName, attribName) { + if (attribName === 'src') { + if (tagName === 'img') { return 'image/*'; } + if (tagName === 'script') { return 'text/javascript'; } + } + return '*/*'; + } + + // TODO(ihab.awad): Does this work on IE, where console output + // goes to a DOM node? + function assert(cond) { + if (!cond) { + if (typeof console !== 'undefined') { + console.error('domita assertion failed'); + console.trace(); + } + throw new Error(); + } + } + + var classUtils = domitaModules.classUtils(); + + var cssSealerUnsealerPair = cajita.makeSealerUnsealerPair(); + + // Implementations of setTimeout, setInterval, clearTimeout, and + // clearInterval that only allow simple functions as timeouts and + // that treat timeout ids as capabilities. + // This is safe even if accessed across frame since the same + // trademark value is never used with more than one version of + // setTimeout. + var timeoutIdTrademark = cajita.Trademark('timeoutId'); + function tameSetTimeout(timeout, delayMillis) { + // Existing browsers treat a timeout of null or undefined as a noop. + var timeoutId; + if (timeout) { + if (typeof timeout === 'string') { + throw new Error( + 'setTimeout called with a string.' + + ' Please pass a function instead of a string of javascript'); + } + timeoutId = setTimeout( + function () { ___.callPub(timeout, 'call', [___.USELESS]); }, + delayMillis | 0); + } else { + // tameClearTimeout checks for NaN and handles it specially. + timeoutId = NaN; + } + return ___.freeze(___.stamp(timeoutIdTrademark, + { timeoutId___: timeoutId })); + } + ___.frozenFunc(tameSetTimeout); + function tameClearTimeout(timeoutId) { + ___.guard(timeoutIdTrademark, timeoutId); + var rawTimeoutId = timeoutId.timeoutId___; + // Skip NaN values created for null timeouts above. + if (rawTimeoutId === rawTimeoutId) { clearTimeout(rawTimeoutId); } + } + ___.frozenFunc(tameClearTimeout); + var intervalIdTrademark = cajita.Trademark('intervalId'); + function tameSetInterval(interval, delayMillis) { + // Existing browsers treat an interval of null or undefined as a noop. + var intervalId; + if (interval) { + if (typeof interval === 'string') { + throw new Error( + 'setInterval called with a string.' + + ' Please pass a function instead of a string of javascript'); + } + intervalId = setInterval( + function () { ___.callPub(interval, 'call', [___.USELESS]); }, + delayMillis | 0); + } else { + intervalId = NaN; + } + return ___.freeze(___.stamp(intervalIdTrademark, + { intervalId___: intervalId })); + } + ___.frozenFunc(tameSetInterval); + function tameClearInterval(intervalId) { + ___.guard(intervalIdTrademark, intervalId); + var rawIntervalId = intervalId.intervalId___; + if (rawIntervalId === rawIntervalId) { clearInterval(rawIntervalId); } + } + ___.frozenFunc(tameClearInterval); + + function makeScrollable(element) { + var overflow; + if (element.currentStyle) { + overflow = element.currentStyle.overflow; + } else if (window.getComputedStyle) { + overflow = window.getComputedStyle(element, void 0).overflow; + } else { + overflow = null; + } + switch (overflow && overflow.toLowerCase()) { + case 'visible': + case 'hidden': + element.style.overflow = 'auto'; + break; + } + } + + /** + * Moves the given pixel within the element's frame of reference as close to + * the top-left-most pixel of the element's viewport as possible without + * moving the viewport beyond the bounds of the content. + * @param {number} x x-coord of a pixel in the element's frame of reference. + * @param {number} y y-coord of a pixel in the element's frame of reference. + */ + function tameScrollTo(element, x, y) { + if (x !== +x || y !== +y || x < 0 || y < 0) { + throw new Error('Cannot scroll to ' + x + ':' + typeof x + ',' + + y + ' : ' + typeof y); + } + element.scrollLeft = x; + element.scrollTop = y; + } + + /** + * Moves the origin of the given element's view-port by the given offset. + * @param {number} dx a delta in pixels. + * @param {number} dy a delta in pixels. + */ + function tameScrollBy(element, dx, dy) { + if (dx !== +dx || dy !== +dy) { + throw new Error('Cannot scroll by ' + dx + ':' + typeof dx + ', ' + + dy + ':' + typeof dy); + } + element.scrollLeft += dx; + element.scrollTop += dy; + } + + function guessPixelsFromCss(cssStr) { + if (!cssStr) { return 0; } + var m = cssStr.match(/^([0-9]+)/); + return m ? +m[1] : 0; + } + + function tameResizeTo(element, w, h) { + if (w !== +w || h !== +h) { + throw new Error('Cannot resize to ' + w + ':' + typeof w + ', ' + + h + ':' + typeof h); + } + element.style.width = w + 'px'; + element.style.height = h + 'px'; + } + + function tameResizeBy(element, dw, dh) { + if (dw !== +dw || dh !== +dh) { + throw new Error('Cannot resize by ' + dw + ':' + typeof dw + ', ' + + dh + ':' + typeof dh); + } + if (!dw && !dh) { return; } + + // scrollWidth is width + padding + border. + // offsetWidth is width + padding + border, but excluding the non-visible + // area. + // clientWidth iw width + padding, and like offsetWidth, clips to the + // viewport. + // margin does not count in any of these calculations. + // + // scrollWidth/offsetWidth + // +------------+ + // | | + // + // +----------------+ + // | | Margin-top + // | +------------+ | + // | |############| | Border-top + // | |#+--------+#| | + // | |#| |#| | Padding-top + // | |#| +----+ |#| | + // | |#| | | |#| | Height + // | |#| | | |#| | + // | |#| +----+ |#| | + // | |#| |#| | + // | |#+--------+#| | + // | |############| | + // | +------------+ | + // | | + // +----------------+ + // + // | | + // +--------+ + // clientWidth (but excludes content outside viewport) + + var style = element.currentStyle; + if (!style) { + style = window.getComputedStyle(element, void 0); + } + + // We guess the padding since it's not always expressed in px on IE + var extraHeight = guessPixelsFromCss(style.paddingBottom) + + guessPixelsFromCss(style.paddingTop); + var extraWidth = guessPixelsFromCss(style.paddingLeft) + + guessPixelsFromCss(style.paddingRight); + + var goalHeight = element.clientHeight + dh; + var goalWidth = element.clientWidth + dw; + + var h = goalHeight - extraHeight; + var w = goalWidth - extraWidth; + + if (dh) { element.style.height = Math.max(0, h) + 'px'; } + if (dw) { element.style.width = Math.max(0, w) + 'px'; } + + // Correct if our guesses re padding and borders were wrong. + // We may still not be able to resize if e.g. the deltas would take + // a dimension negative. + if (dh && element.clientHeight !== goalHeight) { + var hError = element.clientHeight - goalHeight; + element.style.height = Math.max(0, h - hError) + 'px'; + } + if (dw && element.clientWidth !== goalWidth) { + var wError = element.clientWidth - goalWidth; + element.style.width = Math.max(0, w - wError) + 'px'; + } + } + + // See above for a description of this function. + function attachDocumentStub( + idSuffix, uriCallback, imports, pseudoBodyNode, optPseudoWindowLocation) { + if (arguments.length < 4) { + throw new Error('arity mismatch: ' + arguments.length); + } + if (!optPseudoWindowLocation) { + optPseudoWindowLocation = {}; + } + var elementPolicies = {}; + elementPolicies.form = function (attribs) { + // Forms must have a gated onsubmit handler or they must have an + // external target. + var sawHandler = false; + for (var i = 0, n = attribs.length; i < n; i += 2) { + if (attribs[i] === 'onsubmit') { + sawHandler = true; + } + } + if (!sawHandler) { + attribs.push('onsubmit', 'return false'); + } + return attribs; + }; + elementPolicies.a = elementPolicies.area = function (attribs) { + // Anchor tags must have a target. + attribs.push('target', '_blank'); + return attribs; + }; + + + /** Sanitize HTML applying the appropriate transformations. */ + function sanitizeHtml(htmlText) { + var out = []; + htmlSanitizer(htmlText, out); + return out.join(''); + } + var htmlSanitizer = html.makeHtmlSanitizer( + function sanitizeAttributes(tagName, attribs) { + for (var i = 0; i < attribs.length; i += 2) { + var attribName = attribs[i]; + var value = attribs[i + 1]; + var atype = null, attribKey; + if ((attribKey = tagName + ':' + attribName, + html4.ATTRIBS.hasOwnProperty(attribKey)) + || (attribKey = '*:' + attribName, + html4.ATTRIBS.hasOwnProperty(attribKey))) { + atype = html4.ATTRIBS[attribKey]; + value = rewriteAttribute(tagName, attribName, atype, value); + } else { + value = null; + } + if (value !== null && value !== void 0) { + attribs[i + 1] = value; + } else { + attribs.splice(i, 2); + i -= 2; + } + } + var policy = elementPolicies[tagName]; + if (policy && elementPolicies.hasOwnProperty(tagName)) { + return policy(attribs); + } + return attribs; + }); + + /** + * Undoes some of the changes made by sanitizeHtml, e.g. stripping ID + * prefixes. + */ + function tameInnerHtml(htmlText) { + var out = []; + innerHtmlTamer(htmlText, out); + return out.join(''); + } + var innerHtmlTamer = html.makeSaxParser({ + startTag: function (tagName, attribs, out) { + out.push('<', tagName); + for (var i = 0; i < attribs.length; i += 2) { + var attribName = attribs[i]; + if (attribName === 'target') { continue; } + var attribKey; + var atype; + if ((attribKey = tagName + ':' + attribName, + html4.ATTRIBS.hasOwnProperty(attribKey)) + || (attribKey = '*:' + attribName, + html4.ATTRIBS.hasOwnProperty(attribKey))) { + atype = html4.ATTRIBS[attribKey]; + } else { + return; + } + var value = attribs[i + 1]; + switch (atype) { + case html4.atype.ID: + case html4.atype.IDREF: + case html4.atype.IDREFS: + if (value.length <= idSuffix.length + || (idSuffix + !== value.substring(value.length - idSuffix.length))) { + continue; + } + value = value.substring(0, value.length - idSuffix.length); + break; + } + if (value !== null) { + out.push(' ', attribName, '="', html.escapeAttrib(value), '"'); + } + } + out.push('>'); + }, + endTag: function (name, out) { out.push('</', name, '>'); }, + pcdata: function (text, out) { out.push(text); }, + rcdata: function (text, out) { out.push(text); }, + cdata: function (text, out) { out.push(text); } + }); + + var illegalSuffix = /__(?:\s|$)/; + /** + * Returns a normalized attribute value, or null if the attribute should + * be omitted. + * <p>This function satisfies the attribute rewriter interface defined in + * {@link html-sanitizer.js}. As such, the parameters are keys into + * data structures defined in {@link html4-defs.js}. + * + * @param {string} tagName a canonical tag name. + * @param {string} attribName a canonical tag name. + * @param type as defined in html4-defs.js. + * + * @return {string|null} null to indicate that the attribute should not + * be set. + */ + function rewriteAttribute(tagName, attribName, type, value) { + switch (type) { + case html4.atype.ID: + case html4.atype.IDREF: + case html4.atype.IDREFS: + value = String(value); + if (value && !illegalSuffix.test(value) && isXmlName(value)) { + return value + idSuffix; + } + return null; + case html4.atype.CLASSES: + case html4.atype.GLOBAL_NAME: + case html4.atype.LOCAL_NAME: + value = String(value); + if (value && !illegalSuffix.test(value) && isXmlNmTokens(value)) { + return value; + } + return null; + case html4.atype.SCRIPT: + value = String(value); + // Translate a handler that calls a simple function like + // return foo(this, event) + + // TODO(mikesamuel): integrate cajita compiler to allow arbitrary + // cajita in event handlers. + var match = value.match(SIMPLE_HANDLER_PATTERN); + if (!match) { return null; } + var doesReturn = match[1]; + var fnName = match[2]; + var pluginId = ___.getId(imports); + value = (doesReturn ? 'return ' : '') + 'plugin_dispatchEvent___(' + + 'this, event, ' + pluginId + ', "' + + fnName + '");'; + if (attribName === 'onsubmit') { + value = 'try { ' + value + ' } finally { return false; }'; + } + return value; + case html4.atype.URI: + value = String(value); + if (!uriCallback) { return null; } + // TODO(mikesamuel): determine mime type properly. + return uriCallback.rewrite( + value, mimeTypeForAttr(tagName, attribName)) || null; + case html4.atype.STYLE: + if ('function' !== typeof value) { + return sanitizeStyleAttrValue(String(value)); + } + var cssPropertiesAndValues = cssSealerUnsealerPair.unseal(value); + if (!cssPropertiesAndValues) { return null; } + + var css = []; + for (var i = 0; i < cssPropertiesAndValues.length; i += 2) { + var propName = cssPropertiesAndValues[i]; + var propValue = cssPropertiesAndValues[i + 1]; + // If the propertyName differs between DOM and CSS, there will + // be a semicolon between the two. + // E.g., 'background-color;backgroundColor' + // See CssTemplate.toPropertyValueList. + var semi = propName.indexOf(';'); + if (semi >= 0) { propName = propName.substring(0, semi); } + css.push(propName + ' : ' + propValue); + } + return css.join(' ; '); + case html4.atype.FRAME_TARGET: + // Frames are ambient, so disallow reference. + return null; + default: + return String(value); + } + } + + function makeCache() { + var cache = cajita.newTable(false); + cache.set(null, null); + cache.set(void 0, null); + return cache; + } + + var editableTameNodeCache = makeCache(); + var readOnlyTameNodeCache = makeCache(); + + /** + * returns a tame DOM node. + * @param {Node} node + * @param {boolean} editable + * @see <a href="http://www.w3.org/TR/DOM-Level-2-HTML/html.html" + * >DOM Level 2</a> + */ + function tameNode(node, editable) { + if (node === null || node === void 0) { return null; } + // TODO(mikesamuel): make sure it really is a DOM node + + var cache = editable ? editableTameNodeCache : readOnlyTameNodeCache; + var tamed = cache.get(node); + if (tamed !== void 0) { + return tamed; + } + + switch (node.nodeType) { + case 1: // Element + var tagName = node.tagName.toLowerCase(); + switch (tagName) { + case 'a': + tamed = new TameAElement(node, editable); + break; + case 'form': + tamed = new TameFormElement(node, editable); + break; + case 'select': + case 'button': + case 'option': + case 'textarea': + case 'input': + tamed = new TameInputElement(node, editable); + break; + case 'img': + tamed = new TameImageElement(node, editable); + break; + case 'script': + tamed = new TameScriptElement(node, editable); + break; + case 'td': + case 'tr': + case 'thead': + case 'tfoot': + case 'tbody': + case 'th': + tamed = new TameTableCompElement(node, editable); + break; + case 'table': + tamed = new TameTableElement(node, editable); + break; + default: + if (!html4.ELEMENTS.hasOwnProperty(tagName) + || (html4.ELEMENTS[tagName] & html4.eflags.UNSAFE)) { + // If an unrecognized or unsafe node, return a + // placeholder that doesn't prevent tree navigation, + // but that doesn't allow mutation or leak attribute + // information. + tamed = new TameOpaqueNode(node, editable); + } else { + tamed = new TameElement(node, editable, editable); + } + break; + } + break; + case 2: // Attr + tamed = new TameAttrNode(node, editable); + break; + case 3: // Text + tamed = new TameTextNode(node, editable); + break; + case 8: // Comment + tamed = new TameCommentNode(node, editable); + break; + default: + tamed = new TameOpaqueNode(node, editable); + break; + } + + if (node.nodeType === 1) { + cache.set(node, tamed); + } + return tamed; + } + + function tameRelatedNode(node, editable) { + if (node === null || node === void 0) { return null; } + // catch errors because node might be from a different domain + try { + for (var ancestor = node; ancestor; ancestor = ancestor.parentNode) { + // TODO(mikesamuel): replace with cursors so that subtrees are + // delegable. + // TODO: handle multiple classes. + if (idClass === ancestor.className) { + return tameNode(node, editable); + } + } + } catch (e) {} + return null; + } + + /** + * Returns a NodeList like object. + */ + function tameNodeList(nodeList, editable, opt_keyAttrib) { + var tamed = []; + var node; + + // Work around NamedNodeMap bugs in IE, Opera, and Safari as discussed + // at http://code.google.com/p/google-caja/issues/detail?id=935 + var limit = nodeList.length; + if (limit !== +limit) { limit = 1/0; } + for (var i = 0; i < limit && (node = nodeList[i]); ++i) { + node = tameNode(nodeList.item(i), editable); + tamed[i] = node; + // Make the node available via its name if doing so would not mask + // any properties of tamed. + var key = opt_keyAttrib && node.getAttribute(opt_keyAttrib); + // TODO(mikesamuel): if key in tamed, we have an ambiguous match. + // Include neither? This may happen with radio buttons in a form's + // elements list. + if (key && !(key.charAt(key.length - 1) === '_' || (key in tamed) + || key === String(key & 0x7fffffff))) { + tamed[key] = node; + } + } + node = nodeList = null; + + tamed.item = ___.frozenFunc(function (k) { + k &= 0x7fffffff; + if (k !== k) { throw new Error(); } + return tamed[k] || null; + }); + // TODO(mikesamuel): if opt_keyAttrib, could implement getNamedItem + return cajita.freeze(tamed); + } + + function tameGetElementsByTagName(rootNode, tagName, editable) { + tagName = String(tagName); + if (tagName !== '*') { + tagName = tagName.toLowerCase(); + if (!___.hasOwnProp(html4.ELEMENTS, tagName) + || html4.ELEMENTS[tagName] & html4.ELEMENTS.UNSAFE) { + // Allowing getElementsByTagName to work for opaque element types + // would leak information about those elements. + return new fakeNodeList([]); + } + } + return tameNodeList(rootNode.getElementsByTagName(tagName), editable); + } + + /** + * Implements http://www.whatwg.org/specs/web-apps/current-work/#dom-document-getelementsbyclassname + * using an existing implementation on browsers that have one. + */ + function tameGetElementsByClassName(rootNode, className, editable) { + className = String(className); + + // The quotes below are taken from the HTML5 draft referenced above. + + // "having obtained the classes by splitting a string on spaces" + // Instead of using split, we use match with the global modifier so that + // we don't have to remove leading and trailing spaces. + var classes = className.match(/[^\t\n\f\r ]+/g); + + // Filter out classnames in the restricted namespace. + for (var i = classes ? classes.length : 0; --i >= 0;) { + var classi = classes[i]; + if (illegalSuffix.test(classi) || !isXmlNmTokens(classi)) { + classes[i] = classes[classes.length - 1]; + --classes.length; + } + } + + if (!classes || classes.length === 0) { + // "If there are no tokens specified in the argument, then the method + // must return an empty NodeList" [instead of all elements] + // This means that + // htmlEl.ownerDocument.getElementsByClassName(htmlEl.className) + // will return an HtmlCollection containing htmlElement iff + // htmlEl.className contains a non-space character. + return fakeNodeList([]); + } + + // "unordered set of unique space-separated tokens representing classes" + if (typeof rootNode.getElementsByClassName === 'function') { + return tameNodeList( + rootNode.getElementsByClassName(classes.join(' ')), editable); + } else { + // Add spaces around each class so that we can use indexOf later to find + // a match. + // This use of indexOf is strictly incorrect since + // http://www.whatwg.org/specs/web-apps/current-work/#reflecting-content-attributes-in-dom-attributes + // does not normalize spaces in unordered sets of unique space-separated + // tokens. This is not a problem since HTML5 compliant implementations + // already have a getElementsByClassName implementation, and legacy + // implementations do normalize according to comments on issue 935. + + // We assume standards mode, so the HTML5 requirement that + // "If the document is in quirks mode, then the comparisons for the + // classes must be done in an ASCII case-insensitive manner," + // is not operative. + var nClasses = classes.length; + for (var i = nClasses; --i >= 0;) { + classes[i] = ' ' + classes[i] + ' '; + } + + // We comply with the requirement that the result is a list + // "containing all the elements in the document, in tree order," + // since the spec for getElementsByTagName has the same language. + var candidates = rootNode.getElementsByTagName('*'); + var matches = []; + var limit = candidates.length; + if (limit !== +limit) { limit = 1/0; } // See issue 935 + candidate_loop: + for (var j = 0, candidate, k = -1; + j < limit && (candidate = candidates[j]); + ++j) { + var candidateClass = ' ' + candidate.className + ' '; + for (var i = nClasses; --i >= 0;) { + if (-1 === candidateClass.indexOf(classes[i])) { + continue candidate_loop; + } + } + var tamed = tameNode(candidate, editable); + if (tamed) { + matches[++k] = tamed; + } + } + // "the method must return a live NodeList object" + return fakeNodeList(matches); + } + } + + function makeEventHandlerWrapper(thisNode, listener) { + if ('function' !== typeof listener + // Allow disfunctions + && !('object' === (typeof listener) && listener !== null + && ___.canCallPub(listener, 'call'))) { + throw new Error('Expected function not ' + typeof listener); + } + function wrapper(event) { + return plugin_dispatchEvent___( + thisNode, event, ___.getId(imports), listener); + } + return wrapper; + } + + var NOT_EDITABLE = "Node not editable."; + var INVALID_SUFFIX = "Property names may not end in '__'."; + var UNSAFE_TAGNAME = "Unsafe tag name."; + var UNKNOWN_TAGNAME = "Unknown tag name."; + + // Implementation of EventTarget::addEventListener + function tameAddEventListener(name, listener, useCapture) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + if (!this.wrappedListeners___) { this.wrappedListeners___ = []; } + useCapture = Boolean(useCapture); + var wrappedListener = makeEventHandlerWrapper(this.node___, listener); + wrappedListener = bridal.addEventListener( + this.node___, name, wrappedListener, useCapture); + wrappedListener.originalListener___ = listener; + this.wrappedListeners___.push(wrappedListener); + } + + // Implementation of EventTarget::removeEventListener + function tameRemoveEventListener(name, listener, useCapture) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + if (!this.wrappedListeners___) { return; } + var wrappedListener = null; + for (var i = this.wrappedListeners___.length; --i >= 0;) { + if (this.wrappedListeners___[i].originalListener___ === listener) { + wrappedListener = this.wrappedListeners___[i]; + arrayRemove(this.wrappedListeners___, i, i); + break; + } + } + if (!wrappedListener) { return; } + bridal.removeEventListener( + this.node___, name, wrappedListener, useCapture); + } + + // A map of tamed node classes, keyed by DOM Level 2 standard name, which + // will be exposed to the client. + var nodeClasses = {}; + + var tameNodeFields = [ + 'nodeType', 'nodeValue', 'nodeName', 'firstChild', + 'lastChild', 'nextSibling', 'previousSibling', 'parentNode', + 'ownerDocument', 'childNodes', 'attributes']; + + /** + * Base class for a Node wrapper. Do not create directly -- use the + * tameNode factory instead. + * @param {boolean} editable true if the node's value, attributes, children, + * or custom properties are mutable. + * @constructor + */ + function TameNode(editable) { + this.editable___ = editable; + ___.stamp(tameNodeTrademark, this, true); + classUtils.exportFields(this, tameNodeFields); + } + TameNode.prototype.getOwnerDocument = function () { + // TODO(mikesamuel): upward navigation breaks capability discipline. + if (!this.editable___ && tameDocument.editable___) { + throw new Error(NOT_EDITABLE); + } + return tameDocument; + }; + nodeClasses.Node = TameNode; + ___.ctor(TameNode, void 0, 'TameNode'); + // abstract TameNode.prototype.getNodeType + // abstract TameNode.prototype.getNodeName + // abstract TameNode.prototype.getNodeValue + // abstract TameNode.prototype.cloneNode + // abstract TameNode.prototype.appendChild + // abstract TameNode.prototype.insertBefore + // abstract TameNode.prototype.removeChild + // abstract TameNode.prototype.replaceChild + // abstract TameNode.prototype.getFirstChild + // abstract TameNode.prototype.getLastChild + // abstract TameNode.prototype.getNextSibling + // abstract TameNode.prototype.getPreviousSibling + // abstract TameNode.prototype.getParentNode + // abstract TameNode.prototype.getElementsByTagName + // abstract TameNode.prototype.getElementsByClassName + // abstract TameNode.prototype.getChildNodes + // abstract TameNode.prototype.getAttributes + var tameNodeMembers = [ + 'getNodeType', 'getNodeValue', 'getNodeName', 'cloneNode', + 'appendChild', 'insertBefore', 'removeChild', 'replaceChild', + 'getFirstChild', 'getLastChild', 'getNextSibling', 'getPreviousSibling', + 'getElementsByClassName', 'getElementsByTagName', + 'getOwnerDocument', + 'dispatchEvent', + 'hasChildNodes' + ]; + + + /** + * A tame node that is backed by a real node. + * @param {boolean} childrenEditable true iff the child list is mutable. + * @constructor + */ + function TameBackedNode(node, editable, childrenEditable) { + if (!node) { + throw new Error('Creating tame node with undefined native delegate'); + } + this.node___ = node; + this.childrenEditable___ = editable && childrenEditable; + TameNode.call(this, editable); + } + classUtils.extend(TameBackedNode, TameNode); + TameBackedNode.prototype.getNodeType = function () { + return this.node___.nodeType; + }; + TameBackedNode.prototype.getNodeName = function () { + return this.node___.nodeName; + }; + TameBackedNode.prototype.getNodeValue = function () { + return this.node___.nodeValue; + }; + TameBackedNode.prototype.cloneNode = function (deep) { + var clone = bridal.cloneNode(this.node___, Boolean(deep)); + // From http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-3A0ED0A4 + // "Note that cloning an immutable subtree results in a mutable copy" + return tameNode(clone, true); + }; + TameBackedNode.prototype.appendChild = function (child) { + // Child must be editable since appendChild can remove it from its parent. + cajita.guard(tameNodeTrademark, child); + if (!this.childrenEditable___ || !child.editable___) { + throw new Error(NOT_EDITABLE); + } + this.node___.appendChild(child.node___); + }; + TameBackedNode.prototype.insertBefore = function (toInsert, child) { + cajita.guard(tameNodeTrademark, toInsert); + if (child === void 0) { child = null; } + if (child !== null) { cajita.guard(tameNodeTrademark, child); } + if (!this.childrenEditable___ || !toInsert.editable___) { + throw new Error(NOT_EDITABLE); + } + this.node___.insertBefore( + toInsert.node___, child !== null ? child.node___ : null); + }; + TameBackedNode.prototype.removeChild = function (child) { + cajita.guard(tameNodeTrademark, child); + if (!this.childrenEditable___ || !child.editable___) { + throw new Error(NOT_EDITABLE); + } + this.node___.removeChild(child.node___); + }; + TameBackedNode.prototype.replaceChild = function (child, replacement) { + cajita.guard(tameNodeTrademark, child); + cajita.guard(tameNodeTrademark, replacement); + if (!this.childrenEditable___ || !replacement.editable___) { + throw new Error(NOT_EDITABLE); + } + this.node___.replaceChild(child.node___, replacement.node___); + }; + TameBackedNode.prototype.getFirstChild = function () { + return tameNode(this.node___.firstChild, this.childrenEditable___); + }; + TameBackedNode.prototype.getLastChild = function () { + return tameNode(this.node___.lastChild, this.childrenEditable___); + }; + TameBackedNode.prototype.getNextSibling = function () { + // TODO(mikesamuel): replace with cursors so that subtrees are delegable + return tameNode(this.node___.nextSibling, this.editable___); + }; + TameBackedNode.prototype.getPreviousSibling = function () { + // TODO(mikesamuel): replace with cursors so that subtrees are delegable + return tameNode(this.node___.previousSibling, this.editable___); + }; + TameBackedNode.prototype.getParentNode = function () { + var parent = this.node___.parentNode; + if (parent === tameDocument.body___) { + if (tameDocument.editable___ && !this.editable___) { + // FIXME: return a non-editable version of body. + throw new Error(NOT_EDITABLE); + } + return tameDocument.getBody(); + } + return tameRelatedNode(this.node___.parentNode, this.editable___); + }; + TameBackedNode.prototype.getElementsByTagName = function (tagName) { + return tameGetElementsByTagName( + this.node___, tagName, this.childrenEditable___); + }; + TameBackedNode.prototype.getElementsByClassName = function (className) { + return tameGetElementsByClassName( + this.node___, className, this.childrenEditable___); + }; + TameBackedNode.prototype.getChildNodes = function () { + return tameNodeList(this.node___.childNodes, this.childrenEditable___); + }; + TameBackedNode.prototype.getAttributes = function () { + return tameNodeList(this.node___.attributes, this.editable___); + }; + var endsWith__ = /__$/; + // TODO(erights): Come up with some notion of a keeper chain so we can + // say, "let every other keeper try to handle this first". + TameBackedNode.prototype.handleRead___ = function (name) { + name = String(name); + if (endsWith__.test(name)) { return void 0; } + var handlerName = name + '_getter___'; + if (this[handlerName]) { + return this[handlerName](); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName](); + } + if (___.hasOwnProp(this.node___.properties___, name)) { + return this.node___.properties___[name]; + } else { + return void 0; + } + }; + TameBackedNode.prototype.handleCall___ = function (name, args) { + name = String(name); + if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); } + var handlerName = name + '_handler___'; + if (this[handlerName]) { + return this[handlerName].call(this, args); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName].call(this, args); + } + if (___.hasOwnProp(this.node___.properties___, name)) { + return this.node___.properties___[name].call(this, args); + } else { + throw new TypeError(name + ' is not a function.'); + } + }; + TameBackedNode.prototype.handleSet___ = function (name, val) { + name = String(name); + if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); } + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + var handlerName = name + '_setter___'; + if (this[handlerName]) { + return this[handlerName](val); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName](val); + } + if (!this.node___.properties___) { + this.node___.properties___ = {}; + } + this[name + '_canEnum___'] = true; + return this.node___.properties___[name] = val; + }; + TameBackedNode.prototype.handleDelete___ = function (name) { + name = String(name); + if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); } + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + var handlerName = name + '_deleter___'; + if (this[handlerName]) { + return this[handlerName](); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName](); + } + if (this.node___.properties___) { + return ( + delete this.node___.properties___[name] + && delete this[name + '_canEnum___']); + } else { + return true; + } + }; + /** + * @param {boolean} ownFlag ignored + */ + TameBackedNode.prototype.handleEnum___ = function (ownFlag) { + // TODO(metaweta): Add code to list all the other handled stuff we know + // about. + if (this.node___.properties___) { + return cajita.allKeys(this.node___.properties___); + } + return []; + }; + TameBackedNode.prototype.hasChildNodes = function () { + return !!this.node___.hasChildNodes(); + }; + // http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget : + // "The EventTarget interface is implemented by all Nodes" + TameBackedNode.prototype.dispatchEvent = function dispatchEvent(evt) { + cajita.guard(tameEventTrademark, evt); + bridal.dispatchEvent(this.node___, evt.event___); + }; + ___.ctor(TameBackedNode, TameNode, 'TameBackedNode'); + ___.all2(___.grantTypedGeneric, TameBackedNode.prototype, tameNodeMembers); + if (document.documentElement.contains) { // typeof is 'object' on IE + TameBackedNode.prototype.contains = function (other) { + cajita.guard(tameNodeTrademark, other); + var otherNode = other.node___; + return this.node___.contains(otherNode); + }; + } + if ('function' === + typeof document.documentElement.compareDocumentPosition) { + /** + * Speced in <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition">DOM-Level-3</a>. + */ + TameBackedNode.prototype.compareDocumentPosition = function (other) { + cajita.guard(tameNodeTrademark, other); + var otherNode = other.node___; + if (!otherNode) { return 0; } + var bitmask = +this.node___.compareDocumentPosition(otherNode); + // To avoid leaking information about the relative positioning of + // different roots, if neither contains the other, then we mask out + // the preceding/following bits. + // 0x18 is (CONTAINS | CONTAINED) + // 0x1f is all the bits documented at + // http://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition + // except IMPLEMENTATION_SPECIFIC + // 0x01 is DISCONNECTED + /* + if (!(bitmask & 0x18)) { + // TODO: If they are not under the same virtual doc root, return + // DOCUMENT_POSITION_DISCONNECTED instead of leaking information + // about PRECEDING | FOLLOWING. + } + */ + // Firefox3 returns spurious PRECEDING and FOLLOWING bits for + // disconnected trees. + // https://bugzilla.mozilla.org/show_bug.cgi?id=486002 + if (bitmask & 1) { + bitmask &= ~6; + } + return bitmask & 0x1f; + }; + if (!___.hasOwnProp(TameBackedNode.prototype, 'contains')) { + // http://www.quirksmode.org/blog/archives/2006/01/contains_for_mo.html + TameBackedNode.prototype.contains = function (other) { + var docPos = this.compareDocumentPosition(other); + return !(!(docPos & 0x10) && docPos); + }; + } + } + ___.all2(function (o, k) { + if (___.hasOwnProp(o, k)) { ___.grantTypedGeneric(o, k); } + }, TameBackedNode.prototype, + ['contains', 'compareDocumentPosition']); + + /** + * A fake node that is not backed by a real DOM node. + * @constructor + */ + function TamePseudoNode(editable) { + TameNode.call(this, editable); + this.properties___ = {}; + } + classUtils.extend(TamePseudoNode, TameNode); + TamePseudoNode.prototype.appendChild = + TamePseudoNode.prototype.insertBefore = + TamePseudoNode.prototype.removeChild = + TamePseudoNode.prototype.replaceChild = function (child) { + cajita.log("Node not editable; no action performed."); + }; + TamePseudoNode.prototype.getFirstChild = function () { + var children = this.getChildNodes(); + return children.length ? children[0] : null; + }; + TamePseudoNode.prototype.getLastChild = function () { + var children = this.getChildNodes(); + return children.length ? children[children.length - 1] : null; + }; + TamePseudoNode.prototype.getNextSibling = function () { + var parentNode = this.getParentNode(); + if (!parentNode) { return null; } + var siblings = parentNode.getChildNodes(); + for (var i = siblings.length - 1; --i >= 0;) { + if (siblings[i] === this) { return siblings[i + 1]; } + } + return null; + }; + TamePseudoNode.prototype.getPreviousSibling = function () { + var parentNode = this.getParentNode(); + if (!parentNode) { return null; } + var siblings = parentNode.getChildNodes(); + for (var i = siblings.length; --i >= 1;) { + if (siblings[i] === this) { return siblings[i - 1]; } + } + return null; + }; + TamePseudoNode.prototype.handleRead___ = function (name) { + name = String(name); + if (endsWith__.test(name)) { return void 0; } + var handlerName = name + '_getter___'; + if (this[handlerName]) { + return this[handlerName](); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName](); + } + if (___.hasOwnProp(this.properties___, name)) { + return this.properties___[name]; + } else { + return void 0; + } + }; + TamePseudoNode.prototype.handleCall___ = function (name, args) { + name = String(name); + if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); } + var handlerName = name + '_handler___'; + if (this[handlerName]) { + return this[handlerName].call(this, args); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName].call(this, args); + } + if (___.hasOwnProp(this.properties___, name)) { + return this.properties___[name].call(this, args); + } else { + throw new TypeError(name + ' is not a function.'); + } + }; + TamePseudoNode.prototype.handleSet___ = function (name, val) { + name = String(name); + if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); } + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + var handlerName = name + '_setter___'; + if (this[handlerName]) { + return this[handlerName](val); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName](val); + } + if (!this.properties___) { + this.properties___ = {}; + } + this[name + '_canEnum___'] = true; + return this.properties___[name] = val; + }; + TamePseudoNode.prototype.handleDelete___ = function (name) { + name = String(name); + if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); } + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + var handlerName = name + '_deleter___'; + if (this[handlerName]) { + return this[handlerName](); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName](); + } + if (this.properties___) { + return ( + delete this.properties___[name] + && delete this[name + '_canEnum___']); + } else { + return true; + } + }; + TamePseudoNode.prototype.handleEnum___ = function (ownFlag) { + // TODO(metaweta): Add code to list all the other handled stuff we know + // about. + if (this.properties___) { + return cajita.allKeys(this.properties___); + } + return []; + }; + TamePseudoNode.prototype.hasChildNodes = function () { + return this.getFirstChild() != null; + }; + ___.ctor(TamePseudoNode, TameNode, 'TamePseudoNode'); + ___.all2(___.grantTypedGeneric, TamePseudoNode.prototype, tameNodeMembers); + + + function TamePseudoElement( + tagName, tameDoc, childNodesGetter, parentNodeGetter, innerHTMLGetter, + geometryDelegate, editable) { + TamePseudoNode.call(this, editable); + this.tagName___ = tagName; + this.tameDoc___ = tameDoc; + this.childNodesGetter___ = childNodesGetter; + this.parentNodeGetter___ = parentNodeGetter; + this.innerHTMLGetter___ = innerHTMLGetter; + this.geometryDelegate___ = geometryDelegate; + classUtils.exportFields(this, ['tagName', 'innerHTML']); + } + classUtils.extend(TamePseudoElement, TamePseudoNode); + // TODO(mikesamuel): make nodeClasses work. + TamePseudoElement.prototype.getNodeType = function () { return 1; }; + TamePseudoElement.prototype.getNodeName + = function () { return this.tagName___; }; + TamePseudoElement.prototype.getTagName + = function () { return this.tagName___; }; + TamePseudoElement.prototype.getNodeValue = function () { return null; }; + TamePseudoElement.prototype.getAttribute + = function (attribName) { return null; }; + TamePseudoElement.prototype.setAttribute + = function (attribName, value) { }; + TamePseudoElement.prototype.hasAttribute + = function (attribName) { return false; }; + TamePseudoElement.prototype.removeAttribute + = function (attribName) { }; + TamePseudoElement.prototype.getOwnerDocument + = function () { return this.tameDoc___; }; + TamePseudoElement.prototype.getChildNodes + = function () { return this.childNodesGetter___(); }; + TamePseudoElement.prototype.getAttributes + = function () { return tameNodeList([], false); }; + TamePseudoElement.prototype.getParentNode + = function () { return this.parentNodeGetter___(); }; + TamePseudoElement.prototype.getInnerHTML + = function () { return this.innerHTMLGetter___(); }; + TamePseudoElement.prototype.getElementsByTagName = function (tagName) { + tagName = String(tagName).toLowerCase(); + if (tagName === this.tagName___) { + // Works since html, head, body, and title can't contain themselves. + return fakeNodeList([]); + } + return this.getOwnerDocument().getElementsByTagName(tagName); + }; + TamePseudoElement.prototype.getElementsByClassName = function (className) { + return this.getOwnerDocument().getElementsByClassName(className); + }; + TamePseudoElement.prototype.getBoundingClientRect = function () { + return this.geometryDelegate___.getBoundingClientRect(); + }; + TamePseudoElement.prototype.getGeometryDelegate___ = function () { + return this.geometryDelegate___; + }; + TamePseudoElement.prototype.toString = function () { + return '<' + this.tagName___ + '>'; + }; + ___.ctor(TamePseudoElement, TamePseudoNode, 'TamePseudoElement'); + ___.all2(___.grantTypedGeneric, TamePseudoElement.prototype, + ['getTagName', 'getAttribute', 'setAttribute', + 'hasAttribute', 'removeAttribute', + 'getBoundingClientRect', 'getElementsByTagName']); + + function TameOpaqueNode(node, editable) { + TameBackedNode.call(this, node, editable, editable); + } + classUtils.extend(TameOpaqueNode, TameBackedNode); + TameOpaqueNode.prototype.getNodeValue + = TameBackedNode.prototype.getNodeValue; + TameOpaqueNode.prototype.getNodeType + = TameBackedNode.prototype.getNodeType; + TameOpaqueNode.prototype.getNodeName + = TameBackedNode.prototype.getNodeName; + TameOpaqueNode.prototype.getNextSibling + = TameBackedNode.prototype.getNextSibling; + TameOpaqueNode.prototype.getPreviousSibling + = TameBackedNode.prototype.getPreviousSibling; + TameOpaqueNode.prototype.getFirstChild + = TameBackedNode.prototype.getFirstChild; + TameOpaqueNode.prototype.getLastChild + = TameBackedNode.prototype.getLastChild; + TameOpaqueNode.prototype.getParentNode + = TameBackedNode.prototype.getParentNode; + TameOpaqueNode.prototype.getChildNodes + = TameBackedNode.prototype.getChildNodes; + TameOpaqueNode.prototype.getAttributes + = function () { return tameNodeList([], false); }; + for (var i = tameNodeMembers.length; --i >= 0;) { + var k = tameNodeMembers[i]; + if (!TameOpaqueNode.prototype.hasOwnProperty(k)) { + TameOpaqueNode.prototype[k] = ___.frozenFunc(function () { + throw new Error('Node is opaque'); + }); + } + } + ___.all2(___.grantTypedGeneric, TameOpaqueNode.prototype, tameNodeMembers); + + function TameAttrNode(node, editable) { + assert(node.nodeType === 2); + TameBackedNode.call(this, node, editable, editable); + classUtils.exportFields( + this, ['name', 'nodeValue', 'value', 'specified']); + } + classUtils.extend(TameAttrNode, TameBackedNode); + nodeClasses.Attr = TameAttrNode; + TameAttrNode.prototype.setNodeValue = function (value) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.nodeValue = String(value || ''); + return value; + }; + TameAttrNode.prototype.getName = TameAttrNode.prototype.getNodeName; + TameAttrNode.prototype.getValue = TameAttrNode.prototype.getNodeValue; + TameAttrNode.prototype.setValue = TameAttrNode.prototype.setNodeValue; + TameAttrNode.prototype.getSpecified = function () { + return this.node___.specified; + }; + TameAttrNode.prototype.toString = function () { + return '#attr'; + }; + ___.ctor(TameAttrNode, TameBackedNode, 'TameAttrNode'); + + function TameTextNode(node, editable) { + assert(node.nodeType === 3); + + // The below should not be strictly necessary since childrenEditable for + // TameScriptElements is always false, but it protects against tameNode + // being called naively on a text node from container code. + var pn = node.parentNode; + if (editable && pn) { + if (1 === pn.nodeType + && (html4.ELEMENTS[pn.tagName.toLowerCase()] + & html4.eflags.UNSAFE)) { + // Do not allow mutation of text inside script elements. + // See the testScriptLoading testcase for examples of exploits. + editable = false; + } + } + + TameBackedNode.call(this, node, editable, editable); + classUtils.exportFields(this, ['nodeValue', 'data']); + } + classUtils.extend(TameTextNode, TameBackedNode); + nodeClasses.Text = TameTextNode; + TameTextNode.prototype.setNodeValue = function (value) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.nodeValue = String(value || ''); + return value; + }; + TameTextNode.prototype.getData = TameTextNode.prototype.getNodeValue; + TameTextNode.prototype.setData = TameTextNode.prototype.setNodeValue; + TameTextNode.prototype.toString = function () { + return '#text'; + }; + ___.ctor(TameTextNode, TameBackedNode, 'TameTextNode'); + ___.all2(___.grantTypedGeneric, TameTextNode.prototype, + ['setNodeValue', 'getData', 'setData']); + + function TameCommentNode(node, editable) { + assert(node.nodeType === 8); + TameBackedNode.call(this, node, editable, editable); + } + classUtils.extend(TameCommentNode, TameBackedNode); + nodeClasses.CommentNode = TameCommentNode; + TameCommentNode.prototype.toString = function () { + return '#comment'; + }; + ___.ctor(TameCommentNode, TameBackedNode, 'TameCommentNode'); + + function getAttributeType(tagName, attribName) { + var attribKey; + attribKey = tagName + ':' + attribName; + if (html4.ATTRIBS.hasOwnProperty(attribKey)) { + return html4.ATTRIBS[attribKey]; + } + attribKey = '*:' + attribName; + if (html4.ATTRIBS.hasOwnProperty(attribKey)) { + return html4.ATTRIBS[attribKey]; + } + return void 0; + } + + /** + * Plays the role of an Attr node for TameElement objects. + */ + function TameBackedAttributeNode(elem, name){ + TameNode.call(this, false); + classUtils.exportFields(this, + ['name', 'specified', 'value', 'ownerElement']); + this.name___ = name; + this.ownerElement___ = elem; + } + classUtils.extend(TameBackedAttributeNode, TameNode); + ___.ctor(TameBackedAttributeNode, TameNode, 'TameBackedAttributeNode'); + TameBackedAttributeNode.prototype.getNodeName = + TameBackedAttributeNode.prototype.getName = + function () { return this.name___; }; + TameBackedAttributeNode.prototype.getSpecified = + function () { return this.ownerElement___.hasAttribute(this.name___); }; + TameBackedAttributeNode.prototype.getNodeValue = + TameBackedAttributeNode.prototype.getValue = + function () { return this.ownerElement___.getAttribute(this.name___); }; + TameBackedAttributeNode.prototype.getOwnerElement = + function () { return this.ownerElement___; }; + TameBackedAttributeNode.prototype.getNodeType = function () { return 2; }; + TameBackedAttributeNode.prototype.cloneNode = function () { + return new TameBackedAttributeNode(this.ownerElement___, this.name___); + }; + TameBackedAttributeNode.prototype.appendChild = + TameBackedAttributeNode.prototype.insertBefore = + TameBackedAttributeNode.prototype.removeChild = + TameBackedAttributeNode.prototype.replaceChild = + TameBackedAttributeNode.prototype.getFirstChild = + TameBackedAttributeNode.prototype.getLastChild = + TameBackedAttributeNode.prototype.getNextSibling = + TameBackedAttributeNode.prototype.getPreviousSibling = + TameBackedAttributeNode.prototype.getParentNode = + TameBackedAttributeNode.prototype.getElementsByTagName = + TameBackedAttributeNode.prototype.getElementsByClassName = + TameBackedAttributeNode.prototype.getChildNodes = + TameBackedAttributeNode.prototype.getAttributes = function () { + throw new Error ("Not implemented."); + }; + + function TameElement(node, editable, childrenEditable) { + assert(node.nodeType === 1); + TameBackedNode.call(this, node, editable, childrenEditable); + classUtils.exportFields( + this, + ['className', 'id', 'innerHTML', 'tagName', 'style', + 'offsetParent', 'title', 'dir']); + } + classUtils.extend(TameElement, TameBackedNode); + nodeClasses.Element = nodeClasses.HTMLElement = TameElement; + TameElement.prototype.getId = function () { + return this.getAttribute('id') || ''; + }; + TameElement.prototype.setId = function (newId) { + return this.setAttribute('id', newId); + }; + TameElement.prototype.getAttribute = function (attribName) { + attribName = String(attribName).toLowerCase(); + var tagName = this.node___.tagName.toLowerCase(); + var atype = getAttributeType(tagName, attribName); + if (atype === void 0) { + // Unrecognized attribute; use virtual map + if (this.node___.attributes___) { + return this.node___.attributes___[attribName] || null; + } + return null; + } + var value = bridal.getAttribute(this.node___, attribName); + if ('string' !== typeof value) { return value; } + switch (atype) { + case html4.atype.ID: + case html4.atype.IDREF: + case html4.atype.IDREFS: + if (!value) { return null; } + var n = idSuffix.length; + var len = value.length; + var end = len - n; + if (end > 0 && idSuffix === value.substring(end, len)) { + return value.substring(0, end); + } + return null; + default: + if ('' === value) { + // IE creates attribute nodes for any attribute in the HTML schema + // so even when they are deleted, there will be a value, usually + // the empty string. + var attr = bridal.getAttributeNode(this.node___, attribName); + if (attr && !attr.specified) { return null; } + } + return value; + } + }; + TameElement.prototype.getAttributeNode = function (name) { + return new TameBackedAttributeNode(this, name); + }; + TameElement.prototype.hasAttribute = function (attribName) { + attribName = String(attribName).toLowerCase(); + var tagName = this.node___.tagName.toLowerCase(); + var atype = getAttributeType(tagName, attribName); + if (atype === void 0) { + // Unrecognized attribute; use virtual map + return !!( + this.node___.attributes___ && + ___.hasOwnProp(this.node___.attributes___, attribName)); + } else { + return bridal.hasAttribute(this.node___, attribName); + } + }; + TameElement.prototype.setAttribute = function (attribName, value) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + attribName = String(attribName).toLowerCase(); + var tagName = this.node___.tagName.toLowerCase(); + var atype = getAttributeType(tagName, attribName); + if (atype === void 0) { + // Unrecognized attribute; use virtual map + if (!this.node___.attributes___) { this.node___.attributes___ = {}; } + this.node___.attributes___[attribName] = String(value); + } else { + var sanitizedValue = rewriteAttribute( + tagName, attribName, atype, value); + if (sanitizedValue !== null) { + bridal.setAttribute(this.node___, attribName, sanitizedValue); + } + } + return value; + }; + TameElement.prototype.removeAttribute = function (attribName) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + attribName = String(attribName).toLowerCase(); + var tagName = this.node___.tagName.toLowerCase(); + var atype = getAttributeType(tagName, attribName); + if (atype === void 0) { + // Unrecognized attribute; use virtual map + if (this.node___.attributes___) { + delete this.node___.attributes___[attribName]; + } + } else { + this.node___.removeAttribute(attribName); + } + }; + TameElement.prototype.getBoundingClientRect = function () { + var elRect = bridal.getBoundingClientRect(this.node___); + var vbody = bridal.getBoundingClientRect(this.getOwnerDocument().body___); + var vbodyLeft = vbody.left, vbodyTop = vbody.top; + return ({ + top: elRect.top - vbodyTop, + left: elRect.left - vbodyLeft, + right: elRect.right - vbodyLeft, + bottom: elRect.bottom - vbodyTop + }); + }; + TameElement.prototype.getClassName = function () { + return this.getAttribute('class') || ''; + }; + TameElement.prototype.setClassName = function (classes) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + return this.setAttribute('class', String(classes)); + }; + TameElement.prototype.getTitle = function () { + return this.getAttribute('title') || ''; + }; + TameElement.prototype.setTitle = function (classes) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + return this.setAttribute('title', String(classes)); + }; + TameElement.prototype.getDir = function () { + return this.getAttribute('dir') || ''; + }; + TameElement.prototype.setDir = function (classes) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + return this.setAttribute('dir', String(classes)); + }; + TameElement.prototype.getTagName = TameBackedNode.prototype.getNodeName; + TameElement.prototype.getInnerHTML = function () { + var tagName = this.node___.tagName.toLowerCase(); + if (!html4.ELEMENTS.hasOwnProperty(tagName)) { + return ''; // unknown node + } + var flags = html4.ELEMENTS[tagName]; + var innerHtml = this.node___.innerHTML; + if (flags & html4.eflags.CDATA) { + innerHtml = html.escapeAttrib(innerHtml); + } else if (flags & html4.eflags.RCDATA) { + // Make sure we return PCDATA. + // For RCDATA we only need to escape & if they're not part of an entity. + innerHtml = html.normalizeRCData(innerHtml); + } else { + // If we blessed the resulting HTML, then this would round trip better + // but it would still not survive appending, and it would propagate + // event handlers where the setter of innerHTML does not expect it to. + innerHtml = tameInnerHtml(innerHtml); + } + return innerHtml; + }; + TameElement.prototype.setInnerHTML = function (htmlFragment) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + var tagName = this.node___.tagName.toLowerCase(); + if (!html4.ELEMENTS.hasOwnProperty(tagName)) { throw new Error(); } + var flags = html4.ELEMENTS[tagName]; + if (flags & html4.eflags.UNSAFE) { throw new Error(); } + var sanitizedHtml; + if (flags & html4.eflags.RCDATA) { + sanitizedHtml = html.normalizeRCData(String(htmlFragment || '')); + } else { + sanitizedHtml = (htmlFragment instanceof Html + ? safeHtml(htmlFragment) + : sanitizeHtml(String(htmlFragment || ''))); + } + this.node___.innerHTML = sanitizedHtml; + return htmlFragment; + }; + TameElement.prototype.setStyle = function (style) { + this.setAttribute('style', style); + return this.getStyle(); + }; + TameElement.prototype.getStyle = function () { + return new TameStyle(this.node___.style, this.editable___); + }; + TameElement.prototype.updateStyle = function (style) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + var cssPropertiesAndValues = cssSealerUnsealerPair.unseal(style); + if (!cssPropertiesAndValues) { throw new Error(); } + + var styleNode = this.node___.style; + for (var i = 0; i < cssPropertiesAndValues.length; i += 2) { + var propName = cssPropertiesAndValues[i]; + var propValue = cssPropertiesAndValues[i + 1]; + // If the propertyName differs between DOM and CSS, there will + // be a semicolon between the two. + // E.g., 'background-color;backgroundColor' + // See CssTemplate.toPropertyValueList. + var semi = propName.indexOf(';'); + if (semi >= 0) { propName = propName.substring(semi + 1); } + styleNode[propName] = propValue; + } + }; + + TameElement.prototype.getOffsetParent = function () { + return tameRelatedNode(this.node___.offsetParent, this.editable___); + }; + TameElement.prototype.getGeometryDelegate___ = function () { + return this.node___; + }; + TameElement.prototype.toString = function () { + return '<' + this.node___.tagName + '>'; + }; + TameElement.prototype.addEventListener = tameAddEventListener; + TameElement.prototype.removeEventListener = tameRemoveEventListener; + ___.ctor(TameElement, TameBackedNode, 'TameElement'); + ___.all2( + ___.grantTypedGeneric, TameElement.prototype, + ['addEventListener', 'removeEventListener', + 'getAttribute', 'setAttribute', + 'removeAttribute', 'hasAttribute', + 'getAttributeNode', + 'getBoundingClientRect', + 'getClassName', 'setClassName', 'getId', 'setId', + 'getInnerHTML', 'setInnerHTML', 'updateStyle', 'getStyle', 'setStyle', + 'getTagName']); + + cajita.forOwnKeys({ + clientWidth: { + get: function () { return this.getGeometryDelegate___().clientWidth; } + }, + clientHeight: { + get: function () { return this.getGeometryDelegate___().clientHeight; } + }, + offsetLeft: { + get: function () { return this.getGeometryDelegate___().offsetLeft; } + }, + offsetTop: { + get: function () { return this.getGeometryDelegate___().offsetTop; } + }, + offsetWidth: { + get: function () { return this.getGeometryDelegate___().offsetWidth; } + }, + offsetHeight: { + get: function () { return this.getGeometryDelegate___().offsetHeight; } + }, + scrollLeft: { + get: function () { return this.getGeometryDelegate___().scrollLeft; }, + set: function (x) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.getGeometryDelegate___().scrollLeft = +x; + return x; + } + }, + scrollTop: { + get: function () { return this.getGeometryDelegate___().scrollTop; }, + set: function (y) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.getGeometryDelegate___().scrollTop = +y; + return y; + } + }, + scrollWidth: { + get: function () { return this.getGeometryDelegate___().scrollWidth; } + }, + scrollHeight: { + get: function () { return this.getGeometryDelegate___().scrollHeight; } + } + }, ___.func(function (propertyName, def) { + var setter = def.set || propertyOnlyHasGetter; + ___.useGetHandler(TameElement.prototype, propertyName, def.get); + ___.useSetHandler(TameElement.prototype, propertyName, setter); + ___.useGetHandler(TamePseudoElement.prototype, propertyName, def.get); + ___.useSetHandler(TamePseudoElement.prototype, propertyName, setter); + })); + + // Register set handlers for onclick, onmouseover, etc. + (function () { + var attrNameRe = /:(.*)/; + for (var html4Attrib in html4.ATTRIBS) { + if (html4.atype.SCRIPT === html4.ATTRIBS[html4Attrib]) { + (function (attribName) { + ___.useSetHandler( + TameElement.prototype, + attribName, + function eventHandlerSetter(listener) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + if (!listener) { // Clear the current handler + this.node___[attribName] = null; + } else { + // This handler cannot be copied from one node to another + // which is why getters are not yet supported. + this.node___[attribName] = makeEventHandlerWrapper( + this.node___, listener); + } + return listener; + }); + })(html4Attrib.match(attrNameRe)[1]); + } + } + })(); + + function TameAElement(node, editable) { + TameElement.call(this, node, editable, editable); + classUtils.exportFields(this, ['href']); + } + classUtils.extend(TameAElement, TameElement); + nodeClasses.HTMLAnchorElement = TameAElement; + TameAElement.prototype.focus = function () { + this.node___.focus(); + }; + TameAElement.prototype.getHref = function () { + return this.node___.href; + }; + TameAElement.prototype.setHref = function (href) { + this.setAttribute('href', href); + return href; + }; + ___.ctor(TameAElement, TameElement, 'TameAElement'); + ___.all2(___.grantTypedGeneric, TameAElement.prototype, + ['getHref', 'setHref', 'focus']); + + // http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-40002357 + function TameFormElement(node, editable) { + TameElement.call(this, node, editable, editable); + this.length = node.length; + classUtils.exportFields( + this, + ['action', 'elements', 'enctype', 'method', 'target']); + } + classUtils.extend(TameFormElement, TameElement); + nodeClasses.HTMLFormElement = TameFormElement; + TameFormElement.prototype.submit = function () { + return this.node___.submit(); + }; + TameFormElement.prototype.reset = function () { + return this.node___.reset(); + }; + TameFormElement.prototype.getAction = function () { + return this.getAttribute('action') || ''; + }; + TameFormElement.prototype.setAction = function (newVal) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + return this.setAttribute('action', String(newVal)); + }; + TameFormElement.prototype.getElements = function () { + return tameNodeList(this.node___.elements, this.editable___, 'name'); + }; + TameFormElement.prototype.getEnctype = function () { + return this.getAttribute('enctype') || ''; + }; + TameFormElement.prototype.setEnctype = function (newVal) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + return this.setAttribute('enctype', String(newVal)); + }; + TameFormElement.prototype.getMethod = function () { + return this.getAttribute('method') || ''; + }; + TameFormElement.prototype.setMethod = function (newVal) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + return this.setAttribute('method', String(newVal)); + }; + TameFormElement.prototype.getTarget = function () { + return this.getAttribute('target') || ''; + }; + TameFormElement.prototype.setTarget = function (newVal) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + return this.setAttribute('target', String(newVal)); + }; + TameFormElement.prototype.reset = function () { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.reset(); + }; + TameFormElement.prototype.submit = function () { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.submit(); + }; + ___.ctor(TameFormElement, TameElement, 'TameFormElement'); + ___.all2(___.grantTypedGeneric, TameFormElement.prototype, + ['getElements', 'reset', 'submit']); + + + function TameInputElement(node, editable) { + TameElement.call(this, node, editable, editable); + classUtils.exportFields( + this, + ['form', 'value', 'defaultValue', + 'checked', 'disabled', 'readOnly', + 'options', 'selected', 'selectedIndex', + 'name', 'accessKey', 'tabIndex', 'text', + 'defaultChecked', 'defaultSelected', 'maxLength', + 'size', 'type', 'index', 'label', + 'multiple', 'cols', 'rows']); + } + classUtils.extend(TameInputElement, TameElement); + nodeClasses.HTMLInputElement = TameInputElement; + TameInputElement.prototype.getChecked = function () { + return this.node___.checked; + }; + TameInputElement.prototype.setChecked = function (checked) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + return (this.node___.checked = !!checked); + }; + TameInputElement.prototype.getValue = function () { + // For <option> elements, Firefox returns a value even when no value + // attribute is present, using the contained text, but IE does not. + var value = this.node___.value; + return value === null || value === void 0 ? null : String(value); + }; + TameInputElement.prototype.setValue = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.value = ( + newValue === null || newValue === void 0 ? '' : '' + newValue); + return newValue; + }; + TameInputElement.prototype.getDefaultValue = function () { + var value = this.node___.defaultValue; + return value === null || value === void 0 ? null : String(value); + }; + TameInputElement.prototype.setDefaultValue = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.defaultValue = ( + newValue === null || newValue === void 0 ? '' : '' + newValue); + return newValue; + }; + TameInputElement.prototype.focus = function () { + this.node___.focus(); + }; + TameInputElement.prototype.blur = function () { + this.node___.blur(); + }; + TameInputElement.prototype.select = function () { + this.node___.select(); + }; + TameInputElement.prototype.getForm = function () { + return tameRelatedNode(this.node___.form, this.editable___); + }; + TameInputElement.prototype.getDisabled = function () { + return this.node___.disabled; + }; + TameInputElement.prototype.setDisabled = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.disabled = newValue; + return newValue; + }; + TameInputElement.prototype.getReadOnly = function () { + return this.node___.readOnly; + }; + TameInputElement.prototype.setReadOnly = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.readOnly = newValue; + return newValue; + }; + TameInputElement.prototype.getOptions = function () { + return tameNodeList(this.node___.options, this.editable___, 'name'); + }; + TameInputElement.prototype.getDefaultSelected = function () { + return this.node___.defaultSelected; + }; + TameInputElement.prototype.setDefaultSelected = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.defaultSelected = !!newValue; + return newValue; + }; + TameInputElement.prototype.getSelected = function () { + return this.node___.selected; + }; + TameInputElement.prototype.setSelected = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.selected = newValue; + return newValue; + }; + TameInputElement.prototype.getSelectedIndex = function () { + return this.node___.selectedIndex; + }; + TameInputElement.prototype.setSelectedIndex = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.selectedIndex = (newValue | 0); + return newValue; + }; + TameInputElement.prototype.getName = function () { + return this.node___.name; + }; + TameInputElement.prototype.setName = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.name = newValue; + return newValue; + }; + TameInputElement.prototype.getAccessKey = function () { + return this.node___.accessKey; + }; + TameInputElement.prototype.setAccessKey = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.accessKey = newValue; + return newValue; + }; + TameInputElement.prototype.getTabIndex = function () { + return this.node___.tabIndex; + }; + TameInputElement.prototype.getText = function () { + return String(this.node___.text); + }; + TameInputElement.prototype.setTabIndex = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.tabIndex = newValue; + return newValue; + }; + TameInputElement.prototype.getDefaultChecked = function () { + return this.node___.defaultChecked; + }; + TameInputElement.prototype.setDefaultChecked = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.defaultChecked = newValue; + return newValue; + }; + TameInputElement.prototype.getMaxLength = function () { + return this.node___.maxLength; + }; + TameInputElement.prototype.setMaxLength = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.maxLength = newValue; + return newValue; + }; + TameInputElement.prototype.getSize = function () { + return this.node___.size; + }; + TameInputElement.prototype.setSize = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.size = newValue; + return newValue; + }; + TameInputElement.prototype.getType = function () { + return String(this.node___.type); + }; + TameInputElement.prototype.setType = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.type = newValue; + return newValue; + }; + TameInputElement.prototype.getIndex = function () { + return this.node___.index; + }; + TameInputElement.prototype.setIndex = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.index = newValue; + return newValue; + }; + TameInputElement.prototype.getLabel = function () { + return this.node___.label; + }; + TameInputElement.prototype.setLabel = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.label = newValue; + return newValue; + }; + TameInputElement.prototype.getMultiple = function () { + return this.node___.multiple; + }; + TameInputElement.prototype.setMultiple = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.multiple = newValue; + return newValue; + }; + TameInputElement.prototype.getCols = function () { + return this.node___.cols; + }; + TameInputElement.prototype.setCols = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.cols = newValue; + return newValue; + }; + TameInputElement.prototype.getRows = function () { + return this.node___.rows; + }; + TameInputElement.prototype.setRows = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.rows = newValue; + return newValue; + }; + ___.ctor(TameInputElement, TameElement, 'TameInputElement'); + ___.all2(___.grantTypedGeneric, TameInputElement.prototype, + ['getValue', 'setValue', 'focus', 'getForm', 'getType', 'select']); + + + function TameImageElement(node, editable) { + TameElement.call(this, node, editable, editable); + classUtils.exportFields(this, ['src', 'alt']); + } + classUtils.extend(TameImageElement, TameElement); + nodeClasses.HTMLImageElement = TameImageElement; + TameImageElement.prototype.getSrc = function () { + return this.node___.src; + }; + TameImageElement.prototype.setSrc = function (src) { + this.setAttribute('src', src); + return src; + }; + TameImageElement.prototype.getAlt = function () { + return this.node___.alt; + }; + TameImageElement.prototype.setAlt = function (alt) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.alt = String(alt); + return alt; + }; + ___.ctor(TameImageElement, TameElement, 'TameImageElement'); + ___.all2(___.grantTypedGeneric, TameImageElement.prototype, + ['getSrc', 'setSrc', 'getAlt', 'setAlt']); + + /** + * A script element wrapper that allows setting of a src that has been + * rewritten by a URL policy, but not modifying of textual content. + */ + function TameScriptElement(node, editable) { + // Make the child list immutable so that text content can't be added + // or removed. + TameElement.call(this, node, editable, false); + classUtils.exportFields(this, ['src']); + } + classUtils.extend(TameScriptElement, TameElement); + nodeClasses.HTMLScriptElement = TameScriptElement; + TameScriptElement.prototype.getSrc = function () { + return this.node___.src; + }; + TameScriptElement.prototype.setSrc = function (src) { + this.setAttribute('src', src); + return src; + }; + ___.ctor(TameScriptElement, TameElement, 'TameScriptElement'); + + + function TameTableCompElement(node, editable) { + TameElement.call(this, node, editable, editable); + classUtils.exportFields( + this, + ['colSpan','cells','rowSpan','rows','rowIndex','align', + 'vAlign','nowrap']); + } + classUtils.extend(TameTableCompElement, TameElement); + TameTableCompElement.prototype.getColSpan = function () { + return this.node___.colSpan; + }; + TameTableCompElement.prototype.setColSpan = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.colSpan = newValue; + return newValue; + }; + TameTableCompElement.prototype.getCells = function () { + return tameNodeList(this.node___.cells, this.editable___); + }; + TameTableCompElement.prototype.getRowSpan = function () { + return this.node___.rowSpan; + }; + TameTableCompElement.prototype.setRowSpan = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.rowSpan = newValue; + return newValue; + }; + TameTableCompElement.prototype.getRows = function () { + return tameNodeList(this.node___.rows, this.editable___); + }; + TameTableCompElement.prototype.getRowIndex = function () { + return this.node___.rowIndex; + }; + TameTableCompElement.prototype.getAlign = function () { + return this.node___.align; + }; + TameTableCompElement.prototype.setAlign = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.align = newValue; + return newValue; + }; + TameTableCompElement.prototype.getVAlign = function () { + return this.node___.vAlign; + }; + TameTableCompElement.prototype.setVAlign = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.vAlign = newValue; + return newValue; + }; + TameTableCompElement.prototype.getNowrap = function () { + return this.node___.nowrap; + }; + TameTableCompElement.prototype.setNowrap = function (newValue) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.nowrap = newValue; + return newValue; + }; + ___.ctor(TameTableCompElement, TameElement, 'TameTableCompElement'); + + + function TameTableElement(node, editable) { + TameTableCompElement.call(this, node, editable); + classUtils.exportFields(this, ['tBodies','tHead','tFoot']); + } + classUtils.extend(TameTableElement, TameTableCompElement); + nodeClasses.HTMLTableElement = TameTableElement; + TameTableElement.prototype.getTBodies = function () { + return tameNodeList(this.node___.tBodies, this.editable___); + }; + TameTableElement.prototype.getTHead = function () { + return tameNode(this.node___.tHead, this.editable___); + }; + TameTableElement.prototype.getTFoot = function () { + return tameNode(this.node___.tFoot, this.editable___); + }; + TameTableElement.prototype.createTHead = function () { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + return tameNode(this.node___.createTHead(), this.editable___); + }; + TameTableElement.prototype.deleteTHead = function () { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.deleteTHead(); + }; + TameTableElement.prototype.createTFoot = function () { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + return tameNode(this.node___.createTFoot(), this.editable___); + }; + TameTableElement.prototype.deleteTFoot = function () { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + this.node___.deleteTFoot(); + }; + ___.ctor(TameTableElement, TameTableCompElement, 'TameTableElement'); + ___.all2(___.grantTypedGeneric, TameTableElement.prototype, + ['createTHead', 'deleteTHead','createTFoot', 'deleteTFoot']); + + function tameEvent(event) { + if (event.tamed___) { return event.tamed___; } + return event.tamed___ = new TameEvent(event); + } + + function TameEvent(event) { + this.event___ = event; + ___.stamp(tameEventTrademark, this, true); + classUtils.exportFields( + this, + ['type', 'target', 'pageX', 'pageY', 'altKey', + 'ctrlKey', 'metaKey', 'shiftKey', 'button', + 'screenX', 'screenY', + 'currentTarget', 'relatedTarget', + 'fromElement', 'toElement', + 'srcElement', + 'clientX', 'clientY', 'keyCode', 'which']); + } + nodeClasses.Event = TameEvent; + TameEvent.prototype.getType = function () { + return bridal.untameEventType(String(this.event___.type)); + }; + TameEvent.prototype.getTarget = function () { + var event = this.event___; + return tameRelatedNode(event.target || event.srcElement, true); + }; + TameEvent.prototype.getSrcElement = function () { + return tameRelatedNode(this.event___.srcElement, true); + }; + TameEvent.prototype.getCurrentTarget = function () { + var e = this.event___; + return tameRelatedNode(e.currentTarget, true); + }; + TameEvent.prototype.getRelatedTarget = function () { + var e = this.event___; + var t = e.relatedTarget; + if (!t) { + if (e.type === 'mouseout') { + t = e.toElement; + } else if (e.type === 'mouseover') { + t = e.fromElement; + } + } + return tameRelatedNode(t, true); + }; + TameEvent.prototype.getFromElement = function () { + return tameRelatedNode(this.event___.fromElement, true); + }; + TameEvent.prototype.getToElement = function () { + return tameRelatedNode(this.event___.toElement, true); + }; + TameEvent.prototype.getPageX = function () { + return Number(this.event___.pageX); + }; + TameEvent.prototype.getPageY = function () { + return Number(this.event___.pageY); + }; + TameEvent.prototype.stopPropagation = function () { + // TODO(mikesamuel): make sure event doesn't propagate to dispatched + // events for this gadget only. + // But don't allow it to stop propagation to the container. + if (this.event___.stopPropagation) { + this.event___.stopPropagation(); + } else { + this.event___.cancelBubble = true; + } + }; + TameEvent.prototype.preventDefault = function () { + // TODO(mikesamuel): make sure event doesn't propagate to dispatched + // events for this gadget only. + // But don't allow it to stop propagation to the container. + if (this.event___.preventDefault) { + this.event___.preventDefault(); + } else { + this.event___.returnValue = false; + } + }; + TameEvent.prototype.getAltKey = function () { + return Boolean(this.event___.altKey); + }; + TameEvent.prototype.getCtrlKey = function () { + return Boolean(this.event___.ctrlKey); + }; + TameEvent.prototype.getMetaKey = function () { + return Boolean(this.event___.metaKey); + }; + TameEvent.prototype.getShiftKey = function () { + return Boolean(this.event___.shiftKey); + }; + TameEvent.prototype.getButton = function () { + var e = this.event___; + return e.button && Number(e.button); + }; + TameEvent.prototype.getClientX = function () { + return Number(this.event___.clientX); + }; + TameEvent.prototype.getClientY = function () { + return Number(this.event___.clientY); + }; + TameEvent.prototype.getScreenX = function () { + return Number(this.event___.screenX); + }; + TameEvent.prototype.getScreenY = function () { + return Number(this.event___.screenY); + }; + TameEvent.prototype.getWhich = function () { + var w = this.event___.which; + return w && Number(w); + }; + TameEvent.prototype.getKeyCode = function () { + var kc = this.event___.keyCode; + return kc && Number(kc); + }; + TameEvent.prototype.toString = function () { return '[Fake Event]'; }; + ___.ctor(TameEvent, void 0, 'TameEvent'); + ___.all2(___.grantTypedGeneric, TameEvent.prototype, + ['getType', 'getTarget', 'getPageX', 'getPageY', 'stopPropagation', + 'getAltKey', 'getCtrlKey', 'getMetaKey', 'getShiftKey', + 'getButton', 'getClientX', 'getClientY', + 'getScreenX', 'getScreenY', + 'getRelatedTarget', + 'getFromElement', 'getToElement', + 'getSrcElement', + 'preventDefault', + 'getKeyCode', 'getWhich']); + + function TameCustomHTMLEvent(event) { + TameEvent.call(this, event); + this.properties___ = {}; + } + classUtils.extend(TameCustomHTMLEvent, TameEvent); + TameCustomHTMLEvent.prototype.initEvent + = function (type, bubbles, cancelable) { + bridal.initEvent(this.event___, type, bubbles, cancelable); + }; + TameCustomHTMLEvent.prototype.handleRead___ = function (name) { + name = String(name); + if (endsWith__.test(name)) { return void 0; } + var handlerName = name + '_getter___'; + if (this[handlerName]) { + return this[handlerName](); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName](); + } + if (___.hasOwnProp(this.event___.properties___, name)) { + return this.event___.properties___[name]; + } else { + return void 0; + } + }; + TameCustomHTMLEvent.prototype.handleCall___ = function (name, args) { + name = String(name); + if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); } + var handlerName = name + '_handler___'; + if (this[handlerName]) { + return this[handlerName].call(this, args); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName].call(this, args); + } + if (___.hasOwnProp(this.event___.properties___, name)) { + return this.event___.properties___[name].call(this, args); + } else { + throw new TypeError(name + ' is not a function.'); + } + }; + TameCustomHTMLEvent.prototype.handleSet___ = function (name, val) { + name = String(name); + if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); } + var handlerName = name + '_setter___'; + if (this[handlerName]) { + return this[handlerName](val); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName](val); + } + if (!this.event___.properties___) { + this.event___.properties___ = {}; + } + this[name + '_canEnum___'] = true; + return this.event___.properties___[name] = val; + }; + TameCustomHTMLEvent.prototype.handleDelete___ = function (name) { + name = String(name); + if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); } + var handlerName = name + '_deleter___'; + if (this[handlerName]) { + return this[handlerName](); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName](); + } + if (this.event___.properties___) { + return ( + delete this.event___.properties___[name] + && delete this[name + '_canEnum___']); + } else { + return true; + } + }; + TameCustomHTMLEvent.prototype.handleEnum___ = function (ownFlag) { + // TODO(metaweta): Add code to list all the other handled stuff we know + // about. + if (this.event___.properties___) { + return cajita.allKeys(this.event___.properties___); + } + return []; + }; + TameCustomHTMLEvent.prototype.toString = function () { + return '[Fake CustomEvent]'; + }; + ___.grantTypedGeneric(TameCustomHTMLEvent.prototype, 'initEvent'); + ___.ctor(TameCustomHTMLEvent, TameEvent, 'TameCustomHTMLEvent'); + + /** + * Return a fake node list containing tamed nodes. + * @param {Array.<TameNode>} array of tamed nodes. + * @return an array that duck types to a node list. + */ + function fakeNodeList(array) { + array.item = ___.frozenFunc(function(i) { return array[i]; }); + return cajita.freeze(array); + } + + function TameHTMLDocument(doc, body, domain, editable) { + TamePseudoNode.call(this, editable); + this.doc___ = doc; + this.body___ = body; + this.domain___ = domain; + this.onLoadListeners___ = []; + var tameDoc = this; + + var tameBody = tameNode(body, editable); + this.tameBody___ = tameBody; + // TODO(mikesamuel): create a proper class for BODY, HEAD, and HTML along + // with all the other specialized node types. + var tameBodyElement = new TamePseudoElement( + 'BODY', + this, + function () { return tameNodeList(body.childNodes, editable); }, + function () { return tameHtmlElement; }, + function () { return tameInnerHtml(body.innerHTML); }, + tameBody, + editable); + cajita.forOwnKeys( + { appendChild: 0, removeChild: 0, insertBefore: 0, replaceChild: 0 }, + ___.frozenFunc(function (k) { + tameBodyElement[k] = tameBody[k].bind(tameBody); + ___.grantFunc(tameBodyElement, k); + })); + + var title = doc.createTextNode(body.getAttribute('title') || ''); + var tameTitleElement = new TamePseudoElement( + 'TITLE', + this, + function () { return [tameNode(title, false)]; }, + function () { return tameHeadElement; }, + function () { return html.escapeAttrib(title.nodeValue); }, + null, + editable); + var tameHeadElement = new TamePseudoElement( + 'HEAD', + this, + function () { return [tameTitleElement]; }, + function () { return tameHtmlElement; }, + function () { + return '<title>' + tameTitleElement.getInnerHTML() + '</title>'; + }, + null, + editable); + var tameHtmlElement = new TamePseudoElement( + 'HTML', + this, + function () { return [tameHeadElement, tameBodyElement]; }, + function () { return tameDoc; }, + function () { + return ('<head>' + tameHeadElement.getInnerHTML + '<\/head><body>' + + tameBodyElement.getInnerHTML() + '<\/body>'); + }, + tameBody, + editable); + if (body.contains) { // typeof is 'object' on IE + tameHtmlElement.contains = function (other) { + cajita.guard(tameNodeTrademark, other); + var otherNode = other.node___; + return body.contains(otherNode); + }; + ___.grantFunc(tameHtmlElement, 'contains'); + } + if ('function' === typeof body.compareDocumentPosition) { + /** + * Speced in <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition">DOM-Level-3</a>. + */ + tameHtmlElement.compareDocumentPosition = function (other) { + cajita.guard(tameNodeTrademark, other); + var otherNode = other.node___; + if (!otherNode) { return 0; } + var bitmask = +body.compareDocumentPosition(otherNode); + // To avoid leaking information about the relative positioning of + // different roots, if neither contains the other, then we mask out + // the preceding/following bits. + // 0x18 is (CONTAINS | CONTAINED). + // 0x1f is all the bits documented at + // http://www.w3.org/TR/DOM-Level-3-Core/core.html#DocumentPosition + // except IMPLEMENTATION_SPECIFIC. + // 0x01 is DISCONNECTED. + /* + if (!(bitmask & 0x18)) { + // TODO: If they are not under the same virtual doc root, return + // DOCUMENT_POSITION_DISCONNECTED instead of leaking information + // about PRECEEDED | FOLLOWING. + } + */ + return bitmask & 0x1f; + }; + if (!___.hasOwnProp(tameHtmlElement, 'contains')) { + // http://www.quirksmode.org/blog/archives/2006/01/contains_for_mo.html + tameHtmlElement.contains = (function (other) { + var docPos = this.compareDocumentPosition(other); + return !(!(docPos & 0x10) && docPos); + }).bind(tameHtmlElement); + ___.grantFunc(tameHtmlElement, 'contains'); + } + ___.grantFunc(tameHtmlElement, 'compareDocumentPosition'); + } + this.documentElement___ = tameHtmlElement; + classUtils.exportFields( + this, ['documentElement', 'body', 'title', 'domain']); + } + classUtils.extend(TameHTMLDocument, TamePseudoNode); + nodeClasses.HTMLDocument = TameHTMLDocument; + TameHTMLDocument.prototype.getNodeType = function () { return 9; }; + TameHTMLDocument.prototype.getNodeName + = function () { return '#document'; }; + TameHTMLDocument.prototype.getNodeValue = function () { return null; }; + TameHTMLDocument.prototype.getChildNodes + = function () { return [this.documentElement___]; }; + TameHTMLDocument.prototype.getAttributes = function () { return []; }; + TameHTMLDocument.prototype.getParentNode = function () { return null; }; + TameHTMLDocument.prototype.getElementsByTagName = function (tagName) { + tagName = String(tagName).toLowerCase(); + switch (tagName) { + case 'body': return fakeNodeList([ this.getBody() ]); + case 'head': return fakeNodeList([ this.getHead() ]); + case 'title': return fakeNodeList([ this.getTitle() ]); + case 'html': return fakeNodeList([ this.getDocumentElement() ]); + default: + return tameGetElementsByTagName( + this.body___, tagName, this.editable___); + } + }; + TameHTMLDocument.prototype.getDocumentElement = function () { + return this.documentElement___; + }; + TameHTMLDocument.prototype.getBody = function () { + return this.documentElement___.getLastChild(); + }; + TameHTMLDocument.prototype.getHead = function () { + return this.documentElement___.getFirstChild(); + }; + TameHTMLDocument.prototype.getTitle = function () { + return this.getHead().getFirstChild(); + }; + TameHTMLDocument.prototype.getDomain = function () { + return this.domain___; + }; + TameHTMLDocument.prototype.getElementsByClassName = function (className) { + return tameGetElementsByClassName( + this.body___, className, this.editable___); + }; + TameHTMLDocument.prototype.addEventListener = + function (name, listener, useCapture) { + return this.tameBody___.addEventListener(name, listener, useCapture); + }; + TameHTMLDocument.prototype.removeEventListener = + function (name, listener, useCapture) { + return this.tameBody___.removeEventListener( + name, listener, useCapture); + }; + TameHTMLDocument.prototype.createComment = function (text) { + return new TameCommentNode(this.doc___.createComment(" "), true); + }; + TameHTMLDocument.prototype.createDocumentFragment = function () { + return new TameBackedNode(this.doc___.createDocumentFragment(), + this.editable___); + }; + TameHTMLDocument.prototype.createElement = function (tagName) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + tagName = String(tagName).toLowerCase(); + if (!html4.ELEMENTS.hasOwnProperty(tagName)) { + throw new Error(UNKNOWN_TAGNAME + "[" + tagName + "]"); + } + var flags = html4.ELEMENTS[tagName]; + // Script exemption allows dynamic loading of proxied scripts. + if ((flags & html4.eflags.UNSAFE) && !(flags & html4.eflags.SCRIPT)) { + cajita.log(UNSAFE_TAGNAME + "[" + tagName + "]: no action performed"); + return null; + } + var newEl = this.doc___.createElement(tagName); + if (elementPolicies.hasOwnProperty(tagName)) { + var attribs = elementPolicies[tagName]([]); + if (attribs) { + for (var i = 0; i < attribs.length; i += 2) { + bridal.setAttribute(newEl, attribs[i], attribs[i + 1]); + } + } + } + return tameNode(newEl, true); + }; + TameHTMLDocument.prototype.createTextNode = function (text) { + if (!this.editable___) { throw new Error(NOT_EDITABLE); } + return tameNode(this.doc___.createTextNode( + text !== null && text !== void 0 ? '' + text : ''), true); + }; + TameHTMLDocument.prototype.getElementById = function (id) { + id += idSuffix; + var node = this.doc___.getElementById(id); + return tameNode(node, this.editable___); + }; + TameHTMLDocument.prototype.toString = function () { + return '[Fake Document]'; + }; + TameHTMLDocument.prototype.write = function (text) { + // TODO(mikesamuel): Needs implementation + cajita.log('Called document.write() with: ' + text); + }; + // http://www.w3.org/TR/DOM-Level-2-Events/events.html + // #Events-DocumentEvent-createEvent + TameHTMLDocument.prototype.createEvent = function (type) { + type = String(type); + if (type !== 'HTMLEvents') { + // See https://developer.mozilla.org/en/DOM/document.createEvent#Notes + // for a long list of event ypes. + // See http://www.w3.org/TR/DOM-Level-2-Events/events.html + // #Events-eventgroupings + // for the DOM2 list. + throw new Error('Unrecognized event type ' + type); + } + var document = this.doc___; + var rawEvent; + if (document.createEvent) { + rawEvent = document.createEvent(type); + } else { + rawEvent = document.createEventObject(); + rawEvent.eventType = 'ondataavailable'; + } + var tamedEvent = new TameCustomHTMLEvent(rawEvent); + rawEvent.tamed___ = tamedEvent; + return tamedEvent; + }; + TameHTMLDocument.prototype.getOwnerDocument = function () { + return null; + }; + // Called by the html-emitter when the virtual document has been loaded. + TameHTMLDocument.prototype.signalLoaded___ = function () { + var listeners = this.onLoadListeners___; + this.onLoadListeners___ = []; + for (var i = 0, n = listeners.length; i < n; ++i) { + (function (listener) { + var listenerFn = ___.asFunc(listener); + setTimeout(function () { listenerFn.call(cajita.USELESS); }, 0); + })(listeners[i]); + } + }; + + ___.ctor(TameHTMLDocument, TamePseudoNode, 'TameHTMLDocument'); + ___.all2(___.grantTypedGeneric, TameHTMLDocument.prototype, + ['addEventListener', 'removeEventListener', + 'createComment', 'createDocumentFragment', + 'createElement', 'createEvent', 'createTextNode', + 'getElementById', 'getElementsByClassName', + 'getElementsByTagName', + 'write']); + + + imports.tameNode___ = tameNode; + imports.tameEvent___ = tameEvent; + imports.blessHtml___ = blessHtml; + imports.blessCss___ = function (var_args) { + var arr = []; + for (var i = 0, n = arguments.length; i < n; ++i) { + arr[i] = arguments[i]; + } + return cssSealerUnsealerPair.seal(arr); + }; + imports.htmlAttr___ = function (s) { + return html.escapeAttrib(String(s || '')); + }; + imports.html___ = safeHtml; + imports.rewriteUri___ = function (uri, mimeType) { + var s = rewriteAttribute(null, null, html4.atype.URI, uri); + if (!s) { throw new Error(); } + return s; + }; + imports.suffix___ = function (nmtokens) { + var p = String(nmtokens).replace(/^\s+|\s+$/g, '').split(/\s+/g); + var out = []; + for (var i = 0; i < p.length; ++i) { + var nmtoken = rewriteAttribute(null, null, html4.atype.ID, p[i]); + if (!nmtoken) { throw new Error(nmtokens); } + out.push(nmtoken); + } + return out.join(' '); + }; + imports.ident___ = function (nmtokens) { + var p = String(nmtokens).replace(/^\s+|\s+$/g, '').split(/\s+/g); + var out = []; + for (var i = 0; i < p.length; ++i) { + var nmtoken = rewriteAttribute(null, null, html4.atype.CLASSES, p[i]); + if (!nmtoken) { throw new Error(nmtokens); } + out.push(nmtoken); + } + return out.join(' '); + }; + // Maps a lower-cased style property name, e.g. background-image, + // to a style object property name, e.g. backgroundImage, so that + // it can be used as a style object property name as in + // myHtmlElement.style['backgroundImage']. + var canonicalStylePropertyNames = {}; + // Maps style property names, e.g. cssFloat, to property names, e.g. float. + var cssPropertyNames = []; + + cajita.forOwnKeys(css.properties, + ___.frozenFunc(function (cssPropertyName, pattern) { + var baseStylePropertyName = cssPropertyName.replace( + /-([a-z])/g, function (_, letter) { return letter.toUpperCase(); }); + var canonStylePropertyName = baseStylePropertyName; + cssPropertyNames[baseStylePropertyName] + = cssPropertyNames[canonStylePropertyName] + = cssPropertyName; + if (css.alternates.hasOwnProperty(canonStylePropertyName)) { + var alts = css.alternates[canonStylePropertyName]; + for (var i = alts.length; --i >= 0;) { + cssPropertyNames[alts[i]] = cssPropertyName; + // Handle oddities like cssFloat/styleFloat. + if (alts[i] in document.documentElement.style + && !(canonStylePropertyName in document.documentElement.style)) { + canonStylePropertyName = alts[i]; + } + } + } + canonicalStylePropertyNames[cssPropertyName] = canonStylePropertyName; + })); + + function TameStyle(style, editable) { + this.style___ = style; + this.editable___ = editable; + ___.grantCall(this, 'getPropertyValue'); + } + nodeClasses.Style = TameStyle; + TameStyle.prototype.allowProperty___ = function (cssPropertyName) { + return css.properties.hasOwnProperty(cssPropertyName); + }; + TameStyle.prototype.handleRead___ = function (stylePropertyName) { + if (!this.style___ + || !cssPropertyNames.hasOwnProperty(stylePropertyName)) { + return void 0; + } + var cssPropertyName = cssPropertyNames[stylePropertyName]; + if (!this.allowProperty___(cssPropertyName)) { return void 0; } + var canonName = canonicalStylePropertyNames[cssPropertyName]; + return String(this.style___[canonName] || ''); + }; + TameStyle.prototype.getPropertyValue = function (cssPropertyName) { + cssPropertyName = String(cssPropertyName || '').toLowerCase(); + if (!this.allowProperty___(cssPropertyName)) { return ''; } + var canonName = canonicalStylePropertyNames[cssPropertyName]; + return String(this.style___[canonName] || ''); + }; + TameStyle.prototype.handleSet___ = function (stylePropertyName, value) { + if (!this.editable___) { throw new Error('style not editable'); } + if (!cssPropertyNames.hasOwnProperty(stylePropertyName)) { + throw new Error('Unknown CSS property name ' + stylePropertyName); + } + var cssPropertyName = cssPropertyNames[stylePropertyName]; + if (!this.allowProperty___(cssPropertyName)) { return void 0; } + var pattern = css.properties[cssPropertyName]; + if (!pattern) { throw new Error('style not editable'); } + var val = '' + (value || ''); + // CssPropertyPatterns.java only allows styles of the form + // url("..."). See the BUILTINS definition for the "uri" symbol. + val = val.replace( + /\burl\s*\(\s*\"([^\"]*)\"\s*\)/gi, + function (_, url) { + var decodedUrl = decodeCssString(url); + var rewrittenUrl = uriCallback + ? uriCallback.rewrite(decodedUrl, 'image/*') + : null; + if (!rewrittenUrl) { + rewrittenUrl = 'about:blank'; + } + return 'url("' + + rewrittenUrl.replace( + /[\"\'\{\}\(\):\\]/g, + function (ch) { + return '\\' + ch.charCodeAt(0).toString(16) + ' '; + }) + + '")'; + }); + if (val && !pattern.test(val + ' ')) { + throw new Error('bad value `' + val + '` for CSS property ' + + stylePropertyName); + } + var canonName = canonicalStylePropertyNames[cssPropertyName]; + this.style___[canonName] = val; + return value; + }; + TameStyle.prototype.toString = function () { return '[Fake Style]'; }; + + function TameComputedStyle(style) { + TameStyle.call(this, style, false); + } + classUtils.extend(TameComputedStyle, TameStyle); + TameComputedStyle.prototype.allowProperty___ = function (cssPropertyName) { + return css.COMPUTED_STYLE_WHITELIST.hasOwnProperty(cssPropertyName); + }; + TameComputedStyle.prototype.toString = function () { + return '[Fake Computed Style]'; + }; + + nodeClasses.XMLHttpRequest = domitaModules.TameXMLHttpRequest( + domitaModules.XMLHttpRequestCtor( + window.XMLHttpRequest, + window.ActiveXObject), + uriCallback); + + /** + * given a number, outputs the equivalent css text. + * @param {number} num + * @return {string} an CSS representation of a number suitable for both html + * attribs and plain text. + */ + imports.cssNumber___ = function (num) { + if ('number' === typeof num && isFinite(num) && !isNaN(num)) { + return '' + num; + } + throw new Error(num); + }; + /** + * given a number as 24 bits of RRGGBB, outputs a properly formatted CSS + * color. + * @param {number} num + * @return {string} a CSS representation of num suitable for both html + * attribs and plain text. + */ + imports.cssColor___ = function (color) { + // TODO: maybe whitelist the color names defined for CSS if the arg is a + // string. + if ('number' !== typeof color || (color != (color | 0))) { + throw new Error(color); + } + var hex = '0123456789abcdef'; + return '#' + hex.charAt((color >> 20) & 0xf) + + hex.charAt((color >> 16) & 0xf) + + hex.charAt((color >> 12) & 0xf) + + hex.charAt((color >> 8) & 0xf) + + hex.charAt((color >> 4) & 0xf) + + hex.charAt(color & 0xf); + }; + imports.cssUri___ = function (uri, mimeType) { + var s = rewriteAttribute(null, null, html4.atype.URI, uri); + if (!s) { throw new Error(); } + return s; + }; + + /** + * Create a CSS stylesheet with the given text and append it to the DOM. + * @param {string} cssText a well-formed stylesheet production. + */ + imports.emitCss___ = function (cssText) { + this.getCssContainer___().appendChild( + bridal.createStylesheet(document, cssText)); + }; + /** The node to which gadget stylesheets should be added. */ + imports.getCssContainer___ = function () { + return document.getElementsByTagName('head')[0]; + }; + + if (!/^-/.test(idSuffix)) { + throw new Error('id suffix "' + idSuffix + '" must start with "-"'); + } + var idClass = idSuffix.substring(1); + /** A per-gadget class used to separate style rules. */ + imports.getIdClass___ = function () { + return idClass; + }; + + // TODO(mikesamuel): remove these, and only expose them via window once + // Valija works + imports.setTimeout = tameSetTimeout; + imports.setInterval = tameSetInterval; + imports.clearTimeout = tameClearTimeout; + imports.clearInterval = tameClearInterval; + + var tameDocument = new TameHTMLDocument( + document, + pseudoBodyNode, + String(optPseudoWindowLocation.hostname || 'nosuchhost.fake'), + true); + imports.document = tameDocument; + + // TODO(mikesamuel): figure out a mechanism by which the container can + // specify the gadget's apparent URL. + // See http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html#location0 + var tameLocation = ___.primFreeze({ + toString: ___.frozenFunc(function () { return tameLocation.href; }), + href: String(optPseudoWindowLocation.href || 'http://nosuchhost.fake/'), + hash: String(optPseudoWindowLocation.hash || ''), + host: String(optPseudoWindowLocation.host || 'nosuchhost.fake'), + hostname: String(optPseudoWindowLocation.hostname || 'nosuchhost.fake'), + pathname: String(optPseudoWindowLocation.pathname || '/'), + port: String(optPseudoWindowLocation.port || ''), + protocol: String(optPseudoWindowLocation.protocol || 'http:'), + search: String(optPseudoWindowLocation.search || '') + }); + + // See spec at http://www.whatwg.org/specs/web-apps/current-work/multipage/browsers.html#navigator + var tameNavigator = ___.primFreeze({ + appCodeName: 'Caja', + appName: 'Sandbox', + appVersion: '1.0', // Should we expose the user's Locale here? + language: '', // Should we expose the user's Locale here? + platform: 'Caja', + oscpu: 'Caja', + vendor: '', + vendorSub: '', + product: 'Caja', + productSub: '', + userAgent: 'Caja/1.0' + }); + + /** + * Set of allowed pseudo elements as described at + * http://www.w3.org/TR/CSS2/selector.html#q20 + */ + var PSEUDO_ELEMENT_WHITELIST = { + // after and before disallowed since they can leak information about + // arbitrary ancestor nodes. + 'first-letter': true, + 'first-line': true + }; + + /** + * See http://www.whatwg.org/specs/web-apps/current-work/multipage/browsers.html#window for the full API. + */ + function TameWindow() { + this.properties___ = {}; + } + + /** + * An <a href= + * href=http://www.w3.org/TR/DOM-Level-2-Views/views.html#Views-AbstractView + * >AbstractView</a> implementation that exposes styling, positioning, and + * sizing information about the current document's pseudo-body. + * <p> + * The AbstractView spec specifies very little in its IDL description, but + * mozilla defines it thusly:<blockquote> + * document.defaultView is generally a reference to the window object + * for the document, however that is not defined in the specification + * and can't be relied upon for all host environments, particularly as + * not all browsers implement it. + * </blockquote> + * <p> + * We can't provide access to the tamed window directly from document + * since it is the global scope of valija code, and so access to another + * module's tamed window provides an unbounded amount of authority. + * <p> + * Instead, we expose styling, positioning, and sizing properties + * via this class. All of this authority is already available from the + * document. + */ + function TameDefaultView() { + // TODO(mikesamuel): Implement in terms of + // http://www.w3.org/TR/cssom-view/#the-windowview-interface + // TODO: expose a read-only version of the document + this.document = tameDocument; + // Exposing an editable default view that pointed to a read-only + // tameDocument via document.defaultView would allow escalation of + // authority. + assert(tameDocument.editable___); + ___.grantRead(this, 'document'); + } + + cajita.forOwnKeys({ + document: tameDocument, + location: tameLocation, + navigator: tameNavigator, + setTimeout: tameSetTimeout, + setInterval: tameSetInterval, + clearTimeout: tameClearTimeout, + clearInterval: tameClearInterval, + addEventListener: ___.frozenFunc(function (name, listener, useCapture) { + if (name === 'load') { + ___.asFunc(listener); + tameDocument.onLoadListeners___.push(listener); + } + }), + removeEventListener: ___.frozenFunc(function (name, listener, useCapture) { + var listeners = tameDocument.onLoadListeners___; + var k = 0; + for (var i = 0, n = listeners.length; i < n; ++i) { + listeners[i - k] = listeners[i]; + if (listeners[i] === listener) { + ++k; + } + } + listeners.length -= k; + }), + dispathEvent: ___.frozenFunc(function (evt) { + // TODO(ihab.awad): Implement + }) + }, ___.func(function (propertyName, value) { + TameWindow.prototype[propertyName] = value; + ___.grantRead(TameWindow.prototype, propertyName); + })); + cajita.forOwnKeys({ + scrollBy: ___.frozenFunc( + function (dx, dy) { + // The window is always auto scrollable, so make the apparent window + // body scrollable if the gadget tries to scroll it. + if (dx || dy) { makeScrollable(tameDocument.body___); } + tameScrollBy(tameDocument.body___, dx, dy); + }), + scrollTo: ___.frozenFunc( + function (x, y) { + // The window is always auto scrollable, so make the apparent window + // body scrollable if the gadget tries to scroll it. + makeScrollable(tameDocument.body___); + tameScrollTo(tameDocument.body___, x, y); + }), + resizeTo: ___.frozenFunc( + function (w, h) { + tameResizeTo(tameDocument.body___, w, h); + }), + resizeBy: ___.frozenFunc( + function (dw, dh) { + tameResizeBy(tameDocument.body___, dw, dh); + }), + /** A partial implementation of getComputedStyle. */ + getComputedStyle: ___.frozenFunc( + // Pseudo elements are suffixes like :first-line which constrain to + // a portion of the element's content as defined at + // http://www.w3.org/TR/CSS2/selector.html#q20 + function (tameElement, pseudoElement) { + cajita.guard(tameNodeTrademark, tameElement); + // Coerce all nullish values to undefined, since that is the value + // for unspecified parameters. + // Per bug 973: pseudoElement should be null according to the + // spec, but mozilla docs contradict this. + // From https://developer.mozilla.org/En/DOM:window.getComputedStyle + // pseudoElt is a string specifying the pseudo-element to match. + // Should be an empty string for regular elements. + pseudoElement = (pseudoElement === null || pseudoElement === void 0 + || '' === pseudoElement) + ? void 0 : String(pseudoElement).toLowerCase(); + if (pseudoElement !== void 0 + && !PSEUDO_ELEMENT_WHITELIST.hasOwnProperty(pseudoElement)) { + throw new Error('Bad pseudo class ' + pseudoElement); + } + // No need to check editable since computed styles are readonly. + + var rawNode = tameElement.node___; + if (rawNode.currentStyle && pseudoElement === void 0) { + return new TameComputedStyle(rawNode.currentStyle); + } else if (window.getComputedStyle) { + var rawStyleNode = window.getComputedStyle( + tameElement.node___, pseudoElement); + return new TameComputedStyle(rawStyleNode); + } else { + throw new Error( + 'Computed style not available for pseudo element ' + + pseudoElement); + } + }) + + // NOT PROVIDED + // event: a global on IE. We always define it in scopes that can handle + // events. + // opera: defined only on Opera. + }, ___.func(function (propertyName, value) { + TameWindow.prototype[propertyName] = value; + ___.grantRead(TameWindow.prototype, propertyName); + TameDefaultView.prototype[propertyName] = value; + ___.grantRead(TameDefaultView.prototype, propertyName); + })); + TameWindow.prototype.handleRead___ = function (name) { + name = String(name); + if (endsWith__.test(name)) { return void 0; } + var handlerName = name + '_getter___'; + if (this[handlerName]) { + return this[handlerName](); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName](); + } + if (___.hasOwnProp(this, name)) { + return this[name]; + } else { + return void 0; + } + }; + TameWindow.prototype.handleSet___ = function (name, val) { + name = String(name); + if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); } + var handlerName = name + '_setter___'; + if (this[handlerName]) { + return this[handlerName](val); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName](val); + } + this[name + '_canEnum___'] = true; + this[name + '_canRead___'] = true; + return this[name] = val; + }; + TameWindow.prototype.handleDelete___ = function (name) { + name = String(name); + if (endsWith__.test(name)) { throw new Error(INVALID_SUFFIX); } + var handlerName = name + '_deleter___'; + if (this[handlerName]) { + return this[handlerName](); + } + handlerName = handlerName.toLowerCase(); + if (this[handlerName]) { + return this[handlerName](); + } + return ( + delete this[name] + && delete this[name + '_canEnum___'] + && delete this[name + '_canRead___']); + }; + TameWindow.prototype.handleEnum___ = function (ownFlag) { + // TODO(metaweta): Add code to list all the other handled stuff we know + // about. + return cajita.allKeys(this); + }; + + var tameWindow = new TameWindow(); + var tameDefaultView = new TameDefaultView(tameDocument.editable___); + + function propertyOnlyHasGetter(_) { + throw new TypeError('setting a property that only has a getter'); + } + cajita.forOwnKeys({ + // We define all the window positional properties relative to + // the fake body element to maintain the illusion that the fake + // document is completely defined by the nodes under the fake body. + clientLeft: { + get: function () { return tameDocument.body___.clientLeft; } + }, + clientTop: { + get: function () { return tameDocument.body___.clientTop; } + }, + clientHeight: { + get: function () { return tameDocument.body___.clientHeight; } + }, + clientWidth: { + get: function () { return tameDocument.body___.clientWidth; } + }, + offsetLeft: { + get: function () { return tameDocument.body___.offsetLeft; } + }, + offsetTop: { + get: function () { return tameDocument.body___.offsetTop; } + }, + offsetHeight: { + get: function () { return tameDocument.body___.offsetHeight; } + }, + offsetWidth: { + get: function () { return tameDocument.body___.offsetWidth; } + }, + // page{X,Y}Offset appear only as members of window, not on all elements + // but http://www.howtocreate.co.uk/tutorials/javascript/browserwindow + // says that they are identical to the scrollTop/Left on all browsers but + // old versions of Safari. + pageXOffset: { + get: function () { return tameDocument.body___.scrollLeft; } + }, + pageYOffset: { + get: function () { return tameDocument.body___.scrollTop; } + }, + scrollLeft: { + get: function () { return tameDocument.body___.scrollLeft; }, + set: function (x) { tameDocument.body___.scrollLeft = +x; return x; } + }, + scrollTop: { + get: function () { return tameDocument.body___.scrollTop; }, + set: function (y) { tameDocument.body___.scrollTop = +y; return y; } + }, + scrollHeight: { + get: function () { return tameDocument.body___.scrollHeight; } + }, + scrollWidth: { + get: function () { return tameDocument.body___.scrollWidth; } + } + }, ___.func(function (propertyName, def) { + var setter = def.set || propertyOnlyHasGetter; + // TODO(mikesamuel): define on prototype. + ___.useGetHandler(tameWindow, propertyName, def.get); + ___.useSetHandler(tameWindow, propertyName, setter); + ___.useGetHandler(tameDefaultView, propertyName, def.get); + ___.useSetHandler(tameDefaultView, propertyName, setter); + var tameBody = tameDocument.getBody(); + ___.useGetHandler(tameBody, propertyName, def.get); + ___.useSetHandler(tameBody, propertyName, setter); + var tameDocEl = tameDocument.getDocumentElement(); + ___.useGetHandler(tameDocEl, propertyName, def.get); + ___.useSetHandler(tameDocEl, propertyName, setter); + })); + + cajita.forOwnKeys({ + innerHeight: function () { return tameDocument.body___.clientHeight; }, + innerWidth: function () { return tameDocument.body___.clientWidth; }, + outerHeight: function () { return tameDocument.body___.clientHeight; }, + outerWidth: function () { return tameDocument.body___.clientWidth; } + }, ___.func(function (propertyName, handler) { + // TODO(mikesamuel): define on prototype. + ___.useGetHandler(tameWindow, propertyName, handler); + ___.useGetHandler(tameDefaultView, propertyName, handler); + })); + + // Attach reflexive properties to 'window' object + var windowProps = ['top', 'self', 'opener', 'parent', 'window']; + var wpLen = windowProps.length; + for (var i = 0; i < wpLen; ++i) { + var prop = windowProps[i]; + tameWindow[prop] = tameWindow; + ___.grantRead(tameWindow, prop); + } + + if (tameDocument.editable___) { + tameDocument.defaultView = tameDefaultView; + ___.grantRead(tameDocument, 'defaultView'); + } + + // Iterate over all node classes, assigning them to the Window object + // under their DOM Level 2 standard name. + cajita.forOwnKeys(nodeClasses, ___.func(function(name, ctor) { + ___.primFreeze(ctor); + tameWindow[name] = ctor; + ___.grantRead(tameWindow, name); + })); + + // TODO(ihab.awad): Build a more sophisticated virtual class hierarchy by + // creating a table of actual subclasses and instantiating tame nodes by + // table lookups. This will allow the client code to see a truly consistent + // DOM class hierarchy. + var defaultNodeClasses = [ + 'HTMLAppletElement', + 'HTMLAreaElement', + 'HTMLBaseElement', + 'HTMLBaseFontElement', + 'HTMLBodyElement', + 'HTMLBRElement', + 'HTMLButtonElement', + 'HTMLDirectoryElement', + 'HTMLDivElement', + 'HTMLDListElement', + 'HTMLFieldSetElement', + 'HTMLFontElement', + 'HTMLFrameElement', + 'HTMLFrameSetElement', + 'HTMLHeadElement', + 'HTMLHeadingElement', + 'HTMLHRElement', + 'HTMLHtmlElement', + 'HTMLIFrameElement', + 'HTMLIsIndexElement', + 'HTMLLabelElement', + 'HTMLLegendElement', + 'HTMLLIElement', + 'HTMLLinkElement', + 'HTMLMapElement', + 'HTMLMenuElement', + 'HTMLMetaElement', + 'HTMLModElement', + 'HTMLObjectElement', + 'HTMLOListElement', + 'HTMLOptGroupElement', + 'HTMLOptionElement', + 'HTMLParagraphElement', + 'HTMLParamElement', + 'HTMLPreElement', + 'HTMLQuoteElement', + 'HTMLScriptElement', + 'HTMLSelectElement', + 'HTMLStyleElement', + 'HTMLTableCaptionElement', + 'HTMLTableCellElement', + 'HTMLTableColElement', + 'HTMLTableElement', + 'HTMLTableRowElement', + 'HTMLTableSectionElement', + 'HTMLTextAreaElement', + 'HTMLTitleElement', + 'HTMLUListElement' + ]; + + var defaultNodeClassCtor = ___.primFreeze(TameElement); + for (var i = 0; i < defaultNodeClasses.length; i++) { + tameWindow[defaultNodeClasses[i]] = defaultNodeClassCtor; + ___.grantRead(tameWindow, defaultNodeClasses[i]); + } + + var outers = imports.outers; + if (___.isJSONContainer(outers)) { + // For Valija, attach use the window object as outers. + cajita.forOwnKeys(outers, ___.func(function(k, v) { + if (!(k in tameWindow)) { + tameWindow[k] = v; + ___.grantRead(tameWindow, k); + } + })); + imports.outers = tameWindow; + } else { + imports.window = tameWindow; + } + } + + return attachDocumentStub; +})(); + +/** + * Function called from rewritten event handlers to dispatch an event safely. + */ +function plugin_dispatchEvent___(thisNode, event, pluginId, handler) { + event = (event || window.event); + var sig = String(handler).match(/^function\b[^\)]*\)/); + cajita.log( + 'Dispatch ' + (event && event.type) + + 'event thisNode=' + thisNode + ', ' + + 'event=' + event + ', ' + + 'pluginId=' + pluginId + ', ' + + 'handler=' + (sig ? sig[0] : handler)); + var imports = ___.getImports(pluginId); + switch (typeof handler) { + case 'string': + handler = imports[handler]; + break; + case 'function': case 'object': + break; + default: + throw new Error( + 'Expected function as event handler, not ' + typeof handler); + } + if (___.startCallerStack) { ___.startCallerStack(); } + imports.isProcessingEvent___ = true; + try { + return ___.callPub( + handler, 'call', + [___.USELESS, + imports.tameEvent___(event), + imports.tameNode___(thisNode, true) + ]); + } catch (ex) { + if (ex && ex.cajitaStack___ && 'undefined' !== (typeof console)) { + console.error('Event dispatch %s: %s', + handler, ___.unsealCallerStack(ex.cajitaStack___).join('\n')); + } + throw ex; + } finally { + imports.isProcessingEvent___ = false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/ext/html-emitter.js Sun Jun 07 19:29:10 2009 -0700 @@ -0,0 +1,242 @@ +// Copyright (C) 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview + * JavaScript support for TemplateCompiler.java. + * <p> + * This handles the problem of making sure that only the bits of a Gadget's + * static HTML which should be visible to a script are visible, and provides + * mechanisms to reliably find elements using dynamically generated unique IDs + * in the face of DOM modifications by untrusted scripts. + * + * @author mikesamuel@gmail.com + */ +function HtmlEmitter(base, opt_tameDocument) { + if (!base) { throw new Error(); } + + /** + * Contiguous pairs of ex-descendants of base, and their ex-parent. + * The detached elements (even indices) are ordered depth-first. + */ + var detached = null; + /** Makes sure IDs are accessible within removed detached nodes. */ + var idMap = null; + + var arraySplice = Array.prototype.splice; + + function buildIdMap() { + idMap = {}; + var descs = base.getElementsByTagName('*'); + for (var i = 0, desc; (desc = descs[i]); ++i) { + if (desc.id) { idMap[desc.id] = desc; } + } + } + /** + * Returns the element with the given ID under the base node. + * @param id an auto-generated ID since we cannot rely on user supplied IDs + * to be unique. + * @return {Element|null} null if no such element exists. + */ + function byId(id) { + if (!idMap) { buildIdMap(); } + var node = idMap[id]; + if (node) { return node; } + for (; (node = base.ownerDocument.getElementById(id));) { + if (base.contains + ? base.contains(node) + : (base.compareDocumentPosition(node) & 0x10)) { + idMap[id] = node; + return node; + } else { + node.id = ''; + } + } + return null; + } + + // Below we define the attach, unwrap, and finish operations. + // These obey the conventions that: + // (1) All detached nodes, along with their ex-parents are in detached, + // and they are ordered depth-first. + // (2) When a node is specified by an ID, after the operation is performed, + // it is in the tree. + // (3) Each node is attached to the same parent regardless of what the + // script does. Even if a node is removed from the DOM by a script, + // any of its children that appear after the script, will be added. + // As an example, consider this HTML which has the end-tags removed since + // they don't correspond to actual nodes. + // <table> + // <script> + // <tr> + // <td>Foo<script>Bar + // <th>Baz + // <script> + // <p>The-End + // There are two script elements, and we need to make sure that each only + // sees the bits of the DOM that it is supposed to be aware of. + // + // To make sure that things work when javascript is off, we emit the whole + // HTML tree, and then detach everything that shouldn't be present. + // We represent the removed bits as pairs of (removedNode, parentItWasPartOf). + // Including both makes us robust against changes scripts make to the DOM. + // In this case, the detach operation results in the tree + // <table> + // and the detached list + // [<tr><td>FooBar<th>Baz in <table>, <p>The-End in (base)] + + // After the first script executes, we reattach the bits needed by the second + // script, which gives us the DOM + // <table><tr><td>Foo + // and the detached list + // ['Bar' in <td>, <th>Baz in <tr>, <p>The-End in (base)] + // Note that we did not simply remove items from the old detached list. Since + // the second script was deeper than the first, we had to add only a portion + // of the <tr>'s content which required doing a separate mini-detach operation + // and push its operation on to the front of the detached list. + + // After the second script executes, we reattach the bits needed by the third + // script, which gives us the DOM + // <table><tr><td>FooBar<th>Baz + // and the detached list + // [<p>The-End in (base)] + + // After the third script executes, we reattached the rest of the detached + // nodes, and we're done. + + // To perform a detach or reattach operation, we impose a depth-first ordering + // on HTML start tags, and text nodes: + // [0: <table>, 1: <tr>, 2: <td>, 3: 'Foo', 4: 'Bar', 5: <th>, 6: 'Baz', + // 7: <p>, 8: 'The-End'] + // Then the detach operation simply removes the minimal number of nodes from + // the DOM to make sure that only a prefix of those nodes are present. + // In the case above, we are detaching everything after item 0. + // Then the reattach operation advances the number. In the example above, we + // advance the index from 0 to 3, and then from 3 to 6. + // The finish operation simply reattaches the rest, advancing the counter from + // 6 to the end. + + // The minimal detached list from the node with DFS index I is the ordered + // list such that a (node, parent) pair (N, P) is on the list if + // dfs-index(N) > I and there is no pair (P, GP) on the list. + + // To calculate the minimal detached list given a node representing a point in + // that ordering, we rely on the following observations: + // The minimal detached list after a node, is the concatenation of + // (1) that node's children in order + // (2) the next sibling of that node and its later siblings, + // the next sibling of that node's parent and its later siblings, + // the next sibling of that node's grandparent and its later siblings, + // etc., until base is reached. + + function detachOnto(limit, out) { + // Set detached to be the minimal set of nodes that have to be removed + // to make sure that limit is the last attached node in DFS order as + // specified above. + + // First, store all the children. + for (var child = limit.firstChild, next; child; child = next) { + next = child.nextSibling; // removeChild kills nextSibling. + out.push(child, limit); + limit.removeChild(child); + } + + // Second, store your ancestor's next siblings and recurse. + for (var anc = limit, greatAnc; anc && anc !== base; anc = greatAnc) { + greatAnc = anc.parentNode; + for (var sibling = anc.nextSibling, next; sibling; sibling = next) { + next = sibling.nextSibling; + out.push(sibling, greatAnc); + greatAnc.removeChild(sibling); + } + } + } + /** + * Make sure that everything up to and including the node with the given ID + * is attached, and that nothing that follows the node is attached. + */ + function attach(id) { + var limit = byId(id); + if (detached) { + // Build an array of arguments to splice so we can replace the reattached + // nodes with the nodes detached from limit. + var newDetached = [0, 0]; + // Since limit has no parent, detachOnto will bottom out at its sibling. + detachOnto(limit, newDetached); + // Find the node containing limit that appears on detached. + for (var limitAnc = limit, parent; (parent = limitAnc.parentNode);) { + limitAnc = parent; + } + // Reattach up to and including limit ancestor. + var nConsumed = 0; + while (true) { + var toReattach = detached[nConsumed]; + (detached[nConsumed + 1] /* the parent */).appendChild(toReattach); + nConsumed += 2; + if (toReattach === limitAnc) { break; } + } + // Replace the reattached bits with the ones detached from limit. + newDetached[1] = nConsumed; // splice's second arg is the number removed + arraySplice.apply(detached, newDetached); + } else { + // The first time attach is called, the limit is actually part of the DOM. + // There's no point removing anything when all scripts are deferred. + detached = []; + detachOnto(limit, detached); + } + return limit; + } + /** + * Removes a wrapper from a textNode + * When a text node immediately precedes a script block, the limit will be + * a text node. Text nodes can't be addressed by ID, so the TemplateCompiler + * wraps them in a <span> which must be removed to be semantics preserving. + */ + function unwrap(wrapper) { + // Text nodes must have exactly one child, so it must be first on the + // detached list, since children are earlier than siblings by DFS order. + var text = detached[0]; + // If this is not true, the TemplateCompiler must be generating unwrap calls + // out of order. + // An untrusted script block should not be able to nuke the wrapper before + // it's removed so there should be a parentNode. + wrapper.parentNode.replaceChild(text, wrapper); + detached.splice(0, 2); + } + /** + * Reattach any remaining detached bits, free resources, and fire a document + * loaded event. + */ + function finish() { + if (detached) { + for (var i = 0, n = detached.length; i < n; i += 2) { + detached[i + 1].appendChild(detached[i]); + } + } + // Release references so nodes can be garbage collected. + idMap = detached = base = null; + + // Signals the close of the document and fires any window.onload event + // handlers. + var doc = opt_tameDocument; + if (doc) { doc.signalLoaded___(); } + return this; + } + + this.byId = byId; + this.attach = attach; + this.unwrap = unwrap; + this.finish = finish; + this.setAttr = bridal.setAttribute; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/ext/html-sanitizer.js Sun Jun 07 19:29:10 2009 -0700 @@ -0,0 +1,524 @@ +// Copyright (C) 2006 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview + * An HTML sanitizer that can satisfy a variety of security policies. + * + * <p> + * The HTML sanitizer is built around a SAX parser and HTML element and + * attributes schemas. + * + * @author mikesamuel@gmail.com + * @requires html4 + * @provides html, html_sanitize + */ + +/** + * @namespace + */ +var html = (function () { + var lcase; + // The below may not be true on browsers in the Turkish locale. + if ('script' === 'SCRIPT'.toLowerCase()) { + lcase = function (s) { return s.toLowerCase(); }; + } else { + /** + * {@updoc + * $ lcase('SCRIPT') + * # 'script' + * $ lcase('script') + * # 'script' + * } + */ + lcase = function (s) { + return s.replace( + /[A-Z]/g, + function (ch) { + return String.fromCharCode(ch.charCodeAt(0) | 32); + }); + }; + } + + var ENTITIES = { + lt : '<', + gt : '>', + amp : '&', + nbsp : '\240', + quot : '"', + apos : '\'' + }; + + var decimalEscapeRe = /^#(\d+)$/; + var hexEscapeRe = /^#x([0-9A-Fa-f]+)$/; + /** + * Decodes an HTML entity. + * + * {@updoc + * $ lookupEntity('lt') + * # '<' + * $ lookupEntity('GT') + * # '>' + * $ lookupEntity('amp') + * # '&' + * $ lookupEntity('nbsp') + * # '\xA0' + * $ lookupEntity('apos') + * # "'" + * $ lookupEntity('quot') + * # '"' + * $ lookupEntity('#xa') + * # '\n' + * $ lookupEntity('#10') + * # '\n' + * $ lookupEntity('#x0a') + * # '\n' + * $ lookupEntity('#010') + * # '\n' + * $ lookupEntity('#x00A') + * # '\n' + * $ lookupEntity('Pi') // Known failure + * # '\u03A0' + * $ lookupEntity('pi') // Known failure + * # '\u03C0' + * } + * + * @param name the content between the '&' and the ';'. + * @return a single unicode code-point as a string. + */ + function lookupEntity(name) { + name = lcase(name); // TODO: π is different from Π + if (ENTITIES.hasOwnProperty(name)) { return ENTITIES[name]; } + var m = name.match(decimalEscapeRe); + if (m) { + return String.fromCharCode(parseInt(m[1], 10)); + } else if (!!(m = name.match(hexEscapeRe))) { + return String.fromCharCode(parseInt(m[1], 16)); + } + return ''; + } + + function decodeOneEntity(_, name) { + return lookupEntity(name); + } + + var nulRe = /\0/g; + function stripNULs(s) { + return s.replace(nulRe, ''); + } + + var entityRe = /&(#\d+|#x[0-9A-Fa-f]+|\w+);/g; + /** + * The plain text of a chunk of HTML CDATA which possibly containing. + * + * {@updoc + * $ unescapeEntities('') + * # '' + * $ unescapeEntities('hello World!') + * # 'hello World!' + * $ unescapeEntities('1 < 2 && 4 > 3 ') + * # '1 < 2 && 4 > 3\n' + * $ unescapeEntities('<< <- unfinished entity>') + * # '<< <- unfinished entity>' + * $ unescapeEntities('/foo?bar=baz©=true') // & often unescaped in URLS + * # '/foo?bar=baz©=true' + * $ unescapeEntities('pi=ππ, Pi=Π\u03A0') // FIXME: known failure + * # 'pi=\u03C0\u03c0, Pi=\u03A0\u03A0' + * } + * + * @param s a chunk of HTML CDATA. It must not start or end inside an HTML + * entity. + */ + function unescapeEntities(s) { + return s.replace(entityRe, decodeOneEntity); + } + + var ampRe = /&/g; + var looseAmpRe = /&([^a-z#]|#(?:[^0-9x]|x(?:[^0-9a-f]|$)|$)|$)/gi; + var ltRe = /</g; + var gtRe = />/g; + var quotRe = /\"/g; + var eqRe = /\=/g; // Backslash required on JScript.net + + /** + * Escapes HTML special characters in attribute values as HTML entities. + * + * {@updoc + * $ escapeAttrib('') + * # '' + * $ escapeAttrib('"<<&==&>>"') // Do not just escape the first occurrence. + * # '"<<&==&>>"' + * $ escapeAttrib('Hello <World>!') + * # 'Hello <World>!' + * } + */ + function escapeAttrib(s) { + // Escaping '=' defangs many UTF-7 and SGML short-tag attacks. + return s.replace(ampRe, '&').replace(ltRe, '<').replace(gtRe, '>') + .replace(quotRe, '"').replace(eqRe, '='); + } + + /** + * Escape entities in RCDATA that can be escaped without changing the meaning. + * {@updoc + * $ normalizeRCData('1 < 2 && 3 > 4 && 5 < 7&8') + * # '1 < 2 && 3 > 4 && 5 < 7&8' + * } + */ + function normalizeRCData(rcdata) { + return rcdata + .replace(looseAmpRe, '&$1') + .replace(ltRe, '<') + .replace(gtRe, '>'); + } + + + // TODO(mikesamuel): validate sanitizer regexs against the HTML5 grammar at + // http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html + // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html + // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html + // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html + + /** token definitions. */ + var INSIDE_TAG_TOKEN = new RegExp( + // Don't capture space. + '^\\s*(?:' + // Capture an attribute name in group 1, and value in group 3. + // We capture the fact that there was an attribute in group 2, since + // interpreters are inconsistent in whether a group that matches nothing + // is null, undefined, or the empty string. + + ('(?:' + + '([a-z][a-z-]*)' // attribute name + + ('(' // optionally followed + + '\\s*=\\s*' + + ('(' + // A double quoted string. + + '\"[^\"]*\"' + // A single quoted string. + + '|\'[^\']*\'' + // The positive lookahead is used to make sure that in + // <foo bar= baz=boo>, the value for bar is blank, not "baz=boo". + + '|(?=[a-z][a-z-]*\\s*=)' + // An unquoted value that is not an attribute name. + // We know it is not an attribute name because the previous + // zero-width match would've eliminated that possibility. + + '|[^>\"\'\\s]*' + + ')' + ) + + ')' + ) + '?' + + ')' + ) + // End of tag captured in group 3. + + '|(/?>)' + // Don't capture cruft + + '|.[^\\w\\s>]*)', + 'i'); + + var OUTSIDE_TAG_TOKEN = new RegExp( + '^(?:' + // Entity captured in group 1. + + '&(\\#[0-9]+|\\#[x][0-9a-f]+|\\w+);' + // Comment, doctypes, and processing instructions not captured. + + '|<\!--[\\s\\S]*?--\>|<!\\w[^>]*>|<\\?[^>*]*>' + // '/' captured in group 2 for close tags, and name captured in group 3. + + '|<(/)?([a-z][a-z0-9]*)' + // Text captured in group 4. + + '|([^<&>]+)' + // Cruft captured in group 5. + + '|([<&>]))', + 'i'); + + /** + * Given a SAX-like event handler, produce a function that feeds those + * events and a parameter to the event handler. + * + * The event handler has the form:{@code + * { + * // Name is an upper-case HTML tag name. Attribs is an array of + * // alternating upper-case attribute names, and attribute values. The + * // attribs array is reused by the parser. Param is the value passed to + * // the saxParser. + * startTag: function (name, attribs, param) { ... }, + * endTag: function (name, param) { ... }, + * pcdata: function (text, param) { ... }, + * rcdata: function (text, param) { ... }, + * cdata: function (text, param) { ... }, + * startDoc: function (param) { ... }, + * endDoc: function (param) { ... } + * }} + * + * @param {Object} handler a record containing event handlers. + * @return {Function} that takes a chunk of html and a parameter. + * The parameter is passed on to the handler methods. + */ + function makeSaxParser(handler) { + return function parse(htmlText, param) { + htmlText = String(htmlText); + var htmlLower = null; + + var inTag = false; // True iff we're currently processing a tag. + var attribs = []; // Accumulates attribute names and values. + var tagName = void 0; // The name of the tag currently being processed. + var eflags = void 0; // The element flags for the current tag. + var openTag = void 0; // True if the current tag is an open tag. + + if (handler.startDoc) { handler.startDoc(param); } + + while (htmlText) { + var m = htmlText.match(inTag ? INSIDE_TAG_TOKEN : OUTSIDE_TAG_TOKEN); + htmlText = htmlText.substring(m[0].length); + + if (inTag) { + if (m[1]) { // attribute + // setAttribute with uppercase names doesn't work on IE6. + var attribName = lcase(m[1]); + var decodedValue; + if (m[2]) { + var encodedValue = m[3]; + switch (encodedValue.charCodeAt(0)) { // Strip quotes + case 34: case 39: + encodedValue = encodedValue.substring( + 1, encodedValue.length - 1); + break; + } + decodedValue = unescapeEntities(stripNULs(encodedValue)); + } else { + // Use name as value for valueless attribs, so + // <input type=checkbox checked> + // gets attributes ['type', 'checkbox', 'checked', 'checked'] + decodedValue = attribName; + } + attribs.push(attribName, decodedValue); + } else if (m[4]) { + if (eflags !== void 0) { // False if not in whitelist. + if (openTag) { + if (handler.startTag) { + handler.startTag(tagName, attribs, param); + } + } else { + if (handler.endTag) { + handler.endTag(tagName, param); + } + } + } + + if (openTag + && (eflags & (html4.eflags.CDATA | html4.eflags.RCDATA))) { + if (htmlLower === null) { + htmlLower = lcase(htmlText); + } else { + htmlLower = htmlLower.substring( + htmlLower.length - htmlText.length); + } + var dataEnd = htmlLower.indexOf('</' + tagName); + if (dataEnd < 0) { dataEnd = htmlText.length; } + if (eflags & html4.eflags.CDATA) { + if (handler.cdata) { + handler.cdata(htmlText.substring(0, dataEnd), param); + } + } else if (handler.rcdata) { + handler.rcdata( + normalizeRCData(htmlText.substring(0, dataEnd)), param); + } + htmlText = htmlText.substring(dataEnd); + } + + tagName = eflags = openTag = void 0; + attribs.length = 0; + inTag = false; + } + } else { + if (m[1]) { // Entity + if (handler.pcdata) { handler.pcdata(m[0], param); } + } else if (m[3]) { // Tag + openTag = !m[2]; + inTag = true; + tagName = lcase(m[3]); + eflags = html4.ELEMENTS.hasOwnProperty(tagName) + ? html4.ELEMENTS[tagName] : void 0; + } else if (m[4]) { // Text + if (handler.pcdata) { handler.pcdata(m[4], param); } + } else if (m[5]) { // Cruft + if (handler.pcdata) { + switch (m[5]) { + case '<': handler.pcdata('<', param); break; + case '>': handler.pcdata('>', param); break; + default: handler.pcdata('&', param); break; + } + } + } + } + } + + if (handler.endDoc) { handler.endDoc(param); } + }; + } + + return { + normalizeRCData: normalizeRCData, + escapeAttrib: escapeAttrib, + unescapeEntities: unescapeEntities, + makeSaxParser: makeSaxParser + }; +})(); + +/** + * Returns a function that strips unsafe tags and attributes from html. + * @param {Function} sanitizeAttributes + * maps from (tagName, attribs[]) to null or a sanitized attribute array. + * The attribs array can be arbitrarily modified, but the same array + * instance is reused, so should not be held. + * @return {Function} from html to sanitized html + */ +html.makeHtmlSanitizer = function (sanitizeAttributes) { + var stack = []; + var ignoring = false; + return html.makeSaxParser({ + startDoc: function (_) { + stack = []; + ignoring = false; + }, + startTag: function (tagName, attribs, out) { + if (ignoring) { return; } + if (!html4.ELEMENTS.hasOwnProperty(tagName)) { return; } + var eflags = html4.ELEMENTS[tagName]; + if (eflags & html4.eflags.FOLDABLE) { + return; + } else if (eflags & html4.eflags.UNSAFE) { + ignoring = !(eflags & html4.eflags.EMPTY); + return; + } + attribs = sanitizeAttributes(tagName, attribs); + // TODO(mikesamuel): relying on sanitizeAttributes not to + // insert unsafe attribute names. + if (attribs) { + if (!(eflags & html4.eflags.EMPTY)) { + stack.push(tagName); + } + + out.push('<', tagName); + for (var i = 0, n = attribs.length; i < n; i += 2) { + var attribName = attribs[i], + value = attribs[i + 1]; + if (value !== null && value !== void 0) { + out.push(' ', attribName, '="', html.escapeAttrib(value), '"'); + } + } + out.push('>'); + } + }, + endTag: function (tagName, out) { + if (ignoring) { + ignoring = false; + return; + } + if (!html4.ELEMENTS.hasOwnProperty(tagName)) { return; } + var eflags = html4.ELEMENTS[tagName]; + if (!(eflags & (html4.eflags.UNSAFE | html4.eflags.EMPTY + | html4.eflags.FOLDABLE))) { + var index; + if (eflags & html4.eflags.OPTIONAL_ENDTAG) { + for (index = stack.length; --index >= 0;) { + var stackEl = stack[index]; + if (stackEl === tagName) { break; } + if (!(html4.ELEMENTS[stackEl] & html4.eflags.OPTIONAL_ENDTAG)) { + // Don't pop non optional end tags looking for a match. + return; + } + } + } else { + for (index = stack.length; --index >= 0;) { + if (stack[index] === tagName) { break; } + } + } + if (index < 0) { return; } // Not opened. + for (var i = stack.length; --i > index;) { + var stackEl = stack[i]; + if (!(html4.ELEMENTS[stackEl] & html4.eflags.OPTIONAL_ENDTAG)) { + out.push('</', stackEl, '>'); + } + } + stack.length = index; + out.push('</', tagName, '>'); + } + }, + pcdata: function (text, out) { + if (!ignoring) { out.push(text); } + }, + rcdata: function (text, out) { + if (!ignoring) { out.push(text); } + }, + cdata: function (text, out) { + if (!ignoring) { out.push(text); } + }, + endDoc: function (out) { + for (var i = stack.length; --i >= 0;) { + out.push('</', stack[i], '>'); + } + stack.length = 0; + } + }); +}; + + +/** + * Strips unsafe tags and attributes from html. + * @param {string} htmlText to sanitize + * @param {Function} opt_urlPolicy -- a transform to apply to url attribute + * values. + * @param {Function} opt_nmTokenPolicy : string -> string? -- a transform to + * apply to names, ids, and classes. + * @return {string} html + */ +function html_sanitize(htmlText, opt_urlPolicy, opt_nmTokenPolicy) { + var out = []; + html.makeHtmlSanitizer( + function sanitizeAttribs(tagName, attribs) { + for (var i = 0; i < attribs.length; i += 2) { + var attribName = attribs[i]; + var value = attribs[i + 1]; + var atype = null, attribKey; + if ((attribKey = tagName + ':' + attribName, + html4.ATTRIBS.hasOwnProperty(attribKey)) + || (attribKey = '*:' + attribName, + html4.ATTRIBS.hasOwnProperty(attribKey))) { + atype = html4.ATTRIBS[attribKey]; + } + if (atype !== null) { + switch (atype) { + case html4.atype.SCRIPT: + case html4.atype.STYLE: + value = null; + break; + case html4.atype.IDREF: + case html4.atype.IDREFS: + case html4.atype.GLOBAL_NAME: + case html4.atype.LOCAL_NAME: + case html4.atype.CLASSES: + value = opt_nmTokenPolicy ? opt_nmTokenPolicy(value) : value; + break; + case html4.atype.URI: + value = opt_urlPolicy && opt_urlPolicy(value); + break; + } + } else { + value = null; + } + attribs[i + 1] = value; + } + return attribs; + })(htmlText, out); + return out.join(''); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/ext/jquery.js Sun Jun 07 19:29:10 2009 -0700 @@ -0,0 +1,4241 @@ +/*! + * jQuery JavaScript Library v1.3.1 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-01-21 20:42:16 -0500 (Wed, 21 Jan 2009) + * Revision: 6158 + */ +(function(){ + +var + // Will speed up references to window, and allows munging its name. + window = this, + // Will speed up references to undefined, and allows munging its name. + undefined, + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + // Map over the $ in case of overwrite + _$ = window.$, + + jQuery = window.jQuery = window.$ = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/, + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + // Make sure that a selection was provided + selector = selector || document; + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this[0] = selector; + this.length = 1; + this.context = selector; + return this; + } + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + var match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) + selector = jQuery.clean( [ match[1] ], context ); + + // HANDLE: $("#id") + else { + var elem = document.getElementById( match[3] ); + + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem && elem.id != match[3] ) + return jQuery().find( selector ); + + // Otherwise, we inject the element directly into the jQuery object + var ret = jQuery( elem || [] ); + ret.context = document; + ret.selector = selector; + return ret; + } + + // HANDLE: $(expr, [context]) + // (which is just equivalent to: $(content).find(expr) + } else + return jQuery( context ).find( selector ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) + return jQuery( document ).ready( selector ); + + // Make sure that old selector state is passed along + if ( selector.selector && selector.context ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return this.setArray(jQuery.makeArray(selector)); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.3.1", + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num === undefined ? + + // Return a 'clean' array + jQuery.makeArray( this ) : + + // Return just the object + this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery( elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) + ret.selector = this.selector + (this.selector ? " " : "") + selector; + else if ( name ) + ret.selector = this.selector + "." + name + "(" + selector + ")"; + + // Return the newly-formed element set + return ret; + }, + + // Force the current matched set of elements to become + // the specified array of elements (destroying the stack in the process) + // You should use pushStack() in order to do this, but maintain the stack + setArray: function( elems ) { + // Resetting the length to 0, then using the native Array push + // is a super-fast way to populate an object with array-like properties + this.length = 0; + Array.prototype.push.apply( this, elems ); + + return this; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem && elem.jquery ? elem[0] : elem + , this ); + }, + + attr: function( name, value, type ) { + var options = name; + + // Look for the case where we're accessing a style value + if ( typeof name === "string" ) + if ( value === undefined ) + return this[0] && jQuery[ type || "attr" ]( this[0], name ); + + else { + options = {}; + options[ name ] = value; + } + + // Check to see if we're setting style values + return this.each(function(i){ + // Set all the styles + for ( name in options ) + jQuery.attr( + type ? + this.style : + this, + name, jQuery.prop( this, options[ name ], type, i, name ) + ); + }); + }, + + css: function( key, value ) { + // ignore negative width and height values + if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) + value = undefined; + return this.attr( key, value, "curCSS" ); + }, + + text: function( text ) { + if ( typeof text !== "object" && text != null ) + return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); + + var ret = ""; + + jQuery.each( text || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + ret += this.nodeType != 1 ? + this.nodeValue : + jQuery.fn.text( [ this ] ); + }); + }); + + return ret; + }, + + wrapAll: function( html ) { + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).clone(); + + if ( this[0].parentNode ) + wrap.insertBefore( this[0] ); + + wrap.map(function(){ + var elem = this; + + while ( elem.firstChild ) + elem = elem.firstChild; + + return elem; + }).append(this); + } + + return this; + }, + + wrapInner: function( html ) { + return this.each(function(){ + jQuery( this ).contents().wrapAll( html ); + }); + }, + + wrap: function( html ) { + return this.each(function(){ + jQuery( this ).wrapAll( html ); + }); + }, + + append: function() { + return this.domManip(arguments, true, function(elem){ + if (this.nodeType == 1) + this.appendChild( elem ); + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function(elem){ + if (this.nodeType == 1) + this.insertBefore( elem, this.firstChild ); + }); + }, + + before: function() { + return this.domManip(arguments, false, function(elem){ + this.parentNode.insertBefore( elem, this ); + }); + }, + + after: function() { + return this.domManip(arguments, false, function(elem){ + this.parentNode.insertBefore( elem, this.nextSibling ); + }); + }, + + end: function() { + return this.prevObject || jQuery( [] ); + }, + + // For internal use only. + // Behaves like an Array's .push method, not like a jQuery method. + push: [].push, + + find: function( selector ) { + if ( this.length === 1 && !/,/.test(selector) ) { + var ret = this.pushStack( [], "find", selector ); + ret.length = 0; + jQuery.find( selector, this[0], ret ); + return ret; + } else { + var elems = jQuery.map(this, function(elem){ + return jQuery.find( selector, elem ); + }); + + return this.pushStack( /[^+>] [^+>]/.test( selector ) ? + jQuery.unique( elems ) : + elems, "find", selector ); + } + }, + + clone: function( events ) { + // Do the clone + var ret = this.map(function(){ + if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when + // using cloneNode. Calling detachEvent on the + // clone will also remove the events from the orignal + // In order to get around this, we use innerHTML. + // Unfortunately, this means some modifications to + // attributes in IE that are actually only stored + // as properties will not be copied (such as the + // the name attribute on an input). + var clone = this.cloneNode(true), + container = document.createElement("div"); + container.appendChild(clone); + return jQuery.clean([container.innerHTML])[0]; + } else + return this.cloneNode(true); + }); + + // Need to set the expando to null on the cloned set if it exists + // removeData doesn't work here, IE removes it from the original as well + // this is primarily for IE but the data expando shouldn't be copied over in any browser + var clone = ret.find("*").andSelf().each(function(){ + if ( this[ expando ] !== undefined ) + this[ expando ] = null; + }); + + // Copy the events from the original to the clone + if ( events === true ) + this.find("*").andSelf().each(function(i){ + if (this.nodeType == 3) + return; + var events = jQuery.data( this, "events" ); + + for ( var type in events ) + for ( var handler in events[ type ] ) + jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data ); + }); + + // Return the cloned set + return ret; + }, + + filter: function( selector ) { + return this.pushStack( + jQuery.isFunction( selector ) && + jQuery.grep(this, function(elem, i){ + return selector.call( elem, i ); + }) || + + jQuery.multiFilter( selector, jQuery.grep(this, function(elem){ + return elem.nodeType === 1; + }) ), "filter", selector ); + }, + + closest: function( selector ) { + var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null; + + return this.map(function(){ + var cur = this; + while ( cur && cur.ownerDocument ) { + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) + return cur; + cur = cur.parentNode; + } + }); + }, + + not: function( selector ) { + if ( typeof selector === "string" ) + // test special case where just one selector is passed in + if ( isSimple.test( selector ) ) + return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector ); + else + selector = jQuery.multiFilter( selector, this ); + + var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; + return this.filter(function() { + return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; + }); + }, + + add: function( selector ) { + return this.pushStack( jQuery.unique( jQuery.merge( + this.get(), + typeof selector === "string" ? + jQuery( selector ) : + jQuery.makeArray( selector ) + ))); + }, + + is: function( selector ) { + return !!selector && jQuery.multiFilter( selector, this ).length > 0; + }, + + hasClass: function( selector ) { + return !!selector && this.is( "." + selector ); + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if( jQuery.nodeName( elem, 'option' ) ) + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type == "select-one"; + + // Nothing was selected + if ( index < 0 ) + return null; + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) + return value; + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Everything else, we just grab the value + return (elem.value || "").replace(/\r/g, ""); + + } + + return undefined; + } + + if ( typeof value === "number" ) + value += ''; + + return this.each(function(){ + if ( this.nodeType != 1 ) + return; + + if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) ) + this.checked = (jQuery.inArray(this.value, value) >= 0 || + jQuery.inArray(this.name, value) >= 0); + + else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(value); + + jQuery( "option", this ).each(function(){ + this.selected = (jQuery.inArray( this.value, values ) >= 0 || + jQuery.inArray( this.text, values ) >= 0); + }); + + if ( !values.length ) + this.selectedIndex = -1; + + } else + this.value = value; + }); + }, + + html: function( value ) { + return value === undefined ? + (this[0] ? + this[0].innerHTML : + null) : + this.empty().append( value ); + }, + + replaceWith: function( value ) { + return this.after( value ).remove(); + }, + + eq: function( i ) { + return this.slice( i, +i + 1 ); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ), + "slice", Array.prototype.slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function(elem, i){ + return callback.call( elem, i, elem ); + })); + }, + + andSelf: function() { + return this.add( this.prevObject ); + }, + + domManip: function( args, table, callback ) { + if ( this[0] ) { + var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(), + scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ), + first = fragment.firstChild, + extra = this.length > 1 ? fragment.cloneNode(true) : fragment; + + if ( first ) + for ( var i = 0, l = this.length; i < l; i++ ) + callback.call( root(this[i], first), i > 0 ? extra.cloneNode(true) : fragment ); + + if ( scripts ) + jQuery.each( scripts, evalScript ); + } + + return this; + + function root( elem, cur ) { + return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ? + (elem.getElementsByTagName("tbody")[0] || + elem.appendChild(elem.ownerDocument.createElement("tbody"))) : + elem; + } + } +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +function evalScript( i, elem ) { + if ( elem.src ) + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + + else + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + + if ( elem.parentNode ) + elem.parentNode.removeChild( elem ); +} + +function now(){ + return +new Date; +} + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) + target = {}; + + // extend jQuery itself if only one argument is passed + if ( length == i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) + // Extend the base object + for ( var name in options ) { + var src = target[ name ], copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) + continue; + + // Recurse if we're merging object values + if ( deep && copy && typeof copy === "object" && !copy.nodeType ) + target[ name ] = jQuery.extend( deep, + // Never move original objects, clone them + src || ( copy.length != null ? [ ] : { } ) + , copy ); + + // Don't bring in undefined values + else if ( copy !== undefined ) + target[ name ] = copy; + + } + + // Return the modified object + return target; +}; + +// exclude the following css properties to add px +var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, + // cache defaultView + defaultView = document.defaultView || {}, + toString = Object.prototype.toString; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) + window.jQuery = _jQuery; + + return jQuery; + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + // check if an element is in a (or is an) XML document + isXMLDoc: function( elem ) { + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument ); + }, + + // Evalulates a script in a global context + globalEval: function( data ) { + data = jQuery.trim( data ); + + if ( data ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + if ( jQuery.support.scriptEval ) + script.appendChild( document.createTextNode( data ) ); + else + script.text = data; + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, length = object.length; + + if ( args ) { + if ( length === undefined ) { + for ( name in object ) + if ( callback.apply( object[ name ], args ) === false ) + break; + } else + for ( ; i < length; ) + if ( callback.apply( object[ i++ ], args ) === false ) + break; + + // A special, fast, case for the most common use of each + } else { + if ( length === undefined ) { + for ( name in object ) + if ( callback.call( object[ name ], name, object[ name ] ) === false ) + break; + } else + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} + } + + return object; + }, + + prop: function( elem, value, type, i, name ) { + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, i ); + + // Handle passing in a number to a CSS property + return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, classNames ) { + jQuery.each((classNames || "").split(/\s+/), function(i, className){ + if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) + elem.className += (elem.className ? " " : "") + className; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, classNames ) { + if (elem.nodeType == 1) + elem.className = classNames !== undefined ? + jQuery.grep(elem.className.split(/\s+/), function(className){ + return !jQuery.className.has( classNames, className ); + }).join(" ") : + ""; + }, + + // internal only, use hasClass("class") + has: function( elem, className ) { + return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; + } + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback ) { + var old = {}; + // Remember the old values, and insert the new ones + for ( var name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + callback.call( elem ); + + // Revert the old values + for ( var name in options ) + elem.style[ name ] = old[ name ]; + }, + + css: function( elem, name, force ) { + if ( name == "width" || name == "height" ) { + var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; + + function getWH() { + val = name == "width" ? elem.offsetWidth : elem.offsetHeight; + var padding = 0, border = 0; + jQuery.each( which, function() { + padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; + border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; + }); + val -= Math.round(padding + border); + } + + if ( jQuery(elem).is(":visible") ) + getWH(); + else + jQuery.swap( elem, props, getWH ); + + return Math.max(0, val); + } + + return jQuery.curCSS( elem, name, force ); + }, + + curCSS: function( elem, name, force ) { + var ret, style = elem.style; + + // We need to handle opacity special in IE + if ( name == "opacity" && !jQuery.support.opacity ) { + ret = jQuery.attr( style, "opacity" ); + + return ret == "" ? + "1" : + ret; + } + + // Make sure we're using the right name for getting the float value + if ( name.match( /float/i ) ) + name = styleFloat; + + if ( !force && style && style[ name ] ) + ret = style[ name ]; + + else if ( defaultView.getComputedStyle ) { + + // Only "float" is needed here + if ( name.match( /float/i ) ) + name = "float"; + + name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); + + var computedStyle = defaultView.getComputedStyle( elem, null ); + + if ( computedStyle ) + ret = computedStyle.getPropertyValue( name ); + + // We should always get a number back from opacity + if ( name == "opacity" && ret == "" ) + ret = "1"; + + } else if ( elem.currentStyle ) { + var camelCase = name.replace(/\-(\w)/g, function(all, letter){ + return letter.toUpperCase(); + }); + + ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { + // Remember the original values + var left = style.left, rsLeft = elem.runtimeStyle.left; + + // Put in the new values to get a computed value out + elem.runtimeStyle.left = elem.currentStyle.left; + style.left = ret || 0; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + elem.runtimeStyle.left = rsLeft; + } + } + + return ret; + }, + + clean: function( elems, context, fragment ) { + context = context || document; + + // !context.createElement fails in IE with an error but returns typeof 'object' + if ( typeof context.createElement === "undefined" ) + context = context.ownerDocument || context[0] && context[0].ownerDocument || document; + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) { + var match = /^<(\w+)\s*\/?>$/.exec(elems[0]); + if ( match ) + return [ context.createElement( match[1] ) ]; + } + + var ret = [], scripts = [], div = context.createElement("div"); + + jQuery.each(elems, function(i, elem){ + if ( typeof elem === "number" ) + elem += ''; + + if ( !elem ) + return; + + // Convert html string into DOM nodes + if ( typeof elem === "string" ) { + // Fix "XHTML"-style tags in all browsers + elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ + return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? + all : + front + "></" + tag + ">"; + }); + + // Trim whitespace, otherwise indexOf won't work as expected + var tags = jQuery.trim( elem ).toLowerCase(); + + var wrap = + // option or optgroup + !tags.indexOf("<opt") && + [ 1, "<select multiple='multiple'>", "</select>" ] || + + !tags.indexOf("<leg") && + [ 1, "<fieldset>", "</fieldset>" ] || + + tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [ 1, "<table>", "</table>" ] || + + !tags.indexOf("<tr") && + [ 2, "<table><tbody>", "</tbody></table>" ] || + + // <thead> matched above + (!tags.indexOf("<td") || !tags.indexOf("<th")) && + [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] || + + !tags.indexOf("<col") && + [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] || + + // IE can't serialize <link> and <script> tags normally + !jQuery.support.htmlSerialize && + [ 1, "div<div>", "</div>" ] || + + [ 0, "", "" ]; + + // Go to html and back, then peel off extra wrappers + div.innerHTML = wrap[1] + elem + wrap[2]; + + // Move to the right depth + while ( wrap[0]-- ) + div = div.lastChild; + + // Remove IE's autoinserted <tbody> from table fragments + if ( !jQuery.support.tbody ) { + + // String was a <table>, *may* have spurious <tbody> + var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ? + div.firstChild && div.firstChild.childNodes : + + // String was a bare <thead> or <tfoot> + wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ? + div.childNodes : + []; + + for ( var j = tbody.length - 1; j >= 0 ; --j ) + if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) + tbody[ j ].parentNode.removeChild( tbody[ j ] ); + + } + + // IE completely kills leading whitespace when innerHTML is used + if ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) ) + div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild ); + + elem = jQuery.makeArray( div.childNodes ); + } + + if ( elem.nodeType ) + ret.push( elem ); + else + ret = jQuery.merge( ret, elem ); + + }); + + if ( fragment ) { + for ( var i = 0; ret[i]; i++ ) { + if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { + scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); + } else { + if ( ret[i].nodeType === 1 ) + ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); + fragment.appendChild( ret[i] ); + } + } + + return scripts; + } + + return ret; + }, + + attr: function( elem, name, value ) { + // don't set attributes on text and comment nodes + if (!elem || elem.nodeType == 3 || elem.nodeType == 8) + return undefined; + + var notxml = !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // Only do all the following if this is a node (faster for style) + // IE elem.getAttribute passes even for style + if ( elem.tagName ) { + + // These attributes require special treatment + var special = /href|src|style/.test( name ); + + // Safari mis-reports the default selected property of a hidden option + // Accessing the parent's selectedIndex property fixes it + if ( name == "selected" && elem.parentNode ) + elem.parentNode.selectedIndex; + + // If applicable, access the attribute via the DOM 0 way + if ( name in elem && notxml && !special ) { + if ( set ){ + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode ) + throw "type property can't be changed"; + + elem[ name ] = value; + } + + // browsers index elements by id/name on forms, give priority to attributes. + if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) + return elem.getAttributeNode( name ).nodeValue; + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name == "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + return attributeNode && attributeNode.specified + ? attributeNode.value + : elem.nodeName.match(/(button|input|object|select|textarea)/i) + ? 0 + : elem.nodeName.match(/^(a|area)$/i) && elem.href + ? 0 + : undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name == "style" ) + return jQuery.attr( elem.style, "cssText", value ); + + if ( set ) + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + + var attr = !jQuery.support.hrefNormalized && notxml && special + // Some attributes require a special call on IE + ? elem.getAttribute( name, 2 ) + : elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } + + // elem is actually elem.style ... set the style + + // IE uses filters for opacity + if ( !jQuery.support.opacity && name == "opacity" ) { + if ( set ) { + // IE has trouble with opacity if it does not have layout + // Force it by setting the zoom level + elem.zoom = 1; + + // Set the alpha filter to set the opacity + elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) + + (parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")"); + } + + return elem.filter && elem.filter.indexOf("opacity=") >= 0 ? + (parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '': + ""; + } + + name = name.replace(/-([a-z])/ig, function(all, letter){ + return letter.toUpperCase(); + }); + + if ( set ) + elem[ name ] = value; + + return elem[ name ]; + }, + + trim: function( text ) { + return (text || "").replace( /^\s+|\s+$/g, "" ); + }, + + makeArray: function( array ) { + var ret = []; + + if( array != null ){ + var i = array.length; + // The window, strings (and functions) also have 'length' + if( i == null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval ) + ret[0] = array; + else + while( i ) + ret[--i] = array[i]; + } + + return ret; + }, + + inArray: function( elem, array ) { + for ( var i = 0, length = array.length; i < length; i++ ) + // Use === because on IE, window == document + if ( array[ i ] === elem ) + return i; + + return -1; + }, + + merge: function( first, second ) { + // We have to loop this way because IE & Opera overwrite the length + // expando of getElementsByTagName + var i = 0, elem, pos = first.length; + // Also, we need to make sure that the correct elements are being returned + // (IE returns comment nodes in a '*' query) + if ( !jQuery.support.getAll ) { + while ( (elem = second[ i++ ]) != null ) + if ( elem.nodeType != 8 ) + first[ pos++ ] = elem; + + } else + while ( (elem = second[ i++ ]) != null ) + first[ pos++ ] = elem; + + return first; + }, + + unique: function( array ) { + var ret = [], done = {}; + + try { + + for ( var i = 0, length = array.length; i < length; i++ ) { + var id = jQuery.data( array[ i ] ); + + if ( !done[ id ] ) { + done[ id ] = true; + ret.push( array[ i ] ); + } + } + + } catch( e ) { + ret = array; + } + + return ret; + }, + + grep: function( elems, callback, inv ) { + var ret = []; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) + if ( !inv != !callback( elems[ i ], i ) ) + ret.push( elems[ i ] ); + + return ret; + }, + + map: function( elems, callback ) { + var ret = []; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + var value = callback( elems[ i ], i ); + + if ( value != null ) + ret[ ret.length ] = value; + } + + return ret.concat.apply( [], ret ); + } +}); + +// Use of jQuery.browser is deprecated. +// It's included for backwards compatibility and plugins, +// although they should work to migrate away. + +var userAgent = navigator.userAgent.toLowerCase(); + +// Figure out what browser is being used +jQuery.browser = { + version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1], + safari: /webkit/.test( userAgent ), + opera: /opera/.test( userAgent ), + msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ), + mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent ) +}; + +jQuery.each({ + parent: function(elem){return elem.parentNode;}, + parents: function(elem){return jQuery.dir(elem,"parentNode");}, + next: function(elem){return jQuery.nth(elem,2,"nextSibling");}, + prev: function(elem){return jQuery.nth(elem,2,"previousSibling");}, + nextAll: function(elem){return jQuery.dir(elem,"nextSibling");}, + prevAll: function(elem){return jQuery.dir(elem,"previousSibling");}, + siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);}, + children: function(elem){return jQuery.sibling(elem.firstChild);}, + contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);} +}, function(name, fn){ + jQuery.fn[ name ] = function( selector ) { + var ret = jQuery.map( this, fn ); + + if ( selector && typeof selector == "string" ) + ret = jQuery.multiFilter( selector, ret ); + + return this.pushStack( jQuery.unique( ret ), name, selector ); + }; +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function(name, original){ + jQuery.fn[ name ] = function() { + var args = arguments; + + return this.each(function(){ + for ( var i = 0, length = args.length; i < length; i++ ) + jQuery( args[ i ] )[ original ]( this ); + }); + }; +}); + +jQuery.each({ + removeAttr: function( name ) { + jQuery.attr( this, name, "" ); + if (this.nodeType == 1) + this.removeAttribute( name ); + }, + + addClass: function( classNames ) { + jQuery.className.add( this, classNames ); + }, + + removeClass: function( classNames ) { + jQuery.className.remove( this, classNames ); + }, + + toggleClass: function( classNames, state ) { + if( typeof state !== "boolean" ) + state = !jQuery.className.has( this, classNames ); + jQuery.className[ state ? "add" : "remove" ]( this, classNames ); + }, + + remove: function( selector ) { + if ( !selector || jQuery.filter( selector, [ this ] ).length ) { + // Prevent memory leaks + jQuery( "*", this ).add([this]).each(function(){ + jQuery.event.remove(this); + jQuery.removeData(this); + }); + if (this.parentNode) + this.parentNode.removeChild( this ); + } + }, + + empty: function() { + // Remove element nodes and prevent memory leaks + jQuery( ">*", this ).remove(); + + // Remove any remaining nodes + while ( this.firstChild ) + this.removeChild( this.firstChild ); + } +}, function(name, fn){ + jQuery.fn[ name ] = function(){ + return this.each( fn, arguments ); + }; +}); + +// Helper function used by the dimensions and offset modules +function num(elem, prop) { + return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0; +} +var expando = "jQuery" + now(), uuid = 0, windowData = {}; + +jQuery.extend({ + cache: {}, + + data: function( elem, name, data ) { + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ]; + + // Compute a unique ID for the element + if ( !id ) + id = elem[ expando ] = ++uuid; + + // Only generate the data cache if we're + // trying to access or manipulate it + if ( name && !jQuery.cache[ id ] ) + jQuery.cache[ id ] = {}; + + // Prevent overriding the named cache with undefined values + if ( data !== undefined ) + jQuery.cache[ id ][ name ] = data; + + // Return the named cache data, or the ID for the element + return name ? + jQuery.cache[ id ][ name ] : + id; + }, + + removeData: function( elem, name ) { + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( jQuery.cache[ id ] ) { + // Remove the section of cache data + delete jQuery.cache[ id ][ name ]; + + // If we've removed all the data, remove the element's cache + name = ""; + + for ( name in jQuery.cache[ id ] ) + break; + + if ( !name ) + jQuery.removeData( elem ); + } + + // Otherwise, we want to remove all of the element's data + } else { + // Clean up the element expando + try { + delete elem[ expando ]; + } catch(e){ + // IE has trouble directly removing the expando + // but it's ok with using removeAttribute + if ( elem.removeAttribute ) + elem.removeAttribute( expando ); + } + + // Completely remove the data cache + delete jQuery.cache[ id ]; + } + }, + queue: function( elem, type, data ) { + if ( elem ){ + + type = (type || "fx") + "queue"; + + var q = jQuery.data( elem, type ); + + if ( !q || jQuery.isArray(data) ) + q = jQuery.data( elem, type, jQuery.makeArray(data) ); + else if( data ) + q.push( data ); + + } + return q; + }, + + dequeue: function( elem, type ){ + var queue = jQuery.queue( elem, type ), + fn = queue.shift(); + + if( !type || type === "fx" ) + fn = queue[0]; + + if( fn !== undefined ) + fn.call(elem); + } +}); + +jQuery.fn.extend({ + data: function( key, value ){ + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + if ( data === undefined && this.length ) + data = jQuery.data( this[0], key ); + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } else + return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){ + jQuery.data( this, key, value ); + }); + }, + + removeData: function( key ){ + return this.each(function(){ + jQuery.removeData( this, key ); + }); + }, + queue: function(type, data){ + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) + return jQuery.queue( this[0], type ); + + return this.each(function(){ + var queue = jQuery.queue( this, type, data ); + + if( type == "fx" && queue.length == 1 ) + queue[0].call(this); + }); + }, + dequeue: function(type){ + return this.each(function(){ + jQuery.dequeue( this, type ); + }); + } +});/*! + * Sizzle CSS Selector Engine - v0.9.3 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]+['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[]+)+|[>+~])(\s*,\s*)?/g, + done = 0, + toString = Object.prototype.toString; + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) + return []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true; + + // Reset the position of the chunker regexp (start from head) + chunker.lastIndex = 0; + + while ( (m = chunker.exec(selector)) !== null ) { + parts.push( m[1] ); + + if ( m[2] ) { + extra = RegExp.rightContext; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + set = posProcess( selector, set ); + } + } + } else { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) ); + set = Sizzle.filter( ret.expr, ret.set ); + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, isXML(context) ); + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, context, results, seed ); + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.match[ type ].exec( expr )) ) { + var left = RegExp.leftContext; + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound; + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.match[ type ].exec( expr )) != null ) { + var filter = Expr.filter[ type ], found, item; + anyFound = false; + + if ( curLoop == result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + expr = expr.replace(/\s*,\s*/, ""); + + // Improper expression + if ( expr == old ) { + if ( anyFound == null ) { + throw "Syntax error, unrecognized expression: " + expr; + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var cur = elem.previousSibling; + while ( cur && cur.nodeType !== 1 ) { + cur = cur.previousSibling; + } + checkSet[i] = typeof part === "string" ? + cur || false : + cur === part; + } + } + + if ( typeof part === "string" ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part, isXML){ + if ( typeof part === "string" && !/\W/.test(part) ) { + part = isXML ? part : part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = typeof part === "string" ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( typeof part === "string" ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = "done" + (done++), checkFn = dirCheck; + + if ( !part.match(/\W/) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = "done" + (done++), checkFn = dirCheck; + + if ( typeof part === "string" && !part.match(/\W/) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" && !isXML ) { + return context.getElementsByName(match[1]); + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not){ + match = " " + match[1].replace(/\\/g, "") + " "; + + var elem; + for ( var i = 0; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (" " + elem.className + " ").indexOf(match) >= 0 ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = "done" + (done++); + + return match; + }, + ATTR: function(match){ + var name = match[1].replace(/\\/g, ""); + + if ( Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( match[3].match(chunker).length > 1 ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + CHILD: function(elem, match){ + var type = match[1], parent = elem.parentNode; + + var doneName = match[0]; + + if ( parent && (!parent[ doneName ] || !elem.nodeIndex) ) { + var count = 1; + + for ( var node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType == 1 ) { + node.nodeIndex = count++; + } + } + + parent[ doneName ] = count - 1; + } + + if ( type == "first" ) { + return elem.nodeIndex == 1; + } else if ( type == "last" ) { + return elem.nodeIndex == parent[ doneName ]; + } else if ( type == "only" ) { + return parent[ doneName ] == 1; + } else if ( type == "nth" ) { + var add = false, first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + if ( first == 0 ) { + if ( elem.nodeIndex == last ) { + add = true; + } + } else if ( (elem.nodeIndex - last) % first == 0 && (elem.nodeIndex - last) / first >= 0 ) { + add = true; + } + + return add; + } + }, + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return match.test( elem.className ); + }, + ATTR: function(elem, match){ + var result = Expr.attrHandle[ match[1] ] ? Expr.attrHandle[ match[1] ]( elem ) : elem[ match[1] ] || elem.getAttribute( match[1] ), value = result + "", type = match[2], check = match[4]; + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !match[4] ? + result : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +try { + Array.prototype.slice.call( document.documentElement.childNodes ); + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("form"), + id = "script" + (new Date).getTime(); + form.innerHTML = "<input name='" + id + "'/>"; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = "<a href='#'></a>"; + if ( div.firstChild && div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } +})(); + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "<p class='TEST'></p>"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + Sizzle.find = oldSizzle.find; + Sizzle.filter = oldSizzle.filter; + Sizzle.selectors = oldSizzle.selectors; + Sizzle.matches = oldSizzle.matches; +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) { + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context) { + return context.getElementsByClassName(match[1]); + }; +} + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem && elem.nodeType ) { + var done = elem[doneName]; + if ( done ) { + match = checkSet[ done ]; + break; + } + + if ( elem.nodeType === 1 && !isXML ) + elem[doneName] = i; + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem && elem.nodeType ) { + if ( elem[doneName] ) { + match = checkSet[ elem[doneName] ]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) + elem[doneName] = i; + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && isXML( elem.ownerDocument ); +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.filter = Sizzle.filter; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; + +Sizzle.selectors.filters.hidden = function(elem){ + return "hidden" === elem.type || + jQuery.css(elem, "display") === "none" || + jQuery.css(elem, "visibility") === "hidden"; +}; + +Sizzle.selectors.filters.visible = function(elem){ + return "hidden" !== elem.type && + jQuery.css(elem, "display") !== "none" && + jQuery.css(elem, "visibility") !== "hidden"; +}; + +Sizzle.selectors.filters.animated = function(elem){ + return jQuery.grep(jQuery.timers, function(fn){ + return elem === fn.elem; + }).length; +}; + +jQuery.multiFilter = function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return Sizzle.matches(expr, elems); +}; + +jQuery.dir = function( elem, dir ){ + var matched = [], cur = elem[dir]; + while ( cur && cur != document ) { + if ( cur.nodeType == 1 ) + matched.push( cur ); + cur = cur[dir]; + } + return matched; +}; + +jQuery.nth = function(cur, result, dir, elem){ + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) + if ( cur.nodeType == 1 && ++num == result ) + break; + + return cur; +}; + +jQuery.sibling = function(n, elem){ + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType == 1 && n != elem ) + r.push( n ); + } + + return r; +}; + +return; + +window.Sizzle = Sizzle; + +})(); +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function(elem, types, handler, data) { + if ( elem.nodeType == 3 || elem.nodeType == 8 ) + return; + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( elem.setInterval && elem != window ) + elem = window; + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) + handler.guid = this.guid++; + + // if data is passed, bind to handler + if ( data !== undefined ) { + // Create temporary function pointer to original handler + var fn = handler; + + // Create unique handler function, wrapped around original handler + handler = this.proxy( fn ); + + // Store data in unique handler + handler.data = data; + } + + // Init the element's event structure + var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}), + handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){ + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply(arguments.callee.elem, arguments) : + undefined; + }); + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native + // event in IE. + handle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + jQuery.each(types.split(/\s+/), function(index, type) { + // Namespaced event handlers + var namespaces = type.split("."); + type = namespaces.shift(); + handler.type = namespaces.slice().sort().join("."); + + // Get the current list of functions bound to this event + var handlers = events[type]; + + if ( jQuery.event.specialAll[type] ) + jQuery.event.specialAll[type].setup.call(elem, data, namespaces); + + // Init the event handler queue + if (!handlers) { + handlers = events[type] = {}; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) { + // Bind the global event handler to the element + if (elem.addEventListener) + elem.addEventListener(type, handle, false); + else if (elem.attachEvent) + elem.attachEvent("on" + type, handle); + } + } + + // Add the function to the element's handler list + handlers[handler.guid] = handler; + + // Keep track of which events have been used, for global triggering + jQuery.event.global[type] = true; + }); + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + guid: 1, + global: {}, + + // Detach an event or set of events from an element + remove: function(elem, types, handler) { + // don't do events on text and comment nodes + if ( elem.nodeType == 3 || elem.nodeType == 8 ) + return; + + var events = jQuery.data(elem, "events"), ret, index; + + if ( events ) { + // Unbind all events for the element + if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") ) + for ( var type in events ) + this.remove( elem, type + (types || "") ); + else { + // types is actually an event object here + if ( types.type ) { + handler = types.handler; + types = types.type; + } + + // Handle multiple events seperated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + jQuery.each(types.split(/\s+/), function(index, type){ + // Namespaced event handlers + var namespaces = type.split("."); + type = namespaces.shift(); + var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)"); + + if ( events[type] ) { + // remove the given handler for the given type + if ( handler ) + delete events[type][handler.guid]; + + // remove all handlers for the given type + else + for ( var handle in events[type] ) + // Handle the removal of namespaced events + if ( namespace.test(events[type][handle].type) ) + delete events[type][handle]; + + if ( jQuery.event.specialAll[type] ) + jQuery.event.specialAll[type].teardown.call(elem, namespaces); + + // remove generic event handler if no more handlers exist + for ( ret in events[type] ) break; + if ( !ret ) { + if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) { + if (elem.removeEventListener) + elem.removeEventListener(type, jQuery.data(elem, "handle"), false); + else if (elem.detachEvent) + elem.detachEvent("on" + type, jQuery.data(elem, "handle")); + } + ret = null; + delete events[type]; + } + } + }); + } + + // Remove the expando if it's no longer used + for ( ret in events ) break; + if ( !ret ) { + var handle = jQuery.data( elem, "handle" ); + if ( handle ) handle.elem = null; + jQuery.removeData( elem, "events" ); + jQuery.removeData( elem, "handle" ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem, bubbling ) { + // Event object or event type + var type = event.type || event; + + if( !bubbling ){ + event = typeof event === "object" ? + // jQuery.Event object + event[expando] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + // Only trigger if we've ever bound an event for it + if ( this.global[type] ) + jQuery.each( jQuery.cache, function(){ + if ( this.events && this.events[type] ) + jQuery.event.trigger( event, data, this.handle.elem ); + }); + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 ) + return undefined; + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray(data); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = jQuery.data(elem, "handle"); + if ( handle ) + handle.apply( elem, data ); + + // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links) + if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false ) + event.result = false; + + // Trigger the native events (except for clicks on links) + if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) { + this.triggered = true; + try { + elem[ type ](); + // prevent IE from throwing an error for some hidden elements + } catch (e) {} + } + + this.triggered = false; + + if ( !event.isPropagationStopped() ) { + var parent = elem.parentNode || elem.ownerDocument; + if ( parent ) + jQuery.event.trigger(event, data, parent, true); + } + }, + + handle: function(event) { + // returned undefined or false + var all, handlers; + + event = arguments[0] = jQuery.event.fix( event || window.event ); + + // Namespaced event handlers + var namespaces = event.type.split("."); + event.type = namespaces.shift(); + + // Cache this now, all = true means, any handler + all = !namespaces.length && !event.exclusive; + + var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)"); + + handlers = ( jQuery.data(this, "events") || {} )[event.type]; + + for ( var j in handlers ) { + var handler = handlers[j]; + + // Filter the functions by class + if ( all || namespace.test(handler.type) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handler; + event.data = handler.data; + + var ret = handler.apply(this, arguments); + + if( ret !== undefined ){ + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if( event.isImmediatePropagationStopped() ) + break; + + } + } + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function(event) { + if ( event[expando] ) + return event; + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ){ + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) + event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either + + // check if target is a textnode (safari) + if ( event.target.nodeType == 3 ) + event.target = event.target.parentNode; + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) + event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, body = document.body; + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0); + } + + // Add which for key events + if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) + event.which = event.charCode || event.keyCode; + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) + event.metaKey = event.ctrlKey; + + // Add which for click: 1 == left; 2 == middle; 3 == right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button ) + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + + return event; + }, + + proxy: function( fn, proxy ){ + proxy = proxy || function(){ return fn.apply(this, arguments); }; + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++; + // So proxy can be declared as an argument + return proxy; + }, + + special: { + ready: { + // Make sure the ready event is setup + setup: bindReady, + teardown: function() {} + } + }, + + specialAll: { + live: { + setup: function( selector, namespaces ){ + jQuery.event.add( this, namespaces[0], liveHandler ); + }, + teardown: function( namespaces ){ + if ( namespaces.length ) { + var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)"); + + jQuery.each( (jQuery.data(this, "events").live || {}), function(){ + if ( name.test(this.type) ) + remove++; + }); + + if ( remove < 1 ) + jQuery.event.remove( this, namespaces[0], liveHandler ); + } + } + } + } +}; + +jQuery.Event = function( src ){ + // Allow instantiation without the 'new' keyword + if( !this.preventDefault ) + return new jQuery.Event(src); + + // Event object + if( src && src.type ){ + this.originalEvent = src; + this.type = src.type; + // Event type + }else + this.type = src; + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = now(); + + // Mark it as fixed + this[expando] = true; +}; + +function returnFalse(){ + return false; +} +function returnTrue(){ + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if( !e ) + return; + // if preventDefault exists run it on the original event + if (e.preventDefault) + e.preventDefault(); + // otherwise set the returnValue property of the original event to false (IE) + e.returnValue = false; + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if( !e ) + return; + // if stopPropagation exists run it on the original event + if (e.stopPropagation) + e.stopPropagation(); + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation:function(){ + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function(event) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + // Traverse up the tree + while ( parent && parent != this ) + try { parent = parent.parentNode; } + catch(e) { parent = this; } + + if( parent != this ){ + // set the correct event type + event.type = event.data; + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } +}; + +jQuery.each({ + mouseover: 'mouseenter', + mouseout: 'mouseleave' +}, function( orig, fix ){ + jQuery.event.special[ fix ] = { + setup: function(){ + jQuery.event.add( this, orig, withinElement, fix ); + }, + teardown: function(){ + jQuery.event.remove( this, orig, withinElement ); + } + }; +}); + +jQuery.fn.extend({ + bind: function( type, data, fn ) { + return type == "unload" ? this.one(type, data, fn) : this.each(function(){ + jQuery.event.add( this, type, fn || data, fn && data ); + }); + }, + + one: function( type, data, fn ) { + var one = jQuery.event.proxy( fn || data, function(event) { + jQuery(this).unbind(event, one); + return (fn || data).apply( this, arguments ); + }); + return this.each(function(){ + jQuery.event.add( this, type, one, fn && data); + }); + }, + + unbind: function( type, fn ) { + return this.each(function(){ + jQuery.event.remove( this, type, fn ); + }); + }, + + trigger: function( type, data ) { + return this.each(function(){ + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if( this[0] ){ + var event = jQuery.Event(type); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, i = 1; + + // link all the functions, so any of them can unbind this click handler + while( i < args.length ) + jQuery.event.proxy( fn, args[i++] ); + + return this.click( jQuery.event.proxy( fn, function(event) { + // Figure out which function to execute + this.lastToggle = ( this.lastToggle || 0 ) % i; + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ this.lastToggle++ ].apply( this, arguments ) || false; + })); + }, + + hover: function(fnOver, fnOut) { + return this.mouseenter(fnOver).mouseleave(fnOut); + }, + + ready: function(fn) { + // Attach the listeners + bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + else + // Add the function to the wait list + jQuery.readyList.push( fn ); + + return this; + }, + + live: function( type, fn ){ + var proxy = jQuery.event.proxy( fn ); + proxy.guid += this.selector + type; + + jQuery(document).bind( liveConvert(type, this.selector), this.selector, proxy ); + + return this; + }, + + die: function( type, fn ){ + jQuery(document).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null ); + return this; + } +}); + +function liveHandler( event ){ + var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"), + stop = true, + elems = []; + + jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){ + if ( check.test(fn.type) ) { + var elem = jQuery(event.target).closest(fn.data)[0]; + if ( elem ) + elems.push({ elem: elem, fn: fn }); + } + }); + + jQuery.each(elems, function(){ + if ( this.fn.call(this.elem, event, this.fn.data) === false ) + stop = false; + }); + + return stop; +} + +function liveConvert(type, selector){ + return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join("."); +} + +jQuery.extend({ + isReady: false, + readyList: [], + // Handle when the DOM is ready + ready: function() { + // Make sure that the DOM is not already loaded + if ( !jQuery.isReady ) { + // Remember that the DOM is ready + jQuery.isReady = true; + + // If there are functions bound, to execute + if ( jQuery.readyList ) { + // Execute all of them + jQuery.each( jQuery.readyList, function(){ + this.call( document, jQuery ); + }); + + // Reset the list of functions + jQuery.readyList = null; + } + + // Trigger any bound ready events + jQuery(document).triggerHandler("ready"); + } + } +}); + +var readyBound = false; + +function bindReady(){ + if ( readyBound ) return; + readyBound = true; + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", function(){ + document.removeEventListener( "DOMContentLoaded", arguments.callee, false ); + jQuery.ready(); + }, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", function(){ + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", arguments.callee ); + jQuery.ready(); + } + }); + + // If IE and not an iframe + // continually check to see if the document is ready + if ( document.documentElement.doScroll && typeof window.frameElement === "undefined" ) (function(){ + if ( jQuery.isReady ) return; + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch( error ) { + setTimeout( arguments.callee, 0 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); + })(); + } + + // A fallback to window.onload, that will always work + jQuery.event.add( window, "load", jQuery.ready ); +} + +jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," + + "mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," + + "change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){ + + // Handle event binding + jQuery.fn[name] = function(fn){ + return fn ? this.bind(name, fn) : this.trigger(name); + }; +}); + +// Prevent memory leaks in IE +// And prevent errors on refresh with events like mouseover in other browsers +// Window isn't included so as not to unbind existing unload events +jQuery( window ).bind( 'unload', function(){ + for ( var id in jQuery.cache ) + // Skip the window + if ( id != 1 && jQuery.cache[ id ].handle ) + jQuery.event.remove( jQuery.cache[ id ].handle.elem ); +}); +(function(){ + + jQuery.support = {}; + + var root = document.documentElement, + script = document.createElement("script"), + div = document.createElement("div"), + id = "script" + (new Date).getTime(); + + div.style.display = "none"; + div.innerHTML = ' <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>'; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType == 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that you can get all elements in an <object> element + // IE 7 always returns no results + objectAll: !!div.getElementsByTagName("object")[0] + .getElementsByTagName("*").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + opacity: a.style.opacity === "0.5", + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Will be defined later + scriptEval: false, + noCloneEvent: true, + boxModel: null + }; + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e){} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support.scriptEval = true; + delete window[ id ]; + } + + root.removeChild( script ); + + if ( div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function(){ + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", arguments.callee); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function(){ + var div = document.createElement("div"); + div.style.width = "1px"; + div.style.paddingLeft = "1px"; + + document.body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + document.body.removeChild( div ); + }); +})(); + +var styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat"; + +jQuery.props = { + "for": "htmlFor", + "class": "className", + "float": styleFloat, + cssFloat: styleFloat, + styleFloat: styleFloat, + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + tabindex: "tabIndex" +}; +jQuery.fn.extend({ + // Keep a copy of the old load + _load: jQuery.fn.load, + + load: function( url, params, callback ) { + if ( typeof url !== "string" ) + return this._load( url ); + + var off = url.indexOf(" "); + if ( off >= 0 ) { + var selector = url.slice(off, url.length); + url = url.slice(0, off); + } + + // Default to a GET request + var type = "GET"; + + // If the second parameter was provided + if ( params ) + // If it's a function + if ( jQuery.isFunction( params ) ) { + // We assume that it's the callback + callback = params; + params = null; + + // Otherwise, build a param string + } else if( typeof params === "object" ) { + params = jQuery.param( params ); + type = "POST"; + } + + var self = this; + + // Request the remote document + jQuery.ajax({ + url: url, + type: type, + dataType: "html", + data: params, + complete: function(res, status){ + // If successful, inject the HTML into all the matched elements + if ( status == "success" || status == "notmodified" ) + // See if a selector was specified + self.html( selector ? + // Create a dummy div to hold the results + jQuery("<div/>") + // inject the contents of the document in, removing the scripts + // to avoid any 'Permission Denied' errors in IE + .append(res.responseText.replace(/<script(.|\s)*?\/script>/g, "")) + + // Locate the specified elements + .find(selector) : + + // If not, just inject the full result + res.responseText ); + + if( callback ) + self.each( callback, [res.responseText, status, res] ); + } + }); + return this; + }, + + serialize: function() { + return jQuery.param(this.serializeArray()); + }, + serializeArray: function() { + return this.map(function(){ + return this.elements ? jQuery.makeArray(this.elements) : this; + }) + .filter(function(){ + return this.name && !this.disabled && + (this.checked || /select|textarea/i.test(this.nodeName) || + /text|hidden|password/i.test(this.type)); + }) + .map(function(i, elem){ + var val = jQuery(this).val(); + return val == null ? null : + jQuery.isArray(val) ? + jQuery.map( val, function(val, i){ + return {name: elem.name, value: val}; + }) : + {name: elem.name, value: val}; + }).get(); + } +}); + +// Attach a bunch of functions for handling common AJAX events +jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){ + jQuery.fn[o] = function(f){ + return this.bind(o, f); + }; +}); + +var jsc = now(); + +jQuery.extend({ + + get: function( url, data, callback, type ) { + // shift arguments if data argument was ommited + if ( jQuery.isFunction( data ) ) { + callback = data; + data = null; + } + + return jQuery.ajax({ + type: "GET", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + + getScript: function( url, callback ) { + return jQuery.get(url, null, callback, "script"); + }, + + getJSON: function( url, data, callback ) { + return jQuery.get(url, data, callback, "json"); + }, + + post: function( url, data, callback, type ) { + if ( jQuery.isFunction( data ) ) { + callback = data; + data = {}; + } + + return jQuery.ajax({ + type: "POST", + url: url, + data: data, + success: callback, + dataType: type + }); + }, + + ajaxSetup: function( settings ) { + jQuery.extend( jQuery.ajaxSettings, settings ); + }, + + ajaxSettings: { + url: location.href, + global: true, + type: "GET", + contentType: "application/x-www-form-urlencoded", + processData: true, + async: true, + /* + timeout: 0, + data: null, + username: null, + password: null, + */ + // Create the request object; Microsoft failed to properly + // implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available + // This function can be overriden by calling jQuery.ajaxSetup + xhr:function(){ + return window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest(); + }, + accepts: { + xml: "application/xml, text/xml", + html: "text/html", + script: "text/javascript, application/javascript", + json: "application/json, text/javascript", + text: "text/plain", + _default: "*/*" + } + }, + + // Last-Modified header cache for next request + lastModified: {}, + + ajax: function( s ) { + // Extend the settings, but re-extend 's' so that it can be + // checked again later (in the test suite, specifically) + s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s)); + + var jsonp, jsre = /=\?(&|$)/g, status, data, + type = s.type.toUpperCase(); + + // convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) + s.data = jQuery.param(s.data); + + // Handle JSONP Parameter Callbacks + if ( s.dataType == "jsonp" ) { + if ( type == "GET" ) { + if ( !s.url.match(jsre) ) + s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?"; + } else if ( !s.data || !s.data.match(jsre) ) + s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; + s.dataType = "json"; + } + + // Build temporary JSONP function + if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) { + jsonp = "jsonp" + jsc++; + + // Replace the =? sequence both in the query string and the data + if ( s.data ) + s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); + s.url = s.url.replace(jsre, "=" + jsonp + "$1"); + + // We need to make sure + // that a JSONP style response is executed properly + s.dataType = "script"; + + // Handle JSONP-style loading + window[ jsonp ] = function(tmp){ + data = tmp; + success(); + complete(); + // Garbage collect + window[ jsonp ] = undefined; + try{ delete window[ jsonp ]; } catch(e){} + if ( head ) + head.removeChild( script ); + }; + } + + if ( s.dataType == "script" && s.cache == null ) + s.cache = false; + + if ( s.cache === false && type == "GET" ) { + var ts = now(); + // try replacing _= if it is there + var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2"); + // if nothing was replaced, add timestamp to the end + s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : ""); + } + + // If data is available, append data to url for get requests + if ( s.data && type == "GET" ) { + s.url += (s.url.match(/\?/) ? "&" : "?") + s.data; + + // IE likes to send both get and post data, prevent this + s.data = null; + } + + // Watch for a new set of requests + if ( s.global && ! jQuery.active++ ) + jQuery.event.trigger( "ajaxStart" ); + + // Matches an absolute URL, and saves the domain + var parts = /^(\w+:)?\/\/([^\/?#]+)/.exec( s.url ); + + // If we're requesting a remote document + // and trying to load JSON or Script with a GET + if ( s.dataType == "script" && type == "GET" && parts + && ( parts[1] && parts[1] != location.protocol || parts[2] != location.host )){ + + var head = document.getElementsByTagName("head")[0]; + var script = document.createElement("script"); + script.src = s.url; + if (s.scriptCharset) + script.charset = s.scriptCharset; + + // Handle Script loading + if ( !jsonp ) { + var done = false; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function(){ + if ( !done && (!this.readyState || + this.readyState == "loaded" || this.readyState == "complete") ) { + done = true; + success(); + complete(); + head.removeChild( script ); + } + }; + } + + head.appendChild(script); + + // We handle everything using the script element injection + return undefined; + } + + var requestDone = false; + + // Create the request object + var xhr = s.xhr(); + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if( s.username ) + xhr.open(type, s.url, s.async, s.username, s.password); + else + xhr.open(type, s.url, s.async); + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + // Set the correct header, if data is being sent + if ( s.data ) + xhr.setRequestHeader("Content-Type", s.contentType); + + // Set the If-Modified-Since header, if ifModified mode. + if ( s.ifModified ) + xhr.setRequestHeader("If-Modified-Since", + jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" ); + + // Set header so the called script knows that it's an XMLHttpRequest + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + + // Set the Accepts header for the server, depending on the dataType + xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ? + s.accepts[ s.dataType ] + ", */*" : + s.accepts._default ); + } catch(e){} + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && s.beforeSend(xhr, s) === false ) { + // Handle the global AJAX counter + if ( s.global && ! --jQuery.active ) + jQuery.event.trigger( "ajaxStop" ); + // close opended socket + xhr.abort(); + return false; + } + + if ( s.global ) + jQuery.event.trigger("ajaxSend", [xhr, s]); + + // Wait for a response to come back + var onreadystatechange = function(isTimeout){ + // The request was aborted, clear the interval and decrement jQuery.active + if (xhr.readyState == 0) { + if (ival) { + // clear poll interval + clearInterval(ival); + ival = null; + // Handle the global AJAX counter + if ( s.global && ! --jQuery.active ) + jQuery.event.trigger( "ajaxStop" ); + } + // The transfer is complete and the data is available, or the request timed out + } else if ( !requestDone && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) { + requestDone = true; + + // clear poll interval + if (ival) { + clearInterval(ival); + ival = null; + } + + status = isTimeout == "timeout" ? "timeout" : + !jQuery.httpSuccess( xhr ) ? "error" : + s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? "notmodified" : + "success"; + + if ( status == "success" ) { + // Watch for, and catch, XML document parse errors + try { + // process the data (runs the xml through httpData regardless of callback) + data = jQuery.httpData( xhr, s.dataType, s ); + } catch(e) { + status = "parsererror"; + } + } + + // Make sure that the request was successful or notmodified + if ( status == "success" ) { + // Cache Last-Modified header, if ifModified mode. + var modRes; + try { + modRes = xhr.getResponseHeader("Last-Modified"); + } catch(e) {} // swallow exception thrown by FF if header is not available + + if ( s.ifModified && modRes ) + jQuery.lastModified[s.url] = modRes; + + // JSONP handles its own success callback + if ( !jsonp ) + success(); + } else + jQuery.handleError(s, xhr, status); + + // Fire the complete handlers + complete(); + + if ( isTimeout ) + xhr.abort(); + + // Stop memory leaks + if ( s.async ) + xhr = null; + } + }; + + if ( s.async ) { + // don't attach the handler to the request, just poll it instead + var ival = setInterval(onreadystatechange, 13); + + // Timeout checker + if ( s.timeout > 0 ) + setTimeout(function(){ + // Check to see if the request is still happening + if ( xhr && !requestDone ) + onreadystatechange( "timeout" ); + }, s.timeout); + } + + // Send the data + try { + xhr.send(s.data); + } catch(e) { + jQuery.handleError(s, xhr, null, e); + } + + // firefox 1.5 doesn't fire statechange for sync requests + if ( !s.async ) + onreadystatechange(); + + function success(){ + // If a local callback was specified, fire it and pass it the data + if ( s.success ) + s.success( data, status ); + + // Fire the global callback + if ( s.global ) + jQuery.event.trigger( "ajaxSuccess", [xhr, s] ); + } + + function complete(){ + // Process result + if ( s.complete ) + s.complete(xhr, status); + + // The request was completed + if ( s.global ) + jQuery.event.trigger( "ajaxComplete", [xhr, s] ); + + // Handle the global AJAX counter + if ( s.global && ! --jQuery.active ) + jQuery.event.trigger( "ajaxStop" ); + } + + // return XMLHttpRequest to allow aborting the request etc. + return xhr; + }, + + handleError: function( s, xhr, status, e ) { + // If a local callback was specified, fire it + if ( s.error ) s.error( xhr, status, e ); + + // Fire the global callback + if ( s.global ) + jQuery.event.trigger( "ajaxError", [xhr, s, e] ); + }, + + // Counter for holding the number of active queries + active: 0, + + // Determines if an XMLHttpRequest was successful or not + httpSuccess: function( xhr ) { + try { + // IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450 + return !xhr.status && location.protocol == "file:" || + ( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223; + } catch(e){} + return false; + }, + + // Determines if an XMLHttpRequest returns NotModified + httpNotModified: function( xhr, url ) { + try { + var xhrRes = xhr.getResponseHeader("Last-Modified"); + + // Firefox always returns 200. check Last-Modified date + return xhr.status == 304 || xhrRes == jQuery.lastModified[url]; + } catch(e){} + return false; + }, + + httpData: function( xhr, type, s ) { + var ct = xhr.getResponseHeader("content-type"), + xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0, + data = xml ? xhr.responseXML : xhr.responseText; + + if ( xml && data.documentElement.tagName == "parsererror" ) + throw "parsererror"; + + // Allow a pre-filtering function to sanitize the response + // s != null is checked to keep backwards compatibility + if( s && s.dataFilter ) + data = s.dataFilter( data, type ); + + // The filter can actually parse the response + if( typeof data === "string" ){ + + // If the type is "script", eval it in global context + if ( type == "script" ) + jQuery.globalEval( data ); + + // Get the JavaScript object, if JSON is used. + if ( type == "json" ) + data = window["eval"]("(" + data + ")"); + } + + return data; + }, + + // Serialize an array of form elements or a set of + // key/values into a query string + param: function( a ) { + var s = [ ]; + + function add( key, value ){ + s[ s.length ] = encodeURIComponent(key) + '=' + encodeURIComponent(value); + }; + + // If an array was passed in, assume that it is an array + // of form elements + if ( jQuery.isArray(a) || a.jquery ) + // Serialize the form elements + jQuery.each( a, function(){ + add( this.name, this.value ); + }); + + // Otherwise, assume that it's an object of key/value pairs + else + // Serialize the key/values + for ( var j in a ) + // If the value is an array then the key names need to be repeated + if ( jQuery.isArray(a[j]) ) + jQuery.each( a[j], function(){ + add( j, this ); + }); + else + add( j, jQuery.isFunction(a[j]) ? a[j]() : a[j] ); + + // Return the resulting serialization + return s.join("&").replace(/%20/g, "+"); + } + +}); +var elemdisplay = {}, + timerId, + fxAttrs = [ + // height animations + [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], + // width animations + [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], + // opacity animations + [ "opacity" ] + ]; + +function genFx( type, num ){ + var obj = {}; + jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function(){ + obj[ this ] = type; + }); + return obj; +} + +jQuery.fn.extend({ + show: function(speed,callback){ + if ( speed ) { + return this.animate( genFx("show", 3), speed, callback); + } else { + for ( var i = 0, l = this.length; i < l; i++ ){ + var old = jQuery.data(this[i], "olddisplay"); + + this[i].style.display = old || ""; + + if ( jQuery.css(this[i], "display") === "none" ) { + var tagName = this[i].tagName, display; + + if ( elemdisplay[ tagName ] ) { + display = elemdisplay[ tagName ]; + } else { + var elem = jQuery("<" + tagName + " />").appendTo("body"); + + display = elem.css("display"); + if ( display === "none" ) + display = "block"; + + elem.remove(); + + elemdisplay[ tagName ] = display; + } + + this[i].style.display = jQuery.data(this[i], "olddisplay", display); + } + } + + return this; + } + }, + + hide: function(speed,callback){ + if ( speed ) { + return this.animate( genFx("hide", 3), speed, callback); + } else { + for ( var i = 0, l = this.length; i < l; i++ ){ + var old = jQuery.data(this[i], "olddisplay"); + if ( !old && old !== "none" ) + jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display")); + this[i].style.display = "none"; + } + return this; + } + }, + + // Save the old toggle function + _toggle: jQuery.fn.toggle, + + toggle: function( fn, fn2 ){ + var bool = typeof fn === "boolean"; + + return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ? + this._toggle.apply( this, arguments ) : + fn == null || bool ? + this.each(function(){ + var state = bool ? fn : jQuery(this).is(":hidden"); + jQuery(this)[ state ? "show" : "hide" ](); + }) : + this.animate(genFx("toggle", 3), fn, fn2); + }, + + fadeTo: function(speed,to,callback){ + return this.animate({opacity: to}, speed, callback); + }, + + animate: function( prop, speed, easing, callback ) { + var optall = jQuery.speed(speed, easing, callback); + + return this[ optall.queue === false ? "each" : "queue" ](function(){ + + var opt = jQuery.extend({}, optall), p, + hidden = this.nodeType == 1 && jQuery(this).is(":hidden"), + self = this; + + for ( p in prop ) { + if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden ) + return opt.complete.call(this); + + if ( ( p == "height" || p == "width" ) && this.style ) { + // Store display property + opt.display = jQuery.css(this, "display"); + + // Make sure that nothing sneaks out + opt.overflow = this.style.overflow; + } + } + + if ( opt.overflow != null ) + this.style.overflow = "hidden"; + + opt.curAnim = jQuery.extend({}, prop); + + jQuery.each( prop, function(name, val){ + var e = new jQuery.fx( self, opt, name ); + + if ( /toggle|show|hide/.test(val) ) + e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop ); + else { + var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/), + start = e.cur(true) || 0; + + if ( parts ) { + var end = parseFloat(parts[2]), + unit = parts[3] || "px"; + + // We need to compute starting value + if ( unit != "px" ) { + self.style[ name ] = (end || 1) + unit; + start = ((end || 1) / e.cur(true)) * start; + self.style[ name ] = start + unit; + } + + // If a +=/-= token was provided, we're doing a relative animation + if ( parts[1] ) + end = ((parts[1] == "-=" ? -1 : 1) * end) + start; + + e.custom( start, end, unit ); + } else + e.custom( start, val, "" ); + } + }); + + // For JS strict compliance + return true; + }); + }, + + stop: function(clearQueue, gotoEnd){ + var timers = jQuery.timers; + + if (clearQueue) + this.queue([]); + + this.each(function(){ + // go in reverse order so anything added to the queue during the loop is ignored + for ( var i = timers.length - 1; i >= 0; i-- ) + if ( timers[i].elem == this ) { + if (gotoEnd) + // force the next step to be the last + timers[i](true); + timers.splice(i, 1); + } + }); + + // start the next in the queue if the last step wasn't forced + if (!gotoEnd) + this.dequeue(); + + return this; + } + +}); + +// Generate shortcuts for custom animations +jQuery.each({ + slideDown: genFx("show", 1), + slideUp: genFx("hide", 1), + slideToggle: genFx("toggle", 1), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" } +}, function( name, props ){ + jQuery.fn[ name ] = function( speed, callback ){ + return this.animate( props, speed, callback ); + }; +}); + +jQuery.extend({ + + speed: function(speed, easing, fn) { + var opt = typeof speed === "object" ? speed : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction(easing) && easing + }; + + opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : + jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default; + + // Queueing + opt.old = opt.complete; + opt.complete = function(){ + if ( opt.queue !== false ) + jQuery(this).dequeue(); + if ( jQuery.isFunction( opt.old ) ) + opt.old.call( this ); + }; + + return opt; + }, + + easing: { + linear: function( p, n, firstNum, diff ) { + return firstNum + diff * p; + }, + swing: function( p, n, firstNum, diff ) { + return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; + } + }, + + timers: [], + + fx: function( elem, options, prop ){ + this.options = options; + this.elem = elem; + this.prop = prop; + + if ( !options.orig ) + options.orig = {}; + } + +}); + +jQuery.fx.prototype = { + + // Simple function for setting a style value + update: function(){ + if ( this.options.step ) + this.options.step.call( this.elem, this.now, this ); + + (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); + + // Set display property to block for height/width animations + if ( ( this.prop == "height" || this.prop == "width" ) && this.elem.style ) + this.elem.style.display = "block"; + }, + + // Get the current size + cur: function(force){ + if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) + return this.elem[ this.prop ]; + + var r = parseFloat(jQuery.css(this.elem, this.prop, force)); + return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0; + }, + + // Start an animation from one number to another + custom: function(from, to, unit){ + this.startTime = now(); + this.start = from; + this.end = to; + this.unit = unit || this.unit || "px"; + this.now = this.start; + this.pos = this.state = 0; + + var self = this; + function t(gotoEnd){ + return self.step(gotoEnd); + } + + t.elem = this.elem; + + if ( t() && jQuery.timers.push(t) == 1 ) { + timerId = setInterval(function(){ + var timers = jQuery.timers; + + for ( var i = 0; i < timers.length; i++ ) + if ( !timers[i]() ) + timers.splice(i--, 1); + + if ( !timers.length ) { + clearInterval( timerId ); + } + }, 13); + } + }, + + // Simple 'show' function + show: function(){ + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop ); + this.options.show = true; + + // Begin the animation + // Make sure that we start at a small width/height to avoid any + // flash of content + this.custom(this.prop == "width" || this.prop == "height" ? 1 : 0, this.cur()); + + // Start by showing the element + jQuery(this.elem).show(); + }, + + // Simple 'hide' function + hide: function(){ + // Remember where we started, so that we can go back to it later + this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop ); + this.options.hide = true; + + // Begin the animation + this.custom(this.cur(), 0); + }, + + // Each step of an animation + step: function(gotoEnd){ + var t = now(); + + if ( gotoEnd || t >= this.options.duration + this.startTime ) { + this.now = this.end; + this.pos = this.state = 1; + this.update(); + + this.options.curAnim[ this.prop ] = true; + + var done = true; + for ( var i in this.options.curAnim ) + if ( this.options.curAnim[i] !== true ) + done = false; + + if ( done ) { + if ( this.options.display != null ) { + // Reset the overflow + this.elem.style.overflow = this.options.overflow; + + // Reset the display + this.elem.style.display = this.options.display; + if ( jQuery.css(this.elem, "display") == "none" ) + this.elem.style.display = "block"; + } + + // Hide the element if the "hide" operation was done + if ( this.options.hide ) + jQuery(this.elem).hide(); + + // Reset the properties, if the item has been hidden or shown + if ( this.options.hide || this.options.show ) + for ( var p in this.options.curAnim ) + jQuery.attr(this.elem.style, p, this.options.orig[p]); + + // Execute the complete function + this.options.complete.call( this.elem ); + } + + return false; + } else { + var n = t - this.startTime; + this.state = n / this.options.duration; + + // Perform the easing function, defaults to swing + this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration); + this.now = this.start + ((this.end - this.start) * this.pos); + + // Perform the next step of the animation + this.update(); + } + + return true; + } + +}; + +jQuery.extend( jQuery.fx, { + speeds:{ + slow: 600, + fast: 200, + // Default speed + _default: 400 + }, + step: { + + opacity: function(fx){ + jQuery.attr(fx.elem.style, "opacity", fx.now); + }, + + _default: function(fx){ + if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) + fx.elem.style[ fx.prop ] = fx.now + fx.unit; + else + fx.elem[ fx.prop ] = fx.now; + } + } +}); +if ( document.documentElement["getBoundingClientRect"] ) + jQuery.fn.offset = function() { + if ( !this[0] ) return { top: 0, left: 0 }; + if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] ); + var box = this[0].getBoundingClientRect(), doc = this[0].ownerDocument, body = doc.body, docElem = doc.documentElement, + clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, + top = box.top + (self.pageYOffset || jQuery.boxModel && docElem.scrollTop || body.scrollTop ) - clientTop, + left = box.left + (self.pageXOffset || jQuery.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft; + return { top: top, left: left }; + }; +else + jQuery.fn.offset = function() { + if ( !this[0] ) return { top: 0, left: 0 }; + if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] ); + jQuery.offset.initialized || jQuery.offset.initialize(); + + var elem = this[0], offsetParent = elem.offsetParent, prevOffsetParent = elem, + doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement, + body = doc.body, defaultView = doc.defaultView, + prevComputedStyle = defaultView.getComputedStyle(elem, null), + top = elem.offsetTop, left = elem.offsetLeft; + + while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { + computedStyle = defaultView.getComputedStyle(elem, null); + top -= elem.scrollTop, left -= elem.scrollLeft; + if ( elem === offsetParent ) { + top += elem.offsetTop, left += elem.offsetLeft; + if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName)) ) + top += parseInt( computedStyle.borderTopWidth, 10) || 0, + left += parseInt( computedStyle.borderLeftWidth, 10) || 0; + prevOffsetParent = offsetParent, offsetParent = elem.offsetParent; + } + if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) + top += parseInt( computedStyle.borderTopWidth, 10) || 0, + left += parseInt( computedStyle.borderLeftWidth, 10) || 0; + prevComputedStyle = computedStyle; + } + + if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) + top += body.offsetTop, + left += body.offsetLeft; + + if ( prevComputedStyle.position === "fixed" ) + top += Math.max(docElem.scrollTop, body.scrollTop), + left += Math.max(docElem.scrollLeft, body.scrollLeft); + + return { top: top, left: left }; + }; + +jQuery.offset = { + initialize: function() { + if ( this.initialized ) return; + var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop, + html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>'; + + rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' }; + for ( prop in rules ) container.style[prop] = rules[prop]; + + container.innerHTML = html; + body.insertBefore(container, body.firstChild); + innerDiv = container.firstChild, checkDiv = innerDiv.firstChild, td = innerDiv.nextSibling.firstChild.firstChild; + + this.doesNotAddBorder = (checkDiv.offsetTop !== 5); + this.doesAddBorderForTableAndCells = (td.offsetTop === 5); + + innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative'; + this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); + + body.style.marginTop = '1px'; + this.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0); + body.style.marginTop = bodyMarginTop; + + body.removeChild(container); + this.initialized = true; + }, + + bodyOffset: function(body) { + jQuery.offset.initialized || jQuery.offset.initialize(); + var top = body.offsetTop, left = body.offsetLeft; + if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) + top += parseInt( jQuery.curCSS(body, 'marginTop', true), 10 ) || 0, + left += parseInt( jQuery.curCSS(body, 'marginLeft', true), 10 ) || 0; + return { top: top, left: left }; + } +}; + + +jQuery.fn.extend({ + position: function() { + var left = 0, top = 0, results; + + if ( this[0] ) { + // Get *real* offsetParent + var offsetParent = this.offsetParent(), + + // Get correct offsets + offset = this.offset(), + parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset(); + + // Subtract element margins + // note: when an element has margin: auto the offsetLeft and marginLeft + // are the same in Safari causing offset.left to incorrectly be 0 + offset.top -= num( this, 'marginTop' ); + offset.left -= num( this, 'marginLeft' ); + + // Add offsetParent borders + parentOffset.top += num( offsetParent, 'borderTopWidth' ); + parentOffset.left += num( offsetParent, 'borderLeftWidth' ); + + // Subtract the two offsets + results = { + top: offset.top - parentOffset.top, + left: offset.left - parentOffset.left + }; + } + + return results; + }, + + offsetParent: function() { + var offsetParent = this[0].offsetParent || document.body; + while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') ) + offsetParent = offsetParent.offsetParent; + return jQuery(offsetParent); + } +}); + + +// Create scrollLeft and scrollTop methods +jQuery.each( ['Left', 'Top'], function(i, name) { + var method = 'scroll' + name; + + jQuery.fn[ method ] = function(val) { + if (!this[0]) return null; + + return val !== undefined ? + + // Set the scroll offset + this.each(function() { + this == window || this == document ? + window.scrollTo( + !i ? val : jQuery(window).scrollLeft(), + i ? val : jQuery(window).scrollTop() + ) : + this[ method ] = val; + }) : + + // Return the scroll offset + this[0] == window || this[0] == document ? + self[ i ? 'pageYOffset' : 'pageXOffset' ] || + jQuery.boxModel && document.documentElement[ method ] || + document.body[ method ] : + this[0][ method ]; + }; +}); +// Create innerHeight, innerWidth, outerHeight and outerWidth methods +jQuery.each([ "Height", "Width" ], function(i, name){ + + var tl = i ? "Left" : "Top", // top or left + br = i ? "Right" : "Bottom"; // bottom or right + + // innerHeight and innerWidth + jQuery.fn["inner" + name] = function(){ + return this[ name.toLowerCase() ]() + + num(this, "padding" + tl) + + num(this, "padding" + br); + }; + + // outerHeight and outerWidth + jQuery.fn["outer" + name] = function(margin) { + return this["inner" + name]() + + num(this, "border" + tl + "Width") + + num(this, "border" + br + "Width") + + (margin ? + num(this, "margin" + tl) + num(this, "margin" + br) : 0); + }; + + var type = name.toLowerCase(); + + jQuery.fn[ type ] = function( size ) { + // Get window width or height + return this[0] == window ? + // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode + document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] || + document.body[ "client" + name ] : + + // Get document width or height + this[0] == document ? + // Either scroll[Width/Height] or offset[Width/Height], whichever is greater + Math.max( + document.documentElement["client" + name], + document.body["scroll" + name], document.documentElement["scroll" + name], + document.body["offset" + name], document.documentElement["offset" + name] + ) : + + // Get or set width or height on the element + size === undefined ? + // Get width or height on the element + (this.length ? jQuery.css( this[0], type ) : null) : + + // Set the width or height on the element (default to pixels if value is unitless) + this.css( type, typeof size === "string" ? size : size + "px" ); + }; + +});})();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/ext/unicode.js Sun Jun 07 19:29:10 2009 -0700 @@ -0,0 +1,92 @@ +// Copyright (C) 2008 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/** + * @fileoverview + * Unicode character classes. + * + * @see http://www.w3.org/TR/2000/REC-xml-20001006#CharClasses + * @author mikesamuel@gmail.com + */ + + +/** @namespace */ +var unicode = {}; + +unicode.BASE_CHAR = ( + '\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF' + + '\u0100-\u0131\u0134-\u013E\u0141-\u0148\u014A-\u017E\u0180-\u01C3' + + '\u01CD-\u01F0\u01F4-\u01F5\u01FA-\u0217\u0250-\u02A8\u02BB-\u02C1' + + '\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03D6' + + '\u03DA\u03DC\u03DE\u03E0\u03E2-\u03F3\u0401-\u040C\u040E-\u044F' + + '\u0451-\u045C\u045E-\u0481\u0490-\u04C4\u04C7-\u04C8\u04CB-\u04CC' + + '\u04D0-\u04EB\u04EE-\u04F5\u04F8-\u04F9\u0531-\u0556\u0559' + + '\u0561-\u0586\u05D0-\u05EA\u05F0-\u05F2\u0621-\u063A\u0641-\u064A' + + '\u0671-\u06B7\u06BA-\u06BE\u06C0-\u06CE\u06D0-\u06D3\u06D5' + + '\u06E5-\u06E6\u0905-\u0939\u093D\u0958-\u0961\u0985-\u098C' + + '\u098F-\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9' + + '\u09DC-\u09DD\u09DF-\u09E1\u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10' + + '\u0A13-\u0A28\u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39' + + '\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8B\u0A8D\u0A8F-\u0A91' + + '\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3\u0AB5-\u0AB9\u0ABD\u0AE0' + + '\u0B05-\u0B0C\u0B0F-\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33' + + '\u0B36-\u0B39\u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B85-\u0B8A' + + '\u0B8E-\u0B90\u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F' + + '\u0BA3-\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB5\u0BB7-\u0BB9\u0C05-\u0C0C' + + '\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C60-\u0C61' + + '\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9' + + '\u0CDE\u0CE0-\u0CE1\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28' + + '\u0D2A-\u0D39\u0D60-\u0D61\u0E01-\u0E2E\u0E30\u0E32-\u0E33' + + '\u0E40-\u0E45\u0E81-\u0E82\u0E84\u0E87-\u0E88\u0E8A\u0E8D' + + '\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB' + + '\u0EAD-\u0EAE\u0EB0\u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0F40-\u0F47' + + '\u0F49-\u0F69\u10A0-\u10C5\u10D0-\u10F6\u1100\u1102-\u1103' + + '\u1105-\u1107\u1109\u110B-\u110C\u110E-\u1112\u113C\u113E\u1140' + + '\u114C\u114E\u1150\u1154-\u1155\u1159\u115F-\u1161\u1163\u1165' + + '\u1167\u1169\u116D-\u116E\u1172-\u1173\u1175\u119E\u11A8\u11AB' + + '\u11AE-\u11AF\u11B7-\u11B8\u11BA\u11BC-\u11C2\u11EB\u11F0\u11F9' + + '\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45' + + '\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D' + + '\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC' + + '\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC' + + '\u2126\u212A-\u212B\u212E\u2180-\u2182\u3041-\u3094\u30A1-\u30FA' + + '\u3105-\u312C\uAC00-\uD7A3'); +unicode.IDEOGRAPHIC = '\u4E00-\u9FA5\u3007\u3021-\u3029'; +unicode.LETTER = unicode.BASE_CHAR + unicode.IDEOGRAPHIC; +unicode.COMBINING_CHAR = ( + '\u0300-\u0345\u0360-\u0361\u0483-\u0486\u0591-\u05A1\u05A3-\u05B9' + + '\u05BB-\u05BD\u05BF\u05C1-\u05C2\u05C4\u064B-\u0652\u0670' + + '\u06D6-\u06DC\u06DD-\u06DF\u06E0-\u06E4\u06E7-\u06E8\u06EA-\u06ED' + + '\u0901-\u0903\u093C\u093E-\u094C\u094D\u0951-\u0954\u0962-\u0963' + + '\u0981-\u0983\u09BC\u09BE\u09BF\u09C0-\u09C4\u09C7-\u09C8' + + '\u09CB-\u09CD\u09D7\u09E2-\u09E3\u0A02\u0A3C\u0A3E\u0A3F' + + '\u0A40-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A70-\u0A71\u0A81-\u0A83' + + '\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0B01-\u0B03\u0B3C' + + '\u0B3E-\u0B43\u0B47-\u0B48\u0B4B-\u0B4D\u0B56-\u0B57\u0B82-\u0B83' + + '\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C01-\u0C03' + + '\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56\u0C82-\u0C83' + + '\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5-\u0CD6\u0D02-\u0D03' + + '\u0D3E-\u0D43\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0E31\u0E34-\u0E3A' + + '\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EC8-\u0ECD' + + '\u0F18-\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84' + + '\u0F86-\u0F8B\u0F90-\u0F95\u0F97\u0F99-\u0FAD\u0FB1-\u0FB7\u0FB9' + + '\u20D0-\u20DC\u20E1\u302A-\u302F\u3099\u309A'), +unicode.DIGIT = ( + '\u0030-\u0039\u0660-\u0669\u06F0-\u06F9\u0966-\u096F\u09E6-\u09EF' + + '\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0BE7-\u0BEF\u0C66-\u0C6F' + + '\u0CE6-\u0CEF\u0D66-\u0D6F\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F29'); +unicode.EXTENDER = ( + '\u00B7\u02D0\u02D1\u0387\u0640\u0E46\u0EC6\u3005\u3031-\u3035' + + '\u309D-\u309E\u30FC-\u30FE');
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/my-caja-module.co.js Sun Jun 07 19:29:10 2009 -0700 @@ -0,0 +1,18 @@ +{ + ___.loadModule({ + 'instantiate': function (___, IMPORTS___) { + var moduleResult___ = ___.NO_RESULT; + var register = ___.readImport(IMPORTS___, 'register'); + function blarg(x) { + return x + 1; + } + ___.func(blarg, 'blarg');; + moduleResult___ = register.CALL___(___.initializeMap([ + 'blarg', ___.primFreeze(blarg) ])); + return moduleResult___; + }, + 'cajolerName': 'com.google.caja', + 'cajolerVersion': '3532', + 'cajoledDate': 1244334865384 + }); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/my-caja-module.js Sun Jun 07 19:29:10 2009 -0700 @@ -0,0 +1,5 @@ +function blarg(x) { + return x + 1; +} + +register({blarg: blarg});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pavement.py Sun Jun 07 19:29:10 2009 -0700 @@ -0,0 +1,10 @@ +import subprocess +from paver.easy import * + +@task +def auto(options): + subprocess.call( + ["../google-caja-read-only/bin/cajole_html", + "-i", "js/my-caja-module.js", + "-o", "js/my-caja-module.co.js"] + )