From: John Resig Date: Tue, 18 Jan 2011 19:20:05 +0000 (-0500) Subject: Merge branch 'master' of https://github.com/russtacular/jquery into russtacular-master X-Git-Url: http://git.asbjorn.biz/?p=jquery.git;a=commitdiff_plain;h=c1d719b580ea78c33961113030d7fa25bcc98e6f;hp=50170e618059d10132a5319c64660a631b095f44 Merge branch 'master' of https://github.com/russtacular/jquery into russtacular-master --- diff --git a/.gitattributes b/.gitattributes index bcb36db..d6dc470 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -* crlf=input +* eol=lf +*.jar binary diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt index ea33691..5327046 100644 --- a/MIT-LICENSE.txt +++ b/MIT-LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2010 John Resig, http://jquery.com/ +Copyright (c) 2011 John Resig, http://jquery.com/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/Makefile b/Makefile index 0dae732..2e6848d 100644 --- a/Makefile +++ b/Makefile @@ -7,11 +7,8 @@ BUILD_DIR = build PREFIX = . DIST_DIR = ${PREFIX}/dist -RHINO ?= java -jar ${BUILD_DIR}/js.jar - -CLOSURE_COMPILER = ${BUILD_DIR}/google-compiler-20100917.jar - -MINJAR ?= java -jar ${CLOSURE_COMPILER} +JS_ENGINE ?= `which node nodejs` +COMPILER = ${JS_ENGINE} ${BUILD_DIR}/uglify.js --unsafe BASE_FILES = ${SRC_DIR}/core.js\ ${SRC_DIR}/support.js\ @@ -24,6 +21,9 @@ BASE_FILES = ${SRC_DIR}/core.js\ ${SRC_DIR}/manipulation.js\ ${SRC_DIR}/css.js\ ${SRC_DIR}/ajax.js\ + ${SRC_DIR}/ajax/jsonp.js\ + ${SRC_DIR}/ajax/script.js\ + ${SRC_DIR}/ajax/xhr.js\ ${SRC_DIR}/effects.js\ ${SRC_DIR}/offset.js\ ${SRC_DIR}/dimensions.js @@ -75,13 +75,13 @@ init: jquery: ${JQ} jq: ${JQ} -${JQ}: ${MODULES} ${DIST_DIR} +${JQ}: ${MODULES} | ${DIST_DIR} @@echo "Building" ${JQ} @@cat ${MODULES} | \ sed 's/.function..jQuery...{//' | \ sed 's/}...jQuery..;//' | \ - sed 's/Date:./&'"${DATE}"'/' | \ + sed 's/@DATE/'"${DATE}"'/' | \ ${VER} > ${JQ}; ${SRC_DIR}/selector.js: ${SIZZLE_DIR}/sizzle.js @@ -90,17 +90,13 @@ ${SRC_DIR}/selector.js: ${SIZZLE_DIR}/sizzle.js lint: ${JQ} @@echo "Checking jQuery against JSLint..." - @@${RHINO} build/jslint-check.js + @@${JS_ENGINE} build/jslint-check.js min: ${JQ_MIN} ${JQ_MIN}: ${JQ} @@echo "Building" ${JQ_MIN} - - @@head -15 ${JQ} > ${JQ_MIN} - @@${MINJAR} --js ${JQ} --warning_level QUIET --js_output_file ${JQ_MIN}.tmp - @@cat ${JQ_MIN}.tmp >> ${JQ_MIN} - @@rm -f ${JQ_MIN}.tmp + @@${COMPILER} ${JQ} > ${JQ_MIN} clean: @@echo "Removing Distribution directory:" ${DIST_DIR} diff --git a/README.md b/README.md index 384e2d1..d56576c 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,64 @@ [jQuery](http://jquery.com/) - New Wave Javascript -================================ +================================================== What you need to build your own jQuery ---------------------------------------- -* Make sure that you have Java installed (if you want to build a minified version of jQuery). -If not, [go to this page](http://java.sun.com/javase/downloads/index.jsp) and download "Java Runtime Environment (JRE) 5.0" +-------------------------------------- -Build Options --------------- +In order to build jQuery, you need to have GNU make 3.8 or later, Node.js 0.2 or later, and git 1.7 or later. +(Earlier versions might work OK, but are not tested.) -You now have **three** options for building jQuery: +Windows users have two options: -* **`make`**: If you have access to common UNIX commands (like `make`, `mkdir`, `rm`, `cat`, and `echo`) then simply type `make` to build all the components. +1. Install [msysgit](https://code.google.com/p/msysgit/) (Full installer for official Git), + [GNU make for Windows](http://gnuwin32.sourceforge.net/packages/make.htm), and a + [binary version of Node.js](http://node-js.prcn.co.cc/). Make sure all three packages are installed to the same + location (by default, this is C:\Program Files\Git). +2. Install [Cygwin](http://cygwin.com/) (make sure you install the git, make, and which packages), then either follow + the [Node.js build instructions](https://github.com/ry/node/wiki/Building-node.js-on-Cygwin-%28Windows%29) or install + the [binary version of Node.js](http://node-js.prcn.co.cc/). -* **`rake`**: If you have Ruby Rake installed (on either Windows or UNIX/Linux), you can simply type `rake` to build all the components. +Mac OS users should install Xcode (comes on your Mac OS install DVD, or downloadable from +[Apple's Xcode site](http://developer.apple.com/technologies/xcode.html)) and +[http://mxcl.github.com/homebrew/](Homebrew). Once Homebrew is installed, run `brew install git` to install git, +and `brew install node` to install Node.js. -* **`ant`**: If you have Ant installed (or are on Windows and don't have access to make). You can download Ant from here: [http://ant.apache.org/bindownload.cgi]. +Linux/BSD users should use their appropriate package managers to install make, git, and node, or build from source +if you swing that way. Easy-peasy. -How to build your own jQuery ------------------------------ - -*Note: If you are using either `rake` or `ant`, substitute your chosen method in place of `make` in the examples below. They work identically for all intents and purposes. Quick reference is also available for `rake` by typing `rake -T` in the `jquery` directory.* - -In the main directory of the distribution (the one that this file is in), type -the following to make all versions of jQuery: - - make - -*Here are the individual items that are buildable from the Makefile:* - - make init -Pull in all the external dependencies (QUnit, Sizzle) for the project. - - make jquery - -The standard, uncompressed, jQuery code. -Makes: `./dist/jquery.js` - - make min - -A compressed version of jQuery (made the Closure Compiler). -Makes: `./dist/jquery.min.js` +How to build your own jQuery +---------------------------- - make lint +First, clone a copy of the main jQuery git repo by running `git clone git://github.com/jquery/jquery.git`. -Tests a build of jQuery against JSLint, looking for potential errors or bits of confusing code. +Then, to get a complete, minified, jslinted version of jQuery, simply `cd` to the `jquery` directory and type +`make`. If you don't have Node installed and/or want to make a basic, uncompressed, unlinted version of jQuery, use +`make jquery` instead of `make`. - make selector +The built version of jQuery will be put in the `dist/` subdirectory. -Builds the selector library for jQuery from Sizzle. -Makes: `./src/selector.js` +To remove all built files, run `make clean`. -Finally, you can remove all the built files using the command: - - make clean Building to a different directory ----------------------------------- +--------------------------------- -If you want to build jQuery to a directory that is different from the default location, you can... +If you want to build jQuery to a directory that is different from the default location, you can specify the PREFIX +directory: `make PREFIX=/home/jquery/test/ [command]` -**Make only:** Specify the PREFIX directory, for example: - - make PREFIX=/home/john/test/ [command] - -With this example, the output files would be contained in `/home/john/test/dist/` +With this example, the output files would end up in `/home/jquery/test/dist/`. -**Rake only:** Define the DIST_DIR directory, for example: - rake DIST_DIR=/home/john/test/ [command] - -With this example, the output files would be contained in `/home/john/test/` +Troubleshooting +--------------- -*In both examples, `[command]` is optional.* +Sometimes, the various git repositories get into an inconsistent state where builds don't complete properly +(usually this results in the jquery.js or jquery.min.js being 0 bytes). If this happens, run `make clean`, then +run `make` again. -**Ant only:** You cannot currently build to another directory when using Ant. Questions? ---------- -If you have any questions, please feel free to ask them on the Developing jQuery Core -forum, which can be found here: -[http://forum.jquery.com/developing-jquery-core](http://forum.jquery.com/developing-jquery-core) +If you have any questions, please feel free to ask on the +[Developing jQuery Core forum](http://forum.jquery.com/developing-jquery-core) or in #jquery on irc.freenode.net. diff --git a/Rakefile b/Rakefile deleted file mode 100644 index d0d8f66..0000000 --- a/Rakefile +++ /dev/null @@ -1,138 +0,0 @@ -prefix = File.dirname( __FILE__ ) - -# Directory variables -src_dir = File.join( prefix, 'src' ) -build_dir = File.join( prefix, 'build' ) -test_dir = File.join( prefix, 'test' ) - -# A different destination directory can be set by -# setting DIST_DIR before calling rake -dist_dir = ENV['DIST_DIR'] || File.join( prefix, 'dist' ) - -base_files = %w{intro core support data queue attributes event selector traversing manipulation css ajax effects offset dimensions outro}.map { |js| File.join( src_dir, "#{js}.js" ) } - -# Sizzle, QUnit and jQuery files/dirs -sizzle_dir = File.join( src_dir, "sizzle" ) -sizzle = File.join( sizzle_dir, "sizzle.js" ) -selector = File.join( src_dir, "selector.js" ) - -qunit_dir = File.join( test_dir, "qunit" ) -qunit = File.join( qunit_dir, "qunit", "qunit.js" ) - -jq = File.join( dist_dir, "jquery.js" ) -jq_min = File.join( dist_dir, "jquery.min.js" ) - -# General Variables -date = `git log -1`[/^Date:\s+(.+)$/, 1] -version = File.read( File.join( prefix, 'version.txt' ) ).strip - -# Build tools -rhino = "java -jar #{build_dir}/js.jar" -minfier = "java -jar #{build_dir}/google-compiler-20100917.jar" - -# Turn off output other than needed from `sh` and file commands -verbose(false) - -# Tasks -task :default => "all" - -desc "Builds jQuery; Tests with JSLint; Minifies jQuery" -task :all => [:jquery, :lint, :min] do - puts "jQuery build complete." -end - -desc "Builds jQuery: jquery.js (Default task)" -task :jquery => [:selector, jq] - -desc "Builds a minified version of jQuery: jquery.min.js" -task :min => jq_min - - -task :init => [sizzle, qunit] do - sizzle_git = File.join(sizzle_dir, '.git') - qunit_git = File.join(qunit_dir, '.git') - - puts "Updating SizzleJS with latest..." - sh "git --git-dir=#{sizzle_git} pull -q origin master" - - puts "Updating QUnit with latest..." - sh "git --git-dir=#{qunit_git} pull -q origin master" -end - -desc "Removes dist folder, selector.js, and Sizzle/QUnit" -task :clean do - puts "Removing Distribution directory: #{dist_dir}..." - rm_rf dist_dir - - puts "Removing built copy of Sizzle..." - rm_rf selector - - puts "Removing cloned directories..." - rm_rf qunit_dir - rm_rf sizzle_dir -end - -desc "Rebuilds selector.js from SizzleJS" -task :selector => [:init, selector] - -desc "Tests built jquery.js against JSLint" -task :lint => jq do - puts "Checking jQuery against JSLint..." - sh "#{rhino} " + File.join(build_dir, 'jslint-check.js') -end - - -# File and Directory Dependencies -directory dist_dir - -file jq => [dist_dir, base_files].flatten do - puts "Building jquery.js..." - - File.open(jq, 'w') do |f| - f.write cat(base_files).gsub(/(Date:.)/, "\\1#{date}" ).gsub(/@VERSION/, version) - end -end - -file jq_min => jq do - puts "Building jquery.min.js..." - - sh "#{minfier} --js #{jq} --warning_level QUIET --js_output_file #{jq_min}" - - min = File.read( jq_min ) - - # Equivilent of "head" - File.open(jq_min, 'w') do |f| - f.write File.readlines(jq)[0..14].join() - f.write min - end -end - -file selector => [sizzle, :init] do - puts "Building selector code from Sizzle..." - - File.open(selector, 'w') do |f| - f.write File.read(sizzle).gsub( - /^.+EXPOSE$\n/, - '\0' + File.read( File.join( src_dir, 'sizzle-jquery.js' )) - ).gsub( - /^window.Sizzle.+$\n/, '' - ) - end -end - -file sizzle do - puts "Retrieving SizzleJS from Github..." - sh "git clone git://github.com/jeresig/sizzle.git #{sizzle_dir}" -end - -file qunit do - puts "Retrieving QUnit from Github..." - sh "git clone git://github.com/jquery/qunit.git #{qunit_dir}" -end - - -def cat( files ) - files.map do |file| - File.read(file) - end.join('') -end diff --git a/build.xml b/build.xml deleted file mode 100644 index c6be9f9..0000000 --- a/build.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build/google-compiler-20100917.jar b/build/google-compiler-20100917.jar deleted file mode 100644 index 4dfa5ad..0000000 Binary files a/build/google-compiler-20100917.jar and /dev/null differ diff --git a/build/js.jar b/build/js.jar deleted file mode 100644 index a76cc7c..0000000 Binary files a/build/js.jar and /dev/null differ diff --git a/build/jslint-check.js b/build/jslint-check.js index 976975a..72d6701 100644 --- a/build/jslint-check.js +++ b/build/jslint-check.js @@ -1,6 +1,6 @@ -load("build/jslint.js"); - -var src = readFile("dist/jquery.js"); +var JSLINT = require("./lib/jslint").JSLINT, + print = require("sys").print, + src = require("fs").readFileSync("dist/jquery.js", "utf8"); JSLINT(src, { evil: true, forin: true, maxerr: 100 }); @@ -29,8 +29,8 @@ for ( var i = 0; i < e.length; i++ ) { } if ( found > 0 ) { - print( "\n" + found + " Error(s) found." ); + print( "\n" + found + " Error(s) found.\n" ); } else { - print( "JSLint check passed." ); + print( "JSLint check passed.\n" ); } diff --git a/build/jslint.js b/build/lib/jslint.js similarity index 99% rename from build/jslint.js rename to build/lib/jslint.js index f629fec..f563292 100644 --- a/build/jslint.js +++ b/build/lib/jslint.js @@ -5495,6 +5495,10 @@ loop: for (;;) { itself.edition = '2010-02-20'; + if (typeof exports !== "undefined") { + exports.JSLINT = itself; + } + return itself; -}()); \ No newline at end of file +}()); diff --git a/build/lib/parse-js.js b/build/lib/parse-js.js new file mode 100644 index 0000000..7e4fd0e --- /dev/null +++ b/build/lib/parse-js.js @@ -0,0 +1,1239 @@ +/*********************************************************************** + + A JavaScript tokenizer / parser / beautifier / compressor. + + This version is suitable for Node.js. With minimal changes (the + exports stuff) it should work on any JS platform. + + This file contains the tokenizer/parser. It is a port to JavaScript + of parse-js [1], a JavaScript parser library written in Common Lisp + by Marijn Haverbeke. Thank you Marijn! + + [1] http://marijn.haverbeke.nl/parse-js/ + + Exported functions: + + - tokenizer(code) -- returns a function. Call the returned + function to fetch the next token. + + - parse(code) -- returns an AST of the given JavaScript code. + + -------------------------------- (C) --------------------------------- + + Author: Mihai Bazon + + http://mihai.bazon.net/blog + + Distributed under the BSD license: + + Copyright 2010 (c) Mihai Bazon + Based on parse-js (http://marijn.haverbeke.nl/parse-js/). + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + ***********************************************************************/ + +/* -----[ Tokenizer (constants) ]----- */ + +var KEYWORDS = array_to_hash([ + "break", + "case", + "catch", + "const", + "continue", + "default", + "delete", + "do", + "else", + "finally", + "for", + "function", + "if", + "in", + "instanceof", + "new", + "return", + "switch", + "throw", + "try", + "typeof", + "var", + "void", + "while", + "with" +]); + +var RESERVED_WORDS = array_to_hash([ + "abstract", + "boolean", + "byte", + "char", + "class", + "debugger", + "double", + "enum", + "export", + "extends", + "final", + "float", + "goto", + "implements", + "import", + "int", + "interface", + "long", + "native", + "package", + "private", + "protected", + "public", + "short", + "static", + "super", + "synchronized", + "throws", + "transient", + "volatile" +]); + +var KEYWORDS_BEFORE_EXPRESSION = array_to_hash([ + "return", + "new", + "delete", + "throw", + "else", + "case" +]); + +var KEYWORDS_ATOM = array_to_hash([ + "false", + "null", + "true", + "undefined" +]); + +var OPERATOR_CHARS = array_to_hash(characters("+-*&%=<>!?|~^")); + +var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i; +var RE_OCT_NUMBER = /^0[0-7]+$/; +var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i; + +var OPERATORS = array_to_hash([ + "in", + "instanceof", + "typeof", + "new", + "void", + "delete", + "++", + "--", + "+", + "-", + "!", + "~", + "&", + "|", + "^", + "*", + "/", + "%", + ">>", + "<<", + ">>>", + "<", + ">", + "<=", + ">=", + "==", + "===", + "!=", + "!==", + "?", + "=", + "+=", + "-=", + "/=", + "*=", + "%=", + ">>=", + "<<=", + ">>>=", + "~=", + "%=", + "|=", + "^=", + "&=", + "&&", + "||" +]); + +var WHITESPACE_CHARS = array_to_hash(characters(" \n\r\t")); + +var PUNC_BEFORE_EXPRESSION = array_to_hash(characters("[{}(,.;:")); + +var PUNC_CHARS = array_to_hash(characters("[]{}(),;:")); + +var REGEXP_MODIFIERS = array_to_hash(characters("gmsiy")); + +/* -----[ Tokenizer ]----- */ + +function is_alphanumeric_char(ch) { + ch = ch.charCodeAt(0); + return (ch >= 48 && ch <= 57) || + (ch >= 65 && ch <= 90) || + (ch >= 97 && ch <= 122); +}; + +function is_identifier_char(ch) { + return is_alphanumeric_char(ch) || ch == "$" || ch == "_"; +}; + +function is_digit(ch) { + ch = ch.charCodeAt(0); + return ch >= 48 && ch <= 57; +}; + +function parse_js_number(num) { + if (RE_HEX_NUMBER.test(num)) { + return parseInt(num.substr(2), 16); + } else if (RE_OCT_NUMBER.test(num)) { + return parseInt(num.substr(1), 8); + } else if (RE_DEC_NUMBER.test(num)) { + return parseFloat(num); + } +}; + +function JS_Parse_Error(message, line, col, pos) { + this.message = message; + this.line = line; + this.col = col; + this.pos = pos; + try { + ({})(); + } catch(ex) { + this.stack = ex.stack; + }; +}; + +JS_Parse_Error.prototype.toString = function() { + return this.message + " (line: " + this.line + ", col: " + this.col + ", pos: " + this.pos + ")" + "\n\n" + this.stack; +}; + +function js_error(message, line, col, pos) { + throw new JS_Parse_Error(message, line, col, pos); +}; + +function is_token(token, type, val) { + return token.type == type && (val == null || token.value == val); +}; + +var EX_EOF = {}; + +function tokenizer($TEXT, skip_comments) { + + var S = { + text : $TEXT.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''), + pos : 0, + tokpos : 0, + line : 0, + tokline : 0, + col : 0, + tokcol : 0, + newline_before : false, + regex_allowed : false + }; + + function peek() { return S.text.charAt(S.pos); }; + + function next(signal_eof) { + var ch = S.text.charAt(S.pos++); + if (signal_eof && !ch) + throw EX_EOF; + if (ch == "\n") { + S.newline_before = true; + ++S.line; + S.col = 0; + } else { + ++S.col; + } + return ch; + }; + + function eof() { + return !S.peek(); + }; + + function find(what, signal_eof) { + var pos = S.text.indexOf(what, S.pos); + if (signal_eof && pos == -1) throw EX_EOF; + return pos; + }; + + function start_token() { + S.tokline = S.line; + S.tokcol = S.col; + S.tokpos = S.pos; + }; + + function token(type, value) { + S.regex_allowed = ((type == "operator" && !HOP(UNARY_POSTFIX, value)) || + (type == "keyword" && HOP(KEYWORDS_BEFORE_EXPRESSION, value)) || + (type == "punc" && HOP(PUNC_BEFORE_EXPRESSION, value))); + var ret = { + type : type, + value : value, + line : S.tokline, + col : S.tokcol, + pos : S.tokpos, + nlb : S.newline_before + }; + S.newline_before = false; + return ret; + }; + + function skip_whitespace() { + while (HOP(WHITESPACE_CHARS, peek())) + next(); + }; + + function read_while(pred) { + var ret = "", ch = peek(), i = 0; + while (ch && pred(ch, i++)) { + ret += next(); + ch = peek(); + } + return ret; + }; + + function parse_error(err) { + js_error(err, S.tokline, S.tokcol, S.tokpos); + }; + + function read_num(prefix) { + var has_e = false, after_e = false, has_x = false, has_dot = prefix == "."; + var num = read_while(function(ch, i){ + if (ch == "x" || ch == "X") { + if (has_x) return false; + return has_x = true; + } + if (!has_x && (ch == "E" || ch == "e")) { + if (has_e) return false; + return has_e = after_e = true; + } + if (ch == "-") { + if (after_e || (i == 0 && !prefix)) return true; + return false; + } + if (ch == "+") return after_e; + after_e = false; + if (ch == ".") { + if (!has_dot) + return has_dot = true; + return false; + } + return is_alphanumeric_char(ch); + }); + if (prefix) + num = prefix + num; + var valid = parse_js_number(num); + if (!isNaN(valid)) { + return token("num", valid); + } else { + parse_error("Invalid syntax: " + num); + } + }; + + function read_escaped_char() { + var ch = next(true); + switch (ch) { + case "n" : return "\n"; + case "r" : return "\r"; + case "t" : return "\t"; + case "b" : return "\b"; + case "v" : return "\v"; + case "f" : return "\f"; + case "0" : return "\0"; + case "x" : return String.fromCharCode(hex_bytes(2)); + case "u" : return String.fromCharCode(hex_bytes(4)); + default : return ch; + } + }; + + function hex_bytes(n) { + var num = 0; + for (; n > 0; --n) { + var digit = parseInt(next(true), 16); + if (isNaN(digit)) + parse_error("Invalid hex-character pattern in string"); + num = (num << 4) | digit; + } + return num; + }; + + function read_string() { + return with_eof_error("Unterminated string constant", function(){ + var quote = next(), ret = ""; + for (;;) { + var ch = next(true); + if (ch == "\\") ch = read_escaped_char(); + else if (ch == quote) break; + ret += ch; + } + return token("string", ret); + }); + }; + + function read_line_comment() { + next(); + var i = find("\n"), ret; + if (i == -1) { + ret = S.text.substr(S.pos); + S.pos = S.text.length; + } else { + ret = S.text.substring(S.pos, i); + S.pos = i; + } + return token("comment1", ret); + }; + + function read_multiline_comment() { + next(); + return with_eof_error("Unterminated multiline comment", function(){ + var i = find("*/", true), + text = S.text.substring(S.pos, i), + tok = token("comment2", text); + S.pos = i + 2; + S.line += text.split("\n").length - 1; + S.newline_before = text.indexOf("\n") >= 0; + return tok; + }); + }; + + function read_regexp() { + return with_eof_error("Unterminated regular expression", function(){ + var prev_backslash = false, regexp = "", ch, in_class = false; + while ((ch = next(true))) if (prev_backslash) { + regexp += "\\" + ch; + prev_backslash = false; + } else if (ch == "[") { + in_class = true; + regexp += ch; + } else if (ch == "]" && in_class) { + in_class = false; + regexp += ch; + } else if (ch == "/" && !in_class) { + break; + } else if (ch == "\\") { + prev_backslash = true; + } else { + regexp += ch; + } + var mods = read_while(function(ch){ + return HOP(REGEXP_MODIFIERS, ch); + }); + return token("regexp", [ regexp, mods ]); + }); + }; + + function read_operator(prefix) { + function grow(op) { + var bigger = op + peek(); + if (HOP(OPERATORS, bigger)) { + next(); + return grow(bigger); + } else { + return op; + } + }; + return token("operator", grow(prefix || next())); + }; + + var handle_slash = skip_comments ? function() { + next(); + var regex_allowed = S.regex_allowed; + switch (peek()) { + case "/": read_line_comment(); S.regex_allowed = regex_allowed; return next_token(); + case "*": read_multiline_comment(); S.regex_allowed = regex_allowed; return next_token(); + } + return S.regex_allowed ? read_regexp() : read_operator("/"); + } : function() { + next(); + switch (peek()) { + case "/": return read_line_comment(); + case "*": return read_multiline_comment(); + } + return S.regex_allowed ? read_regexp() : read_operator("/"); + }; + + function handle_dot() { + next(); + return is_digit(peek()) + ? read_num(".") + : token("punc", "."); + }; + + function read_word() { + var word = read_while(is_identifier_char); + return !HOP(KEYWORDS, word) + ? token("name", word) + : HOP(OPERATORS, word) + ? token("operator", word) + : HOP(KEYWORDS_ATOM, word) + ? token("atom", word) + : token("keyword", word); + }; + + function with_eof_error(eof_error, cont) { + try { + return cont(); + } catch(ex) { + if (ex === EX_EOF) parse_error(eof_error); + else throw ex; + } + }; + + function next_token(force_regexp) { + if (force_regexp) + return read_regexp(); + skip_whitespace(); + start_token(); + var ch = peek(); + if (!ch) return token("eof"); + if (is_digit(ch)) return read_num(); + if (ch == '"' || ch == "'") return read_string(); + if (HOP(PUNC_CHARS, ch)) return token("punc", next()); + if (ch == ".") return handle_dot(); + if (ch == "/") return handle_slash(); + if (HOP(OPERATOR_CHARS, ch)) return read_operator(); + if (is_identifier_char(ch)) return read_word(); + parse_error("Unexpected character '" + ch + "'"); + }; + + next_token.context = function(nc) { + if (nc) S = nc; + return S; + }; + + return next_token; + +}; + +/* -----[ Parser (constants) ]----- */ + +var UNARY_PREFIX = array_to_hash([ + "typeof", + "void", + "delete", + "--", + "++", + "!", + "~", + "-", + "+" +]); + +var UNARY_POSTFIX = array_to_hash([ "--", "++" ]); + +var ASSIGNMENT = (function(a, ret, i){ + while (i < a.length) { + ret[a[i]] = a[i].substr(0, a[i].length - 1); + i++; + } + return ret; +})( + ["+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "~=", "%=", "|=", "^=", "&="], + { "=": true }, + 0 +); + +var PRECEDENCE = (function(a, ret){ + for (var i = 0, n = 1; i < a.length; ++i, ++n) { + var b = a[i]; + for (var j = 0; j < b.length; ++j) { + ret[b[j]] = n; + } + } + return ret; +})( + [ + ["||"], + ["&&"], + ["|"], + ["^"], + ["&"], + ["==", "===", "!=", "!=="], + ["<", ">", "<=", ">=", "in", "instanceof"], + [">>", "<<", ">>>"], + ["+", "-"], + ["*", "/", "%"] + ], + {} +); + +var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]); + +var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]); + +/* -----[ Parser ]----- */ + +function NodeWithToken(str, start, end) { + this.name = str; + this.start = start; + this.end = end; +}; + +NodeWithToken.prototype.toString = function() { return this.name; }; + +function parse($TEXT, strict_mode, embed_tokens) { + + var S = { + input: tokenizer($TEXT, true), + token: null, + prev: null, + peeked: null, + in_function: 0, + in_loop: 0, + labels: [] + }; + + S.token = next(); + + function is(type, value) { + return is_token(S.token, type, value); + }; + + function peek() { return S.peeked || (S.peeked = S.input()); }; + + function next() { + S.prev = S.token; + if (S.peeked) { + S.token = S.peeked; + S.peeked = null; + } else { + S.token = S.input(); + } + return S.token; + }; + + function prev() { + return S.prev; + }; + + function croak(msg, line, col, pos) { + var ctx = S.input.context(); + js_error(msg, + line != null ? line : ctx.tokline, + col != null ? col : ctx.tokcol, + pos != null ? pos : ctx.tokpos); + }; + + function token_error(token, msg) { + croak(msg, token.line, token.col); + }; + + function unexpected(token) { + if (token == null) + token = S.token; + token_error(token, "Unexpected token: " + token.type + " (" + token.value + ")"); + }; + + function expect_token(type, val) { + if (is(type, val)) { + return next(); + } + token_error(S.token, "Unexpected token " + S.token.type + ", expected " + type); + }; + + function expect(punc) { return expect_token("punc", punc); }; + + function can_insert_semicolon() { + return !strict_mode && ( + S.token.nlb || is("eof") || is("punc", "}") + ); + }; + + function semicolon() { + if (is("punc", ";")) next(); + else if (!can_insert_semicolon()) unexpected(); + }; + + function as() { + return slice(arguments); + }; + + function parenthesised() { + expect("("); + var ex = expression(); + expect(")"); + return ex; + }; + + function add_tokens(str, start, end) { + return new NodeWithToken(str, start, end); + }; + + var statement = embed_tokens ? function() { + var start = S.token; + var stmt = $statement(); + stmt[0] = add_tokens(stmt[0], start, prev()); + return stmt; + } : $statement; + + function $statement() { + if (is("operator", "/")) { + S.peeked = null; + S.token = S.input(true); // force regexp + } + switch (S.token.type) { + case "num": + case "string": + case "regexp": + case "operator": + case "atom": + return simple_statement(); + + case "name": + return is_token(peek(), "punc", ":") + ? labeled_statement(prog1(S.token.value, next, next)) + : simple_statement(); + + case "punc": + switch (S.token.value) { + case "{": + return as("block", block_()); + case "[": + case "(": + return simple_statement(); + case ";": + next(); + return as("block"); + default: + unexpected(); + } + + case "keyword": + switch (prog1(S.token.value, next)) { + case "break": + return break_cont("break"); + + case "continue": + return break_cont("continue"); + + case "debugger": + semicolon(); + return as("debugger"); + + case "do": + return (function(body){ + expect_token("keyword", "while"); + return as("do", prog1(parenthesised, semicolon), body); + })(in_loop(statement)); + + case "for": + return for_(); + + case "function": + return function_(true); + + case "if": + return if_(); + + case "return": + if (S.in_function == 0) + croak("'return' outside of function"); + return as("return", + is("punc", ";") + ? (next(), null) + : can_insert_semicolon() + ? null + : prog1(expression, semicolon)); + + case "switch": + return as("switch", parenthesised(), switch_block_()); + + case "throw": + return as("throw", prog1(expression, semicolon)); + + case "try": + return try_(); + + case "var": + return prog1(var_, semicolon); + + case "const": + return prog1(const_, semicolon); + + case "while": + return as("while", parenthesised(), in_loop(statement)); + + case "with": + return as("with", parenthesised(), statement()); + + default: + unexpected(); + } + } + }; + + function labeled_statement(label) { + S.labels.push(label); + var start = S.token, stat = statement(); + if (strict_mode && !HOP(STATEMENTS_WITH_LABELS, stat[0])) + unexpected(start); + S.labels.pop(); + return as("label", label, stat); + }; + + function simple_statement() { + return as("stat", prog1(expression, semicolon)); + }; + + function break_cont(type) { + var name = is("name") ? S.token.value : null; + if (name != null) { + next(); + if (!member(name, S.labels)) + croak("Label " + name + " without matching loop or statement"); + } + else if (S.in_loop == 0) + croak(type + " not inside a loop or switch"); + semicolon(); + return as(type, name); + }; + + function for_() { + expect("("); + var has_var = is("keyword", "var"); + if (has_var) + next(); + if (is("name") && is_token(peek(), "operator", "in")) { + // for (i in foo) + var name = S.token.value; + next(); next(); + var obj = expression(); + expect(")"); + return as("for-in", has_var, name, obj, in_loop(statement)); + } else { + // classic for + var init = is("punc", ";") ? null : has_var ? var_() : expression(); + expect(";"); + var test = is("punc", ";") ? null : expression(); + expect(";"); + var step = is("punc", ")") ? null : expression(); + expect(")"); + return as("for", init, test, step, in_loop(statement)); + } + }; + + function function_(in_statement) { + var name = is("name") ? prog1(S.token.value, next) : null; + if (in_statement && !name) + unexpected(); + expect("("); + return as(in_statement ? "defun" : "function", + name, + // arguments + (function(first, a){ + while (!is("punc", ")")) { + if (first) first = false; else expect(","); + if (!is("name")) unexpected(); + a.push(S.token.value); + next(); + } + next(); + return a; + })(true, []), + // body + (function(){ + ++S.in_function; + var loop = S.in_loop; + S.in_loop = 0; + var a = block_(); + --S.in_function; + S.in_loop = loop; + return a; + })()); + }; + + function if_() { + var cond = parenthesised(), body = statement(), belse; + if (is("keyword", "else")) { + next(); + belse = statement(); + } + return as("if", cond, body, belse); + }; + + function block_() { + expect("{"); + var a = []; + while (!is("punc", "}")) { + if (is("eof")) unexpected(); + a.push(statement()); + } + next(); + return a; + }; + + var switch_block_ = curry(in_loop, function(){ + expect("{"); + var a = [], cur = null; + while (!is("punc", "}")) { + if (is("eof")) unexpected(); + if (is("keyword", "case")) { + next(); + cur = []; + a.push([ expression(), cur ]); + expect(":"); + } + else if (is("keyword", "default")) { + next(); + expect(":"); + cur = []; + a.push([ null, cur ]); + } + else { + if (!cur) unexpected(); + cur.push(statement()); + } + } + next(); + return a; + }); + + function try_() { + var body = block_(), bcatch, bfinally; + if (is("keyword", "catch")) { + next(); + expect("("); + if (!is("name")) + croak("Name expected"); + var name = S.token.value; + next(); + expect(")"); + bcatch = [ name, block_() ]; + } + if (is("keyword", "finally")) { + next(); + bfinally = block_(); + } + if (!bcatch && !bfinally) + croak("Missing catch/finally blocks"); + return as("try", body, bcatch, bfinally); + }; + + function vardefs() { + var a = []; + for (;;) { + if (!is("name")) + unexpected(); + var name = S.token.value; + next(); + if (is("operator", "=")) { + next(); + a.push([ name, expression(false) ]); + } else { + a.push([ name ]); + } + if (!is("punc", ",")) + break; + next(); + } + return a; + }; + + function var_() { + return as("var", vardefs()); + }; + + function const_() { + return as("const", vardefs()); + }; + + function new_() { + var newexp = expr_atom(false), args; + if (is("punc", "(")) { + next(); + args = expr_list(")"); + } else { + args = []; + } + return subscripts(as("new", newexp, args), true); + }; + + function expr_atom(allow_calls) { + if (is("operator", "new")) { + next(); + return new_(); + } + if (is("operator") && HOP(UNARY_PREFIX, S.token.value)) { + return make_unary("unary-prefix", + prog1(S.token.value, next), + expr_atom(allow_calls)); + } + if (is("punc")) { + switch (S.token.value) { + case "(": + next(); + return subscripts(prog1(expression, curry(expect, ")")), allow_calls); + case "[": + next(); + return subscripts(array_(), allow_calls); + case "{": + next(); + return subscripts(object_(), allow_calls); + } + unexpected(); + } + if (is("keyword", "function")) { + next(); + return subscripts(function_(false), allow_calls); + } + if (HOP(ATOMIC_START_TOKEN, S.token.type)) { + var atom = S.token.type == "regexp" + ? as("regexp", S.token.value[0], S.token.value[1]) + : as(S.token.type, S.token.value); + return subscripts(prog1(atom, next), allow_calls); + } + unexpected(); + }; + + function expr_list(closing, allow_trailing_comma) { + var first = true, a = []; + while (!is("punc", closing)) { + if (first) first = false; else expect(","); + if (allow_trailing_comma && is("punc", closing)) + break; + a.push(expression(false)); + } + next(); + return a; + }; + + function array_() { + return as("array", expr_list("]", !strict_mode)); + }; + + function object_() { + var first = true, a = []; + while (!is("punc", "}")) { + if (first) first = false; else expect(","); + if (!strict_mode && is("punc", "}")) + // allow trailing comma + break; + var type = S.token.type; + var name = as_property_name(); + if (type == "name" && (name == "get" || name == "set") && !is("punc", ":")) { + a.push([ as_name(), function_(false), name ]); + } else { + expect(":"); + a.push([ name, expression(false) ]); + } + } + next(); + return as("object", a); + }; + + function as_property_name() { + switch (S.token.type) { + case "num": + case "string": + return prog1(S.token.value, next); + } + return as_name(); + }; + + function as_name() { + switch (S.token.type) { + case "name": + case "operator": + case "keyword": + case "atom": + return prog1(S.token.value, next); + default: + unexpected(); + } + }; + + function subscripts(expr, allow_calls) { + if (is("punc", ".")) { + next(); + return subscripts(as("dot", expr, as_name()), allow_calls); + } + if (is("punc", "[")) { + next(); + return subscripts(as("sub", expr, prog1(expression, curry(expect, "]"))), allow_calls); + } + if (allow_calls && is("punc", "(")) { + next(); + return subscripts(as("call", expr, expr_list(")")), true); + } + if (allow_calls && is("operator") && HOP(UNARY_POSTFIX, S.token.value)) { + return prog1(curry(make_unary, "unary-postfix", S.token.value, expr), + next); + } + return expr; + }; + + function make_unary(tag, op, expr) { + if ((op == "++" || op == "--") && !is_assignable(expr)) + croak("Invalid use of " + op + " operator"); + return as(tag, op, expr); + }; + + function expr_op(left, min_prec) { + var op = is("operator") ? S.token.value : null; + var prec = op != null ? PRECEDENCE[op] : null; + if (prec != null && prec > min_prec) { + next(); + var right = expr_op(expr_atom(true), prec); + return expr_op(as("binary", op, left, right), min_prec); + } + return left; + }; + + function expr_ops() { + return expr_op(expr_atom(true), 0); + }; + + function maybe_conditional() { + var expr = expr_ops(); + if (is("operator", "?")) { + next(); + var yes = expression(false); + expect(":"); + return as("conditional", expr, yes, expression(false)); + } + return expr; + }; + + function is_assignable(expr) { + switch (expr[0]) { + case "dot": + case "sub": + return true; + case "name": + return expr[1] != "this"; + } + }; + + function maybe_assign() { + var left = maybe_conditional(), val = S.token.value; + if (is("operator") && HOP(ASSIGNMENT, val)) { + if (is_assignable(left)) { + next(); + return as("assign", ASSIGNMENT[val], left, maybe_assign()); + } + croak("Invalid assignment"); + } + return left; + }; + + function expression(commas) { + if (arguments.length == 0) + commas = true; + var expr = maybe_assign(); + if (commas && is("punc", ",")) { + next(); + return as("seq", expr, expression()); + } + return expr; + }; + + function in_loop(cont) { + try { + ++S.in_loop; + return cont(); + } finally { + --S.in_loop; + } + }; + + return as("toplevel", (function(a){ + while (!is("eof")) + a.push(statement()); + return a; + })([])); + +}; + +/* -----[ Utilities ]----- */ + +function curry(f) { + var args = slice(arguments, 1); + return function() { return f.apply(this, args.concat(slice(arguments))); }; +}; + +function prog1(ret) { + if (ret instanceof Function) + ret = ret(); + for (var i = 1, n = arguments.length; --n > 0; ++i) + arguments[i](); + return ret; +}; + +function array_to_hash(a) { + var ret = {}; + for (var i = 0; i < a.length; ++i) + ret[a[i]] = true; + return ret; +}; + +function slice(a, start) { + return Array.prototype.slice.call(a, start == null ? 0 : start); +}; + +function characters(str) { + return str.split(""); +}; + +function member(name, array) { + for (var i = array.length; --i >= 0;) + if (array[i] === name) + return true; + return false; +}; + +function HOP(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +}; + +/* -----[ Exports ]----- */ + +exports.tokenizer = tokenizer; +exports.parse = parse; +exports.slice = slice; +exports.curry = curry; +exports.member = member; +exports.array_to_hash = array_to_hash; +exports.PRECEDENCE = PRECEDENCE; +exports.KEYWORDS_ATOM = KEYWORDS_ATOM; +exports.RESERVED_WORDS = RESERVED_WORDS; +exports.KEYWORDS = KEYWORDS; +exports.ATOMIC_START_TOKEN = ATOMIC_START_TOKEN; +exports.OPERATORS = OPERATORS; +exports.is_alphanumeric_char = is_alphanumeric_char; diff --git a/build/lib/process.js b/build/lib/process.js new file mode 100644 index 0000000..edcf599 --- /dev/null +++ b/build/lib/process.js @@ -0,0 +1,1562 @@ +/*********************************************************************** + + A JavaScript tokenizer / parser / beautifier / compressor. + + This version is suitable for Node.js. With minimal changes (the + exports stuff) it should work on any JS platform. + + This file implements some AST processors. They work on data built + by parse-js. + + Exported functions: + + - ast_mangle(ast, include_toplevel) -- mangles the + variable/function names in the AST. Returns an AST. Pass true + as second argument to mangle toplevel names too. + + - ast_squeeze(ast) -- employs various optimizations to make the + final generated code even smaller. Returns an AST. + + - gen_code(ast, beautify) -- generates JS code from the AST. Pass + true (or an object, see the code for some options) as second + argument to get "pretty" (indented) code. + + -------------------------------- (C) --------------------------------- + + Author: Mihai Bazon + + http://mihai.bazon.net/blog + + Distributed under the BSD license: + + Copyright 2010 (c) Mihai Bazon + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + ***********************************************************************/ + +var jsp = require("./parse-js"), + slice = jsp.slice, + member = jsp.member, + PRECEDENCE = jsp.PRECEDENCE, + OPERATORS = jsp.OPERATORS; + +/* -----[ helper for AST traversal ]----- */ + +function ast_walker(ast) { + function _vardefs(defs) { + return MAP(defs, function(def){ + var a = [ def[0] ]; + if (def.length > 1) + a[1] = walk(def[1]); + return a; + }); + }; + var walkers = { + "string": function(str) { + return [ "string", str ]; + }, + "num": function(num) { + return [ "num", num ]; + }, + "name": function(name) { + return [ "name", name ]; + }, + "toplevel": function(statements) { + return [ "toplevel", MAP(statements, walk) ]; + }, + "block": function(statements) { + var out = [ "block" ]; + if (statements != null) + out.push(MAP(statements, walk)); + return out; + }, + "var": function(defs) { + return [ "var", _vardefs(defs) ]; + }, + "const": function(defs) { + return [ "const", _vardefs(defs) ]; + }, + "try": function(t, c, f) { + return [ + "try", + MAP(t, walk), + c != null ? [ c[0], MAP(c[1], walk) ] : null, + f != null ? MAP(f, walk) : null + ]; + }, + "throw": function(expr) { + return [ "throw", walk(expr) ]; + }, + "new": function(ctor, args) { + return [ "new", walk(ctor), MAP(args, walk) ]; + }, + "switch": function(expr, body) { + return [ "switch", walk(expr), MAP(body, function(branch){ + return [ branch[0] ? walk(branch[0]) : null, + MAP(branch[1], walk) ]; + }) ]; + }, + "break": function(label) { + return [ "break", label ]; + }, + "continue": function(label) { + return [ "continue", label ]; + }, + "conditional": function(cond, t, e) { + return [ "conditional", walk(cond), walk(t), walk(e) ]; + }, + "assign": function(op, lvalue, rvalue) { + return [ "assign", op, walk(lvalue), walk(rvalue) ]; + }, + "dot": function(expr) { + return [ "dot", walk(expr) ].concat(slice(arguments, 1)); + }, + "call": function(expr, args) { + return [ "call", walk(expr), MAP(args, walk) ]; + }, + "function": function(name, args, body) { + return [ "function", name, args.slice(), MAP(body, walk) ]; + }, + "defun": function(name, args, body) { + return [ "defun", name, args.slice(), MAP(body, walk) ]; + }, + "if": function(conditional, t, e) { + return [ "if", walk(conditional), walk(t), walk(e) ]; + }, + "for": function(init, cond, step, block) { + return [ "for", walk(init), walk(cond), walk(step), walk(block) ]; + }, + "for-in": function(has_var, key, hash, block) { + return [ "for-in", has_var, key, walk(hash), walk(block) ]; + }, + "while": function(cond, block) { + return [ "while", walk(cond), walk(block) ]; + }, + "do": function(cond, block) { + return [ "do", walk(cond), walk(block) ]; + }, + "return": function(expr) { + return [ "return", walk(expr) ]; + }, + "binary": function(op, left, right) { + return [ "binary", op, walk(left), walk(right) ]; + }, + "unary-prefix": function(op, expr) { + return [ "unary-prefix", op, walk(expr) ]; + }, + "unary-postfix": function(op, expr) { + return [ "unary-postfix", op, walk(expr) ]; + }, + "sub": function(expr, subscript) { + return [ "sub", walk(expr), walk(subscript) ]; + }, + "object": function(props) { + return [ "object", MAP(props, function(p){ + return p.length == 2 + ? [ p[0], walk(p[1]) ] + : [ p[0], walk(p[1]), p[2] ]; // get/set-ter + }) ]; + }, + "regexp": function(rx, mods) { + return [ "regexp", rx, mods ]; + }, + "array": function(elements) { + return [ "array", MAP(elements, walk) ]; + }, + "stat": function(stat) { + return [ "stat", walk(stat) ]; + }, + "seq": function() { + return [ "seq" ].concat(MAP(slice(arguments), walk)); + }, + "label": function(name, block) { + return [ "label", name, walk(block) ]; + }, + "with": function(expr, block) { + return [ "with", walk(expr), walk(block) ]; + }, + "atom": function(name) { + return [ "atom", name ]; + } + }; + + var user = {}; + var stack = []; + function walk(ast) { + if (ast == null) + return null; + try { + stack.push(ast); + var type = ast[0]; + var gen = user[type]; + if (gen) { + var ret = gen.apply(ast, ast.slice(1)); + if (ret != null) + return ret; + } + gen = walkers[type]; + return gen.apply(ast, ast.slice(1)); + } finally { + stack.pop(); + } + }; + + function with_walkers(walkers, cont){ + var save = {}, i; + for (i in walkers) if (HOP(walkers, i)) { + save[i] = user[i]; + user[i] = walkers[i]; + } + var ret = cont(); + for (i in save) if (HOP(save, i)) { + if (!save[i]) delete user[i]; + else user[i] = save[i]; + } + return ret; + }; + + return { + walk: walk, + with_walkers: with_walkers, + parent: function() { + return stack[stack.length - 2]; // last one is current node + }, + stack: function() { + return stack; + } + }; +}; + +/* -----[ Scope and mangling ]----- */ + +function Scope(parent) { + this.names = {}; // names defined in this scope + this.mangled = {}; // mangled names (orig.name => mangled) + this.rev_mangled = {}; // reverse lookup (mangled => orig.name) + this.cname = -1; // current mangled name + this.refs = {}; // names referenced from this scope + this.uses_with = false; // will become TRUE if eval() is detected in this or any subscopes + this.uses_eval = false; // will become TRUE if with() is detected in this or any subscopes + this.parent = parent; // parent scope + this.children = []; // sub-scopes + if (parent) { + this.level = parent.level + 1; + parent.children.push(this); + } else { + this.level = 0; + } +}; + +var base54 = (function(){ + var DIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_"; + return function(num) { + var ret = ""; + do { + ret = DIGITS.charAt(num % 54) + ret; + num = Math.floor(num / 54); + } while (num > 0); + return ret; + }; +})(); + +Scope.prototype = { + has: function(name) { + for (var s = this; s; s = s.parent) + if (HOP(s.names, name)) + return s; + }, + has_mangled: function(mname) { + for (var s = this; s; s = s.parent) + if (HOP(s.rev_mangled, mname)) + return s; + }, + toJSON: function() { + return { + names: this.names, + uses_eval: this.uses_eval, + uses_with: this.uses_with + }; + }, + + next_mangled: function() { + // we must be careful that the new mangled name: + // + // 1. doesn't shadow a mangled name from a parent + // scope, unless we don't reference the original + // name from this scope OR from any sub-scopes! + // This will get slow. + // + // 2. doesn't shadow an original name from a parent + // scope, in the event that the name is not mangled + // in the parent scope and we reference that name + // here OR IN ANY SUBSCOPES! + // + // 3. doesn't shadow a name that is referenced but not + // defined (possibly global defined elsewhere). + for (;;) { + var m = base54(++this.cname), prior; + + // case 1. + prior = this.has_mangled(m); + if (prior && this.refs[prior.rev_mangled[m]] === prior) + continue; + + // case 2. + prior = this.has(m); + if (prior && prior !== this && this.refs[m] === prior && !prior.has_mangled(m)) + continue; + + // case 3. + if (HOP(this.refs, m) && this.refs[m] == null) + continue; + + // I got "do" once. :-/ + if (!is_identifier(m)) + continue; + + return m; + } + }, + get_mangled: function(name, newMangle) { + if (this.uses_eval || this.uses_with) return name; // no mangle if eval or with is in use + var s = this.has(name); + if (!s) return name; // not in visible scope, no mangle + if (HOP(s.mangled, name)) return s.mangled[name]; // already mangled in this scope + if (!newMangle) return name; // not found and no mangling requested + + var m = s.next_mangled(); + s.rev_mangled[m] = name; + return s.mangled[name] = m; + }, + define: function(name) { + if (name != null) + return this.names[name] = name; + } +}; + +function ast_add_scope(ast) { + + var current_scope = null; + var w = ast_walker(), walk = w.walk; + var having_eval = []; + + function with_new_scope(cont) { + current_scope = new Scope(current_scope); + var ret = current_scope.body = cont(); + ret.scope = current_scope; + current_scope = current_scope.parent; + return ret; + }; + + function define(name) { + return current_scope.define(name); + }; + + function reference(name) { + current_scope.refs[name] = true; + }; + + function _lambda(name, args, body) { + return [ this[0], define(name), args, with_new_scope(function(){ + MAP(args, define); + return MAP(body, walk); + })]; + }; + + return with_new_scope(function(){ + // process AST + var ret = w.with_walkers({ + "function": _lambda, + "defun": _lambda, + "with": function(expr, block) { + for (var s = current_scope; s; s = s.parent) + s.uses_with = true; + }, + "var": function(defs) { + MAP(defs, function(d){ define(d[0]) }); + }, + "const": function(defs) { + MAP(defs, function(d){ define(d[0]) }); + }, + "try": function(t, c, f) { + if (c != null) return [ + "try", + MAP(t, walk), + [ define(c[0]), MAP(c[1], walk) ], + f != null ? MAP(f, walk) : null + ]; + }, + "name": function(name) { + if (name == "eval") + having_eval.push(current_scope); + reference(name); + }, + "for-in": function(has_var, name) { + if (has_var) define(name); + else reference(name); + } + }, function(){ + return walk(ast); + }); + + // the reason why we need an additional pass here is + // that names can be used prior to their definition. + + // scopes where eval was detected and their parents + // are marked with uses_eval, unless they define the + // "eval" name. + MAP(having_eval, function(scope){ + if (!scope.has("eval")) while (scope) { + scope.uses_eval = true; + scope = scope.parent; + } + }); + + // for referenced names it might be useful to know + // their origin scope. current_scope here is the + // toplevel one. + function fixrefs(scope, i) { + // do children first; order shouldn't matter + for (i = scope.children.length; --i >= 0;) + fixrefs(scope.children[i]); + for (i in scope.refs) if (HOP(scope.refs, i)) { + // find origin scope and propagate the reference to origin + for (var origin = scope.has(i), s = scope; s; s = s.parent) { + s.refs[i] = origin; + if (s === origin) break; + } + } + }; + fixrefs(current_scope); + + return ret; + }); + +}; + +/* -----[ mangle names ]----- */ + +function ast_mangle(ast, do_toplevel) { + var w = ast_walker(), walk = w.walk, scope; + + function get_mangled(name, newMangle) { + if (!do_toplevel && !scope.parent) return name; // don't mangle toplevel + return scope.get_mangled(name, newMangle); + }; + + function _lambda(name, args, body) { + if (name) name = get_mangled(name); + body = with_scope(body.scope, function(){ + args = MAP(args, function(name){ return get_mangled(name) }); + return MAP(body, walk); + }); + return [ this[0], name, args, body ]; + }; + + function with_scope(s, cont) { + var _scope = scope; + scope = s; + for (var i in s.names) if (HOP(s.names, i)) { + get_mangled(i, true); + } + var ret = cont(); + ret.scope = s; + scope = _scope; + return ret; + }; + + function _vardefs(defs) { + return MAP(defs, function(d){ + return [ get_mangled(d[0]), walk(d[1]) ]; + }); + }; + + return w.with_walkers({ + "function": _lambda, + "defun": function() { + // move function declarations to the top when + // they are not in some block. + var ast = _lambda.apply(this, arguments); + switch (w.parent()[0]) { + case "toplevel": + case "function": + case "defun": + return MAP.at_top(ast); + } + return ast; + }, + "var": function(defs) { + return [ "var", _vardefs(defs) ]; + }, + "const": function(defs) { + return [ "const", _vardefs(defs) ]; + }, + "name": function(name) { + return [ "name", get_mangled(name) ]; + }, + "try": function(t, c, f) { + return [ "try", + MAP(t, walk), + c != null ? [ get_mangled(c[0]), MAP(c[1], walk) ] : null, + f != null ? MAP(f, walk) : null ]; + }, + "toplevel": function(body) { + return with_scope(this.scope, function(){ + return [ "toplevel", MAP(body, walk) ]; + }); + }, + "for-in": function(has_var, name, obj, stat) { + return [ "for-in", has_var, get_mangled(name), walk(obj), walk(stat) ]; + } + }, function() { + return walk(ast_add_scope(ast)); + }); +}; + +/* -----[ + - compress foo["bar"] into foo.bar, + - remove block brackets {} where possible + - join consecutive var declarations + - various optimizations for IFs: + - if (cond) foo(); else bar(); ==> cond?foo():bar(); + - if (cond) foo(); ==> cond&&foo(); + - if (foo) return bar(); else return baz(); ==> return foo?bar():baz(); // also for throw + - if (foo) return bar(); else something(); ==> {if(foo)return bar();something()} + ]----- */ + +var warn = function(){}; + +function best_of(ast1, ast2) { + return gen_code(ast1).length > gen_code(ast2[0] == "stat" ? ast2[1] : ast2).length ? ast2 : ast1; +}; + +function last_stat(b) { + if (b[0] == "block" && b[1] && b[1].length > 0) + return b[1][b[1].length - 1]; + return b; +} + +function aborts(t) { + if (t) { + t = last_stat(t); + if (t[0] == "return" || t[0] == "break" || t[0] == "continue" || t[0] == "throw") + return true; + } +}; + +function negate(c) { + var not_c = [ "unary-prefix", "!", c ]; + switch (c[0]) { + case "unary-prefix": + return c[1] == "!" ? c[2] : not_c; + case "binary": + var op = c[1], left = c[2], right = c[3]; + switch (op) { + case "<=": return [ "binary", ">", left, right ]; + case "<": return [ "binary", ">=", left, right ]; + case ">=": return [ "binary", "<", left, right ]; + case ">": return [ "binary", "<=", left, right ]; + case "==": return [ "binary", "!=", left, right ]; + case "!=": return [ "binary", "==", left, right ]; + case "===": return [ "binary", "!==", left, right ]; + case "!==": return [ "binary", "===", left, right ]; + case "&&": return best_of(not_c, [ "binary", "||", negate(left), negate(right) ]); + case "||": return best_of(not_c, [ "binary", "&&", negate(left), negate(right) ]); + } + break; + } + return not_c; +}; + +function make_conditional(c, t, e) { + if (c[0] == "unary-prefix" && c[1] == "!") { + return e ? [ "conditional", c[2], e, t ] : [ "binary", "||", c[2], t ]; + } else { + return e ? [ "conditional", c, t, e ] : [ "binary", "&&", c, t ]; + } +}; + +function empty(b) { + return !b || (b[0] == "block" && (!b[1] || b[1].length == 0)); +}; + +function ast_squeeze(ast, options) { + options = defaults(options, { + make_seqs : true, + dead_code : true, + no_warnings : false, + extra : false + }); + + var w = ast_walker(), walk = w.walk, scope; + + function with_scope(s, cont) { + var _scope = scope; + scope = s; + var ret = cont(); + ret.scope = s; + scope = _scope; + return ret; + }; + + function is_constant(node) { + return node[0] == "string" || node[0] == "num"; + }; + + function find_first_execute(node) { + if (!node) + return false; + + switch (node[0]) { + case "num": + case "string": + case "name": + return node; + case "call": + case "conditional": + case "for": + case "if": + case "new": + case "return": + case "stat": + case "switch": + case "throw": + return find_first_execute(node[1]); + case "binary": + return find_first_execute(node[2]); + case "assign": + if (node[1] === true) + return find_first_execute(node[3]); + break; + case "var": + if (node[1][0].length > 1) + return find_first_execute(node[1][0][1]); + break; + } + return null; + } + + function find_assign_recursive(p, v) { + if (p[0] == "assign" && p[1] != true || p[0] == "unary-prefix") { + if (p[2][0] == "name" && v[0] == "name" && p[2][1] == v[1]) + return true; + return false; + } + + if (p[0] != "assign" || p[1] !== true) + return false; + + if ((is_constant(p[3]) && p[3][0] == v[0] && p[3][1] == v[1]) || + (p[3][0] == "name" && v[0] == "name" && p[3][1] == v[1]) || + (p[2][0] == "name" && v[0] == "name" && p[2][1] == v[1])) + return true; + + return find_assign_recursive(p[3], v); + }; + + function rmblock(block) { + if (block != null && block[0] == "block" && block[1] && block[1].length == 1) + block = block[1][0]; + return block; + }; + + function clone(obj) { + if (obj && obj.constructor == Array) + return MAP(obj, clone); + return obj; + }; + + function make_seq_to_statements(node) { + if (node[0] != "seq") { + switch (node[0]) { + case "var": + case "const": + return [ node ]; + default: + return [ [ "stat", node ] ]; + } + } + + var ret = []; + for (var i = 1; i < node.length; i++) + ret.push.apply(ret, make_seq_to_statements(node[i])); + + return ret; + }; + + function _lambda(name, args, body) { + return [ this[0], name, args, with_scope(body.scope, function(){ + return tighten(MAP(body, walk), "lambda"); + }) ]; + }; + + // we get here for blocks that have been already transformed. + // this function does a few things: + // 1. discard useless blocks + // 2. join consecutive var declarations + // 3. remove obviously dead code + // 4. transform consecutive statements using the comma operator + // 5. if block_type == "lambda" and it detects constructs like if(foo) return ... - rewrite like if (!foo) { ... } + function tighten(statements, block_type) { + statements = statements.reduce(function(a, stat){ + if (stat[0] == "block") { + if (stat[1]) { + a.push.apply(a, stat[1]); + } + } else { + a.push(stat); + } + return a; + }, []); + + if (options.extra) { + // Detightening things. We do this because then we can assume that the + // statements are structured in a specific way. + statements = (function(a, prev) { + statements.forEach(function(cur) { + switch (cur[0]) { + case "for": + if (cur[1] != null) { + a.push.apply(a, make_seq_to_statements(cur[1])); + cur[1] = null; + } + a.push(cur); + break; + case "stat": + var stats = make_seq_to_statements(cur[1]); + stats.forEach(function(s) { + if (s[1][0] == "unary-postfix") + s[1][0] = "unary-prefix"; + }); + a.push.apply(a, stats); + break; + default: + a.push(cur); + } + }); + return a; + })([]); + + statements = (function(a, prev) { + statements.forEach(function(cur) { + if (!(prev && prev[0] == "stat")) { + a.push(cur); + prev = cur; + return; + } + + var p = prev[1]; + var c = find_first_execute(cur); + if (c && find_assign_recursive(p, c)) { + var old_cur = clone(cur); + c.splice(0, c.length); + c.push.apply(c, p); + var tmp_cur = best_of(cur, [ "toplevel", [ prev, old_cur ] ]); + if (tmp_cur == cur) { + a[a.length -1] = cur; + } else { + cur = old_cur; + a.push(cur); + } + } else { + a.push(cur); + } + prev = cur; + }); + return a; + })([]); + } + + statements = (function(a, prev){ + statements.forEach(function(cur){ + if (prev && ((cur[0] == "var" && prev[0] == "var") || + (cur[0] == "const" && prev[0] == "const"))) { + prev[1] = prev[1].concat(cur[1]); + } else { + a.push(cur); + prev = cur; + } + }); + return a; + })([]); + + if (options.dead_code) statements = (function(a, has_quit){ + statements.forEach(function(st){ + if (has_quit) { + if (member(st[0], [ "function", "defun" , "var", "const" ])) { + a.push(st); + } + else if (!options.no_warnings) + warn("Removing unreachable code: " + gen_code(st, true)); + } + else { + a.push(st); + if (member(st[0], [ "return", "throw", "break", "continue" ])) + has_quit = true; + } + }); + return a; + })([]); + + if (options.make_seqs) statements = (function(a, prev) { + statements.forEach(function(cur){ + if (prev && prev[0] == "stat" && cur[0] == "stat") { + prev[1] = [ "seq", prev[1], cur[1] ]; + } else { + a.push(cur); + prev = cur; + } + }); + return a; + })([]); + + if (options.extra) { + statements = (function(a, prev){ + statements.forEach(function(cur){ + var replaced = false; + if (prev && cur[0] == "for" && cur[1] == null && (prev[0] == "var" || prev[0] == "const" || prev[0] == "stat")) { + cur[1] = prev; + a[a.length - 1] = cur; + } else { + a.push(cur); + } + prev = cur; + }); + return a; + })([]); + } + + if (block_type == "lambda") statements = (function(i, a, stat){ + while (i < statements.length) { + stat = statements[i++]; + if (stat[0] == "if" && !stat[3]) { + if (stat[2][0] == "return" && stat[2][1] == null) { + a.push(make_if(negate(stat[1]), [ "block", statements.slice(i) ])); + break; + } + var last = last_stat(stat[2]); + if (last[0] == "return" && last[1] == null) { + a.push(make_if(stat[1], [ "block", stat[2][1].slice(0, -1) ], [ "block", statements.slice(i) ])); + break; + } + } + a.push(stat); + } + return a; + })(0, []); + + return statements; + }; + + function make_if(c, t, e) { + c = walk(c); + t = walk(t); + e = walk(e); + + if (empty(t)) { + c = negate(c); + t = e; + e = null; + } else if (empty(e)) { + e = null; + } else { + // if we have both else and then, maybe it makes sense to switch them? + (function(){ + var a = gen_code(c); + var n = negate(c); + var b = gen_code(n); + if (b.length < a.length) { + var tmp = t; + t = e; + e = tmp; + c = n; + } + })(); + } + if (empty(e) && empty(t)) + return [ "stat", c ]; + var ret = [ "if", c, t, e ]; + if (t[0] == "stat") { + if (e) { + if (e[0] == "stat") { + ret = best_of(ret, [ "stat", make_conditional(c, t[1], e[1]) ]); + } + } + else { + ret = best_of(ret, [ "stat", make_conditional(c, t[1]) ]); + } + } + else if (e && t[0] == e[0] && (t[0] == "return" || t[0] == "throw")) { + ret = best_of(ret, [ t[0], make_conditional(c, t[1], e[1] ) ]); + } + else if (e && aborts(t)) { + ret = [ [ "if", c, t ] ]; + if (e[0] == "block") { + if (e[1]) ret = ret.concat(e[1]); + } + else { + ret.push(e); + } + ret = walk([ "block", ret ]); + } + else if (t && aborts(e)) { + ret = [ [ "if", negate(c), e ] ]; + if (t[0] == "block") { + if (t[1]) ret = ret.concat(t[1]); + } else { + ret.push(t); + } + ret = walk([ "block", ret ]); + } + return ret; + }; + + return w.with_walkers({ + "sub": function(expr, subscript) { + if (subscript[0] == "string") { + var name = subscript[1]; + if (is_identifier(name)) { + return [ "dot", walk(expr), name ]; + } + } + }, + "if": make_if, + "toplevel": function(body) { + return [ "toplevel", with_scope(this.scope, function(){ + return tighten(MAP(body, walk)); + }) ]; + }, + "switch": function(expr, body) { + var last = body.length - 1; + return [ "switch", walk(expr), MAP(body, function(branch, i){ + var block = tighten(MAP(branch[1], walk)); + if (i == last && block.length > 0) { + var node = block[block.length - 1]; + if (node[0] == "break" && !node[1]) + block.pop(); + } + return [ branch[0] ? walk(branch[0]) : null, block ]; + }) ]; + }, + "function": _lambda, + "defun": _lambda, + "block": function(body) { + if (body) return rmblock([ "block", tighten(MAP(body, walk)) ]); + }, + "binary": function(op, left, right) { + left = walk(left); + right = walk(right); + var best = [ "binary", op, left, right ]; + if (is_constant(right)) { + if (is_constant(left)) { + var val = null; + switch (op) { + case "+": val = left[1] + right[1]; break; + case "*": val = left[1] * right[1]; break; + case "/": val = left[1] / right[1]; break; + case "-": val = left[1] - right[1]; break; + case "<<": val = left[1] << right[1]; break; + case ">>": val = left[1] >> right[1]; break; + case ">>>": val = left[1] >>> right[1]; break; + } + if (val != null) { + best = best_of(best, [ typeof val == "string" ? "string" : "num", val ]); + } + } else if (left[0] == "binary" && left[1] == "+" && left[3][0] == "string") { + best = best_of(best, [ "binary", "+", left[2], [ "string", left[3][1] + right[1] ] ]); + } + } + return best; + }, + "conditional": function(c, t, e) { + return make_conditional(walk(c), walk(t), walk(e)); + }, + "try": function(t, c, f) { + return [ + "try", + tighten(MAP(t, walk)), + c != null ? [ c[0], tighten(MAP(c[1], walk)) ] : null, + f != null ? tighten(MAP(f, walk)) : null + ]; + }, + "unary-prefix": function(op, cond) { + if (op == "!") { + cond = walk(cond); + if (cond[0] == "unary-prefix" && cond[1] == "!") { + var p = w.parent(); + if (p[0] == "unary-prefix" && p[1] == "!") + return cond[2]; + return [ "unary-prefix", "!", cond ]; + } + return best_of(this, negate(cond)); + } + }, + "name": function(name) { + switch (name) { + case "true": return [ "unary-prefix", "!", [ "num", 0 ]]; + case "false": return [ "unary-prefix", "!", [ "num", 1 ]]; + } + }, + "new": function(ctor, args) { + if (ctor[0] == "name" && ctor[1] == "Array" && !scope.has("Array")) { + if (args.length != 1) { + return [ "array", args ]; + } else { + return [ "call", [ "name", "Array" ], args ]; + } + } + }, + "call": function(expr, args) { + if (expr[0] == "name" && expr[1] == "Array" && args.length != 1 && !scope.has("Array")) { + return [ "array", args ]; + } + } + }, function() { + return walk(ast_add_scope(ast)); + }); +}; + +/* -----[ re-generate code from the AST ]----- */ + +var DOT_CALL_NO_PARENS = jsp.array_to_hash([ + "name", + "array", + "string", + "dot", + "sub", + "call", + "regexp" +]); + +function make_string(str) { + var dq = 0, sq = 0; + str = str.replace(/[\\\b\f\n\r\t\x22\x27]/g, function(s){ + switch (s) { + case "\\": return "\\\\"; + case "\b": return "\\b"; + case "\f": return "\\f"; + case "\n": return "\\n"; + case "\r": return "\\r"; + case "\t": return "\\t"; + case '"': ++dq; return '"'; + case "'": ++sq; return "'"; + } + return s; + }); + if (dq > sq) { + return "'" + str.replace(/\x27/g, "\\'") + "'"; + } else { + return '"' + str.replace(/\x22/g, '\\"') + '"'; + } +}; + +function gen_code(ast, beautify) { + if (beautify) beautify = defaults(beautify, { + indent_start : 0, + indent_level : 4, + quote_keys : false, + space_colon : false + }); + var indentation = 0, + newline = beautify ? "\n" : "", + space = beautify ? " " : ""; + + function indent(line) { + if (line == null) + line = ""; + if (beautify) + line = repeat_string(" ", beautify.indent_start + indentation * beautify.indent_level) + line; + return line; + }; + + function with_indent(cont, incr) { + if (incr == null) incr = 1; + indentation += incr; + try { return cont.apply(null, slice(arguments, 1)); } + finally { indentation -= incr; } + }; + + function add_spaces(a) { + if (beautify) + return a.join(" "); + var b = []; + for (var i = 0; i < a.length; ++i) { + var next = a[i + 1]; + b.push(a[i]); + if (next && + ((/[a-z0-9_\x24]$/i.test(a[i].toString()) && /^[a-z0-9_\x24]/i.test(next.toString())) || + (/[\+\-]$/.test(a[i].toString()) && /^[\+\-]/.test(next.toString())))) { + b.push(" "); + } + } + return b.join(""); + }; + + function add_commas(a) { + return a.join("," + space); + }; + + function parenthesize(expr) { + var gen = make(expr); + for (var i = 1; i < arguments.length; ++i) { + var el = arguments[i]; + if ((el instanceof Function && el(expr)) || expr[0] == el) + return "(" + gen + ")"; + } + return gen; + }; + + function best_of(a) { + if (a.length == 1) { + return a[0]; + } + if (a.length == 2) { + var b = a[1]; + a = a[0]; + return a.length <= b.length ? a : b; + } + return best_of([ a[0], best_of(a.slice(1)) ]); + }; + + function needs_parens(expr) { + if (expr[0] == "function") { + // dot/call on a literal function requires the + // function literal itself to be parenthesized + // only if it's the first "thing" in a + // statement. This means that the parent is + // "stat", but it could also be a "seq" and + // we're the first in this "seq" and the + // parent is "stat", and so on. Messy stuff, + // but it worths the trouble. + var a = slice($stack), self = a.pop(), p = a.pop(); + while (p) { + if (p[0] == "stat") return true; + if ((p[0] == "seq" && p[1] === self) || + (p[0] == "call" && p[1] === self) || + (p[0] == "binary" && p[2] === self)) { + self = p; + p = a.pop(); + } else { + return false; + } + } + } + return !HOP(DOT_CALL_NO_PARENS, expr[0]); + }; + + function make_num(num) { + var str = num.toString(10), a = [ str.replace(/^0\./, ".") ], m; + if (Math.floor(num) === num) { + a.push("0x" + num.toString(16).toLowerCase(), // probably pointless + "0" + num.toString(8)); // same. + if ((m = /^(.*?)(0+)$/.exec(num))) { + a.push(m[1] + "e" + m[2].length); + } + } else if ((m = /^0?\.(0+)(.*)$/.exec(num))) { + a.push(m[2] + "e-" + (m[1].length + m[2].length), + str.substr(str.indexOf("."))); + } + return best_of(a); + }; + + var generators = { + "string": make_string, + "num": make_num, + "name": make_name, + "toplevel": function(statements) { + return make_block_statements(statements) + .join(newline + newline); + }, + "block": make_block, + "var": function(defs) { + return "var " + add_commas(MAP(defs, make_1vardef)) + ";"; + }, + "const": function(defs) { + return "const " + add_commas(MAP(defs, make_1vardef)) + ";"; + }, + "try": function(tr, ca, fi) { + var out = [ "try", make_block(tr) ]; + if (ca) out.push("catch", "(" + ca[0] + ")", make_block(ca[1])); + if (fi) out.push("finally", make_block(fi)); + return add_spaces(out); + }, + "throw": function(expr) { + return add_spaces([ "throw", make(expr) ]) + ";"; + }, + "new": function(ctor, args) { + args = args.length > 0 ? "(" + add_commas(MAP(args, make)) + ")" : ""; + return add_spaces([ "new", parenthesize(ctor, "seq", "binary", "conditional", "assign", function(expr){ + var w = ast_walker(), has_call = {}; + try { + w.with_walkers({ + "call": function() { throw has_call }, + "function": function() { return this } + }, function(){ + w.walk(expr); + }); + } catch(ex) { + if (ex === has_call) + return true; + throw ex; + } + }) + args ]); + }, + "switch": function(expr, body) { + return add_spaces([ "switch", "(" + make(expr) + ")", make_switch_block(body) ]); + }, + "break": function(label) { + var out = "break"; + if (label != null) + out += " " + make_name(label); + return out + ";"; + }, + "continue": function(label) { + var out = "continue"; + if (label != null) + out += " " + make_name(label); + return out + ";"; + }, + "conditional": function(co, th, el) { + return add_spaces([ parenthesize(co, "assign", "seq", "conditional"), "?", + parenthesize(th, "seq"), ":", + parenthesize(el, "seq") ]); + }, + "assign": function(op, lvalue, rvalue) { + if (op && op !== true) op += "="; + else op = "="; + return add_spaces([ make(lvalue), op, parenthesize(rvalue, "seq") ]); + }, + "dot": function(expr) { + var out = make(expr), i = 1; + if (expr[0] == "num") + out += "."; + else if (needs_parens(expr)) + out = "(" + out + ")"; + while (i < arguments.length) + out += "." + make_name(arguments[i++]); + return out; + }, + "call": function(func, args) { + var f = make(func); + if (needs_parens(func)) + f = "(" + f + ")"; + return f + "(" + add_commas(MAP(args, function(expr){ + return parenthesize(expr, "seq"); + })) + ")"; + }, + "function": make_function, + "defun": make_function, + "if": function(co, th, el) { + var out = [ "if", "(" + make(co) + ")", el ? make_then(th) : make(th) ]; + if (el) { + out.push("else", make(el)); + } + return add_spaces(out); + }, + "for": function(init, cond, step, block) { + var out = [ "for" ]; + init = (init != null ? make(init) : "").replace(/;*\s*$/, ";" + space); + cond = (cond != null ? make(cond) : "").replace(/;*\s*$/, ";" + space); + step = (step != null ? make(step) : "").replace(/;*\s*$/, ""); + var args = init + cond + step; + if (args == "; ; ") args = ";;"; + out.push("(" + args + ")", make(block)); + return add_spaces(out); + }, + "for-in": function(has_var, key, hash, block) { + var out = add_spaces([ "for", "(" ]); + if (has_var) + out += "var "; + out += add_spaces([ make_name(key) + " in " + make(hash) + ")", make(block) ]); + return out; + }, + "while": function(condition, block) { + return add_spaces([ "while", "(" + make(condition) + ")", make(block) ]); + }, + "do": function(condition, block) { + return add_spaces([ "do", make(block), "while", "(" + make(condition) + ")" ]) + ";"; + }, + "return": function(expr) { + var out = [ "return" ]; + if (expr != null) out.push(make(expr)); + return add_spaces(out) + ";"; + }, + "binary": function(operator, lvalue, rvalue) { + var left = make(lvalue), right = make(rvalue); + // XXX: I'm pretty sure other cases will bite here. + // we need to be smarter. + // adding parens all the time is the safest bet. + if (member(lvalue[0], [ "assign", "conditional", "seq" ]) || + lvalue[0] == "binary" && PRECEDENCE[operator] > PRECEDENCE[lvalue[1]]) { + left = "(" + left + ")"; + } + if (member(rvalue[0], [ "assign", "conditional", "seq" ]) || + rvalue[0] == "binary" && PRECEDENCE[operator] >= PRECEDENCE[rvalue[1]]) { + right = "(" + right + ")"; + } + return add_spaces([ left, operator, right ]); + }, + "unary-prefix": function(operator, expr) { + var val = make(expr); + if (!(expr[0] == "num" || (expr[0] == "unary-prefix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr))) + val = "(" + val + ")"; + return operator + (jsp.is_alphanumeric_char(operator.charAt(0)) ? " " : "") + val; + }, + "unary-postfix": function(operator, expr) { + var val = make(expr); + if (!(expr[0] == "num" || (expr[0] == "unary-postfix" && !HOP(OPERATORS, operator + expr[1])) || !needs_parens(expr))) + val = "(" + val + ")"; + return val + operator; + }, + "sub": function(expr, subscript) { + var hash = make(expr); + if (needs_parens(expr)) + hash = "(" + hash + ")"; + return hash + "[" + make(subscript) + "]"; + }, + "object": function(props) { + if (props.length == 0) + return "{}"; + return "{" + newline + with_indent(function(){ + return MAP(props, function(p){ + if (p.length == 3) { + // getter/setter. The name is in p[0], the arg.list in p[1][2], the + // body in p[1][3] and type ("get" / "set") in p[2]. + return indent(make_function(p[0], p[1][2], p[1][3], p[2])); + } + var key = p[0], val = make(p[1]); + if (beautify && beautify.quote_keys) { + key = make_string(key); + } else if (typeof key == "number" || !beautify && +key + "" == key) { + key = make_num(+key); + } else if (!is_identifier(key)) { + key = make_string(key); + } + return indent(add_spaces(beautify && beautify.space_colon + ? [ key, ":", val ] + : [ key + ":", val ])); + }).join("," + newline); + }) + newline + indent("}"); + }, + "regexp": function(rx, mods) { + return "/" + rx + "/" + mods; + }, + "array": function(elements) { + if (elements.length == 0) return "[]"; + return add_spaces([ "[", add_commas(MAP(elements, function(el){ + return parenthesize(el, "seq"); + })), "]" ]); + }, + "stat": function(stmt) { + return make(stmt).replace(/;*\s*$/, ";"); + }, + "seq": function() { + return add_commas(MAP(slice(arguments), make)); + }, + "label": function(name, block) { + return add_spaces([ make_name(name), ":", make(block) ]); + }, + "with": function(expr, block) { + return add_spaces([ "with", "(" + make(expr) + ")", make(block) ]); + }, + "atom": function(name) { + return make_name(name); + }, + "comment1": function(text) { + return "//" + text + "\n"; + }, + "comment2": function(text) { + return "/*" + text + "*/"; + } + }; + + // The squeezer replaces "block"-s that contain only a single + // statement with the statement itself; technically, the AST + // is correct, but this can create problems when we output an + // IF having an ELSE clause where the THEN clause ends in an + // IF *without* an ELSE block (then the outer ELSE would refer + // to the inner IF). This function checks for this case and + // adds the block brackets if needed. + function make_then(th) { + if (th[0] == "do") { + // https://github.com/mishoo/UglifyJS/issues/#issue/57 + // IE croaks with "syntax error" on code like this: + // if (foo) do ... while(cond); else ... + // we need block brackets around do/while + return make([ "block", [ th ]]); + } + var b = th; + while (true) { + var type = b[0]; + if (type == "if") { + if (!b[3]) + // no else, we must add the block + return make([ "block", [ th ]]); + b = b[3]; + } + else if (type == "while" || type == "do") b = b[2]; + else if (type == "for" || type == "for-in") b = b[4]; + else break; + } + return make(th); + }; + + function make_function(name, args, body, keyword) { + var out = keyword || "function"; + if (name) { + out += " " + make_name(name); + } + out += "(" + add_commas(MAP(args, make_name)) + ")"; + return add_spaces([ out, make_block(body) ]); + }; + + function make_name(name) { + return name.toString(); + }; + + function make_block_statements(statements) { + for (var a = [], last = statements.length - 1, i = 0; i <= last; ++i) { + var stat = statements[i]; + var code = make(stat); + if (code != ";") { + if (!beautify && i == last) + code = code.replace(/;+\s*$/, ""); + a.push(code); + } + } + return MAP(a, indent); + }; + + function make_switch_block(body) { + var n = body.length; + if (n == 0) return "{}"; + return "{" + newline + MAP(body, function(branch, i){ + var has_body = branch[1].length > 0, code = with_indent(function(){ + return indent(branch[0] + ? add_spaces([ "case", make(branch[0]) + ":" ]) + : "default:"); + }, 0.5) + (has_body ? newline + with_indent(function(){ + return make_block_statements(branch[1]).join(newline); + }) : ""); + if (!beautify && has_body && i < n - 1) + code += ";"; + return code; + }).join(newline) + newline + indent("}"); + }; + + function make_block(statements) { + if (!statements) return ";"; + if (statements.length == 0) return "{}"; + return "{" + newline + with_indent(function(){ + return make_block_statements(statements).join(newline); + }) + newline + indent("}"); + }; + + function make_1vardef(def) { + var name = def[0], val = def[1]; + if (val != null) + name = add_spaces([ name, "=", make(val) ]); + return name; + }; + + var $stack = []; + + function make(node) { + var type = node[0]; + var gen = generators[type]; + if (!gen) + throw new Error("Can't find generator for \"" + type + "\""); + $stack.push(node); + var ret = gen.apply(type, node.slice(1)); + $stack.pop(); + return ret; + }; + + return make(ast); +}; + +/* -----[ Utilities ]----- */ + +function repeat_string(str, i) { + if (i <= 0) return ""; + if (i == 1) return str; + var d = repeat_string(str, i >> 1); + d += d; + if (i & 1) d += str; + return d; +}; + +function defaults(args, defs) { + var ret = {}; + if (args === true) + args = {}; + for (var i in defs) if (HOP(defs, i)) { + ret[i] = (args && HOP(args, i)) ? args[i] : defs[i]; + } + return ret; +}; + +function is_identifier(name) { + return /^[a-z_$][a-z0-9_$]*$/i.test(name) + && name != "this" + && !HOP(jsp.KEYWORDS_ATOM, name) + && !HOP(jsp.RESERVED_WORDS, name) + && !HOP(jsp.KEYWORDS, name); +}; + +function HOP(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +}; + +// some utilities + +var MAP; + +(function(){ + MAP = function(a, f, o) { + var ret = []; + for (var i = 0; i < a.length; ++i) { + var val = f.call(o, a[i], i); + if (val instanceof AtTop) ret.unshift(val.v); + else ret.push(val); + } + return ret; + }; + MAP.at_top = function(val) { return new AtTop(val) }; + function AtTop(val) { this.v = val }; +})(); + +/* -----[ Exports ]----- */ + +exports.ast_walker = ast_walker; +exports.ast_mangle = ast_mangle; +exports.ast_squeeze = ast_squeeze; +exports.gen_code = gen_code; +exports.ast_add_scope = ast_add_scope; +exports.ast_squeeze_more = require("./squeeze-more").ast_squeeze_more; +exports.set_logger = function(logger) { warn = logger }; diff --git a/build/lib/squeeze-more.js b/build/lib/squeeze-more.js new file mode 100644 index 0000000..12380af --- /dev/null +++ b/build/lib/squeeze-more.js @@ -0,0 +1,22 @@ +var jsp = require("./parse-js"), + pro = require("./process"), + slice = jsp.slice, + member = jsp.member, + PRECEDENCE = jsp.PRECEDENCE, + OPERATORS = jsp.OPERATORS; + +function ast_squeeze_more(ast) { + var w = pro.ast_walker(), walk = w.walk; + return w.with_walkers({ + "call": function(expr, args) { + if (expr[0] == "dot" && expr[2] == "toString" && args.length == 0) { + // foo.toString() ==> foo+"" + return [ "binary", "+", expr[1], [ "string", "" ]]; + } + } + }, function() { + return walk(ast); + }); +}; + +exports.ast_squeeze_more = ast_squeeze_more; diff --git a/build/uglify.js b/build/uglify.js new file mode 100644 index 0000000..943ddd8 --- /dev/null +++ b/build/uglify.js @@ -0,0 +1,199 @@ +#! /usr/bin/env node +// -*- js2 -*- + +global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util"); +var fs = require("fs"), + jsp = require("./lib/parse-js"), + pro = require("./lib/process"); + +pro.set_logger(function(msg){ + sys.debug(msg); +}); + +var options = { + ast: false, + mangle: true, + mangle_toplevel: false, + squeeze: true, + make_seqs: true, + dead_code: true, + beautify: false, + verbose: false, + show_copyright: true, + out_same_file: false, + extra: false, + unsafe: false, // XXX: extra & unsafe? but maybe we don't want both, so.... + beautify_options: { + indent_level: 4, + indent_start: 0, + quote_keys: false, + space_colon: false + }, + output: true // stdout +}; + +var args = jsp.slice(process.argv, 2); +var filename; + +out: while (args.length > 0) { + var v = args.shift(); + switch (v) { + case "-b": + case "--beautify": + options.beautify = true; + break; + case "-i": + case "--indent": + options.beautify_options.indent_level = args.shift(); + break; + case "-q": + case "--quote-keys": + options.beautify_options.quote_keys = true; + break; + case "-mt": + case "--mangle-toplevel": + options.mangle_toplevel = true; + break; + case "--no-mangle": + case "-nm": + options.mangle = false; + break; + case "--no-squeeze": + case "-ns": + options.squeeze = false; + break; + case "--no-seqs": + options.make_seqs = false; + break; + case "--no-dead-code": + options.dead_code = false; + break; + case "--no-copyright": + case "-nc": + options.show_copyright = false; + break; + case "-o": + case "--output": + options.output = args.shift(); + break; + case "--overwrite": + options.out_same_file = true; + break; + case "-v": + case "--verbose": + options.verbose = true; + break; + case "--ast": + options.ast = true; + break; + case "--extra": + options.extra = true; + break; + case "--unsafe": + options.unsafe = true; + break; + default: + filename = v; + break out; + } +} + +if (filename) { + fs.readFile(filename, "utf8", function(err, text){ + if (err) { + throw err; + } + output(squeeze_it(text)); + }); +} else { + var stdin = process.openStdin(); + stdin.setEncoding("utf8"); + var text = ""; + stdin.on("data", function(chunk){ + text += chunk; + }); + stdin.on("end", function() { + output(squeeze_it(text)); + }); +} + +function output(text) { + var out; + if (options.out_same_file && filename) + options.output = filename; + if (options.output === true) { + out = process.stdout; + } else { + out = fs.createWriteStream(options.output, { + flags: "w", + encoding: "utf8", + mode: 0644 + }); + } + out.write(text); + out.end(); +}; + +// --------- main ends here. + +function show_copyright(comments) { + var ret = ""; + for (var i = 0; i < comments.length; ++i) { + var c = comments[i]; + if (c.type == "comment1") { + ret += "//" + c.value + "\n"; + } else { + ret += "/*" + c.value + "*/"; + } + } + return ret; +}; + +function squeeze_it(code) { + var result = ""; + if (options.show_copyright) { + var initial_comments = []; + // keep first comment + var tok = jsp.tokenizer(code, false), c; + c = tok(); + var prev = null; + while (/^comment/.test(c.type) && (!prev || prev == c.type)) { + initial_comments.push(c); + prev = c.type; + c = tok(); + } + result += show_copyright(initial_comments); + } + try { + var ast = time_it("parse", function(){ return jsp.parse(code); }); + if (options.mangle) + ast = time_it("mangle", function(){ return pro.ast_mangle(ast, options.mangle_toplevel); }); + if (options.squeeze) + ast = time_it("squeeze", function(){ + ast = pro.ast_squeeze(ast, { + make_seqs : options.make_seqs, + dead_code : options.dead_code, + extra : options.extra + }); + if (options.unsafe) + ast = pro.ast_squeeze_more(ast); + return ast; + }); + if (options.ast) + return sys.inspect(ast, null, null); + result += time_it("generate", function(){ return pro.gen_code(ast, options.beautify && options.beautify_options) }); + return result; + } catch(ex) { + sys.debug(ex.stack); + sys.debug(sys.inspect(ex)); + sys.debug(JSON.stringify(ex)); + } +}; + +function time_it(name, cont) { + if (!options.verbose) + return cont(); + var t1 = new Date().getTime(); + try { return cont(); } + finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); } +}; diff --git a/src/ajax.js b/src/ajax.js index d10b931..871481d 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -1,17 +1,20 @@ (function( jQuery ) { -var jsc = jQuery.now(), - rscript = /)<[^<]*)*<\/script>/gi, - rselectTextarea = /^(?:select|textarea)/i, +var r20 = /%20/g, + rbracket = /\[\]$/, + rhash = /#.*$/, + rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, rnoContent = /^(?:GET|HEAD)$/, - rbracket = /\[\]$/, - jsre = /\=\?(&|$)/, rquery = /\?/, + rscript = /)<[^<]*)*<\/script>/gi, + rselectTextarea = /^(?:select|textarea)/i, rts = /([?&])_=[^&]*/, - rurl = /^(\w+:)?\/\/([^\/?#]+)/, - r20 = /%20/g, - rhash = /#.*$/, + rurl = /^(\w+:)?\/\/([^\/?#:]+)(?::(\d+))?/, + rCRLF = /\r?\n/g, + + // Slice function + sliceFunc = Array.prototype.slice, // Keep a copy of the old load method _load = jQuery.fn.load; @@ -58,26 +61,34 @@ jQuery.fn.extend({ type: type, dataType: "html", data: params, - complete: function( res, status ) { + // Complete callback (responseText is used internally) + complete: function( jXHR, status, responseText ) { + // Store the response as specified by the jXHR object + responseText = jXHR.responseText; // If successful, inject the HTML into all the matched elements - if ( status === "success" || status === "notmodified" ) { + if ( jXHR.isResolved() ) { + // #4825: Get the actual response in case + // a dataFilter is present in ajaxSettings + jXHR.done(function( r ) { + responseText = r; + }); // See if a selector was specified self.html( selector ? // Create a dummy div to hold the results jQuery("
") // inject the contents of the document in, removing the scripts // to avoid any 'Permission Denied' errors in IE - .append(res.responseText.replace(rscript, "")) + .append(responseText.replace(rscript, "")) // Locate the specified elements .find(selector) : // If not, just inject the full result - res.responseText ); + responseText ); } if ( callback ) { - self.each( callback, [res.responseText, status, res] ); + self.each( callback, [responseText, status, jXHR] ); } } }); @@ -90,37 +101,37 @@ jQuery.fn.extend({ }, serializeArray: function() { - return this.map(function() { + return this.map(function(){ return this.elements ? jQuery.makeArray(this.elements) : this; }) - .filter(function() { + .filter(function(){ return this.name && !this.disabled && (this.checked || rselectTextarea.test(this.nodeName) || rinput.test(this.type)); }) - .map(function( i, elem ) { + .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 }; + jQuery.map( val, function(val, i){ + return { name: elem.name, value: val.replace(rCRLF, "\r\n") }; }) : - { name: elem.name, value: val }; + { name: elem.name, value: val.replace(rCRLF, "\r\n") }; }).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 ) { +jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function(i,o){ + jQuery.fn[o] = function(f){ return this.bind(o, f); }; }); -jQuery.extend({ - get: function( url, data, callback, type ) { +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { // shift arguments if data argument was omited if ( jQuery.isFunction( data ) ) { type = type || callback; @@ -129,13 +140,16 @@ jQuery.extend({ } return jQuery.ajax({ - type: "GET", + type: method, url: url, data: data, success: callback, dataType: type }); - }, + }; +}); + +jQuery.extend({ getScript: function( url, callback ) { return jQuery.get(url, null, callback, "script"); @@ -145,25 +159,9 @@ jQuery.extend({ return jQuery.get(url, data, callback, "json"); }, - post: function( url, data, callback, type ) { - // shift arguments if data argument was omited - if ( jQuery.isFunction( data ) ) { - type = type || callback; - callback = data; - data = {}; - } - - return jQuery.ajax({ - type: "POST", - url: url, - data: data, - success: callback, - dataType: type - }); - }, - ajaxSetup: function( settings ) { - jQuery.extend( jQuery.ajaxSettings, settings ); + jQuery.extend( true, jQuery.ajaxSettings, settings ); + return this; }, ajaxSettings: { @@ -176,332 +174,554 @@ jQuery.extend({ /* timeout: 0, data: null, + dataType: null, username: null, password: null, + cache: null, traditional: false, + headers: {}, + crossDomain: null, */ - // This function can be overriden by calling jQuery.ajaxSetup xhr: function() { return new window.XMLHttpRequest(); }, + accepts: { xml: "application/xml, text/xml", html: "text/html", - script: "text/javascript, application/javascript", - json: "application/json, text/javascript", text: "text/plain", - _default: "*/*" + json: "application/json, text/javascript", + "*": "*/*" + }, + + contents: { + xml: /xml/, + html: /html/, + json: /json/ + }, + + // Prefilters + // 1) They are useful to introduce custom dataTypes (see transport/jsonp for an example) + // 2) These are called: + // * BEFORE asking for a transport + // * AFTER param serialization (s.data is a string if s.processData is true) + // 3) key is the dataType + // 4) the catchall symbol "*" can be used + // 5) execution will start with transport dataType and THEN continue down to "*" if needed + prefilters: {}, + + // Transports bindings + // 1) key is the dataType + // 2) the catchall symbol "*" can be used + // 3) selection will start with transport dataType and THEN go to "*" if needed + transports: {}, + + // List of data converters + // 1) key format is "source_type destination_type" (a single space in-between) + // 2) the catchall symbol "*" can be used for source_type + converters: { + + // Convert anything to text + "* text": window.String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": jQuery.parseJSON, + + // Parse text as xml + "text xml": jQuery.parseXML } }, - ajax: function( origSettings ) { - var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings), - jsonp, status, data, type = s.type.toUpperCase(), noContent = rnoContent.test(type); + // Main method + // (s is used internally) + ajax: function( url , options , s ) { + + // Handle varargs + if ( arguments.length === 1 ) { + options = url; + url = options ? options.url : undefined; + } + + // Force options to be an object + options = options || {}; + + // Get the url if provided separately + options.url = url || options.url; + + // Create the final options object + s = jQuery.extend( true , {} , jQuery.ajaxSettings , options ); + + // We force the original context + // (plain objects used as context get extended) + s.context = options.context; + + var // jQuery lists + jQuery_lastModified = jQuery.lastModified, + jQuery_etag = jQuery.etag, + // Callbacks contexts + callbackContext = s.context || s, + globalEventContext = s.context ? jQuery( s.context ) : jQuery.event, + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery._Deferred(), + // Status-dependent callbacks + statusCode = s.statusCode || {}, + // Headers (they are sent all at once) + requestHeaders = {}, + // Response headers + responseHeadersString, + responseHeaders, + // transport + transport, + // timeout handle + timeoutTimer, + // Cross-domain detection vars + loc = document.location, + parts, + // The jXHR state + state = 0, + // Loop variable + i, + // Fake xhr + jXHR = { + + readyState: 0, + + // Caches the header + setRequestHeader: function(name,value) { + if ( state === 0 ) { + requestHeaders[ name.toLowerCase() ] = value; + } + return this; + }, - s.url = s.url.replace( rhash, "" ); + // Raw string + getAllResponseHeaders: function() { + return state === 2 ? responseHeadersString : null; + }, - // Use original (not extended) context object if it was provided - s.context = origSettings && origSettings.context != null ? origSettings.context : s; + // Builds headers hashtable if needed + // (match is used internally) + getResponseHeader: function( key , match ) { - // convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } + if ( state === 2 ) { + + if ( responseHeaders === undefined ) { + + responseHeaders = {}; + + if ( typeof responseHeadersString === "string" ) { + + while( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + } + } + } + match = responseHeaders[ key.toLowerCase() ]; + + } else { + + match = null; + } - // Handle JSONP Parameter Callbacks - if ( s.dataType === "jsonp" ) { - if ( type === "GET" ) { - if ( !jsre.test( s.url ) ) { - s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?"; + return match; + }, + + // Cancel the request + abort: function( statusText ) { + if ( transport ) { + transport.abort( statusText || "abort" ); + } + done( 0 , statusText ); + return this; } - } else if ( !s.data || !jsre.test(s.data) ) { - s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?"; + }; + + // Callback for when everything is done + // It is defined here because jslint complains if it is declared + // at the end of the function (which would be more logical and readable) + function done( status , statusText , response , headers) { + + // Called once + if ( state === 2 ) { + return; } - s.dataType = "json"; - } - // Build temporary JSONP function - if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) { - jsonp = s.jsonpCallback || ("jsonp" + jsc++); + // State is "done" now + state = 2; - // Replace the =? sequence both in the query string and the data - if ( s.data ) { - s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1"); + // Dereference transport for early garbage collection + // (no matter how long the jXHR transport will be used + transport = 0; + + // Set readyState + jXHR.readyState = status ? 4 : 0; + + // Cache response headers + responseHeadersString = headers || ""; + + // Clear timeout if it exists + if ( timeoutTimer ) { + clearTimeout(timeoutTimer); } - s.url = s.url.replace(jsre, "=" + jsonp + "$1"); + var // Reference url + url = s.url, + // and ifModified status + ifModified = s.ifModified, + + // Is it a success? + isSuccess = 0, + // Stored success + success, + // Stored error + error; - // We need to make sure - // that a JSONP style response is executed properly - s.dataType = "script"; + // If successful, handle type chaining + if ( status >= 200 && status < 300 || status === 304 ) { - // Handle JSONP-style loading - var customJsonp = window[ jsonp ]; + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { - window[ jsonp ] = function( tmp ) { - if ( jQuery.isFunction( customJsonp ) ) { - customJsonp( tmp ); + var lastModified = jXHR.getResponseHeader("Last-Modified"), + etag = jXHR.getResponseHeader("Etag"); + if (lastModified) { + jQuery_lastModified[ s.url ] = lastModified; + } + if (etag) { + jQuery_etag[ s.url ] = etag; + } + } + + // If not modified + if ( status === 304 ) { + + // Set the statusText accordingly + statusText = "notmodified"; + // Mark as a success + isSuccess = 1; + + // If we have data } else { - // Garbage collect - window[ jsonp ] = undefined; + // Set the statusText accordingly + statusText = "success"; + + // Chain data conversions and determine the final value + // (if an exception is thrown in the process, it'll be notified as an error) try { - delete window[ jsonp ]; - } catch( jsonpError ) {} - } - data = tmp; - jQuery.handleSuccess( s, xhr, status, data ); - jQuery.handleComplete( s, xhr, status, data ); - - if ( head ) { - head.removeChild( script ); + var i, + // Current dataType + current, + // Previous dataType + prev, + // Conversion function + conv, + // Conversion functions (when text is used in-between) + conv1, + conv2, + // Local references to dataTypes & converters + dataTypes = s.dataTypes, + converters = s.converters, + // DataType to responseXXX field mapping + responses = { + "xml": "XML", + "text": "Text" + }; + + // For each dataType in the chain + for( i = 0 ; i < dataTypes.length ; i++ ) { + + current = dataTypes[ i ]; + + // If a responseXXX field for this dataType exists + // and if it hasn't been set yet + if ( responses[ current ] ) { + // Set it + jXHR[ "response" + responses[ current ] ] = response; + // Mark it as set + responses[ current ] = 0; + } + + // If this is not the first element + if ( i ) { + + // Get the dataType to convert from + prev = dataTypes[ i - 1 ]; + + // If no catch-all and dataTypes are actually different + if ( prev !== "*" && current !== "*" && prev !== current ) { + + // Get the converter + conv = converters[ prev + " " + current ] || + converters[ "* " + current ]; + + conv1 = conv2 = 0; + + // If there is no direct converter and none of the dataTypes is text + if ( ! conv && prev !== "text" && current !== "text" ) { + // Try with text in-between + conv1 = converters[ prev + " text" ] || converters[ "* text" ]; + conv2 = converters[ "text " + current ]; + // Revert back to a single converter + // if one of the converter is an equivalence + if ( conv1 === true ) { + conv = conv2; + } else if ( conv2 === true ) { + conv = conv1; + } + } + // If we found no converter, dispatch an error + if ( ! ( conv || conv1 && conv2 ) ) { + throw conversion; + } + // If found converter is not an equivalence + if ( conv !== true ) { + // Convert with 1 or 2 converters accordingly + response = conv ? conv( response ) : conv2( conv1( response ) ); + } + } + // If it is the first element of the chain + // and we have a dataFilter + } else if ( s.dataFilter ) { + // Apply the dataFilter + response = s.dataFilter( response , current ); + // Get dataTypes again in case the filter changed them + dataTypes = s.dataTypes; + } + } + // End of loop + + // We have a real success + success = response; + isSuccess = 1; + + // If an exception was thrown + } catch(e) { + + // We have a parsererror + statusText = "parsererror"; + error = "" + e; + + } } - }; - } - if ( s.dataType === "script" && s.cache === null ) { - s.cache = false; - } + // if not success, mark it as an error + } else { - if ( s.cache === false && noContent ) { - var ts = jQuery.now(); + error = statusText = statusText || "error"; - // try replacing _= if it is there - var ret = s.url.replace(rts, "$1_=" + ts); + // Set responseText if needed + if ( response ) { + jXHR.responseText = response; + } + } - // if nothing was replaced, add timestamp to the end - s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : ""); - } + // Set data for the fake xhr object + jXHR.status = status; + jXHR.statusText = statusText; - // If data is available, append data to url for GET/HEAD requests - if ( s.data && noContent ) { - s.url += (rquery.test(s.url) ? "&" : "?") + s.data; - } + // Success/Error + if ( isSuccess ) { + deferred.fire( callbackContext , [ success , statusText , jXHR ] ); + } else { + deferred.fireReject( callbackContext , [ jXHR , statusText , error ] ); + } - // Watch for a new set of requests - if ( s.global && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } + // Status-dependent callbacks + jXHR.statusCode( statusCode ); - // Matches an absolute URL, and saves the domain - var parts = rurl.exec( s.url ), - remote = parts && (parts[1] && parts[1].toLowerCase() !== location.protocol || parts[2].toLowerCase() !== location.host); - - // If we're requesting a remote document - // and trying to load JSON or Script with a GET - if ( s.dataType === "script" && type === "GET" && remote ) { - var head = document.getElementsByTagName("head")[0] || document.documentElement; - var script = document.createElement("script"); - if ( s.scriptCharset ) { - script.charset = s.scriptCharset; + if ( s.global ) { + globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ) , + [ jXHR , s , isSuccess ? success : error ] ); } - script.src = s.url; - - // 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; - jQuery.handleSuccess( s, xhr, status, data ); - jQuery.handleComplete( s, xhr, status, data ); - - // Handle memory leak in IE - script.onload = script.onreadystatechange = null; - if ( head && script.parentNode ) { - head.removeChild( script ); + + // Complete + completeDeferred.fire( callbackContext, [ jXHR , statusText ] ); + + if ( s.global ) { + globalEventContext.trigger( "ajaxComplete" , [ jXHR , s] ); + // Handle the global AJAX counter + if ( ! --jQuery.active ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + // Attach deferreds + deferred.promise( jXHR ); + jXHR.success = jXHR.done; + jXHR.error = jXHR.fail; + jXHR.complete = completeDeferred.done; + + // Status-dependent callbacks + jXHR.statusCode = function( map ) { + if ( map ) { + var resolved = jXHR.isResolved(), + tmp; + if ( resolved || jXHR.isRejected() ) { + tmp = map[ jXHR.status ]; + if ( tmp ) { + if ( map === statusCode ) { + delete statusCode[ jXHR.status ]; } + jXHR[ resolved ? "done" : "fail" ]( tmp ); } - }; + } else { + for( tmp in map ) { + statusCode[ tmp ] = [ statusCode[ tmp ] , map[ tmp ] ]; + } + } } + return this; + }; + + // Remove hash character (#7531: and string promotion) + s.url = ( "" + s.url ).replace( rhash , "" ); - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709 and #4378). - head.insertBefore( script, head.firstChild ); + // Extract dataTypes list + s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( /\s+/ ); - // We handle everything using the script element injection - return undefined; + // Determine if a cross-domain request is in order + if ( ! s.crossDomain ) { + parts = rurl.exec( s.url.toLowerCase() ); + s.crossDomain = !!( + parts && + ( parts[ 1 ] && parts[ 1 ] != loc.protocol || + parts[ 2 ] != loc.hostname || + ( parts[ 3 ] || 80 ) != ( loc.port || 80 ) ) + ); } - var requestDone = false; + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data , s.traditional ); + } - // Create the request object - var xhr = s.xhr(); + // Apply prefilters + jQuery.ajaxPrefilter( s , options ); - if ( !xhr ) { - return; - } + // Uppercase the type + s.type = s.type.toUpperCase(); - // 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); + // Determine if request has content + s.hasContent = ! rnoContent.test( s.type ); + + // Watch for a new set of requests + if ( s.global && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); } - // Need an extra try/catch for cross domain requests in Firefox 3 - try { - // Set content-type if data specified and content-body is valid for this type - if ( (s.data != null && !noContent) || (origSettings && origSettings.contentType) ) { - xhr.setRequestHeader("Content-Type", s.contentType); + // More options handling for requests with no content + if ( ! s.hasContent ) { + + // If data is available, append data to url + if ( s.data ) { + s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; } - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[s.url] ) { - xhr.setRequestHeader("If-Modified-Since", jQuery.lastModified[s.url]); - } + // Add anti-cache in url if needed + if ( s.cache === false ) { - if ( jQuery.etag[s.url] ) { - xhr.setRequestHeader("If-None-Match", jQuery.etag[s.url]); - } - } + var ts = jQuery.now(), + // try replacing _= if it is there + ret = s.url.replace( rts , "$1_=" + ts ); - // Set header so the called script knows that it's an XMLHttpRequest - // Only send the header if it's not a remote XHR - if ( !remote ) { - xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + // if nothing was replaced, add timestamp to the end + s.url = ret + ( (ret == s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : ""); } + } - // Set the Accepts header for the server, depending on the dataType - xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ? - s.accepts[ s.dataType ] + ", */*; q=0.01" : - s.accepts._default ); - } catch( headerError ) {} + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + requestHeaders[ "content-type" ] = s.contentType; + } - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false ) { - // Handle the global AJAX counter - if ( s.global && jQuery.active-- === 1 ) { - jQuery.event.trigger( "ajaxStop" ); + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery_lastModified[ s.url ] ) { + requestHeaders[ "if-modified-since" ] = jQuery_lastModified[ s.url ]; + } + if ( jQuery_etag[ s.url ] ) { + requestHeaders[ "if-none-match" ] = jQuery_etag[ s.url ]; } - - // close opended socket - xhr.abort(); - return false; } - if ( s.global ) { - jQuery.triggerGlobal( s, "ajaxSend", [xhr, s] ); + // Set the Accepts header for the server, depending on the dataType + requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : + s.accepts[ "*" ]; + + // Check for headers option + for ( i in s.headers ) { + requestHeaders[ i.toLowerCase() ] = s.headers[ i ]; } - // Wait for a response to come back - var onreadystatechange = xhr.onreadystatechange = function( isTimeout ) { - // The request was aborted - if ( !xhr || xhr.readyState === 0 || isTimeout === "abort" ) { - // Opera doesn't call onreadystatechange before this point - // so we simulate the call - if ( !requestDone ) { - jQuery.handleComplete( s, xhr, status, data ); - } + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && ( s.beforeSend.call( callbackContext , jXHR , s ) === false || state === 2 ) ) { - requestDone = true; - if ( xhr ) { - xhr.onreadystatechange = jQuery.noop; - } + // Abort if not done already + done( 0 , "abort" ); - // 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; - xhr.onreadystatechange = jQuery.noop; + // Return false + jXHR = false; - status = isTimeout === "timeout" ? - "timeout" : - !jQuery.httpSuccess( xhr ) ? - "error" : - s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? - "notmodified" : - "success"; + } else { - var errMsg; + // Install callbacks on deferreds + for ( i in { success:1, error:1, complete:1 } ) { + jXHR[ i ]( s[ i ] ); + } - 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( parserError ) { - status = "parsererror"; - errMsg = parserError; - } - } + // Get transport + transport = jQuery.ajaxTransport( s ); - // Make sure that the request was successful or notmodified - if ( status === "success" || status === "notmodified" ) { - // JSONP handles its own success callback - if ( !jsonp ) { - jQuery.handleSuccess( s, xhr, status, data ); - } - } else { - jQuery.handleError( s, xhr, status, errMsg ); - } + // If no transport, we auto-abort + if ( ! transport ) { - // Fire the complete handlers - if ( !jsonp ) { - jQuery.handleComplete( s, xhr, status, data ); - } + done( 0 , "notransport" ); - if ( isTimeout === "timeout" ) { - xhr.abort(); - } + } else { - // Stop memory leaks - if ( s.async ) { - xhr = null; - } - } - }; + // Set state as sending + state = jXHR.readyState = 1; - // Override the abort handler, if we can (IE 6 doesn't allow it, but that's OK) - // Opera doesn't fire onreadystatechange at all on abort - try { - var oldAbort = xhr.abort; - xhr.abort = function() { - if ( xhr ) { - // oldAbort has no call property in IE7 so - // just do it this way, which works in all - // browsers - Function.prototype.call.call( oldAbort, xhr ); + // Send global event + if ( s.global ) { + globalEventContext.trigger( "ajaxSend" , [ jXHR , s ] ); } - onreadystatechange( "abort" ); - }; - } catch( abortError ) {} - - // Timeout checker - if ( s.async && s.timeout > 0 ) { - setTimeout(function() { - // Check to see if the request is still happening - if ( xhr && !requestDone ) { - onreadystatechange( "timeout" ); + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = setTimeout(function(){ + jXHR.abort( "timeout" ); + }, s.timeout); } - }, s.timeout); - } - // Send the data - try { - xhr.send( noContent || s.data == null ? null : s.data ); + // Try to send + try { + transport.send(requestHeaders, done); + } catch (e) { + // Propagate exception as error if not done + if ( status === 1 ) { - } catch( sendError ) { - jQuery.handleError( s, xhr, null, sendError ); + done(0, "error", "" + e); + jXHR = false; - // Fire the complete handlers - jQuery.handleComplete( s, xhr, status, data ); - } - - // firefox 1.5 doesn't fire statechange for sync requests - if ( !s.async ) { - onreadystatechange(); + // Simply rethrow otherwise + } else { + jQuery.error(e); + } + } + } } - // return XMLHttpRequest to allow aborting the request etc. - return xhr; + return jXHR; }, // Serialize an array of form elements or a set of @@ -513,19 +733,19 @@ jQuery.extend({ value = jQuery.isFunction(value) ? value() : value; s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); }; - + // Set traditional to true for jQuery <= 1.3.2 behavior. if ( traditional === undefined ) { traditional = jQuery.ajaxSettings.traditional; } - + // 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 ); }); - + } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. @@ -558,9 +778,11 @@ function buildParams( prefix, obj, traditional, add ) { buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); } }); - + } else if ( !traditional && obj != null && typeof obj === "object" ) { - if ( jQuery.isEmptyObject( obj ) ) { + // If we see an array here, it is empty and should be treated as an empty + // object + if ( jQuery.isArray( obj ) || jQuery.isEmptyObject( obj ) ) { add( prefix, "" ); // Serialize object item. @@ -569,7 +791,7 @@ function buildParams( prefix, obj, traditional, add ) { buildParams( prefix + "[" + k + "]", v, traditional, add ); }); } - + } else { // Serialize scalar item. add( prefix, obj ); @@ -585,112 +807,175 @@ jQuery.extend({ // Last-Modified header cache for next request lastModified: {}, - etag: {}, + etag: {} - handleError: function( s, xhr, status, e ) { - // If a local callback was specified, fire it - if ( s.error ) { - s.error.call( s.context, xhr, status, e ); - } +}); - // Fire the global callback - if ( s.global ) { - jQuery.triggerGlobal( s, "ajaxError", [xhr, s, e] ); - } - }, +//Execute or select from functions in a given structure of options +function ajax_selectOrExecute( structure , s ) { - handleSuccess: function( s, xhr, status, data ) { - // If a local callback was specified, fire it and pass it the data - if ( s.success ) { - s.success.call( s.context, data, status, xhr ); - } + var dataTypes = s.dataTypes, + transportDataType, + list, + selected, + i, + length, + checked = {}, + flag, + noSelect = structure !== "transports"; - // Fire the global callback - if ( s.global ) { - jQuery.triggerGlobal( s, "ajaxSuccess", [xhr, s] ); - } - }, + function initSearch( dataType ) { - handleComplete: function( s, xhr, status ) { - // Process result - if ( s.complete ) { - s.complete.call( s.context, xhr, status ); - } + flag = transportDataType !== dataType && ! checked[ dataType ]; - // The request was completed - if ( s.global ) { - jQuery.triggerGlobal( s, "ajaxComplete", [xhr, s] ); - } + if ( flag ) { - // Handle the global AJAX counter - if ( s.global && jQuery.active-- === 1 ) { - jQuery.event.trigger( "ajaxStop" ); + checked[ dataType ] = 1; + transportDataType = dataType; + list = s[ structure ][ dataType ]; + i = -1; + length = list ? list.length : 0 ; } - }, - - triggerGlobal: function( s, type, args ) { - (s.context && s.context.url == null ? jQuery(s.context) : jQuery.event).trigger(type, args); - }, - // 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 flag; + } - return false; - }, + initSearch( dataTypes[ 0 ] ); + + for ( i = 0 ; ( noSelect || ! selected ) && i <= length ; i++ ) { + + if ( i === length ) { + + initSearch( "*" ); + + } else { + + selected = list[ i ]( s , determineDataType ); - // Determines if an XMLHttpRequest returns NotModified - httpNotModified: function( xhr, url ) { - var lastModified = xhr.getResponseHeader("Last-Modified"), - etag = xhr.getResponseHeader("Etag"); + // If we got redirected to another dataType + // Search there (if not in progress or already tried) + if ( typeof( selected ) === "string" && + initSearch( selected ) ) { - if ( lastModified ) { - jQuery.lastModified[url] = lastModified; + dataTypes.unshift( selected ); + selected = 0; + } } + } + + return noSelect ? jQuery : selected; +} + +// Add an element to one of the structures in ajaxSettings +function ajax_addElement( structure , args ) { + + var i, + start = 0, + length = args.length, + dataTypes = [ "*" ], + dLength = 1, + dataType, + functors = [], + first, + append, + list; + + if ( length ) { - if ( etag ) { - jQuery.etag[url] = etag; + first = jQuery.type( args[ 0 ] ); + + if ( first === "object" ) { + return ajax_selectOrExecute( structure , args[ 0 ] ); } - return xhr.status === 304; - }, + structure = jQuery.ajaxSettings[ structure ]; + + if ( first !== "function" ) { - httpData: function( xhr, type, s ) { - var ct = xhr.getResponseHeader("content-type") || "", - xml = type === "xml" || !type && ct.indexOf("xml") >= 0, - data = xml ? xhr.responseXML : xhr.responseText; + dataTypes = args[ 0 ].toLowerCase().split(/\s+/); + dLength = dataTypes.length; + start = 1; - if ( xml && data.documentElement.nodeName === "parsererror" ) { - jQuery.error( "parsererror" ); } - // Allow a pre-filtering function to sanitize the response - // s is checked to keep backwards compatibility - if ( s && s.dataFilter ) { - data = s.dataFilter( data, type ); + if ( dLength && start < length ) { + + functors = sliceFunc.call( args , start ); + + for( i = 0 ; i < dLength ; i++ ) { + + dataType = dataTypes[ i ]; + + first = /^\+/.test( dataType ); + + if (first) { + dataType = dataType.substr(1); + } + + if ( dataType !== "" ) { + + append = Array.prototype[ first ? "unshift" : "push" ]; + list = structure[ dataType ] = structure[ dataType ] || []; + append.apply( list , functors ); + } + } } + } - // The filter can actually parse the response - if ( typeof data === "string" ) { - // Get the JavaScript object, if JSON is used. - if ( type === "json" || !type && ct.indexOf("json") >= 0 ) { - data = jQuery.parseJSON( data ); + return jQuery; +} - // If the type is "script", eval it in global context - } else if ( type === "script" || !type && ct.indexOf("javascript") >= 0 ) { - jQuery.globalEval( data ); +// Install prefilter & transport methods +jQuery.each( [ "Prefilter" , "Transport" ] , function( _ , name ) { + _ = name.toLowerCase() + "s"; + jQuery[ "ajax" + name ] = function() { + return ajax_addElement( _ , arguments ); + }; +} ); + +// Utility function that handles dataType when response is received +// (for those transports that can give text or xml responses) +function determineDataType( s , ct , text , xml ) { + + var contents = s.contents, + type, + regexp, + dataTypes = s.dataTypes, + transportDataType = dataTypes[0], + response; + + // Auto (xml, json, script or text determined given headers) + if ( transportDataType === "*" ) { + + for ( type in contents ) { + if ( ( regexp = contents[ type ] ) && regexp.test( ct ) ) { + transportDataType = dataTypes[0] = type; + break; } } + } + + // xml and parsed as such + if ( transportDataType === "xml" && + xml && + xml.documentElement /* #4958 */ ) { + + response = xml; + + // Text response was provided + } else { + + response = text; + + // If it's not really text, defer to converters + if ( transportDataType !== "text" ) { + dataTypes.unshift( "text" ); + } - return data; } -}); + return response; +} /* * Create the request object; Microsoft failed to properly @@ -701,19 +986,24 @@ jQuery.extend({ */ if ( window.ActiveXObject ) { jQuery.ajaxSettings.xhr = function() { - if ( window.location.protocol !== "file:" ) { - try { - return new window.XMLHttpRequest(); - } catch(xhrError) {} - } - + if ( window.location.protocol !== "file:" ) { try { - return new window.ActiveXObject("Microsoft.XMLHTTP"); - } catch(activeError) {} + return new window.XMLHttpRequest(); + } catch( xhrError ) {} + } + + try { + return new window.ActiveXObject("Microsoft.XMLHTTP"); + } catch( activeError ) {} }; } +var testXHR = jQuery.ajaxSettings.xhr(); + // Does this browser support XHR requests? -jQuery.support.ajax = !!jQuery.ajaxSettings.xhr(); +jQuery.support.ajax = !!testXHR; + +// Does this browser support crossDomain XHR requests +jQuery.support.cors = testXHR && "withCredentials" in testXHR; })( jQuery ); diff --git a/src/ajax/jsonp.js b/src/ajax/jsonp.js new file mode 100644 index 0000000..883876f --- /dev/null +++ b/src/ajax/jsonp.js @@ -0,0 +1,86 @@ +(function( jQuery ) { + +var jsc = jQuery.now(), + jsre = /(\=)(?:\?|%3F)(&|$)|()(?:\?\?|%3F%3F)()/i; + +// Default jsonp settings +jQuery.ajaxSetup({ + jsonp: "callback", + jsonpCallback: function() { + return "jsonp" + jsc++; + } + +// Detect, normalize options and install callbacks for jsonp requests +// (dataIsString is used internally) +}).ajaxPrefilter("json jsonp", function(s, originalSettings, dataIsString) { + + dataIsString = ( typeof(s.data) === "string" ); + + if ( s.dataTypes[ 0 ] === "jsonp" || + originalSettings.jsonpCallback || + originalSettings.jsonp != null || + s.jsonp !== false && ( jsre.test( s.url ) || + dataIsString && jsre.test( s.data ) ) ) { + + var responseContainer, + jsonpCallback = s.jsonpCallback = + jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, + previous = window[ jsonpCallback ], + url = s.url, + data = s.data, + replace = "$1" + jsonpCallback + "$2"; + + if ( s.jsonp !== false ) { + url = url.replace( jsre, replace ); + if ( s.url === url ) { + if ( dataIsString ) { + data = data.replace( jsre, replace ); + } + if ( s.data === data ) { + // Add callback manually + url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; + } + } + } + + s.url = url; + s.data = data; + + window [ jsonpCallback ] = function( response ) { + responseContainer = [response]; + }; + + s.complete = [function() { + + // Set callback back to previous value + window[ jsonpCallback ] = previous; + + // Call if it was a function and we have a response + if ( previous) { + if ( responseContainer && jQuery.isFunction ( previous ) ) { + window[ jsonpCallback ] ( responseContainer[0] ); + } + } else { + // else, more memory leak avoidance + try{ delete window[ jsonpCallback ]; } catch(e){} + } + + }, s.complete ]; + + // Use data converter to retrieve json after script execution + s.converters["script json"] = function() { + if ( ! responseContainer ) { + jQuery.error( jsonpCallback + " was not called" ); + } + return responseContainer[ 0 ]; + }; + + // force json dataType + s.dataTypes[ 0 ] = "json"; + + // Delegate to script + return "script"; + } +}); + +})( jQuery ); diff --git a/src/ajax/script.js b/src/ajax/script.js new file mode 100644 index 0000000..b0e576f --- /dev/null +++ b/src/ajax/script.js @@ -0,0 +1,89 @@ +(function( jQuery ) { + +// Install script dataType +jQuery.ajaxSetup({ + + accepts: { + script: "text/javascript, application/javascript" + }, + + contents: { + script: /javascript/ + }, + + converters: { + "text script": jQuery.globalEval + } + +// Handle cache's special case and global +}).ajaxPrefilter("script", function(s) { + + if ( s.cache === undefined ) { + s.cache = false; + } + + if ( s.crossDomain ) { + s.type = "GET"; + s.global = false; + } + +// Bind script tag hack transport +}).ajaxTransport("script", function(s) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + + var script, + head = document.getElementsByTagName("head")[0] || document.documentElement; + + return { + + send: function(_, callback) { + + script = document.createElement("script"); + + script.async = "async"; + + if ( s.scriptCharset ) { + script.charset = s.scriptCharset; + } + + script.src = s.url; + + // Attach handlers for all browsers + script.onload = script.onreadystatechange = function( _ , isAbort ) { + + if ( ! script.readyState || /loaded|complete/.test( script.readyState ) ) { + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + + // Remove the script + if ( head && script.parentNode ) { + head.removeChild( script ); + } + + // Dereference the script + script = 0; + + // Callback if not abort + if ( ! isAbort ) { + callback( 200, "success" ); + } + } + }; + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709 and #4378). + head.insertBefore( script, head.firstChild ); + }, + + abort: function() { + if ( script ) { + script.onload(0,1); + } + } + }; + } +}); + +})( jQuery ); diff --git a/src/ajax/xhr.js b/src/ajax/xhr.js new file mode 100644 index 0000000..34aa832 --- /dev/null +++ b/src/ajax/xhr.js @@ -0,0 +1,183 @@ +(function( jQuery ) { + +var // Next active xhr id + xhrId = jQuery.now(), + + // active xhrs + xhrs = {}, + + // #5280: see below + xhrUnloadAbortInstalled; + + +jQuery.ajaxTransport( function( s , determineDataType ) { + + // Cross domain only allowed if supported through XMLHttpRequest + if ( ! s.crossDomain || jQuery.support.cors ) { + + var callback; + + return { + + send: function(headers, complete) { + + // #5280: we need to abort on unload or IE will keep connections alive + if ( ! xhrUnloadAbortInstalled ) { + + xhrUnloadAbortInstalled = 1; + + jQuery(window).bind( "unload" , function() { + + // Abort all pending requests + jQuery.each(xhrs, function(_, xhr) { + if ( xhr.onreadystatechange ) { + xhr.onreadystatechange( 1 ); + } + }); + + }); + } + + // Get a new xhr + var xhr = s.xhr(), + handle; + + // Open the socket + // Passing null username, generates a login popup on Opera (#2865) + if ( s.username ) { + xhr.open(s.type, s.url, s.async, s.username, s.password); + } else { + xhr.open(s.type, s.url, s.async); + } + + // Requested-With header + // Not set for crossDomain requests with no content + // (see why at http://trac.dojotoolkit.org/ticket/9486) + // Won't change header if already provided + if ( ! ( s.crossDomain && ! s.hasContent ) && ! headers["x-requested-with"] ) { + headers["x-requested-with"] = "XMLHttpRequest"; + } + + // Need an extra try/catch for cross domain requests in Firefox 3 + try { + + jQuery.each(headers, function(key,value) { + xhr.setRequestHeader(key,value); + }); + + } catch(_) {} + + // Do send the request + try { + xhr.send( ( s.hasContent && s.data ) || null ); + } catch(e) { + complete(0, "error", "" + e); + return; + } + + // Listener + callback = function( _ , isAbort ) { + + // Was never called and is aborted or complete + if ( callback && ( isAbort || xhr.readyState === 4 ) ) { + + // Only called once + callback = 0; + + // Do not keep as active anymore + // and store back into pool + if (handle) { + xhr.onreadystatechange = jQuery.noop; + delete xhrs[ handle ]; + } + + // If it's an abort + if ( isAbort ) { + + // Abort it manually if needed + if ( xhr.readyState !== 4 ) { + xhr.abort(); + } + } else { + + // Get info + var status = xhr.status, + statusText, + response, + responseHeaders = xhr.getAllResponseHeaders(); + + try { // Firefox throws an exception when accessing statusText for faulty cross-domain requests + + statusText = xhr.statusText; + + } catch( e ) { + + statusText = ""; // We normalize with Webkit giving an empty statusText + + } + + // Filter status for non standard behaviours + // (so many they seem to be the actual "standard") + status = + // Opera returns 0 when it should be 304 + // Webkit returns 0 for failing cross-domain no matter the real status + status === 0 ? + ( + ! s.crossDomain || statusText ? // Webkit, Firefox: filter out faulty cross-domain requests + ( + responseHeaders ? // Opera: filter out real aborts #6060 + 304 + : + 0 + ) + : + 302 // We assume 302 but could be anything cross-domain related + ) + : + ( + status == 1223 ? // IE sometimes returns 1223 when it should be 204 (see #1450) + 204 + : + status + ); + + // Guess response & update dataType accordingly + response = + determineDataType( + s, + xhr.getResponseHeader("content-type"), + xhr.responseText, + xhr.responseXML ); + + // Call complete + complete(status,statusText,response,responseHeaders); + } + } + }; + + // if we're in sync mode + // or it's in cache and has been retrieved directly (IE6 & IE7) + // we need to manually fire the callback + if ( ! s.async || xhr.readyState === 4 ) { + + callback(); + + } else { + + // Add to list of active xhrs + handle = xhrId++; + xhrs[ handle ] = xhr; + xhr.onreadystatechange = callback; + } + }, + + abort: function() { + if ( callback ) { + callback(0,1); + } + } + }; + } +}); + +})( jQuery ); diff --git a/src/attributes.js b/src/attributes.js index 4824c29..d37400a 100644 --- a/src/attributes.js +++ b/src/attributes.js @@ -1,6 +1,6 @@ (function( jQuery ) { -var rclass = /[\n\t]/g, +var rclass = /[\n\t\r]/g, rspaces = /\s+/, rreturn = /\r/g, rspecialurl = /^(?:href|src|style)$/, @@ -133,11 +133,11 @@ jQuery.fn.extend({ } else if ( type === "undefined" || type === "boolean" ) { if ( this.className ) { // store className if set - jQuery.data( this, "__className__", this.className ); + jQuery._data( this, "__className__", this.className ); } // toggle whole className - this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; } }); }, @@ -182,7 +182,7 @@ jQuery.fn.extend({ var option = options[ i ]; // Don't return options that are disabled or in a disabled optgroup - if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { // Get the specific value for the option @@ -205,7 +205,6 @@ jQuery.fn.extend({ if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { return elem.getAttribute("value") === null ? "on" : elem.value; } - // Everything else, we just grab the value return (elem.value || "").replace(rreturn, ""); @@ -271,10 +270,10 @@ jQuery.extend({ height: true, offset: true }, - + attr: function( elem, name, value, pass ) { - // don't set attributes on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) { return undefined; } @@ -289,88 +288,96 @@ jQuery.extend({ // Try to normalize/fix the name name = notxml && jQuery.props[ name ] || name; - // These attributes require special treatment - var special = rspecialurl.test( name ); + // Only do all the following if this is a node (faster for style) + if ( elem.nodeType === 1 ) { + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } - // Safari mis-reports the default selected property of an option - // Accessing the parent's selectedIndex property fixes it - if ( name === "selected" && !jQuery.support.optSelected ) { - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; + // If applicable, access the attribute via the DOM 0 way + // 'in' checks fail in Blackberry 4.7 #6931 + if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + if ( value === null ) { + if ( elem.nodeType === 1 ) { + elem.removeAttribute( name ); + } - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; + } else { + elem[ name ] = value; + } } - } - } - // If applicable, access the attribute via the DOM 0 way - // 'in' checks fail in Blackberry 4.7 #6931 - if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { - if ( set ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); + // 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; } - if ( value === null ) { - if ( elem.nodeType === 1 ) { - elem.removeAttribute( name ); - } + // 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" ); - } else { - elem[ name ] = value; + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; } - } - // 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; + return elem[ name ]; } - // 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" ); + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } - return attributeNode && attributeNode.specified ? - attributeNode.value : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; + return elem.style.cssText; } - return elem[ name ]; - } - - if ( !jQuery.support.style && notxml && name === "style" ) { if ( set ) { - elem.style.cssText = "" + value; + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); } - return elem.style.cssText; - } + // Ensure that missing attributes return undefined + // Blackberry 4.7 returns "" from getAttribute #6938 + if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { + return undefined; + } - 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 ); - // Ensure that missing attributes return undefined - // Blackberry 4.7 returns "" from getAttribute #6938 - if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { - return undefined; + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; } - - 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; + // Handle everything which isn't a DOM element node + if ( set ) { + elem[ name ] = value; + } + return elem[ name ]; } }); diff --git a/src/core.js b/src/core.js index 9e1bfc6..4311e31 100644 --- a/src/core.js +++ b/src/core.js @@ -3,7 +3,7 @@ var jQuery = (function() { // Define a local copy of jQuery var jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context ); + return new jQuery.fn.init( selector, context, rootjQuery ); }, // Map over jQuery in case of overwrite @@ -56,12 +56,15 @@ var jQuery = function( selector, context ) { // For matching the engine and version of the browser browserMatch, - + // Has the ready events already been bound? readyBound = false, - - // The functions to execute on DOM ready - readyList = [], + + // The deferred used on DOM ready + readyList, + + // Promise methods + promiseMethods = "then done fail isResolved isRejected promise".split( " " ), // The ready event handler DOMContentLoaded, @@ -73,12 +76,13 @@ var jQuery = function( selector, context ) { slice = Array.prototype.slice, trim = String.prototype.trim, indexOf = Array.prototype.indexOf, - + // [[Class]] -> type pairs class2type = {}; jQuery.fn = jQuery.prototype = { - init: function( selector, context ) { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) @@ -92,7 +96,7 @@ jQuery.fn = jQuery.prototype = { this.length = 1; return this; } - + // The body element only exists once, optimize finding it if ( selector === "body" && !context && document.body ) { this.context = document; @@ -112,6 +116,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(html) -> $(array) if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; doc = (context ? context.ownerDocument || context : document); // If a single string is passed in and it's a single tag @@ -129,11 +134,11 @@ jQuery.fn = jQuery.prototype = { } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); - selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + selector = (ret.cacheable ? jQuery(ret.fragment).clone()[0] : ret.fragment).childNodes; } - + return jQuery.merge( this, selector ); - + // HANDLE: $("#id") } else { elem = document.getElementById( match[2] ); @@ -171,7 +176,7 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { - return jQuery( context ).find( selector ); + return this.constructor( context ).find( selector ); } // HANDLE: $(function) @@ -215,18 +220,18 @@ jQuery.fn = jQuery.prototype = { this.toArray() : // Return just the object - ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); + ( num < 0 ? this[ this.length + num ] : 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(); + var ret = this.constructor(); if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); - + } else { jQuery.merge( ret, elems ); } @@ -252,25 +257,15 @@ jQuery.fn = jQuery.prototype = { each: function( callback, args ) { return jQuery.each( this, callback, args ); }, - - ready: function( fn ) { + + ready: function() { // Attach the listeners jQuery.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 if ( readyList ) { - // Add the function to the wait list - readyList.push( fn ); - } - - return this; + // Change ready & apply + return ( jQuery.fn.ready = readyList.done ).apply( this , arguments ); }, - + eq: function( i ) { return i === -1 ? this.slice( i ) : @@ -295,9 +290,9 @@ jQuery.fn = jQuery.prototype = { return callback.call( elem, i, elem ); })); }, - + end: function() { - return this.prevObject || jQuery(null); + return this.prevObject || this.constructor(null); }, // For internal use only. @@ -384,14 +379,14 @@ jQuery.extend({ return jQuery; }, - + // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, - + // Handle when the DOM is ready ready: function( wait ) { // A third-party is pushing the ready event forwards @@ -415,27 +410,15 @@ jQuery.extend({ } // If there are functions bound, to execute - if ( readyList ) { - // Execute all of them - var fn, - i = 0, - ready = readyList; - - // Reset the list of functions - readyList = null; + readyList.fire( document , [ jQuery ] ); - while ( (fn = ready[ i++ ]) ) { - fn.call( document, jQuery ); - } - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger( "ready" ).unbind( "ready" ); - } + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); } } }, - + bindReady: function() { if ( readyBound ) { return; @@ -454,7 +437,7 @@ jQuery.extend({ if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - + // A fallback to window.onload, that will always work window.addEventListener( "load", jQuery.ready, false ); @@ -463,7 +446,7 @@ jQuery.extend({ // ensure firing before onload, // maybe late but safe also for iframes document.attachEvent("onreadystatechange", DOMContentLoaded); - + // A fallback to window.onload, that will always work window.attachEvent( "onload", jQuery.ready ); @@ -514,20 +497,20 @@ jQuery.extend({ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { return false; } - + // Not own constructor property must be Object if ( obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } - + // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. - + var key; for ( key in obj ) {} - + return key === undefined || hasOwn.call( obj, key ); }, @@ -537,11 +520,11 @@ jQuery.extend({ } return true; }, - + error: function( msg ) { throw msg; }, - + parseJSON: function( data ) { if ( typeof data !== "string" || !data ) { return null; @@ -549,7 +532,7 @@ jQuery.extend({ // Make sure leading/trailing whitespace is removed (IE can't handle it) data = jQuery.trim( data ); - + // Make sure the incoming data is actual JSON // Logic borrowed from http://json.org/json2.js if ( rvalidchars.test(data.replace(rvalidescape, "@") @@ -566,6 +549,28 @@ jQuery.extend({ } }, + // Cross-browser xml parsing + // (xml & tmp used internally) + parseXML: function( data , xml , tmp ) { + + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + + tmp = xml.documentElement; + + if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { + jQuery.error( "Invalid XML: " + data ); + } + + return xml; + }, + noop: function() {}, // Evalulates a script in a global context @@ -578,7 +583,7 @@ jQuery.extend({ script.type = "text/javascript"; - if ( jQuery.support.scriptEval ) { + if ( jQuery.support.scriptEval() ) { script.appendChild( document.createTextNode( data ) ); } else { script.text = data; @@ -691,7 +696,7 @@ jQuery.extend({ for ( var l = second.length; j < l; j++ ) { first[ i++ ] = second[ j ]; } - + } else { while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; @@ -733,6 +738,7 @@ jQuery.extend({ } } + // Flatten any nested arrays return ret.concat.apply( [], ret ); }, @@ -771,7 +777,7 @@ jQuery.extend({ // The value/s can be optionally by executed if its a function access: function( elems, key, value, exec, fn, pass ) { var length = elems.length; - + // Setting many attributes if ( typeof key === "object" ) { for ( var k in key ) { @@ -779,19 +785,19 @@ jQuery.extend({ } return elems; } - + // Setting one attribute if ( value !== undefined ) { // Optionally, function values get executed if exec is true exec = !pass && exec && jQuery.isFunction(value); - + for ( var i = 0; i < length; i++ ) { fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); } - + return elems; } - + // Getting an attribute return length ? fn( elems[0], key ) : undefined; }, @@ -800,6 +806,178 @@ jQuery.extend({ return (new Date()).getTime(); }, + // Create a simple deferred (one callbacks list) + _Deferred: function() { + + var // callbacks list + callbacks = [], + // stored [ context , args ] + fired, + // to avoid firing when already doing so + firing, + // flag to know if the deferred has been cancelled + cancelled, + // the deferred itself + deferred = { + + // done( f1, f2, ...) + done: function () { + + if ( ! cancelled ) { + + var args = arguments, + i, + length, + elem, + type, + _fired; + + if ( fired ) { + _fired = fired; + fired = 0; + } + + for ( i = 0, length = args.length ; i < length ; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + deferred.done.apply( deferred , elem ); + } else if ( type === "function" ) { + callbacks.push( elem ); + } + } + + if ( _fired ) { + deferred.fire( _fired[ 0 ] , _fired[ 1 ] ); + } + } + + return this; + }, + + // resolve with given context and args + fire: function( context , args ) { + if ( ! cancelled && ! fired && ! firing ) { + + firing = 1; + + try { + while( callbacks[ 0 ] ) { + callbacks.shift().apply( context , args ); + } + } + finally { + fired = [ context , args ]; + firing = 0; + } + } + return this; + }, + + // resolve with this as context and given arguments + resolve: function() { + deferred.fire( jQuery.isFunction( this.promise ) ? this.promise() : this , arguments ); + return this; + }, + + // Has this deferred been resolved? + isResolved: function() { + return !!( firing || fired ); + }, + + // Cancel + cancel: function() { + cancelled = 1; + callbacks = []; + return this; + } + }; + + return deferred; + }, + + // Full fledged deferred (two callbacks list) + // Typical success/error system + Deferred: function( func ) { + + var deferred = jQuery._Deferred(), + failDeferred = jQuery._Deferred(), + promise; + + // Add errorDeferred methods, then and promise + jQuery.extend( deferred , { + + then: function( doneCallbacks , failCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ); + return this; + }, + fail: failDeferred.done, + fireReject: failDeferred.fire, + reject: failDeferred.resolve, + isRejected: failDeferred.isResolved, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + // (i is used internally) + promise: function( obj , i ) { + if ( obj == null ) { + if ( promise ) { + return promise; + } + promise = obj = {}; + } + i = promiseMethods.length; + while( i-- ) { + obj[ promiseMethods[ i ] ] = deferred[ promiseMethods[ i ] ]; + } + return obj; + } + + } ); + + // Make sure only one callback list will be used + deferred.then( failDeferred.cancel , deferred.cancel ); + + // Unexpose cancel + delete deferred.cancel; + + // Call given func if any + if ( func ) { + func.call( deferred , deferred ); + } + + return deferred; + }, + + // Deferred helper + when: function( object ) { + var args = arguments, + length = args.length, + deferred = length <= 1 && object && jQuery.isFunction( object.promise ) ? + object : + jQuery.Deferred(), + promise = deferred.promise(), + resolveArray; + + if ( length > 1 ) { + resolveArray = new Array( length ); + jQuery.each( args, function( index, element, args ) { + jQuery.when( element ).done( function( value ) { + args = arguments; + resolveArray[ index ] = args.length > 1 ? slice.call( args , 0 ) : value; + if( ! --length ) { + deferred.fire( promise, resolveArray ); + } + }).fail( function() { + deferred.fireReject( promise, arguments ); + }); + return !deferred.isRejected(); + }); + } else if ( deferred !== object ) { + deferred.resolve( object ); + } + return promise; + }, + // Use of jQuery.browser is frowned upon. // More details: http://docs.jquery.com/Utilities/jQuery.browser uaMatch: function( ua ) { @@ -814,9 +992,31 @@ jQuery.extend({ return { browser: match[1] || "", version: match[2] || "0" }; }, + subclass: function(){ + function jQuerySubclass( selector, context ) { + return new jQuerySubclass.fn.init( selector, context ); + } + jQuerySubclass.superclass = this; + jQuerySubclass.fn = jQuerySubclass.prototype = this(); + jQuerySubclass.fn.constructor = jQuerySubclass; + jQuerySubclass.subclass = this.subclass; + jQuerySubclass.fn.init = function init( selector, context ) { + if (context && context instanceof jQuery && !(context instanceof jQuerySubclass)){ + context = jQuerySubclass(context); + } + return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass ); + }; + jQuerySubclass.fn.init.prototype = jQuerySubclass.fn; + var rootjQuerySubclass = jQuerySubclass(document); + return jQuerySubclass; + }, + browser: {} }); +// Create readyList deferred +readyList = jQuery._Deferred(); + // Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); diff --git a/src/css.js b/src/css.js index 8a83c60..19c6342 100644 --- a/src/css.js +++ b/src/css.js @@ -263,8 +263,9 @@ if ( document.defaultView && document.defaultView.getComputedStyle ) { if ( document.documentElement.currentStyle ) { currentStyle = function( elem, name ) { - var left, rsLeft, + var left, ret = elem.currentStyle && elem.currentStyle[ name ], + rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ], style = elem.style; // From the awesome hack by Dean Edwards @@ -275,16 +276,19 @@ if ( document.documentElement.currentStyle ) { if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { // Remember the original values left = style.left; - rsLeft = elem.runtimeStyle.left; // Put in the new values to get a computed value out - elem.runtimeStyle.left = elem.currentStyle.left; + if ( rsLeft ) { + elem.runtimeStyle.left = elem.currentStyle.left; + } style.left = name === "fontSize" ? "1em" : (ret || 0); ret = style.pixelLeft + "px"; // Revert the changed values style.left = left; - elem.runtimeStyle.left = rsLeft; + if ( rsLeft ) { + elem.runtimeStyle.left = rsLeft; + } } return ret === "" ? "auto" : ret; diff --git a/src/data.js b/src/data.js index f7f65ef..21f0e3a 100644 --- a/src/data.js +++ b/src/data.js @@ -1,7 +1,6 @@ (function( jQuery ) { -var windowData = {}, - rbrace = /^(?:\{.*\}|\[.*\])$/; +var rbrace = /^(?:\{.*\}|\[.*\])$/; jQuery.extend({ cache: {}, @@ -9,8 +8,9 @@ jQuery.extend({ // Please use with caution uuid: 0, - // Unique for each copy of jQuery on the page - expando: "jQuery" + jQuery.now(), + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), // The following elements throw uncatchable exceptions if you // attempt to add expando properties to them. @@ -21,103 +21,171 @@ jQuery.extend({ "applet": true }, - data: function( elem, name, data ) { + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + + return !!elem && !jQuery.isEmptyObject(elem); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } - elem = elem == window ? - windowData : - elem; + var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, - var isNode = elem.nodeType, - id = isNode ? elem[ jQuery.expando ] : null, - cache = jQuery.cache, thisCache; + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; - if ( isNode && !id && typeof name === "string" && data === undefined ) { + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) { return; } - // Get the data from the object directly - if ( !isNode ) { - cache = elem; + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ jQuery.expando ] = id = ++jQuery.uuid; + } else { + id = jQuery.expando; + } + } - // Compute a unique ID for the element - } else if ( !id ) { - elem[ jQuery.expando ] = id = ++jQuery.uuid; + if ( !cache[ id ] ) { + cache[ id ] = {}; } - // Avoid generating a new cache unless none exists and we - // want to manipulate it. + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache if ( typeof name === "object" ) { - if ( isNode ) { + if ( pvt ) { + cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); + } else { cache[ id ] = jQuery.extend(cache[ id ], name); + } + } - } else { - jQuery.extend( cache, name ); + thisCache = cache[ id ]; + + // Internal jQuery data is stored in a separate object inside the object's data + // cache in order to avoid key collisions between internal data and user-defined + // data + if ( pvt ) { + if ( !thisCache[ internalKey ] ) { + thisCache[ internalKey ] = {}; } - } else if ( isNode && !cache[ id ] ) { - cache[ id ] = {}; + thisCache = thisCache[ internalKey ]; } - thisCache = isNode ? cache[ id ] : cache; - - // Prevent overriding the named cache with undefined values if ( data !== undefined ) { thisCache[ name ] = data; } - return typeof name === "string" ? thisCache[ name ] : thisCache; + // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should + // not attempt to inspect the internal events object using jQuery.data, as this + // internal data object is undocumented and subject to change. + if ( name === "events" && !thisCache[name] ) { + return thisCache[ internalKey ] && thisCache[ internalKey ].events; + } + + return getByName ? thisCache[ name ] : thisCache; }, - removeData: function( elem, name ) { + removeData: function( elem, name, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } - elem = elem == window ? - windowData : - elem; + var internalKey = jQuery.expando, isNode = elem.nodeType, - var isNode = elem.nodeType, - id = isNode ? elem[ jQuery.expando ] : elem, - cache = jQuery.cache, - thisCache = isNode ? cache[ id ] : id; + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } - // If we want to remove a specific section of the element's data if ( name ) { + var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; + if ( thisCache ) { - // Remove the section of cache data delete thisCache[ name ]; - // If we've removed all the data, remove the element's cache - if ( isNode && jQuery.isEmptyObject(thisCache) ) { - jQuery.removeData( elem ); + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !jQuery.isEmptyObject(thisCache) ) { + return; } } + } + + // See jQuery.data for more information + if ( pvt ) { + delete cache[ id ][ internalKey ]; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !jQuery.isEmptyObject(cache[ id ]) ) { + return; + } + } - // Otherwise, we want to remove all of the element's data + var internalCache = cache[ id ][ internalKey ]; + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + if ( jQuery.support.deleteExpando || cache != window ) { + delete cache[ id ]; } else { - if ( isNode && jQuery.support.deleteExpando ) { - delete elem[ jQuery.expando ]; + cache[ id ] = null; + } + // We destroyed the entire user cache at once because it's faster than + // iterating through each key, but we need to continue to persist internal + // data if it existed + if ( internalCache ) { + cache[ id ] = {}; + cache[ id ][ internalKey ] = internalCache; + + // Otherwise, we need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + } else if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); - - // Completely remove the data cache - } else if ( isNode ) { - delete cache[ id ]; - - // Remove all fields from the object } else { - for ( var n in elem ) { - delete elem[ n ]; - } + elem[ jQuery.expando ] = null; } } }, + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + // A method for determining if a DOM node can handle the data expando acceptData: function( elem ) { if ( elem.nodeName ) { @@ -138,15 +206,17 @@ jQuery.fn.extend({ if ( typeof key === "undefined" ) { if ( this.length ) { - var attr = this[0].attributes, name; data = jQuery.data( this[0] ); - for ( var i = 0, l = attr.length; i < l; i++ ) { - name = attr[i].name; + if ( this[0].nodeType === 1 ) { + var attr = this[0].attributes, name; + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = name.substr( 5 ); - dataAttr( this[0], name, data[ name ] ); + if ( name.indexOf( "data-" ) === 0 ) { + name = name.substr( 5 ); + dataAttr( this[0], name, data[ name ] ); + } } } } diff --git a/src/dimensions.js b/src/dimensions.js index f35b0ac..17b4f8f 100644 --- a/src/dimensions.js +++ b/src/dimensions.js @@ -25,7 +25,7 @@ jQuery.each([ "Height", "Width" ], function( i, name ) { if ( !elem ) { return size == null ? null : this; } - + if ( jQuery.isFunction( size ) ) { return this.each(function( i ) { var self = jQuery( this ); diff --git a/src/effects.js b/src/effects.js index 51ce0c5..b067539 100644 --- a/src/effects.js +++ b/src/effects.js @@ -2,7 +2,7 @@ var elemdisplay = {}, rfxtypes = /^(?:toggle|show|hide)$/, - rfxnum = /^([+\-]=)?([\d+.\-]+)(.*)$/, + rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, timerId, fxAttrs = [ // height animations @@ -27,7 +27,7 @@ jQuery.fn.extend({ // Reset the inline display of this element to learn if it is // being hidden by cascaded rules or not - if ( !jQuery.data(elem, "olddisplay") && display === "none" ) { + if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { display = elem.style.display = ""; } @@ -35,7 +35,7 @@ jQuery.fn.extend({ // in a stylesheet to whatever the default browser style is // for such an element if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { - jQuery.data(elem, "olddisplay", defaultDisplay(elem.nodeName)); + jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName)); } } @@ -46,7 +46,7 @@ jQuery.fn.extend({ display = elem.style.display; if ( display === "" || display === "none" ) { - elem.style.display = jQuery.data(elem, "olddisplay") || ""; + elem.style.display = jQuery._data(elem, "olddisplay") || ""; } } @@ -62,8 +62,8 @@ jQuery.fn.extend({ for ( var i = 0, j = this.length; i < j; i++ ) { var display = jQuery.css( this[i], "display" ); - if ( display !== "none" ) { - jQuery.data( this[i], "olddisplay", display ); + if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { + jQuery._data( this[i], "olddisplay", display ); } } @@ -337,7 +337,7 @@ jQuery.fx.prototype = { } var r = parseFloat( jQuery.css( this.elem, this.prop ) ); - return r && r > -10000 ? r : 0; + return r || 0; }, // Start an animation from one number to another diff --git a/src/event.js b/src/event.js index 3cfc817..2ddf288 100644 --- a/src/event.js +++ b/src/event.js @@ -8,7 +8,8 @@ var rnamespaces = /\.(.*)$/, fcleanup = function( nm ) { return nm.replace(rescape, "\\$&"); }, - focusCounts = { focusin: 0, focusout: 0 }; + focusCounts = { focusin: 0, focusout: 0 }, + eventKey = "events"; /* * A number of helper functions used for managing events. @@ -50,7 +51,7 @@ jQuery.event = { } // Init the element's event structure - var elemData = jQuery.data( elem ); + var elemData = jQuery._data( elem ); // If no elemData is found then we must be trying to bind to one of the // banned noData elements @@ -58,12 +59,9 @@ jQuery.event = { return; } - // Use a key less likely to result in collisions for plain JS objects. - // Fixes bug #7150. - var eventKey = elem.nodeType ? "events" : "__events__", - events = elemData[ eventKey ], + var events = elemData[ eventKey ], eventHandle = elemData.handle; - + if ( typeof events === "function" ) { // On plain objects events is a fn that holds the the data // which prevents this data from being JSON serialized @@ -143,9 +141,9 @@ jQuery.event = { } } } - - if ( special.add ) { - special.add.call( elem, handleObj ); + + if ( special.add ) { + special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; @@ -177,14 +175,13 @@ jQuery.event = { } var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, - eventKey = elem.nodeType ? "events" : "__events__", - elemData = jQuery.data( elem ), + elemData = jQuery.hasData( elem ) && jQuery._data( elem ), events = elemData && elemData[ eventKey ]; if ( !elemData || !events ) { return; } - + if ( typeof events === "function" ) { elemData = events; events = events.events; @@ -222,7 +219,7 @@ jQuery.event = { namespaces = type.split("."); type = namespaces.shift(); - namespace = new RegExp("(^|\\.)" + + namespace = new RegExp("(^|\\.)" + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); } @@ -290,10 +287,10 @@ jQuery.event = { delete elemData.handle; if ( typeof elemData === "function" ) { - jQuery.removeData( elem, eventKey ); + jQuery.removeData( elem, eventKey, true ); } else if ( jQuery.isEmptyObject( elemData ) ) { - jQuery.removeData( elem ); + jQuery.removeData( elem, undefined, true ); } } }, @@ -325,9 +322,16 @@ jQuery.event = { // Only trigger if we've ever bound an event for it if ( jQuery.event.global[ type ] ) { + // XXX This code smells terrible. event.js should not be directly + // inspecting the data cache jQuery.each( jQuery.cache, function() { - if ( this.events && this.events[type] ) { - jQuery.event.trigger( event, data, this.handle.elem ); + // internalKey variable is just used to make it easier to find + // and potentially change this stuff later; currently it just + // points to jQuery.expando + var internalKey = jQuery.expando, + internalCache = this[ internalKey ]; + if ( internalCache && internalCache.events && internalCache.events[type] ) { + jQuery.event.trigger( event, data, internalCache.handle.elem ); } }); } @@ -353,8 +357,8 @@ jQuery.event = { // Trigger the event, it is assumed that "handle" is a function var handle = elem.nodeType ? - jQuery.data( elem, "handle" ) : - (jQuery.data( elem, "__events__" ) || {}).handle; + jQuery._data( elem, "handle" ) : + (jQuery._data( elem, eventKey ) || {}).handle; if ( handle ) { handle.apply( elem, data ); @@ -384,7 +388,7 @@ jQuery.event = { isClick = jQuery.nodeName( target, "a" ) && targetType === "click", special = jQuery.event.special[ targetType ] || {}; - if ( (!special._default || special._default.call( elem, event ) === false) && + if ( (!special._default || special._default.call( elem, event ) === false) && !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { try { @@ -432,7 +436,7 @@ jQuery.event = { event.namespace = event.namespace || namespace_sort.join("."); - events = jQuery.data(this, this.nodeType ? "events" : "__events__"); + events = jQuery._data(this, eventKey); if ( typeof events === "function" ) { events = events.events; @@ -454,7 +458,7 @@ jQuery.event = { event.handler = handleObj.handler; event.data = handleObj.data; event.handleObj = handleObj; - + var ret = handleObj.handler.apply( this, args ); if ( ret !== undefined ) { @@ -553,7 +557,7 @@ jQuery.event = { add: function( handleObj ) { jQuery.event.add( this, liveConvert( handleObj.origType, handleObj.selector ), - jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); + jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); }, remove: function( handleObj ) { @@ -583,7 +587,7 @@ jQuery.removeEvent = document.removeEventListener ? if ( elem.removeEventListener ) { elem.removeEventListener( type, handle, false ); } - } : + } : function( elem, type, handle ) { if ( elem.detachEvent ) { elem.detachEvent( "on" + type, handle ); @@ -600,6 +604,12 @@ jQuery.Event = function( src ) { if ( src && src.type ) { this.originalEvent = src; this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; + // Event type } else { this.type = src; @@ -630,7 +640,7 @@ jQuery.Event.prototype = { if ( !e ) { return; } - + // if preventDefault exists run it on the original event if ( e.preventDefault ) { e.preventDefault(); @@ -716,7 +726,7 @@ if ( !jQuery.support.submitBubbles ) { jQuery.event.special.submit = { setup: function( data, namespaces ) { - if ( this.nodeName.toLowerCase() !== "form" ) { + if ( this.nodeName && this.nodeName.toLowerCase() !== "form" ) { jQuery.event.add(this, "click.specialSubmit", function( e ) { var elem = e.target, type = elem.type; @@ -726,7 +736,7 @@ if ( !jQuery.support.submitBubbles ) { return trigger( "submit", this, arguments ); } }); - + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { var elem = e.target, type = elem.type; @@ -781,14 +791,14 @@ if ( !jQuery.support.changeBubbles ) { return; } - data = jQuery.data( elem, "_change_data" ); + data = jQuery._data( elem, "_change_data" ); val = getVal(elem); // the current data will be also retrieved by beforeactivate if ( e.type !== "focusout" || elem.type !== "radio" ) { - jQuery.data( elem, "_change_data", val ); + jQuery._data( elem, "_change_data", val ); } - + if ( data === undefined || val === data ) { return; } @@ -802,7 +812,7 @@ if ( !jQuery.support.changeBubbles ) { jQuery.event.special.change = { filters: { - focusout: testChange, + focusout: testChange, beforedeactivate: testChange, @@ -831,7 +841,7 @@ if ( !jQuery.support.changeBubbles ) { // information beforeactivate: function( e ) { var elem = e.target; - jQuery.data( elem, "_change_data", getVal(elem) ); + jQuery._data( elem, "_change_data", getVal(elem) ); } }, @@ -873,15 +883,15 @@ if ( document.addEventListener ) { if ( focusCounts[fix]++ === 0 ) { document.addEventListener( orig, handler, true ); } - }, - teardown: function() { + }, + teardown: function() { if ( --focusCounts[fix] === 0 ) { document.removeEventListener( orig, handler, true ); } } }; - function handler( e ) { + function handler( e ) { e = jQuery.event.fix( e ); e.type = fix; return jQuery.event.trigger( e, null, e.target ); @@ -898,7 +908,7 @@ jQuery.each(["bind", "one"], function( i, name ) { } return this; } - + if ( jQuery.isFunction( data ) || data === false ) { fn = data; data = undefined; @@ -938,20 +948,20 @@ jQuery.fn.extend({ return this; }, - + delegate: function( selector, types, data, fn ) { return this.live( types, data, fn, selector ); }, - + undelegate: function( selector, types, fn ) { if ( arguments.length === 0 ) { return this.unbind( "live" ); - + } else { return this.die( types, null, fn, selector ); } }, - + trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); @@ -980,8 +990,8 @@ jQuery.fn.extend({ return this.click( jQuery.proxy( fn, function( event ) { // Figure out which function to execute - var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; - jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); // Make sure that clicks stop event.preventDefault(); @@ -1008,12 +1018,12 @@ jQuery.each(["live", "die"], function( i, name ) { var type, i = 0, match, namespaces, preType, selector = origSelector || this.selector, context = origSelector ? this : jQuery( this.context ); - + if ( typeof types === "object" && !types.preventDefault ) { for ( var key in types ) { context[ name ]( key, data, types[key], selector ); } - + return this; } @@ -1060,7 +1070,7 @@ jQuery.each(["live", "die"], function( i, name ) { context.unbind( "live." + liveConvert( type, selector ), fn ); } } - + return this; }; }); @@ -1069,17 +1079,17 @@ function liveHandler( event ) { var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, elems = [], selectors = [], - events = jQuery.data( this, this.nodeType ? "events" : "__events__" ); + events = jQuery._data( this, eventKey ); if ( typeof events === "function" ) { events = events.events; } - // Make sure we avoid non-left-click bubbling in Firefox (#3861) - if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { + // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) + if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { return; } - + if ( event.namespace ) { namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); } @@ -1177,21 +1187,4 @@ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblcl } }); -// Prevent memory leaks in IE -// Window isn't included so as not to unbind existing unload events -// More info: -// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ -if ( window.attachEvent && !window.addEventListener ) { - jQuery(window).bind("unload", function() { - for ( var id in jQuery.cache ) { - if ( jQuery.cache[ id ].handle ) { - // Try/Catch is to handle iframes being unloaded, see #4280 - try { - jQuery.event.remove( jQuery.cache[ id ].handle.elem ); - } catch(e) {} - } - } - }); -} - })( jQuery ); diff --git a/src/intro.js b/src/intro.js index cb15705..a75f311 100644 --- a/src/intro.js +++ b/src/intro.js @@ -11,7 +11,7 @@ * Copyright 2010, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * - * Date: + * Date: @DATE */ (function( window, undefined ) { diff --git a/src/manipulation.js b/src/manipulation.js index d8fa020..596a457 100644 --- a/src/manipulation.js +++ b/src/manipulation.js @@ -9,7 +9,6 @@ var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, rnocache = /<(?:script|object|embed|option|style)/i, // checked="checked" or checked (html5) rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - raction = /\=([^="'>\s]+\/)>/g, wrapMap = { option: [ 1, "" ], legend: [ 1, "
", "
" ], @@ -149,7 +148,7 @@ jQuery.fn.extend({ return set; } }, - + // keepData is for internal use only--do not document remove: function( selector, keepData ) { for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { @@ -164,7 +163,7 @@ jQuery.fn.extend({ } } } - + return this; }, @@ -180,38 +179,37 @@ jQuery.fn.extend({ elem.removeChild( elem.firstChild ); } } - + return this; }, 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 html = this.outerHTML, - ownerDocument = this.ownerDocument; - - if ( !html ) { - var div = ownerDocument.createElement("div"); - div.appendChild( this.cloneNode(true) ); - html = div.innerHTML; + var clone = this.cloneNode(true); + if ( !jQuery.support.noCloneEvent && (this.nodeType === 1 || this.nodeType === 11) && !jQuery.isXMLDoc(this) ) { + // IE copies events bound via attachEvent when using cloneNode. + // Calling detachEvent on the clone will also remove the events + // from the original. In order to get around this, we use some + // proprietary methods to clear the events. Thanks to MooTools + // guys for this hotness. + + // Using Sizzle here is crazy slow, so we use getElementsByTagName + // instead + var srcElements = this.getElementsByTagName("*"), + destElements = clone.getElementsByTagName("*"); + + // Weird iteration because IE will replace the length property + // with an element if you are cloning the body and one of the + // elements on the page has a name or id of "length" + for ( var i = 0; srcElements[i]; ++i ) { + cloneFixAttributes( srcElements[i], destElements[i] ); } - return jQuery.clean([html.replace(rinlinejQuery, "") - // Handle the case in IE 8 where action=/test/> self-closes a tag - .replace(raction, '="$1">') - .replace(rleadingWhitespace, "")], ownerDocument)[0]; - } else { - return this.cloneNode(true); + cloneFixAttributes( this, clone ); } + + return clone; }); // Copy the events from the original to the clone @@ -331,9 +329,9 @@ jQuery.fn.extend({ } else { results = jQuery.buildFragment( args, this, scripts ); } - + fragment = results.fragment; - + if ( fragment.childNodes.length === 1 ) { first = fragment = fragment.firstChild; } else { @@ -348,8 +346,8 @@ jQuery.fn.extend({ table ? root(this[i], first) : this[i], - i > 0 || results.cacheable || this.length > 1 ? - fragment.cloneNode(true) : + i > 0 || results.cacheable || (this.length > 1 && i > 0) ? + jQuery(fragment).clone(true)[0] : fragment ); } @@ -372,40 +370,103 @@ function root( elem, cur ) { } function cloneCopyEvent(orig, ret) { - var i = 0; - - ret.each(function() { - if ( this.nodeName !== (orig[i] && orig[i].nodeName) ) { + ret.each(function (nodeIndex) { + if ( this.nodeType !== 1 || !jQuery.hasData(orig[nodeIndex]) ) { return; } - var oldData = jQuery.data( orig[i++] ), - curData = jQuery.data( this, oldData ), - events = oldData && oldData.events; + // XXX remove for 1.5 RC or merge back in if there is actually a reason for this check that has been + // unexposed by unit tests + if ( this.nodeName !== (orig[nodeIndex] && orig[nodeIndex].nodeName) ) { + throw "Cloned data mismatch"; + } + + var internalKey = jQuery.expando, + oldData = jQuery.data( orig[nodeIndex] ), + curData = jQuery.data( this, oldData ); - if ( events ) { - delete curData.handle; - curData.events = {}; + // Switch to use the internal data object, if it exists, for the next + // stage of data copying + if ( (oldData = oldData[ internalKey ]) ) { + var events = oldData.events; + curData = curData[ internalKey ] = jQuery.extend({}, oldData); - for ( var type in events ) { - for ( var handler in events[ type ] ) { - jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data ); + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( var type in events ) { + for ( var i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( this, type, events[ type ][ i ], events[ type ][ i ].data ); + } } } } }); } +function cloneFixAttributes(src, dest) { + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + var nodeName = dest.nodeName.toLowerCase(); + + // clearAttributes removes the attributes, which we don't want, + // but also removes the attachEvent events, which we *do* want + dest.clearAttributes(); + + // mergeAttributes, in contrast, only merges back on the + // original attributes, not the events + dest.mergeAttributes(src); + + // IE6-8 fail to clone children inside object elements that use + // the proprietary classid attribute value (rather than the type + // attribute) to identify the type of content to display + if ( nodeName === "object" ) { + dest.outerHTML = src.outerHTML; + + } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + if ( src.checked ) { + dest.defaultChecked = dest.checked = src.checked; + } + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } + + // Event data gets referenced instead of copied if the expando + // gets copied too + dest.removeAttribute( jQuery.expando ); +} + jQuery.buildFragment = function( args, nodes, scripts ) { var fragment, cacheable, cacheresults, doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document); - // Only cache "small" (1/2 KB) strings that are associated with the main document + // Only cache "small" (1/2 KB) HTML strings that are associated with the main document // Cloning options loses the selected state, so don't cache them // IE 6 doesn't like it when you put or elements in a fragment // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && - !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { + args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { cacheable = true; cacheresults = jQuery.fragments[ args[0] ]; @@ -441,18 +502,18 @@ jQuery.each({ var ret = [], insert = jQuery( selector ), parent = this.length === 1 && this[0].parentNode; - + if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { insert[ original ]( this[0] ); return this; - + } else { for ( var i = 0, l = insert.length; i < l; i++ ) { var elems = (i > 0 ? this.clone(true) : this).get(); jQuery( insert[i] )[ original ]( elems ); ret = ret.concat( elems ); } - + return this.pushStack( ret, name, insert.selector ); } }; @@ -540,7 +601,7 @@ jQuery.extend({ for ( i = 0; ret[i]; i++ ) { if ( scripts && 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"))) ); @@ -552,40 +613,45 @@ jQuery.extend({ return ret; }, - + cleanData: function( elems ) { - var data, id, cache = jQuery.cache, - special = jQuery.event.special, + var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special, deleteExpando = jQuery.support.deleteExpando; - + for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { continue; } id = elem[ jQuery.expando ]; - + if ( id ) { - data = cache[ id ]; - + data = cache[ id ] && cache[ id ][ internalKey ]; + if ( data && data.events ) { for ( var type in data.events ) { if ( special[ type ] ) { jQuery.event.remove( elem, type ); + // This is a shortcut to avoid jQuery.event.remove's overhead } else { jQuery.removeEvent( elem, type, data.handle ); } } + + // Null the DOM reference to avoid IE6/7/8 leak (#7054) + if ( data.handle ) { + data.handle.elem = null; + } } - + if ( deleteExpando ) { delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); } - + delete cache[ id ]; } } diff --git a/src/offset.js b/src/offset.js index dab053e..a10d30a 100644 --- a/src/offset.js +++ b/src/offset.js @@ -7,7 +7,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { jQuery.fn.offset = function( options ) { var elem = this[0], box; - if ( options ) { + if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); @@ -49,7 +49,7 @@ if ( "getBoundingClientRect" in document.documentElement ) { jQuery.fn.offset = function( options ) { var elem = this[0]; - if ( options ) { + if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); @@ -168,7 +168,7 @@ jQuery.offset = { return { top: top, left: left }; }, - + setOffset: function( elem, options, i ) { var position = jQuery.css( elem, "position" ); @@ -202,7 +202,7 @@ jQuery.offset = { if (options.left != null) { props.left = (options.left - curOffset.left) + curLeft; } - + if ( "using" in options ) { options.using.call( elem, props ); } else { @@ -262,7 +262,7 @@ jQuery.each( ["Left", "Top"], function( i, name ) { jQuery.fn[ method ] = function(val) { var elem = this[0], win; - + if ( !elem ) { return null; } diff --git a/src/queue.js b/src/queue.js index 735b0e1..9e3e2fb 100644 --- a/src/queue.js +++ b/src/queue.js @@ -7,7 +7,7 @@ jQuery.extend({ } type = (type || "fx") + "queue"; - var q = jQuery.data( elem, type ); + var q = jQuery._data( elem, type ); // Speed up dequeue by getting out quickly if this is just a lookup if ( !data ) { @@ -15,7 +15,7 @@ jQuery.extend({ } if ( !q || jQuery.isArray(data) ) { - q = jQuery.data( elem, type, jQuery.makeArray(data) ); + q = jQuery._data( elem, type, jQuery.makeArray(data) ); } else { q.push( data ); @@ -46,6 +46,10 @@ jQuery.extend({ jQuery.dequeue(elem, type); }); } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue", true ); + } } }); diff --git a/src/support.js b/src/support.js index 67b41c4..f502811 100644 --- a/src/support.js +++ b/src/support.js @@ -4,10 +4,7 @@ jQuery.support = {}; - var root = document.documentElement, - script = document.createElement("script"), - div = document.createElement("div"), - id = "script" + jQuery.now(); + var div = document.createElement("div"); div.style.display = "none"; div.innerHTML = "
a"; @@ -64,7 +61,7 @@ deleteExpando: true, optDisabled: false, checkClone: false, - scriptEval: false, + _scriptEval: null, noCloneEvent: true, boxModel: null, inlineBlockNeedsLayout: false, @@ -77,32 +74,45 @@ select.disabled = true; jQuery.support.optDisabled = !opt.disabled; - 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 ]; - } + jQuery.support.scriptEval = function() { + if ( jQuery.support._scriptEval === null) { + var root = document.documentElement, + script = document.createElement("script"), + id = "script" + jQuery.now(); + + 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 ]; + } else { + jQuery.support._scriptEval = false; + } + + root.removeChild( script ); + // release memory in IE + root = script = id = null; + } + return jQuery.support._scriptEval; + }; // Test to see if it's possible to delete an expando from an element // Fails in Internet Explorer try { - delete script.test; + delete div.test; } catch(e) { jQuery.support.deleteExpando = false; } - root.removeChild( script ); - if ( div.attachEvent && div.fireEvent ) { div.attachEvent("onclick", function click() { // Cloning a node shouldn't copy over any @@ -147,7 +157,7 @@ jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; } - div.innerHTML = "
t
"; + div.innerHTML = "
t
"; var tds = div.getElementsByTagName("td"); // Check if table cells still have offsetWidth/Height when they are set @@ -191,6 +201,6 @@ jQuery.support.changeBubbles = eventSupported("change"); // release memory in IE - root = script = div = all = a = null; + div = all = a = null; })(); })( jQuery ); diff --git a/src/traversing.js b/src/traversing.js index 15446bd..b36ce3d 100644 --- a/src/traversing.js +++ b/src/traversing.js @@ -6,7 +6,14 @@ var runtil = /Until$/, rmultiselector = /,/, isSimple = /^.[^:#\[\.,]*$/, slice = Array.prototype.slice, - POS = jQuery.expr.match.POS; + POS = jQuery.expr.match.POS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; jQuery.fn.extend({ find: function( selector ) { @@ -51,7 +58,7 @@ jQuery.fn.extend({ filter: function( selector ) { return this.pushStack( winnow(this, selector, true), "filter", selector ); }, - + is: function( selector ) { return !!selector && jQuery.filter( selector, this ).length > 0; }, @@ -69,7 +76,7 @@ jQuery.fn.extend({ selector = selectors[i]; if ( !matches[selector] ) { - matches[selector] = jQuery.expr.match.POS.test( selector ) ? + matches[selector] = jQuery.expr.match.POS.test( selector ) ? jQuery( selector, context || this.context ) : selector; } @@ -92,7 +99,7 @@ jQuery.fn.extend({ return ret; } - var pos = POS.test( selectors ) ? + var pos = POS.test( selectors ) ? jQuery( selectors, context || this.context ) : null; for ( i = 0, l = this.length; i < l; i++ ) { @@ -113,10 +120,10 @@ jQuery.fn.extend({ } ret = ret.length > 1 ? jQuery.unique(ret) : ret; - + return this.pushStack( ret, "closest", selectors ); }, - + // Determine the position of an element within // the matched set of elements index: function( elem ) { @@ -196,8 +203,9 @@ jQuery.each({ } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); - + var ret = jQuery.map( this, fn, until ), + args = slice.call(arguments); + if ( !runtil.test( name ) ) { selector = until; } @@ -206,13 +214,13 @@ jQuery.each({ ret = jQuery.filter( selector, ret ); } - ret = this.length > 1 ? jQuery.unique( ret ) : ret; + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { ret = ret.reverse(); } - return this.pushStack( ret, name, slice.call(arguments).join(",") ); + return this.pushStack( ret, name, args.join(",") ); }; }); @@ -226,7 +234,7 @@ jQuery.extend({ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : jQuery.find.matches(expr, elems); }, - + dir: function( elem, dir, until ) { var matched = [], cur = elem[ dir ]; diff --git a/test/data/atom+xml.php b/test/data/atom+xml.php new file mode 100644 index 0000000..944591a --- /dev/null +++ b/test/data/atom+xml.php @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/test/data/css.php b/test/data/css.php new file mode 100644 index 0000000..9d079e7 --- /dev/null +++ b/test/data/css.php @@ -0,0 +1,15 @@ + + div# { margin-left: 27px } + \ No newline at end of file diff --git a/test/data/errorWithText.php b/test/data/errorWithText.php new file mode 100644 index 0000000..abd8732 --- /dev/null +++ b/test/data/errorWithText.php @@ -0,0 +1,5 @@ + $value ) { + + $key = str_replace( "_" , "-" , substr( $key , 0 , 5 ) == "HTTP_" ? substr( $key , 5 ) : $key ); + $headers[ $key ] = $value; + +} + +foreach( explode( "_" , $_GET[ "keys" ] ) as $key ) { + echo "$key: " . @$headers[ strtoupper( $key ) ] . "\n"; +} diff --git a/test/data/jsonp.php b/test/data/jsonp.php index 9ae1d84..6c13d72 100644 --- a/test/data/jsonp.php +++ b/test/data/jsonp.php @@ -1,6 +1,10 @@ -$value ) echo "$value"; -?> +?>
$value ) echo "$value"; -?> +?>
\ No newline at end of file diff --git a/test/data/testinit.js b/test/data/testinit.js index a66f71d..c478390 100644 --- a/test/data/testinit.js +++ b/test/data/testinit.js @@ -45,3 +45,52 @@ function t(a,b,c) { function url(value) { return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000); } + +(function () { + // Store the old counts so that we only assert on tests that have actually leaked, + // instead of asserting every time a test has leaked sometime in the past + var oldCacheLength = 0, + oldFragmentsLength = 0, + oldTimersLength = 0, + oldActive = 0; + + /** + * Ensures that tests have cleaned up properly after themselves. Should be passed as the + * teardown function on all modules' lifecycle object. + */ + this.moduleTeardown = function () { + var i, fragmentsLength = 0, cacheLength = 0; + + // Allow QUnit.reset to clean up any attached elements before checking for leaks + QUnit.reset(); + + for ( i in jQuery.cache ) { + ++cacheLength; + } + + jQuery.fragments = {}; + + for ( i in jQuery.fragments ) { + ++fragmentsLength; + } + + // Because QUnit doesn't have a mechanism for retrieving the number of expected assertions for a test, + // if we unconditionally assert any of these, the test will fail with too many assertions :| + if ( cacheLength !== oldCacheLength ) { + equals( cacheLength, oldCacheLength, "No unit tests leak memory in jQuery.cache" ); + oldCacheLength = cacheLength; + } + if ( fragmentsLength !== oldFragmentsLength ) { + equals( fragmentsLength, oldFragmentsLength, "No unit tests leak memory in jQuery.fragments" ); + oldFragmentsLength = fragmentsLength; + } + if ( jQuery.timers.length !== oldTimersLength ) { + equals( jQuery.timers.length, oldTimersLength, "No timers are still running" ); + oldTimersLength = jQuery.timers.length; + } + if ( jQuery.active !== oldActive ) { + equals( jQuery.active, 0, "No AJAX requests are still active" ); + oldActive = jQuery.active; + } + } +}()); \ No newline at end of file diff --git a/test/data/text.php b/test/data/text.php index c06ef4b..b9df4cf 100644 --- a/test/data/text.php +++ b/test/data/text.php @@ -1,12 +1,12 @@ -Lorem ipsum dolor sit amet -consectetuer adipiscing elit -Sed lorem leo -lorem leo consectetuer adipiscing elit -Sed lorem leo -rhoncus sit amet -elementum at -bibendum at, eros -Cras at mi et tortor egestas vestibulum -sed Cras at mi vestibulum -Phasellus sed felis sit amet -orci dapibus semper. +Lorem ipsum dolor sit amet +consectetuer adipiscing elit +Sed lorem leo +lorem leo consectetuer adipiscing elit +Sed lorem leo +rhoncus sit amet +elementum at +bibendum at, eros +Cras at mi et tortor egestas vestibulum +sed Cras at mi vestibulum +Phasellus sed felis sit amet +orci dapibus semper. diff --git a/test/data/with_fries_over_jsonp.php b/test/data/with_fries_over_jsonp.php new file mode 100644 index 0000000..456aeb3 --- /dev/null +++ b/test/data/with_fries_over_jsonp.php @@ -0,0 +1,7 @@ + diff --git a/test/delegatetest.html b/test/delegatetest.html index b2840c1..6479d26 100644 --- a/test/delegatetest.html +++ b/test/delegatetest.html @@ -1,242 +1,242 @@ - - - - - - -

Change Tests

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Change each: - - - - - - - -
- - - - -
- -
- - - - -
- - - - - - - $(document).bind('change')
Live:SELECTMULTICHECKBOXRADIOFILETEXTTEXTAREADOCUMENT
Bind:SELECTMULTICHECKBOXRADIOFILETEXTTEXTAREA
Focusin:SELECTMULTICHECKBOXRADIOFILETEXTTEXTAREADOCUMENT
Focusout:SELECTMULTICHECKBOXRADIOFILETEXTTEXTAREADOCUMENT
Live Focus:SELECTMULTICHECKBOXRADIOFILETEXTTEXTAREA
Live Blur:SELECTMULTICHECKBOXRADIOFILETEXTTEXTAREA
-

Submit Tests

- - - - - - - - - - - - - - - -
- Submit each: - -
- -
-
-
- -
-
-
- -
-
$(document).bind('submit')
Results:TEXTPASSWORDBUTTONDOCUMENT
- -
    - - - - + + + + + + +

    Change Tests

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Change each: + + + + + + + +
    + + + + +
    + +
    + + + + +
    + + + + + + + $(document).bind('change')
    Live:SELECTMULTICHECKBOXRADIOFILETEXTTEXTAREADOCUMENT
    Bind:SELECTMULTICHECKBOXRADIOFILETEXTTEXTAREA
    Focusin:SELECTMULTICHECKBOXRADIOFILETEXTTEXTAREADOCUMENT
    Focusout:SELECTMULTICHECKBOXRADIOFILETEXTTEXTAREADOCUMENT
    Live Focus:SELECTMULTICHECKBOXRADIOFILETEXTTEXTAREA
    Live Blur:SELECTMULTICHECKBOXRADIOFILETEXTTEXTAREA
    +

    Submit Tests

    + + + + + + + + + + + + + + + +
    + Submit each: + +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    $(document).bind('submit')
    Results:TEXTPASSWORDBUTTONDOCUMENT
    + +
      + + + + diff --git a/test/index.html b/test/index.html index e668727..fc5f667 100644 --- a/test/index.html +++ b/test/index.html @@ -20,6 +20,9 @@ + + + @@ -259,9 +262,9 @@ Z
      slideToggleIn
      slideToggleIn
      slideToggleOut
      slideToggleOut
      - +
      fadeToggleIn
      fadeToggleIn
      -
      fadeToggleOut
      fadeToggleOut
      +
      fadeToggleOut
      fadeToggleOut
      fadeTo
      fadeTo
      diff --git a/test/polluted.php b/test/polluted.php index 3ddb7ac..55df0dd 100644 --- a/test/polluted.php +++ b/test/polluted.php @@ -15,7 +15,7 @@ $suite = file_get_contents('index.html'); echo str_replace( '', $includes, $suite ); exit; - } + } ?> @@ -43,7 +43,7 @@

      jQuery Test Suite

      Choose other libraries to include

      - +
      ").load(url("data/name.html"), function(responseText) { + strictEqual( div.html(), "Hello World" , "Test div was filled with filtered data" ); + strictEqual( responseText, "Hello World" , "Test callback receives filtered data" ); + jQuery.ajaxSetup({ dataFilter: 0 }); + start(); + }); +}); + test("load(String, Object, Function)", function() { expect(2); stop(); @@ -709,205 +1114,249 @@ test("jQuery.getScript(String, Function) - no callback", function() { }); }); -test("jQuery.ajax() - JSONP, Local", function() { - expect(8); - - var count = 0; - function plus(){ if ( ++count == 8 ) start(); } +jQuery.each( [ "Same Domain", "Cross Domain" ] , function( crossDomain , label ) { - stop(); + test("jQuery.ajax() - JSONP, " + label, function() { + expect(17); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, no callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, no callback)" ); - plus(); - } - }); + var count = 0; + function plus(){ if ( ++count == 17 ) start(); } - jQuery.ajax({ - url: "data/jsonp.php?callback=?", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, url callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, url callback)" ); - plus(); - } - }); + stop(); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - data: "callback=?", - success: function(data){ - ok( data.data, "JSON results returned (GET, data callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (GET, no callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, no callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - jsonp: "callback", - success: function(data){ - ok( data.data, "JSON results returned (GET, data obj callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data obj callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php?callback=?", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (GET, url callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, url callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - jsonpCallback: "jsonpResults", - success: function(data){ - ok( data.data, "JSON results returned (GET, custom callback name)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, custom callback name)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + data: "callback=?", + success: function(data){ + ok( data.data, "JSON results returned (GET, data callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, data callback)" ); + plus(); + } + }); - jQuery.ajax({ - type: "POST", - url: "data/jsonp.php", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (POST, no callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data obj callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php?callback=??", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (GET, url context-free callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, url context-free callback)" ); + plus(); + } + }); - jQuery.ajax({ - type: "POST", - url: "data/jsonp.php", - data: "callback=?", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (POST, data callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (POST, data callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + data: "callback=??", + success: function(data){ + ok( data.data, "JSON results returned (GET, data context-free callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, data context-free callback)" ); + plus(); + } + }); - jQuery.ajax({ - type: "POST", - url: "data/jsonp.php", - jsonp: "callback", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (POST, data obj callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (POST, data obj callback)" ); - plus(); - } - }); -}); + jQuery.ajax({ + url: "data/jsonp.php/??", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (GET, REST-like)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, REST-like)" ); + plus(); + } + }); -test("JSONP - Custom JSONP Callback", function() { - expect(1); - stop(); + jQuery.ajax({ + url: "data/jsonp.php/???json=1", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + strictEqual( jQuery.type(data), "array", "JSON results returned (GET, REST-like with param)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, REST-like with param)" ); + plus(); + } + }); - window.jsonpResults = function(data) { - ok( data.data, "JSON results returned (GET, custom callback function)" ); - start(); - }; + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + data: { + callback: "?" + }, + success: function(data){ + ok( data.data, "JSON results returned (GET, processed data callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, processed data callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: "data/jsonp.php", - dataType: "jsonp", - jsonpCallback: "jsonpResults" - }); -}); + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + jsonp: "callback", + success: function(data){ + ok( data.data, "JSON results returned (GET, data obj callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, data obj callback)" ); + plus(); + } + }); -test("jQuery.ajax() - JSONP, Remote", function() { - expect(4); + window.jsonpResults = function(data) { + ok( data.data, "JSON results returned (GET, custom callback function)" ); + window.jsonpResults = undefined; + plus(); + }; - var count = 0; - function plus(){ if ( ++count == 4 ) start(); } + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + jsonpCallback: "jsonpResults", + success: function(data){ + ok( data.data, "JSON results returned (GET, custom callback name)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, custom callback name)" ); + plus(); + } + }); - var base = window.location.href.replace(/[^\/]*$/, ""); + jQuery.ajax({ + type: "POST", + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (POST, no callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, data obj callback)" ); + plus(); + } + }); - stop(); + jQuery.ajax({ + type: "POST", + url: "data/jsonp.php", + data: "callback=?", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (POST, data callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (POST, data callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: base + "data/jsonp.php", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, no callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, no callback)" ); - plus(); - } - }); + jQuery.ajax({ + type: "POST", + url: "data/jsonp.php", + jsonp: "callback", + dataType: "jsonp", + crossDomain: crossDomain, + success: function(data){ + ok( data.data, "JSON results returned (POST, data obj callback)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (POST, data obj callback)" ); + plus(); + } + }); - jQuery.ajax({ - url: base + "data/jsonp.php?callback=?", - dataType: "jsonp", - success: function(data){ - ok( data.data, "JSON results returned (GET, url callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, url callback)" ); - plus(); - } - }); + //#7578 + jQuery.ajax({ + url: "data/jsonp.php", + dataType: "jsonp", + crossDomain: crossDomain, + beforeSend: function(){ + strictEqual( this.cache, false, "cache must be false on JSON request" ); + plus(); + return false; + } + }); - jQuery.ajax({ - url: base + "data/jsonp.php", - dataType: "jsonp", - data: "callback=?", - success: function(data){ - ok( data.data, "JSON results returned (GET, data callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data callback)" ); - plus(); - } - }); + jQuery.ajax({ + url: "data/jsonp.php?callback=XXX", + dataType: "jsonp", + jsonp: false, + jsonpCallback: "XXX", + crossDomain: crossDomain, + beforeSend: function() { + ok( /^data\/jsonp.php\?callback=XXX&_=\d+$/.test( this.url ) , + "The URL wasn't messed with (GET, custom callback name with no url manipulation)" ); + plus(); + }, + success: function(data){ + ok( data.data, "JSON results returned (GET, custom callback name with no url manipulation)" ); + plus(); + }, + error: function(data){ + ok( false, "Ajax error JSON (GET, custom callback name with no url manipulation)" ); + plus(); + } + }); - jQuery.ajax({ - url: base + "data/jsonp.php", - dataType: "jsonp", - jsonp: "callback", - success: function(data){ - ok( data.data, "JSON results returned (GET, data obj callback)" ); - plus(); - }, - error: function(data){ - ok( false, "Ajax error JSON (GET, data obj callback)" ); - plus(); - } }); }); @@ -1022,6 +1471,30 @@ test("jQuery.ajax() - json by content-type", function() { }); }); +test("jQuery.ajax() - json by content-type disabled with options", function() { + expect(6); + + stop(); + + jQuery.ajax({ + url: url("data/json.php"), + data: { header: "json", json: "array" }, + contents: { + json: false + }, + success: function( text ) { + equals( typeof text , "string" , "json wasn't auto-determined" ); + var json = jQuery.parseJSON( text ); + ok( json.length >= 2, "Check length"); + equals( json[0].name, 'John', 'Check JSON: first, name' ); + equals( json[0].age, 21, 'Check JSON: first, age' ); + equals( json[1].name, 'Peter', 'Check JSON: second, name' ); + equals( json[1].age, 25, 'Check JSON: second, age' ); + start(); + } + }); +}); + test("jQuery.getJSON(String, Hash, Function) - JSON array", function() { expect(5); stop(); @@ -1049,7 +1522,7 @@ test("jQuery.getJSON(String, Function) - JSON object", function() { test("jQuery.getJSON - Using Native JSON", function() { expect(2); - + var old = window.JSON; JSON = { parse: function(str){ @@ -1238,7 +1711,7 @@ test("data option: evaluate function values (#2806)", function() { equals( result, "key=value" ); start(); } - }) + }); }); test("data option: empty bodies for non-GET requests", function() { @@ -1251,7 +1724,7 @@ test("data option: empty bodies for non-GET requests", function() { equals( result, "" ); start(); } - }) + }); }); test("jQuery.ajax - If-Modified-Since support", function() { @@ -1264,22 +1737,22 @@ test("jQuery.ajax - If-Modified-Since support", function() { jQuery.ajax({ url: url, ifModified: true, - success: function(data, status) { + success: function(data, status) { equals(status, "success"); - + jQuery.ajax({ url: url, ifModified: true, - success: function(data, status) { + success: function(data, status) { if ( data === "FAIL" ) { ok(true, "Opera is incapable of doing .setRequestHeader('If-Modified-Since')."); ok(true, "Opera is incapable of doing .setRequestHeader('If-Modified-Since')."); } else { equals(status, "notmodified"); - ok(data == null, "response body should be empty") + ok(data == null, "response body should be empty"); } start(); - }, + }, error: function() { // Do this because opera simply refuses to implement 304 handling :( // A feature-driven way of detecting this would be appreciated @@ -1287,10 +1760,11 @@ test("jQuery.ajax - If-Modified-Since support", function() { ok(jQuery.browser.opera, "error"); ok(jQuery.browser.opera, "error"); start(); - } + } }); }, error: function() { + equals(false, "error"); // Do this because opera simply refuses to implement 304 handling :( // A feature-driven way of detecting this would be appreciated // See: http://gist.github.com/599419 @@ -1310,23 +1784,23 @@ test("jQuery.ajax - Etag support", function() { jQuery.ajax({ url: url, ifModified: true, - success: function(data, status) { + success: function(data, status) { equals(status, "success"); - + jQuery.ajax({ url: url, ifModified: true, - success: function(data, status) { + success: function(data, status) { if ( data === "FAIL" ) { ok(true, "Opera is incapable of doing .setRequestHeader('If-None-Match')."); ok(true, "Opera is incapable of doing .setRequestHeader('If-None-Match')."); } else { equals(status, "notmodified"); - ok(data == null, "response body should be empty") + ok(data == null, "response body should be empty"); } start(); - }, - error: function() { + }, + error: function() { // Do this because opera simply refuses to implement 304 handling :( // A feature-driven way of detecting this would be appreciated // See: http://gist.github.com/599419 @@ -1346,11 +1820,128 @@ test("jQuery.ajax - Etag support", function() { }); }); +test("jQuery ajax - failing cross-domain", function() { + + expect( 2 ); + + stop(); + + var i = 2; + + jQuery.ajax({ + url: 'http://somewebsitethatdoesnotexist-67864863574657654.com', + success: function(){ ok( false , "success" ); }, + error: function(xhr,_,e){ ok( true , "file not found: " + xhr.status + " => " + e ); }, + complete: function() { if ( ! --i ) start(); } + }); + + jQuery.ajax({ + url: 'http://www.google.com', + success: function(){ ok( false , "success" ); }, + error: function(xhr,_,e){ ok( true , "access denied: " + xhr.status + " => " + e ); }, + complete: function() { if ( ! --i ) start(); } + }); + +}); + +test("jQuery ajax - atom+xml", function() { + + stop(); + + jQuery.ajax({ + url: url( 'data/atom+xml.php' ), + success: function(){ ok( true , "success" ); }, + error: function(){ ok( false , "error" ); }, + complete: function() { start(); } + }); + +}); + test("jQuery.ajax - active counter", function() { ok( jQuery.active == 0, "ajax active counter should be zero: " + jQuery.active ); }); +test( "jQuery.ajax - Location object as url (#7531)", 1, function () { + var success = false; + try { + var xhr = jQuery.ajax({ url: window.location }); + success = true; + xhr.abort(); + } catch (e) {} + + ok( success, "document.location did not generate exception" ); +}); + +test( "jQuery.ajax - statusCode" , function() { + + var count = 10; + + expect( 16 ); + stop(); + + function countComplete() { + if ( ! --count ) { + start(); + } + } + + function createStatusCodes( name , isSuccess ) { + name = "Test " + name + " " + ( isSuccess ? "success" : "error" ); + return { + 200: function() { + ok( isSuccess , name ); + }, + 404: function() { + ok( ! isSuccess , name ); + } + }; + } + + jQuery.each( { + "data/name.html": true, + "data/someFileThatDoesNotExist.html": false + } , function( uri , isSuccess ) { + + jQuery.ajax( url( uri ) , { + statusCode: createStatusCodes( "in options" , isSuccess ), + complete: countComplete + }); + + jQuery.ajax( url( uri ) , { + complete: countComplete + }).statusCode( createStatusCodes( "immediately with method" , isSuccess ) ); + + jQuery.ajax( url( uri ) , { + complete: function(jXHR) { + jXHR.statusCode( createStatusCodes( "on complete" , isSuccess ) ); + countComplete(); + } + }); + + jQuery.ajax( url( uri ) , { + complete: function(jXHR) { + setTimeout( function() { + jXHR.statusCode( createStatusCodes( "very late binding" , isSuccess ) ); + countComplete(); + } , 100 ); + } + }); + + jQuery.ajax( url( uri ) , { + statusCode: createStatusCodes( "all (options)" , isSuccess ), + complete: function(jXHR) { + jXHR.statusCode( createStatusCodes( "all (on complete)" , isSuccess ) ); + setTimeout( function() { + jXHR.statusCode( createStatusCodes( "all (very late binding)" , isSuccess ) ); + countComplete(); + } , 100 ); + } + }).statusCode( createStatusCodes( "all (immediately with method)" , isSuccess ) ); + + }); + +}); } -//} +//} \ No newline at end of file diff --git a/test/unit/attributes.js b/test/unit/attributes.js index 2d0a0d6..c58111d 100644 --- a/test/unit/attributes.js +++ b/test/unit/attributes.js @@ -1,14 +1,39 @@ -module("attributes"); +module("attributes", { teardown: moduleTeardown }); var bareObj = function(value) { return value; }; var functionReturningObj = function(value) { return (function() { return value; }); }; +test("jQuery.props: itegrity test", function() { + + expect(1); + + // This must be maintained and equal jQuery.props + // Ensure that accidental or erroneous property + // overwrites don't occur + // This is simply for better code coverage and future proofing. + var propsShouldBe = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" + }; + + same(propsShouldBe, jQuery.props, "jQuery.props passes integrity check"); + +}); + test("attr(String)", function() { - expect(31); + expect(37); // This one sometimes fails randomly ?! equals( jQuery('#text1').attr('value'), "Test", 'Check for value attribute' ); - + equals( jQuery('#text1').attr('value', "Test2").attr('defaultValue'), "Test", 'Check for defaultValue attribute' ); equals( jQuery('#text1').attr('type'), "text", 'Check for type attribute' ); equals( jQuery('#radio1').attr('type'), "radio", 'Check for type attribute' ); @@ -67,6 +92,14 @@ test("attr(String)", function() { ok( jQuery().attr("doesntexist") === undefined, "Make sure undefined is returned when no element is there." ); equals( jQuery(document).attr("nodeName"), "#document", "attr works correctly on document nodes (bug #7451)." ); + + var attributeNode = document.createAttribute("irrelevant"), + commentNode = document.createComment("some comment"), + textNode = document.createTextNode("some text"), + obj = {}; + jQuery.each( [document, attributeNode, commentNode, textNode, obj, "#firstp"], function( i, ele ) { + strictEqual( jQuery(ele).attr("nonexisting"), undefined, "attr works correctly for non existing attributes (bug #7500)." ); + }); }); if ( !isLocal ) { @@ -100,7 +133,7 @@ test("attr(Hash)", function() { }); test("attr(String, Object)", function() { - expect(24); + expect(30); var div = jQuery("div").attr("foo", "bar"), fail = false; @@ -134,6 +167,25 @@ test("attr(String, Object)", function() { jQuery("#name").attr('maxLength', '10'); equals( document.getElementById('name').maxLength, '10', 'Set maxlength attribute' ); + var attributeNode = document.createAttribute("irrelevant"), + commentNode = document.createComment("some comment"), + textNode = document.createTextNode("some text"), + obj = {}; + jQuery.each( [document, obj, "#firstp"], function( i, ele ) { + var $ele = jQuery( ele ); + $ele.attr( "nonexisting", "foo" ); + equal( $ele.attr("nonexisting"), "foo", "attr(name, value) works correctly for non existing attributes (bug #7500)." ); + }); + jQuery.each( [commentNode, textNode, attributeNode], function( i, ele ) { + var $ele = jQuery( ele ); + $ele.attr( "nonexisting", "foo" ); + strictEqual( $ele.attr("nonexisting"), undefined, "attr(name, value) works correctly on comment and text nodes (bug #7500)." ); + }); + //cleanup + jQuery.each( [document, "#firstp"], function( i, ele ) { + jQuery( ele ).removeAttr("nonexisting"); + }); + var table = jQuery('#table').append("cellcellcellcellcell"), td = table.find('td:first'); td.attr("rowspan", "2"); @@ -203,30 +255,30 @@ test("attr(String, Object)", function() { test("attr(jquery_method)", function(){ expect(7); - + var $elem = jQuery("
      "), elem = $elem[0]; - - // one at a time + + // one at a time $elem.attr({'html': 'foo'}, true); equals( elem.innerHTML, 'foo', 'attr(html)'); - + $elem.attr({'text': 'bar'}, true); equals( elem.innerHTML, 'bar', 'attr(text)'); - + $elem.attr({'css': {color:'red'}}, true); ok( /^(#ff0000|red)$/i.test(elem.style.color), 'attr(css)'); - + $elem.attr({'height': 10}, true); equals( elem.style.height, '10px', 'attr(height)'); - + // Multiple attributes - + $elem.attr({ width:10, css:{ paddingLeft:1, paddingRight:1 } }, true); - + equals( elem.style.width, '10px', 'attr({...})'); equals( elem.style.paddingLeft, '1px', 'attr({...})'); equals( elem.style.paddingRight, '1px', 'attr({...})'); @@ -304,8 +356,26 @@ test("attr('tabindex', value)", function() { }); test("removeAttr(String)", function() { - expect(1); + expect(7); equals( jQuery('#mark').removeAttr( "class" )[0].className, "", "remove class" ); + + var attributeNode = document.createAttribute("irrelevant"), + commentNode = document.createComment("some comment"), + textNode = document.createTextNode("some text"), + obj = {}; + //removeAttr only really removes on DOM element nodes handle all other seperatyl + strictEqual( jQuery( "#firstp" ).attr( "nonexisting", "foo" ).removeAttr( "nonexisting" )[0].nonexisting, undefined, "removeAttr works correctly on DOM element nodes" ); + + jQuery.each( [document, obj], function( i, ele ) { + var $ele = jQuery( ele ); + $ele.attr( "nonexisting", "foo" ).removeAttr( "nonexisting" ); + strictEqual( ele.nonexisting, "", "removeAttr works correctly on non DOM element nodes (bug #7500)." ); + }); + jQuery.each( [commentNode, textNode, attributeNode], function( i, ele ) { + $ele = jQuery( ele ); + $ele.attr( "nonexisting", "foo" ).removeAttr( "nonexisting" ); + strictEqual( ele.nonexisting, undefined, "removeAttr works correctly on non DOM element nodes (bug #7500)." ); + }); }); test("val()", function() { @@ -421,7 +491,7 @@ test( "val(Array of Numbers) (Bug #7123)", function() { ok( elements[1].checked, "Second element was checked" ); ok( !elements[2].checked, "Third element was unchecked" ); ok( !elements[3].checked, "Fourth element remained unchecked" ); - + elements.remove(); }); @@ -602,7 +672,7 @@ test("removeClass(Function) with incoming value", function() { ok( !$divs.is('.test'), "Remove Class" ); - QUnit.reset(); + QUnit.reset(); }); var testToggleClass = function(valueObj) { @@ -633,12 +703,12 @@ var testToggleClass = function(valueObj) { // toggleClass storage e.toggleClass(true); - ok( e.get(0).className === "", "Assert class is empty (data was empty)" ); + ok( e[0].className === "", "Assert class is empty (data was empty)" ); e.addClass("testD testE"); ok( e.is(".testD.testE"), "Assert class present" ); e.toggleClass(); ok( !e.is(".testD.testE"), "Assert class not present" ); - ok( e.data('__className__') === 'testD testE', "Assert data was stored" ); + ok( jQuery._data(e[0], '__className__') === 'testD testE', "Assert data was stored" ); e.toggleClass(); ok( e.is(".testD.testE"), "Assert class present (restored from data)" ); e.toggleClass(false); @@ -650,11 +720,9 @@ var testToggleClass = function(valueObj) { e.toggleClass(); ok( e.is(".testD.testE"), "Assert class present (restored from data)" ); - - // Cleanup e.removeClass("testD"); - e.removeData('__className__'); + jQuery.removeData(e[0], '__className__', true); }; test("toggleClass(String|boolean|undefined[, boolean])", function() { @@ -670,21 +738,21 @@ test("toggleClass(Fucntion[, boolean]) with incoming value", function() { var e = jQuery("#firstp"), old = e.attr("class"); ok( !e.is(".test"), "Assert class not present" ); - + e.toggleClass(function(i, val) { equals( val, old, "Make sure the incoming value is correct." ); return "test"; }); ok( e.is(".test"), "Assert class present" ); - + old = e.attr("class"); - + e.toggleClass(function(i, val) { equals( val, old, "Make sure the incoming value is correct." ); return "test"; }); ok( !e.is(".test"), "Assert class not present" ); - + old = e.attr("class"); // class name with a boolean @@ -694,18 +762,18 @@ test("toggleClass(Fucntion[, boolean]) with incoming value", function() { return "test"; }, false ); ok( !e.is(".test"), "Assert class not present" ); - + old = e.attr("class"); - + e.toggleClass(function(i, val, state) { equals( val, old, "Make sure the incoming value is correct." ); equals( state, true, "Make sure that the state is passed in." ); return "test"; }, true ); ok( e.is(".test"), "Assert class present" ); - + old = e.attr("class"); - + e.toggleClass(function(i, val, state) { equals( val, old, "Make sure the incoming value is correct." ); equals( state, false, "Make sure that the state is passed in." ); @@ -715,41 +783,45 @@ test("toggleClass(Fucntion[, boolean]) with incoming value", function() { // Cleanup e.removeClass("test"); - e.removeData('__className__'); + jQuery.removeData(e[0], '__className__', true); }); test("addClass, removeClass, hasClass", function() { - expect(14); - + expect(17); + var jq = jQuery("

      Hi

      "), x = jq[0]; - + jq.addClass("hi"); equals( x.className, "hi", "Check single added class" ); - + jq.addClass("foo bar"); equals( x.className, "hi foo bar", "Check more added classes" ); - + jq.removeClass(); equals( x.className, "", "Remove all classes" ); - + jq.addClass("hi foo bar"); jq.removeClass("foo"); equals( x.className, "hi bar", "Check removal of one class" ); - + ok( jq.hasClass("hi"), "Check has1" ); ok( jq.hasClass("bar"), "Check has2" ); - - var jq = jQuery("

      "); - ok( jq.hasClass("class1"), "Check hasClass with carriage return" ); - ok( jq.is(".class1"), "Check is with carriage return" ); + + var jq = jQuery("

      "); + ok( jq.hasClass("class1"), "Check hasClass with line feed" ); + ok( jq.is(".class1"), "Check is with line feed" ); ok( jq.hasClass("class2"), "Check hasClass with tab" ); ok( jq.is(".class2"), "Check is with tab" ); ok( jq.hasClass("cla.ss3"), "Check hasClass with dot" ); - + ok( jq.hasClass("class4"), "Check hasClass with carriage return" ); + ok( jq.is(".class4"), "Check is with carriage return" ); + jq.removeClass("class2"); ok( jq.hasClass("class2")==false, "Check the class has been properly removed" ); jq.removeClass("cla"); ok( jq.hasClass("cla.ss3"), "Check the dotted class has not been removed" ); jq.removeClass("cla.ss3"); ok( jq.hasClass("cla.ss3")==false, "Check the dotted class has been removed" ); + jq.removeClass("class4"); + ok( jq.hasClass("class4")==false, "Check the class has been properly removed" ); }); diff --git a/test/unit/core.js b/test/unit/core.js index 7ef2ad7..8e57edf 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -1,4 +1,4 @@ -module("core"); +module("core", { teardown: moduleTeardown }); test("Basic requirements", function() { expect(7); @@ -12,7 +12,7 @@ test("Basic requirements", function() { }); test("jQuery()", function() { - expect(23); + expect(24); // Basic constructor's behavior @@ -21,7 +21,7 @@ test("jQuery()", function() { equals( jQuery(null).length, 0, "jQuery(null) === jQuery([])" ); equals( jQuery("").length, 0, "jQuery('') === jQuery([])" ); - var obj = jQuery("div") + var obj = jQuery("div"); equals( jQuery(obj).selector, "div", "jQuery(jQueryObj) == jQueryObj" ); // can actually yield more than one, when iframes are included, the window is an array as well @@ -84,6 +84,17 @@ test("jQuery()", function() { exec = true; elem.click(); + + // manually clean up detached elements + elem.remove(); + + for ( var i = 0; i < 3; ++i ) { + elem = jQuery(""); + } + equals( elem[0].defaultValue, "TEST", "Ensure cached nodes are cloned properly (Bug #6655)" ); + + // manually clean up detached elements + elem.remove(); }); test("selector state", function() { @@ -151,7 +162,7 @@ test("selector state", function() { test = jQuery("#main").eq(0); equals( test.selector, "#main.slice(0,1)", "#main eq Selector" ); equals( test.context, document, "#main eq Context" ); - + var d = "
      "; equals( jQuery(d).appendTo(jQuery(d)).selector, @@ -253,38 +264,38 @@ test("isPlainObject", function() { // The use case that we want to match ok(jQuery.isPlainObject({}), "{}"); - + // Not objects shouldn't be matched ok(!jQuery.isPlainObject(""), "string"); ok(!jQuery.isPlainObject(0) && !jQuery.isPlainObject(1), "number"); ok(!jQuery.isPlainObject(true) && !jQuery.isPlainObject(false), "boolean"); ok(!jQuery.isPlainObject(null), "null"); ok(!jQuery.isPlainObject(undefined), "undefined"); - + // Arrays shouldn't be matched ok(!jQuery.isPlainObject([]), "array"); - + // Instantiated objects shouldn't be matched ok(!jQuery.isPlainObject(new Date), "new Date"); - + var fn = function(){}; - + // Functions shouldn't be matched ok(!jQuery.isPlainObject(fn), "fn"); - + // Again, instantiated objects shouldn't be matched ok(!jQuery.isPlainObject(new fn), "new fn (no methods)"); - + // Makes the function a little more realistic // (and harder to detect, incidentally) fn.prototype = {someMethod: function(){}}; - + // Again, instantiated objects shouldn't be matched ok(!jQuery.isPlainObject(new fn), "new fn"); // DOM Element ok(!jQuery.isPlainObject(document.createElement("div")), "DOM Element"); - + // Window ok(!jQuery.isPlainObject(window), "window"); @@ -298,7 +309,7 @@ test("isPlainObject", function() { document.body.removeChild( iframe ); start(); }; - + var doc = iframe.contentDocument || iframe.contentWindow.document; doc.open(); doc.write(""); @@ -547,15 +558,15 @@ test("toArray()", function() { }) test("get(Number)", function() { - expect(1); + expect(2); equals( jQuery("p").get(0), document.getElementById("firstp"), "Get A Single Element" ); + strictEqual( jQuery("#firstp").get(1), undefined, "Try get with index larger elements count" ); }); test("get(-Number)",function() { - expect(1); - equals( jQuery("p").get(-1), - document.getElementById("first"), - "Get a single element with negative index" ) + expect(2); + equals( jQuery("p").get(-1), document.getElementById("first"), "Get a single element with negative index" ); + strictEqual( jQuery("#firstp").get(-2), undefined, "Try get with index negative index larger then elements count" ); }) test("each(Function)", function() { @@ -659,7 +670,7 @@ test("jQuery.merge()", function() { // Fixed at [5998], #3641 same( parse([-2,-1], [0,1,2]), [-2,-1,0,1,2], "Second array including a zero (falsy)"); - + // After fixing #5527 same( parse([], [null, undefined]), [null, undefined], "Second array including null and undefined values"); same( parse({length:0}, [1,2]), {length:2, 0:1, 1:2}, "First array like"); @@ -694,7 +705,7 @@ test("jQuery.extend(Object, Object)", function() { equals( deep1.foo2, document, "Make sure that a deep clone was not attempted on the document" ); ok( jQuery.extend(true, {}, nestedarray).arr !== arr, "Deep extend of object must clone child array" ); - + // #5991 ok( jQuery.isArray( jQuery.extend(true, { arr: {} }, nestedarray).arr ), "Cloned array heve to be an Array" ); ok( jQuery.isPlainObject( jQuery.extend(true, { arr: arr }, { arr: {} }).arr ), "Cloned object heve to be an plain object" ); @@ -715,13 +726,13 @@ test("jQuery.extend(Object, Object)", function() { empty = {}; jQuery.extend(true, empty, optionsWithCustomObject); ok( empty.foo && empty.foo.date === customObject, "Custom objects copy correctly (no methods)" ); - + // Makes the class a little more realistic myKlass.prototype = { someMethod: function(){} }; empty = {}; jQuery.extend(true, empty, optionsWithCustomObject); ok( empty.foo && empty.foo.date === customObject, "Custom objects copy correctly" ); - + var ret = jQuery.extend(true, { foo: 4 }, { foo: new Number(5) } ); ok( ret.foo == 5, "Wrapped numbers copy correctly" ); @@ -849,10 +860,10 @@ test("jQuery.makeArray", function(){ test("jQuery.isEmptyObject", function(){ expect(2); - + equals(true, jQuery.isEmptyObject({}), "isEmptyObject on empty object literal" ); equals(false, jQuery.isEmptyObject({a:1}), "isEmptyObject on non-empty object literal" ); - + // What about this ? // equals(true, jQuery.isEmptyObject(null), "isEmptyObject on null" ); }); @@ -878,23 +889,23 @@ test("jQuery.proxy", function(){ test("jQuery.parseJSON", function(){ expect(8); - + equals( jQuery.parseJSON(), null, "Nothing in, null out." ); equals( jQuery.parseJSON( null ), null, "Nothing in, null out." ); equals( jQuery.parseJSON( "" ), null, "Nothing in, null out." ); - + same( jQuery.parseJSON("{}"), {}, "Plain object parsing." ); same( jQuery.parseJSON('{"test":1}'), {"test":1}, "Plain object parsing." ); same( jQuery.parseJSON('\n{"test":1}'), {"test":1}, "Make sure leading whitespaces are handled." ); - + try { jQuery.parseJSON("{a:1}"); ok( false, "Test malformed JSON string." ); } catch( e ) { ok( true, "Test malformed JSON string." ); } - + try { jQuery.parseJSON("{'a':1}"); ok( false, "Test malformed JSON string." ); @@ -902,3 +913,290 @@ test("jQuery.parseJSON", function(){ ok( true, "Test malformed JSON string." ); } }); + +test("jQuery._Deferred()", function() { + + expect( 10 ); + + var deferred, + object, + test; + + deferred = jQuery._Deferred(); + + test = false; + + deferred.done( function( value ) { + equals( value , "value" , "Test pre-resolve callback" ); + test = true; + } ); + + deferred.resolve( "value" ); + + ok( test , "Test pre-resolve callbacks called right away" ); + + test = false; + + deferred.done( function( value ) { + equals( value , "value" , "Test post-resolve callback" ); + test = true; + } ); + + ok( test , "Test post-resolve callbacks called right away" ); + + deferred.cancel(); + + test = true; + + deferred.done( function() { + ok( false , "Cancel was ignored" ); + test = false; + } ); + + ok( test , "Test cancel" ); + + deferred = jQuery._Deferred().resolve(); + + try { + deferred.done( function() { + throw "Error"; + } , function() { + ok( true , "Test deferred do not cancel on exception" ); + } ); + } catch( e ) { + strictEqual( e , "Error" , "Test deferred propagates exceptions"); + deferred.done(); + } + + test = ""; + deferred = jQuery._Deferred().done( function() { + + test += "A"; + + }, function() { + + test += "B"; + + } ).resolve(); + + strictEqual( test , "AB" , "Test multiple done parameters" ); + + test = ""; + + deferred.done( function() { + + deferred.done( function() { + + test += "C"; + + } ); + + test += "A"; + + }, function() { + + test += "B"; + } ); + + strictEqual( test , "ABC" , "Test done callbacks order" ); + + deferred = jQuery._Deferred(); + + deferred.fire( jQuery , [ document ] ).done( function( doc ) { + ok( this === jQuery && arguments.length === 1 && doc === document , "Test fire context & args" ); + }); +}); + +test("jQuery.Deferred()", function() { + + expect( 10 ); + + jQuery.Deferred( function( defer ) { + strictEqual( this , defer , "Defer passed as this & first argument" ); + this.resolve( "done" ); + }).then( function( value ) { + strictEqual( value , "done" , "Passed function executed" ); + }); + + jQuery.Deferred().resolve().then( function() { + ok( true , "Success on resolve" ); + }, function() { + ok( false , "Error on resolve" ); + }); + + jQuery.Deferred().reject().then( function() { + ok( false , "Success on reject" ); + }, function() { + ok( true , "Error on reject" ); + }); + + ( new jQuery.Deferred( function( defer ) { + strictEqual( this , defer , "Defer passed as this & first argument (new)" ); + this.resolve( "done" ); + }) ).then( function( value ) { + strictEqual( value , "done" , "Passed function executed (new)" ); + }); + + ( new jQuery.Deferred() ).resolve().then( function() { + ok( true , "Success on resolve (new)" ); + }, function() { + ok( false , "Error on resolve (new)" ); + }); + + ( new jQuery.Deferred() ).reject().then( function() { + ok( false , "Success on reject (new)" ); + }, function() { + ok( true , "Error on reject (new)" ); + }); + + var tmp = jQuery.Deferred(); + + strictEqual( tmp.promise() , tmp.promise() , "Test deferred always return same promise" ); + strictEqual( tmp.promise() , tmp.promise().promise() , "Test deferred's promise always return same promise as deferred" ); +}); + +test("jQuery.when()", function() { + + expect( 23 ); + + // Some other objects + jQuery.each( { + + "an empty string": "", + "a non-empty string": "some string", + "zero": 0, + "a number other than zero": 1, + "true": true, + "false": false, + "null": null, + "undefined": undefined, + "a plain object": {} + + } , function( message , value ) { + + ok( jQuery.isFunction( jQuery.when( value ).then( function( resolveValue ) { + strictEqual( resolveValue , value , "Test the promise was resolved with " + message ); + } ).promise ) , "Test " + message + " triggers the creation of a new Promise" ); + + } ); + + ok( jQuery.isFunction( jQuery.when().then( function( resolveValue ) { + strictEqual( resolveValue , undefined , "Test the promise was resolved with no parameter" ); + } ).promise ) , "Test calling when with no parameter triggers the creation of a new Promise" ); + + var cache, i; + + for( i = 1 ; i < 4 ; i++ ) { + jQuery.when( cache || jQuery.Deferred( function() { + this.resolve( i ); + }) ).then( function( value ) { + strictEqual( value , 1 , "Function executed" + ( i > 1 ? " only once" : "" ) ); + cache = value; + }, function() { + ok( false , "Fail called" ); + }); + } +}); + +test("jQuery.when() - joined", function() { + + expect(8); + + jQuery.when( 1, 2, 3 ).done( function( a, b, c ) { + strictEqual( a , 1 , "Test first param is first resolved value - non-observables" ); + strictEqual( b , 2 , "Test second param is second resolved value - non-observables" ); + strictEqual( c , 3 , "Test third param is third resolved value - non-observables" ); + }).fail( function() { + ok( false , "Test the created deferred was resolved - non-observables"); + }); + + var successDeferred = jQuery.Deferred().resolve( 1 , 2 , 3 ), + errorDeferred = jQuery.Deferred().reject( "error" , "errorParam" ); + + jQuery.when( 1 , successDeferred , 3 ).done( function( a, b, c ) { + strictEqual( a , 1 , "Test first param is first resolved value - resolved observable" ); + same( b , [ 1 , 2 , 3 ] , "Test second param is second resolved value - resolved observable" ); + strictEqual( c , 3 , "Test third param is third resolved value - resolved observable" ); + }).fail( function() { + ok( false , "Test the created deferred was resolved - resolved observable"); + }); + + jQuery.when( 1 , errorDeferred , 3 ).done( function() { + ok( false , "Test the created deferred was rejected - rejected observable"); + }).fail( function( error , errorParam ) { + strictEqual( error , "error" , "Test first param is first rejected value - rejected observable" ); + strictEqual( errorParam , "errorParam" , "Test second param is second rejected value - rejected observable" ); + }); +}); + +test("jQuery.subclass", function(){ + expect(378); + + var Subclass = jQuery.subclass(), + SubclassSubclass = Subclass.subclass(), + jQueryDocument = jQuery(document), + selectors, contexts, methods, method, arg, description; + + jQueryDocument.toString = function(){ return 'jQueryDocument'; }; + + Subclass.fn.subclassMethod = function(){}; + SubclassSubclass.fn.subclassSubclassMethod = function(){}; + + selectors = [ + 'body', + 'html, body', + '
      ' + ]; + + methods = [ // all methods that return a new jQuery instance + ['eq', 1], + ['add', document], + ['end'], + ['has'], + ['closest', 'div'], + ['filter', document], + ['find', 'div'] + ]; + + contexts = [undefined, document, jQueryDocument]; + + jQuery.each(selectors, function(i, selector){ + + jQuery.each(methods, function(){ + method = this[0]; + arg = this[1]; + + jQuery.each(contexts, function(i, context){ + + description = '("'+selector+'", '+context+').'+method+'('+(arg||'')+')'; + + same( + jQuery(selector, context)[method](arg).subclassMethod, undefined, + 'jQuery'+description+' doesnt have Subclass methods' + ); + same( + jQuery(selector, context)[method](arg).subclassSubclassMethod, undefined, + 'jQuery'+description+' doesnt have SubclassSubclass methods' + ); + same( + Subclass(selector, context)[method](arg).subclassMethod, Subclass.fn.subclassMethod, + 'Subclass'+description+' has Subclass methods' + ); + same( + Subclass(selector, context)[method](arg).subclassSubclassMethod, undefined, + 'Subclass'+description+' doesnt have SubclassSubclass methods' + ); + same( + SubclassSubclass(selector, context)[method](arg).subclassMethod, Subclass.fn.subclassMethod, + 'SubclassSubclass'+description+' has Subclass methods' + ); + same( + SubclassSubclass(selector, context)[method](arg).subclassSubclassMethod, SubclassSubclass.fn.subclassSubclassMethod, + 'SubclassSubclass'+description+' has SubclassSubclass methods' + ); + + }); + }); + }); + +}); diff --git a/test/unit/css.js b/test/unit/css.js index cddd902..555f135 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -1,4 +1,4 @@ -module("css"); +module("css", { teardown: moduleTeardown }); test("css(String|Hash)", function() { expect(41); @@ -178,24 +178,24 @@ if ( !jQuery.support.opacity ) { test("css(String, Function)", function() { expect(3); - + var sizes = ["10px", "20px", "30px"]; - - jQuery("
      " + - "
      " + + + jQuery("
      " + + "
      " + "
      ") .appendTo("body"); - + var index = 0; - + jQuery("#cssFunctionTest div").css("font-size", function() { var size = sizes[index]; index++; return size; }); - + index = 0; - + jQuery("#cssFunctionTest div").each(function() { var computedSize = jQuery(this).css("font-size") var expectedSize = sizes[index] @@ -208,24 +208,24 @@ test("css(String, Function)", function() { test("css(String, Function) with incoming value", function() { expect(3); - + var sizes = ["10px", "20px", "30px"]; - - jQuery("
      " + - "
      " + + + jQuery("
      " + + "
      " + "
      ") .appendTo("body"); - + var index = 0; - + jQuery("#cssFunctionTest div").css("font-size", function() { var size = sizes[index]; index++; return size; }); - + index = 0; - + jQuery("#cssFunctionTest div").css("font-size", function(i, computedSize) { var expectedSize = sizes[index] equals( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize ); @@ -238,61 +238,61 @@ test("css(String, Function) with incoming value", function() { test("css(Object) where values are Functions", function() { expect(3); - + var sizes = ["10px", "20px", "30px"]; - - jQuery("
      " + - "
      " + + + jQuery("
      " + + "
      " + "
      ") .appendTo("body"); var index = 0; - + jQuery("#cssFunctionTest div").css({fontSize: function() { var size = sizes[index]; index++; return size; }}); - + index = 0; - + jQuery("#cssFunctionTest div").each(function() { var computedSize = jQuery(this).css("font-size") var expectedSize = sizes[index] equals( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize ); index++; }); - + jQuery("#cssFunctionTest").remove(); }); test("css(Object) where values are Functions with incoming values", function() { expect(3); - + var sizes = ["10px", "20px", "30px"]; - - jQuery("
      " + - "
      " + + + jQuery("
      " + + "
      " + "
      ") .appendTo("body"); var index = 0; - + jQuery("#cssFunctionTest div").css({fontSize: function() { var size = sizes[index]; index++; return size; }}); - + index = 0; - + jQuery("#cssFunctionTest div").css({"font-size": function(i, computedSize) { var expectedSize = sizes[index] equals( computedSize, expectedSize, "Div #" + index + " should be " + expectedSize ); index++; return computedSize; }}); - + jQuery("#cssFunctionTest").remove(); }); @@ -320,3 +320,16 @@ test(":visible selector works properly on children with a hidden parent (bug #45 jQuery('#table').css('display', 'none').html('cellcell'); equals(jQuery('#table td:visible').length, 0, "hidden cell children not perceived as visible"); }); + +test("internal ref to elem.runtimeStyle (bug #7608)", function () { + expect(1); + var result = true; + + try { + jQuery("#foo").css( { width: "0%" } ).css("width"); + } catch (e) { + result = false; + } + + ok( result, "elem.runtimeStyle does not throw exception" ); +}); diff --git a/test/unit/data.js b/test/unit/data.js index 315043c..889fc2d 100644 --- a/test/unit/data.js +++ b/test/unit/data.js @@ -1,22 +1,159 @@ -module("data"); +module("data", { teardown: moduleTeardown }); test("expando", function(){ - expect(6); + expect(1); equals("expando" in jQuery, true, "jQuery is exposing the expando"); +}); - var obj = {}; - equals( jQuery.data(obj), obj, "jQuery.data(obj) returns the object"); - equals( jQuery.expando in obj, false, "jQuery.data(obj) did not add an expando to the object" ); +function dataTests (elem) { + // expect(32) + + function getCacheLength() { + var cacheLength = 0; + for (var i in jQuery.cache) { + ++cacheLength; + } + + return cacheLength; + } + + equals( jQuery.data(elem, "foo"), undefined, "No data exists initially" ); + strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees no data exists initially" ); + + var dataObj = jQuery.data(elem); + equals( typeof dataObj, "object", "Calling data with no args gives us a data object reference" ); + strictEqual( jQuery.data(elem), dataObj, "Calling jQuery.data returns the same data object when called multiple times" ); + + strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees no data exists even when an empty data obj exists" ); + + dataObj.foo = "bar"; + equals( jQuery.data(elem, "foo"), "bar", "Data is readable by jQuery.data when set directly on a returned data object" ); + + strictEqual( jQuery.hasData(elem), true, "jQuery.hasData agrees data exists when data exists" ); + + jQuery.data(elem, "foo", "baz"); + equals( jQuery.data(elem, "foo"), "baz", "Data can be changed by jQuery.data" ); + equals( dataObj.foo, "baz", "Changes made through jQuery.data propagate to referenced data object" ); + + jQuery.data(elem, "foo", undefined); + equals( jQuery.data(elem, "foo"), "baz", "Data is not unset by passing undefined to jQuery.data" ); + + jQuery.data(elem, "foo", null); + strictEqual( jQuery.data(elem, "foo"), null, "Setting null using jQuery.data works OK" ); + + jQuery.data(elem, "foo", "foo1"); + + jQuery.data(elem, { "bar" : "baz", "boom" : "bloz" }); + strictEqual( jQuery.data(elem, "foo"), "foo1", "Passing an object extends the data object instead of replacing it" ); + equals( jQuery.data(elem, "boom"), "bloz", "Extending the data object works" ); + + jQuery._data(elem, "foo", "foo2"); + equals( jQuery._data(elem, "foo"), "foo2", "Setting internal data works" ); + equals( jQuery.data(elem, "foo"), "foo1", "Setting internal data does not override user data" ); + + var internalDataObj = jQuery.data(elem, jQuery.expando); + strictEqual( jQuery._data(elem), internalDataObj, "Internal data object is accessible via jQuery.expando property" ); + notStrictEqual( dataObj, internalDataObj, "Internal data object is not the same as user data object" ); + + strictEqual( elem.boom, undefined, "Data is never stored directly on the object" ); + + jQuery.removeData(elem, "foo"); + strictEqual( jQuery.data(elem, "foo"), undefined, "jQuery.removeData removes single properties" ); + + jQuery.removeData(elem); + strictEqual( jQuery.data(elem, jQuery.expando), internalDataObj, "jQuery.removeData does not remove internal data if it exists" ); + + jQuery.removeData(elem, undefined, true); - obj = {}; - jQuery.data(obj, 'test'); - equals( jQuery.expando in obj, false, "jQuery.data(obj,key) did not add an expando to the object" ); + strictEqual( jQuery.data(elem, jQuery.expando), undefined, "jQuery.removeData on internal data works" ); + strictEqual( jQuery.hasData(elem), false, "jQuery.hasData agrees all data has been removed from object" ); - obj = {}; - jQuery.data(obj, "foo", "bar"); - equals( jQuery.expando in obj, false, "jQuery.data(obj,key,value) did not add an expando to the object" ); - equals( obj.foo, "bar", "jQuery.data(obj,key,value) sets fields directly on the object." ); + jQuery._data(elem, "foo", "foo2"); + strictEqual( jQuery.hasData(elem), true, "jQuery.hasData shows data exists even if it is only internal data" ); + + jQuery.data(elem, "foo", "foo1"); + equals( jQuery._data(elem, "foo"), "foo2", "Setting user data does not override internal data" ); + + jQuery.removeData(elem, undefined, true); + equals( jQuery.data(elem, "foo"), "foo1", "jQuery.removeData for internal data does not remove user data" ); + + if (elem.nodeType) { + var oldCacheLength = getCacheLength(); + jQuery.removeData(elem, "foo"); + + equals( getCacheLength(), oldCacheLength - 1, "Removing the last item in the data object destroys it" ); + } + else { + jQuery.removeData(elem, "foo"); + var expected, actual; + + if (jQuery.support.deleteExpando) { + expected = false; + actual = jQuery.expando in elem; + } + else { + expected = null; + actual = elem[ jQuery.expando ]; + } + + equals( actual, expected, "Removing the last item in the data object destroys it" ); + } + + jQuery.data(elem, "foo", "foo1"); + jQuery._data(elem, "foo", "foo2"); + + equals( jQuery.data(elem, "foo"), "foo1", "(sanity check) Ensure data is set in user data object" ); + equals( jQuery._data(elem, "foo"), "foo2", "(sanity check) Ensure data is set in internal data object" ); + + jQuery.removeData(elem, "foo", true); + + strictEqual( jQuery.data(elem, jQuery.expando), undefined, "Removing the last item in internal data destroys the internal data object" ); + + jQuery._data(elem, "foo", "foo2"); + equals( jQuery._data(elem, "foo"), "foo2", "(sanity check) Ensure data is set in internal data object" ); + + jQuery.removeData(elem, "foo"); + equals( jQuery._data(elem, "foo"), "foo2", "(sanity check) jQuery.removeData for user data does not remove internal data" ); + + if (elem.nodeType) { + oldCacheLength = getCacheLength(); + jQuery.removeData(elem, "foo", true); + equals( getCacheLength(), oldCacheLength - 1, "Removing the last item in the internal data object also destroys the user data object when it is empty" ); + } + else { + jQuery.removeData(elem, "foo", true); + + if (jQuery.support.deleteExpando) { + expected = false; + actual = jQuery.expando in elem; + } + else { + expected = null; + actual = elem[ jQuery.expando ]; + } + + equals( actual, expected, "Removing the last item in the internal data object also destroys the user data object when it is empty" ); + } +} + +test("jQuery.data", function() { + expect(128); + + var div = document.createElement("div"); + + dataTests(div); + dataTests({}); + + // remove bound handlers from window object to stop potential false positives caused by fix for #5280 in + // transports/xhr.js + jQuery(window).unbind("unload"); + + dataTests(window); + dataTests(document); + + // clean up unattached element + jQuery(div).remove(); }); test("jQuery.acceptData", function() { @@ -37,59 +174,20 @@ test("jQuery.acceptData", function() { ok( !jQuery.acceptData( applet ), "applet" ); }); -test("jQuery.data", function() { - expect(15); - var div = document.createElement("div"); - - ok( jQuery.data(div, "test") === undefined, "Check for no data exists" ); - - jQuery.data(div, "test", "success"); - equals( jQuery.data(div, "test"), "success", "Check for added data" ); - - ok( jQuery.data(div, "notexist") === undefined, "Check for no data exists" ); - - var data = jQuery.data(div); - same( data, { "test": "success" }, "Return complete data set" ); - - jQuery.data(div, "test", "overwritten"); - equals( jQuery.data(div, "test"), "overwritten", "Check for overwritten data" ); - - jQuery.data(div, "test", undefined); - equals( jQuery.data(div, "test"), "overwritten", "Check that data wasn't removed"); - - jQuery.data(div, "test", null); - ok( jQuery.data(div, "test") === null, "Check for null data"); - - jQuery.data(div, "test3", "orig"); - jQuery.data(div, { "test": "in", "test2": "in2" }); - equals( jQuery.data(div, "test"), "in", "Verify setting an object in data" ); - equals( jQuery.data(div, "test2"), "in2", "Verify setting an object in data" ); - equals( jQuery.data(div, "test3"), "orig", "Verify original not overwritten" ); - - var obj = {}; - jQuery.data( obj, "prop", true ); - - ok( obj.prop, "Data is being stored on the object" ); - equals( jQuery.data( obj, "prop" ), true, "Make sure the right value is retrieved" ); - - jQuery.data( window, "BAD", true ); - ok( !window[ jQuery.expando ], "Make sure there is no expando on the window object." ); - ok( !window.BAD, "And make sure that the property wasn't set directly on the window." ); - ok( jQuery.data( window, "BAD" ), "Make sure that the value was set." ); -}); - test(".data()", function() { - expect(4); + expect(5); var div = jQuery("#foo"); strictEqual( div.data("foo"), undefined, "Make sure that missing result is undefined" ); - div.data("test", "success"); same( div.data(), {test: "success"}, "data() get the entire data object" ); strictEqual( div.data("foo"), undefined, "Make sure that missing result is still undefined" ); var nodiv = jQuery("#unfound"); equals( nodiv.data(), null, "data() on empty set returns null" ); + + var obj = { foo: "bar" }; + deepEqual( jQuery(obj).data(), {}, "Retrieve data object from a wrapped JS object (#7524)" ); }) test(".data(String) and .data(String, Object)", function() { @@ -176,11 +274,14 @@ test(".data(String) and .data(String, Object)", function() { equals( $elem.data('null',null).data('null'), null, "null's are preserved"); equals( $elem.data('emptyString','').data('emptyString'), '', "Empty strings are preserved"); equals( $elem.data('false',false).data('false'), false, "false's are preserved"); - equals( $elem.data('exists'), true, "Existing data is returned" ); - + equals( $elem.data('exists'), undefined, "Existing data is not returned" ); + // Clean up $elem.removeData(); - ok( jQuery.isEmptyObject( $elem[0] ), "removeData clears the object" ); + deepEqual( $elem[0], {exists:true}, "removeData does not clear the object" ); + + // manually clean up detached elements + parent.remove(); }); test("data-* attributes", function() { @@ -188,7 +289,7 @@ test("data-* attributes", function() { var div = jQuery("
      "), child = jQuery("
      "), dummy = jQuery("
      "); - + equals( div.data("attr"), undefined, "Check for non-existing data-attr attribute" ); div.attr("data-attr", "exists"); @@ -196,10 +297,12 @@ test("data-* attributes", function() { div.attr("data-attr", "exists2"); equals( div.data("attr"), "exists", "Check that updates to data- don't update .data()" ); - + div.data("attr", "internal").attr("data-attr", "external"); equals( div.data("attr"), "internal", "Check for .data('attr') precedence (internal > external data-* attribute)" ); - + + div.remove(); + child.appendTo('#main'); equals( child.data("myobj"), "old data", "Value accessed from data-* attribute"); @@ -211,6 +314,8 @@ test("data-* attributes", function() { var obj = child.data(), obj2 = dummy.data(), check = [ "myobj", "ignored", "other" ], num = 0, num2 = 0; + dummy.remove(); + for ( var i = 0, l = check.length; i < l; i++ ) { ok( obj[ check[i] ], "Make sure data- property exists when calling data-." ); ok( obj2[ check[i] ], "Make sure data- property exists when calling data-." ); @@ -246,7 +351,7 @@ test("data-* attributes", function() { .attr("data-space", " ") .attr("data-null", "null") .attr("data-string", "test"); - + strictEqual( child.data('true'), true, "Primitive true read from attribute"); strictEqual( child.data('false'), false, "Primitive false read from attribute"); strictEqual( child.data('five'), 5, "Primitive number read from attribute"); @@ -262,7 +367,7 @@ test("data-* attributes", function() { strictEqual( child.data('string'), "test", "Typical string read from attribute"); child.remove(); - + // tests from metadata plugin function testData(index, elem) { switch (index) { @@ -286,10 +391,10 @@ test("data-* attributes", function() { ok(false, ["Assertion failed on index ", index, ", with data ", data].join('')); } } - + var metadata = '
      1. Some stuff
      2. Some stuff
      3. Some stuff
      4. Some stuff
      ', elem = jQuery(metadata).appendTo('#main'); - + elem.find("li").each(testData); elem.remove(); }); @@ -302,16 +407,20 @@ test(".data(Object)", function() { div.data({ "test": "in", "test2": "in2" }); equals( div.data("test"), "in", "Verify setting an object in data" ); equals( div.data("test2"), "in2", "Verify setting an object in data" ); - + var obj = {test:"unset"}, jqobj = jQuery(obj); + jqobj.data("test", "unset"); jqobj.data({ "test": "in", "test2": "in2" }); - equals( obj.test, "in", "Verify setting an object on an object extends the object" ); - equals( obj.test2, "in2", "Verify setting an object on an object extends the object" ); + equals( jQuery.data(obj).test, "in", "Verify setting an object on an object extends the data object" ); + equals( obj.test2, undefined, "Verify setting an object on an object does not extend the object" ); + + // manually clean up detached elements + div.remove(); }); test("jQuery.removeData", function() { - expect(7); + expect(6); var div = jQuery("#foo")[0]; jQuery.data(div, "test", "testing"); jQuery.removeData(div, "test"); @@ -321,13 +430,12 @@ test("jQuery.removeData", function() { jQuery.removeData( div ); ok( !jQuery.data(div, "test2"), "Make sure that the data property no longer exists." ); ok( !div[ jQuery.expando ], "Make sure the expando no longer exists, as well." ); - + var obj = {}; jQuery.data(obj, "test", "testing"); - equals( obj.test, "testing", "verify data on plain object"); + equals( jQuery(obj).data("test"), "testing", "verify data on plain object"); jQuery.removeData(obj, "test"); equals( jQuery.data(obj, "test"), undefined, "Check removal of data on plain object" ); - equals( obj.test, undefined, "Check removal of data directly from plain object" ); jQuery.data( window, "BAD", true ); jQuery.removeData( window, "BAD" ); @@ -353,4 +461,4 @@ test(".removeData()", function() { div.removeData("test.foo"); equals( div.data("test.foo"), undefined, "Make sure data is intact" ); -}); +}); \ No newline at end of file diff --git a/test/unit/dimensions.js b/test/unit/dimensions.js index 8255bf3..fa59a9f 100644 --- a/test/unit/dimensions.js +++ b/test/unit/dimensions.js @@ -1,4 +1,4 @@ -module("dimensions"); +module("dimensions", { teardown: moduleTeardown }); function pass( val ) { return val; @@ -33,6 +33,8 @@ function testWidth( val ) { var blah = jQuery("blah"); equals( blah.width( val(10) ), blah, "Make sure that setting a width on an empty set returns the set." ); equals( blah.width(), null, "Make sure 'null' is returned on an empty set"); + + jQuery.removeData($div[0], 'olddisplay', true); } test("width()", function() { @@ -45,13 +47,13 @@ test("width() with function", function() { test("width() with function args", function() { expect( 2 ); - + var $div = jQuery("#nothiddendiv"); $div.width( 30 ).width(function(i, width) { equals( width, 30, "Make sure previous value is corrrect." ); return width + 1; }); - + equals( $div.width(), 31, "Make sure value was modified correctly." ); }); @@ -80,6 +82,8 @@ function testHeight( val ) { var blah = jQuery("blah"); equals( blah.height( val(10) ), blah, "Make sure that setting a height on an empty set returns the set." ); equals( blah.height(), null, "Make sure 'null' is returned on an empty set"); + + jQuery.removeData($div[0], 'olddisplay', true); } test("height()", function() { @@ -92,13 +96,13 @@ test("height() with function", function() { test("height() with function args", function() { expect( 2 ); - + var $div = jQuery("#nothiddendiv"); $div.height( 30 ).height(function(i, height) { equals( height, 30, "Make sure previous value is corrrect." ); return height + 1; }); - + equals( $div.height(), 31, "Make sure value was modified correctly." ); }); @@ -112,13 +116,13 @@ test("innerWidth()", function() { border: "2px solid #fff", width: 30 }); - + equals($div.innerWidth(), 30, "Test with margin and border"); $div.css("padding", "20px"); equals($div.innerWidth(), 70, "Test with margin, border and padding"); $div.hide(); equals($div.innerWidth(), 70, "Test hidden div"); - + // reset styles $div.css({ display: "", border: "", padding: "", width: "", height: "" }); @@ -126,11 +130,14 @@ test("innerWidth()", function() { // Temporarily require 0 for backwards compat - should be auto equals( div.innerWidth(), 0, "Make sure that disconnected nodes are handled." ); + + div.remove(); + jQuery.removeData($div[0], 'olddisplay', true); }); test("innerHeight()", function() { expect(4); - + var $div = jQuery("#nothiddendiv"); // set styles $div.css({ @@ -138,13 +145,13 @@ test("innerHeight()", function() { border: "2px solid #fff", height: 30 }); - + equals($div.innerHeight(), 30, "Test with margin and border"); $div.css("padding", "20px"); equals($div.innerHeight(), 70, "Test with margin, border and padding"); $div.hide(); equals($div.innerHeight(), 70, "Test hidden div"); - + // reset styles $div.css({ display: "", border: "", padding: "", width: "", height: "" }); @@ -152,14 +159,17 @@ test("innerHeight()", function() { // Temporarily require 0 for backwards compat - should be auto equals( div.innerHeight(), 0, "Make sure that disconnected nodes are handled." ); + + div.remove(); + jQuery.removeData($div[0], 'olddisplay', true); }); test("outerWidth()", function() { expect(7); - + var $div = jQuery("#nothiddendiv"); $div.css("width", 30); - + equals($div.outerWidth(), 30, "Test with only width set"); $div.css("padding", "20px"); equals($div.outerWidth(), 70, "Test with padding"); @@ -171,7 +181,7 @@ test("outerWidth()", function() { equals($div.outerWidth(true), 94, "Test with padding, border and margin with margin option"); $div.hide(); equals($div.outerWidth(true), 94, "Test hidden div with padding, border and margin with margin option"); - + // reset styles $div.css({ position: "", display: "", border: "", padding: "", width: "", height: "" }); @@ -179,14 +189,17 @@ test("outerWidth()", function() { // Temporarily require 0 for backwards compat - should be auto equals( div.outerWidth(), 0, "Make sure that disconnected nodes are handled." ); + + div.remove(); + jQuery.removeData($div[0], 'olddisplay', true); }); test("outerHeight()", function() { expect(7); - + var $div = jQuery("#nothiddendiv"); $div.css("height", 30); - + equals($div.outerHeight(), 30, "Test with only width set"); $div.css("padding", "20px"); equals($div.outerHeight(), 70, "Test with padding"); @@ -197,7 +210,7 @@ test("outerHeight()", function() { equals($div.outerHeight(true), 94, "Test with padding, border and margin with margin option"); $div.hide(); equals($div.outerHeight(true), 94, "Test hidden div with padding, border and margin with margin option"); - + // reset styles $div.css({ display: "", border: "", padding: "", width: "", height: "" }); @@ -205,4 +218,7 @@ test("outerHeight()", function() { // Temporarily require 0 for backwards compat - should be auto equals( div.outerHeight(), 0, "Make sure that disconnected nodes are handled." ); + + div.remove(); + jQuery.removeData($div[0], 'olddisplay', true); }); diff --git a/test/unit/effects.js b/test/unit/effects.js index 74b336f..b1dd288 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -1,4 +1,4 @@ -module("effects"); +module("effects", { teardown: moduleTeardown }); test("sanity check", function() { expect(1); @@ -14,7 +14,7 @@ test("show()", function() { equals( hiddendiv.css("display"), "block", "Make sure a pre-hidden div is visible." ); - var div = jQuery("
      ").hide().appendTo("body").show(); + var div = jQuery("
      ").hide().appendTo("#main").show(); equal( div.css("display"), "block", "Make sure pre-hidden divs show" ); @@ -64,7 +64,7 @@ test("show()", function() { // #show-tests * is set display: none in CSS jQuery("#main").append('

      '); - + var old = jQuery("#test-table").show().css("display") !== "table"; jQuery("#test-table").remove(); @@ -130,6 +130,45 @@ test("show(Number) - other displays", function() { }); }); + + +// Supports #7397 +test("Persist correct display value", function() { + expect(3); + QUnit.reset(); + stop(); + + // #show-tests * is set display: none in CSS + jQuery("#main").append('
      foo
      '); + + var $span = jQuery("#show-tests span"), + displayNone = $span.css("display"), + display = '', num = 0; + + $span.show(); + + display = $span.css("display"); + + $span.hide(); + + $span.fadeIn(100, function() { + + equals($span.css("display"), display, "Expecting display: " + display); + + $span.fadeOut(100, function () { + + equals($span.css("display"), displayNone, "Expecting display: " + displayNone); + + $span.fadeIn(100, function() { + + equals($span.css("display"), display, "Expecting display: " + display); + + start(); + }); + }); + }); +}); + test("animate(Hash, Object, Function)", function() { expect(1); stop(); @@ -155,7 +194,7 @@ test("animate block as inline width/height", function() { var span = jQuery("").css("display", "inline-block").appendTo("body"), expected = span.css("display"); - + span.remove(); if ( jQuery.support.inlineBlockNeedsLayout || expected === "inline-block" ) { @@ -181,7 +220,7 @@ test("animate native inline width/height", function() { var span = jQuery("").css("display", "inline-block").appendTo("body"), expected = span.css("display"); - + span.remove(); if ( jQuery.support.inlineBlockNeedsLayout || expected === "inline-block" ) { @@ -364,13 +403,16 @@ test("animate duration 0", function() { $elem.hide(0, function(){ ok(true, "Hide callback with no duration"); }); + + // manually clean up detached elements + $elem.remove(); }); test("animate hyphenated properties", function(){ expect(1); stop(); - jQuery("#nothiddendiv") + jQuery("#foo") .css("font-size", 10) .animate({"font-size": 20}, 200, function(){ equals( this.style.fontSize, "20px", "The font-size property was animated." ); @@ -394,7 +436,7 @@ test("stop()", function() { expect(3); stop(); - var $foo = jQuery("#nothiddendiv"); + var $foo = jQuery("#foo"); var w = 0; $foo.hide().width(200).width(); @@ -407,6 +449,8 @@ test("stop()", function() { nw = $foo.width(); notEqual( nw, w, "Stop didn't reset the animation " + nw + "px " + w + "px"); setTimeout(function(){ + $foo.removeData(); + $foo.removeData(undefined, true); equals( nw, $foo.width(), "The animation didn't continue" ); start(); }, 100); @@ -417,7 +461,7 @@ test("stop() - several in queue", function() { expect(3); stop(); - var $foo = jQuery("#nothiddendivchild"); + var $foo = jQuery("#foo"); var w = 0; $foo.hide().width(200).width(); @@ -442,7 +486,7 @@ test("stop(clearQueue)", function() { expect(4); stop(); - var $foo = jQuery("#nothiddendiv"); + var $foo = jQuery("#foo"); var w = 0; $foo.hide().width(200).width(); @@ -469,7 +513,7 @@ test("stop(clearQueue, gotoEnd)", function() { expect(1); stop(); - var $foo = jQuery("#nothiddendivchild"); + var $foo = jQuery("#foo"); var w = 0; $foo.hide().width(200).width(); @@ -497,7 +541,7 @@ test("stop(clearQueue, gotoEnd)", function() { test("toggle()", function() { expect(6); - var x = jQuery("#nothiddendiv"); + var x = jQuery("#foo"); ok( x.is(":visible"), "is visible" ); x.toggle(); ok( x.is(":hidden"), "is hidden" ); @@ -521,6 +565,23 @@ jQuery.checkOverflowDisplay = function(){ start(); } +test("support negative values < -10000 (bug #7193)", function () { + expect(1); + stop(); + + jQuery.extend(jQuery.fx.step, { + "marginBottom": function(fx) { + equals( fx.cur(), -11000, "Element has margin-bottom of -11000" ); + delete jQuery.fx.step.marginBottom; + } + }); + + jQuery("#main").css("marginBottom", "-11000px").animate({ marginBottom: "-11001px" }, { + duration: 1, + complete: start + }); +}); + test("JS Overflow and Display", function() { expect(2); stop(); @@ -681,6 +742,9 @@ jQuery.each( { } } + // manually remove generated element + jQuery(this).remove(); + start(); }); }); @@ -707,6 +771,10 @@ jQuery.checkState = function(){ var cur = self.style[ c ] || jQuery.css(self, c); equals( cur, v, "Make sure that " + c + " is reset (Old: " + v + " Cur: " + cur + ")"); }); + + // manually clean data on modified element + jQuery.removeData(this, 'olddisplay', true); + start(); } @@ -773,9 +841,6 @@ jQuery.makeTest = function( text ){ jQuery("

      ") .text( text ) .appendTo("#fx-tests") - .click(function(){ - jQuery(this).next().toggle(); - }) .after( elem ); return elem; @@ -839,7 +904,7 @@ test("hide hidden elements (bug #7141)", function() { var div = jQuery("
      ").appendTo("#main"); equals( div.css("display"), "none", "Element is hidden by default" ); div.hide(); - ok( !div.data("olddisplay"), "olddisplay is undefined after hiding an already-hidden element" ); + ok( !jQuery._data(div, "olddisplay"), "olddisplay is undefined after hiding an already-hidden element" ); div.show(); equals( div.css("display"), "block", "Show a double-hidden element" ); @@ -850,11 +915,11 @@ test("hide hidden elements, with animation (bug #7141)", function() { expect(3); QUnit.reset(); stop(); - + var div = jQuery("
      ").appendTo("#main"); equals( div.css("display"), "none", "Element is hidden by default" ); div.hide(1, function () { - ok( !div.data("olddisplay"), "olddisplay is undefined after hiding an already-hidden element" ); + ok( !jQuery._data(div, "olddisplay"), "olddisplay is undefined after hiding an already-hidden element" ); div.show(1, function () { equals( div.css("display"), "block", "Show a double-hidden element" ); start(); diff --git a/test/unit/event.js b/test/unit/event.js index 54431dd..02824a9 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -1,23 +1,23 @@ -module("event"); +module("event", { teardown: moduleTeardown }); test("null or undefined handler", function() { expect(2); // Supports Fixes bug #7229 try { - + jQuery("#firstp").click(null); - + ok(true, "Passing a null handler will not throw an exception"); - } catch (e) {} + } catch (e) {} try { - + jQuery("#firstp").click(undefined); - + ok(true, "Passing an undefined handler will not throw an exception"); - } catch (e) {} + } catch (e) {} }); test("bind(), with data", function() { @@ -28,7 +28,7 @@ test("bind(), with data", function() { }; jQuery("#firstp").bind("click", {foo: "bar"}, handler).click().unbind("click", handler); - ok( !jQuery.data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." ); + ok( !jQuery._data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." ); }); test("click(), with data", function() { @@ -39,7 +39,7 @@ test("click(), with data", function() { }; jQuery("#firstp").click({foo: "bar"}, handler).click().unbind("click", handler); - ok( !jQuery.data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." ); + ok( !jQuery._data(jQuery("#firstp")[0], "events"), "Event handler unbound when using data." ); }); test("bind(), with data, trigger with data", function() { @@ -80,6 +80,9 @@ test("bind(), multiple events at once and namespaces", function() { cur = "focusin"; div.trigger("focusin.a"); + // manually clean up detached elements + div.remove(); + div = jQuery("
      ").bind("click mouseover", obj, function(e) { equals( e.type, cur, "Verify right multi event was fired." ); equals( e.data, obj, "Make sure the data came in correctly." ); @@ -91,6 +94,9 @@ test("bind(), multiple events at once and namespaces", function() { cur = "mouseover"; div.trigger("mouseover"); + // manually clean up detached elements + div.remove(); + div = jQuery("
      ").bind("focusin.a focusout.b", function(e) { equals( e.type, cur, "Verify right multi event was fired." ); }); @@ -100,6 +106,9 @@ test("bind(), multiple events at once and namespaces", function() { cur = "focusout"; div.trigger("focusout.b"); + + // manually clean up detached elements + div.remove(); }); test("bind(), namespace with special add", function() { @@ -175,7 +184,7 @@ test("bind(), no data", function() { test("bind/one/unbind(Object)", function(){ expect(6); - + var clickCounter = 0, mouseoverCounter = 0; function handler(event) { if (event.type == "click") @@ -183,18 +192,18 @@ test("bind/one/unbind(Object)", function(){ else if (event.type == "mouseover") mouseoverCounter++; }; - + function handlerWithData(event) { if (event.type == "click") clickCounter += event.data; else if (event.type == "mouseover") mouseoverCounter += event.data; }; - + function trigger(){ $elem.trigger("click").trigger("mouseover"); } - + var $elem = jQuery("#firstp") // Regular bind .bind({ @@ -206,16 +215,16 @@ test("bind/one/unbind(Object)", function(){ click:handlerWithData, mouseover:handlerWithData }, 2 ); - + trigger(); - + equals( clickCounter, 3, "bind(Object)" ); equals( mouseoverCounter, 3, "bind(Object)" ); - + trigger(); equals( clickCounter, 4, "bind(Object)" ); equals( mouseoverCounter, 4, "bind(Object)" ); - + jQuery("#firstp").unbind({ click:handler, mouseover:handler @@ -228,10 +237,10 @@ test("bind/one/unbind(Object)", function(){ test("live/die(Object), delegate/undelegate(String, Object)", function() { expect(6); - + var clickCounter = 0, mouseoverCounter = 0, $p = jQuery("#firstp"), $a = $p.find("a:first"); - + var events = { click: function( event ) { clickCounter += ( event.data || 1 ); @@ -240,26 +249,26 @@ test("live/die(Object), delegate/undelegate(String, Object)", function() { mouseoverCounter += ( event.data || 1 ); } }; - + function trigger() { $a.trigger("click").trigger("mouseover"); } - + $a.live( events ); $p.delegate( "a", events, 2 ); - + trigger(); equals( clickCounter, 3, "live/delegate" ); equals( mouseoverCounter, 3, "live/delegate" ); - + $p.undelegate( "a", events ); - + trigger(); equals( clickCounter, 4, "undelegate" ); equals( mouseoverCounter, 4, "undelegate" ); - + $a.die( events ); - + trigger(); equals( clickCounter, 4, "die" ); equals( mouseoverCounter, 4, "die" ); @@ -267,12 +276,12 @@ test("live/die(Object), delegate/undelegate(String, Object)", function() { test("live/delegate immediate propagation", function() { expect(2); - + var $p = jQuery("#firstp"), $a = $p.find("a:first"), lastClick; - + lastClick = ""; - $a.live( "click", function(e) { - lastClick = "click1"; + $a.live( "click", function(e) { + lastClick = "click1"; e.stopImmediatePropagation(); }); $a.live( "click", function(e) { @@ -281,10 +290,10 @@ test("live/delegate immediate propagation", function() { $a.trigger( "click" ); equals( lastClick, "click1", "live stopImmediatePropagation" ); $a.die( "click" ); - + lastClick = ""; - $p.delegate( "a", "click", function(e) { - lastClick = "click1"; + $p.delegate( "a", "click", function(e) { + lastClick = "click1"; e.stopImmediatePropagation(); }); $p.delegate( "a", "click", function(e) { @@ -295,10 +304,53 @@ test("live/delegate immediate propagation", function() { $p.undelegate( "click" ); }); +test("bind/delegate bubbling, isDefaultPrevented", function() { + expect(2); + var $anchor2 = jQuery( "#anchor2" ), + $main = jQuery( "#main" ), + fakeClick = function($jq) { + // Use a native click so we don't get jQuery simulated bubbling + if ( document.createEvent ) { + var e = document.createEvent( 'MouseEvents' ); + e.initEvent( "click", true, true ); + $jq[0].dispatchEvent(e); + } + else if ( $jq[0].click ) { + $jq[0].click(); // IE + } + }; + $anchor2.click(function(e) { + e.preventDefault(); + }); + $main.delegate("#foo", "click", function(e) { + var orig = e.originalEvent; + + if ( typeof(orig.defaultPrevented) === "boolean" || typeof(orig.returnValue) === "boolean" || orig.getPreventDefault ) { + equals( e.isDefaultPrevented(), true, "isDefaultPrevented true passed to bubbled event" ); + + } else { + // Opera < 11 doesn't implement any interface we can use, so give it a pass + ok( true, "isDefaultPrevented not supported by this browser, test skipped" ); + } + }); + fakeClick( $anchor2 ); + $anchor2.unbind( "click" ); + $main.undelegate( "click" ); + $anchor2.click(function(e) { + // Let the default action occur + }); + $main.delegate("#foo", "click", function(e) { + equals( e.isDefaultPrevented(), false, "isDefaultPrevented false passed to bubbled event" ); + }); + fakeClick( $anchor2 ); + $anchor2.unbind( "click" ); + $main.undelegate( "click" ); +}); + test("bind(), iframes", function() { // events don't work with iframes, see #939 - this test fails in IE because of contentDocument var doc = jQuery("#loadediframe").contents(); - + jQuery("div", doc).bind("click", function() { ok( true, "Binding to element inside iframe" ); }).click().unbind('click'); @@ -360,7 +412,7 @@ test("bind(), namespaced events, cloned events", function() { test("bind(), multi-namespaced events", function() { expect(6); - + var order = [ "click.test.abc", "click.test.abc", @@ -369,7 +421,7 @@ test("bind(), multi-namespaced events", function() { "click.test", "custom.test2" ]; - + function check(name, msg){ same(name, order.shift(), msg); } @@ -389,7 +441,7 @@ test("bind(), multi-namespaced events", function() { jQuery("#firstp").bind("click.test.abc",function(e){ check("click.test.abc", "Namespaced click triggered"); }); - + // Those would not trigger/unbind (#5303) jQuery("#firstp").trigger("click.a.test"); jQuery("#firstp").unbind("click.a.test"); @@ -453,7 +505,7 @@ test("bind(), make sure order is maintained", function() { elem.unbind("click"); }); - + test("bind(), with different this object", function() { expect(4); var thisObject = { myThis: true }, @@ -465,12 +517,12 @@ test("bind(), with different this object", function() { equals( this, thisObject, "bind() with different this object and data" ); equals( event.data, data, "bind() with different this object and data" ); }; - + jQuery("#firstp") .bind("click", jQuery.proxy(handler1, thisObject)).click().unbind("click", handler1) .bind("click", data, jQuery.proxy(handler2, thisObject)).click().unbind("click", handler2); - ok( !jQuery.data(jQuery("#firstp")[0], "events"), "Event handler unbound when using different this object and data." ); + ok( !jQuery._data(jQuery("#firstp")[0], "events"), "Event handler unbound when using different this object and data." ); }); test("bind(name, false), unbind(name, false)", function() { @@ -490,10 +542,13 @@ test("bind(name, false), unbind(name, false)", function() { jQuery("#ap").unbind("click", false); jQuery("#ap").trigger("click"); equals( main, 1, "Verify that the trigger happened correctly." ); + + // manually clean up events from elements outside the fixture + jQuery("#main").unbind("click"); }); test("bind()/trigger()/unbind() on plain object", function() { - expect( 7 ); + expect( 8 ); var obj = {}; @@ -503,11 +558,16 @@ test("bind()/trigger()/unbind() on plain object", function() { // Make sure it doesn't complain when no events are found jQuery(obj).unbind("test"); - jQuery(obj).bind("test", function(){ - ok( true, "Custom event run." ); + jQuery(obj).bind({ + test: function() { + ok( true, "Custom event run." ); + }, + submit: function() { + ok( true, "Custom submit event run." ); + } }); - var events = jQuery(obj).data("__events__"); + var events = jQuery._data(obj, "events"); ok( events, "Object has events bound." ); equals( obj.events, undefined, "Events object on plain objects is not events" ); equals( typeof events, "function", "'events' expando is a function on plain objects." ); @@ -516,37 +576,41 @@ test("bind()/trigger()/unbind() on plain object", function() { // Should trigger 1 jQuery(obj).trigger("test"); + jQuery(obj).trigger("submit"); jQuery(obj).unbind("test"); + jQuery(obj).unbind("submit"); // Should trigger 0 jQuery(obj).trigger("test"); // Make sure it doesn't complain when no events are found jQuery(obj).unbind("test"); - - equals( obj.__events__, undefined, "Make sure events object is removed" ); + + equals( obj && obj[ jQuery.expando ] && + obj[ jQuery.expando ][ jQuery.expando ] && + obj[ jQuery.expando ][ jQuery.expando ].events, undefined, "Make sure events object is removed" ); }); test("unbind(type)", function() { expect( 0 ); - + var $elem = jQuery("#firstp"), message; function error(){ ok( false, message ); } - + message = "unbind passing function"; $elem.bind('error1', error).unbind('error1',error).triggerHandler('error1'); - + message = "unbind all from event"; $elem.bind('error1', error).unbind('error1').triggerHandler('error1'); - + message = "unbind all"; $elem.bind('error1', error).unbind().triggerHandler('error1'); - + message = "unbind many with function"; $elem.bind('error1 error2',error) .unbind('error1 error2', error ) @@ -556,7 +620,7 @@ test("unbind(type)", function() { $elem.bind('error1 error2',error) .unbind('error1 error2') .trigger('error1').triggerHandler('error2'); - + message = "unbind without a type or handler"; $elem.bind("error1 error2.test",error) .unbind() @@ -565,7 +629,7 @@ test("unbind(type)", function() { test("unbind(eventObject)", function() { expect(4); - + var $elem = jQuery("#firstp"), num; @@ -574,7 +638,7 @@ test("unbind(eventObject)", function() { $elem.trigger('foo').triggerHandler('bar'); equals( num, expected, "Check the right handlers are triggered" ); } - + $elem // This handler shouldn't be unbound .bind('foo', function(){ @@ -588,14 +652,14 @@ test("unbind(eventObject)", function() { .bind('bar', function(){ num += 4; }); - + assert( 7 ); assert( 5 ); - + $elem.unbind('bar'); assert( 1 ); - - $elem.unbind(); + + $elem.unbind(); assert( 0 ); }); @@ -619,34 +683,42 @@ test("hover()", function() { test("trigger() shortcuts", function() { expect(6); - jQuery('
    • Change location
    • ').prependTo('#firstUL').find('a').bind('click', function() { + + var elem = jQuery('
    • Change location
    • ').prependTo('#firstUL'); + elem.find('a').bind('click', function() { var close = jQuery('spanx', this); // same with jQuery(this).find('span'); equals( close.length, 0, "Context element does not exist, length must be zero" ); ok( !close[0], "Context element does not exist, direct access to element must return undefined" ); return false; }).click(); - + + // manually clean up detached elements + elem.remove(); + jQuery("#check1").click(function() { ok( true, "click event handler for checkbox gets fired twice, see #815" ); }).click(); - + var counter = 0; jQuery('#firstp')[0].onclick = function(event) { counter++; }; jQuery('#firstp').click(); equals( counter, 1, "Check that click, triggers onclick event handler also" ); - + var clickCounter = 0; jQuery('#simon1')[0].onclick = function(event) { clickCounter++; }; jQuery('#simon1').click(); equals( clickCounter, 1, "Check that click, triggers onclick event handler on an a tag also" ); - - jQuery('').load(function(){ + + elem = jQuery('').load(function(){ ok( true, "Trigger the load event, using the shortcut .load() (#2819)"); }).load(); + + // manually clean up detached elements + elem.remove(); }); test("trigger() bubbling", function() { @@ -681,6 +753,10 @@ test("trigger() bubbling", function() { equals( body, 2, "ap bubble" ); equals( main, 1, "ap bubble" ); equals( ap, 1, "ap bubble" ); + + // manually clean up events from elements outside the fixture + jQuery(document).unbind("click"); + jQuery("html, body, #main").unbind("click"); }); test("trigger(type, [data], [fn])", function() { @@ -721,10 +797,10 @@ test("trigger(type, [data], [fn])", function() { pass = false; } ok( pass, "Trigger focus on hidden element" ); - + pass = true; try { - jQuery('table:first').bind('test:test', function(){}).trigger('test:test'); + jQuery('#main table:first').bind('test:test', function(){}).trigger('test:test'); } catch (e) { pass = false; } @@ -761,28 +837,28 @@ test("jQuery.Event.currentTarget", function(){ test("trigger(eventObject, [data], [fn])", function() { expect(25); - + var $parent = jQuery('
      ').hide().appendTo('body'), $child = jQuery('

      foo

      ').appendTo( $parent ); - - var event = jQuery.Event("noNew"); + + var event = jQuery.Event("noNew"); ok( event != window, "Instantiate jQuery.Event without the 'new' keyword" ); equals( event.type, "noNew", "Verify its type" ); - + equals( event.isDefaultPrevented(), false, "Verify isDefaultPrevented" ); equals( event.isPropagationStopped(), false, "Verify isPropagationStopped" ); equals( event.isImmediatePropagationStopped(), false, "Verify isImmediatePropagationStopped" ); - + event.preventDefault(); equals( event.isDefaultPrevented(), true, "Verify isDefaultPrevented" ); event.stopPropagation(); equals( event.isPropagationStopped(), true, "Verify isPropagationStopped" ); - + event.isPropagationStopped = function(){ return false }; event.stopImmediatePropagation(); equals( event.isPropagationStopped(), true, "Verify isPropagationStopped" ); equals( event.isImmediatePropagationStopped(), true, "Verify isPropagationStopped" ); - + $parent.bind('foo',function(e){ // Tries bubbling equals( e.type, 'foo', 'Verify event type when passed passing an event object' ); @@ -790,72 +866,72 @@ test("trigger(eventObject, [data], [fn])", function() { equals( e.currentTarget.id, 'par', 'Verify event.target when passed passing an event object' ); equals( e.secret, 'boo!', 'Verify event object\'s custom attribute when passed passing an event object' ); }); - + // test with an event object event = new jQuery.Event("foo"); event.secret = 'boo!'; $child.trigger(event); - + // test with a literal object $child.trigger({type:'foo', secret:'boo!'}); - + $parent.unbind(); function error(){ ok( false, "This assertion shouldn't be reached"); } - + $parent.bind('foo', error ); - + $child.bind('foo',function(e, a, b, c ){ equals( arguments.length, 4, "Check arguments length"); equals( a, 1, "Check first custom argument"); equals( b, 2, "Check second custom argument"); equals( c, 3, "Check third custom argument"); - + equals( e.isDefaultPrevented(), false, "Verify isDefaultPrevented" ); equals( e.isPropagationStopped(), false, "Verify isPropagationStopped" ); equals( e.isImmediatePropagationStopped(), false, "Verify isImmediatePropagationStopped" ); - + // Skips both errors e.stopImmediatePropagation(); - + return "result"; }); - + // We should add this back in when we want to test the order // in which event handlers are iterated. //$child.bind('foo', error ); - + event = new jQuery.Event("foo"); $child.trigger( event, [1,2,3] ).unbind(); equals( event.result, "result", "Check event.result attribute"); - + // Will error if it bubbles $child.triggerHandler('foo'); - + $child.unbind(); $parent.unbind().remove(); }); test("jQuery.Event.currentTarget", function(){ expect(1); - + var counter = 0, $elem = jQuery('').click(function(e){ equals( e.currentTarget, this, "Check currentTarget on "+(counter++?"native":"fake") +" event" ); }); - + // Fake event $elem.trigger('click'); - + // Cleanup $elem.unbind(); }); test("toggle(Function, Function, ...)", function() { expect(16); - + var count = 0, fn1 = function(e) { count++; }, fn2 = function(e) { count--; }, @@ -878,7 +954,7 @@ test("toggle(Function, Function, ...)", function() { }); return false; }).click().click().click(); - + var turn = 0; var fns = [ function(){ @@ -891,7 +967,7 @@ test("toggle(Function, Function, ...)", function() { turn = 3; } ]; - + var $div = jQuery("
       
      ").toggle( fns[0], fns[1], fns[2] ); $div.click(); equals( turn, 1, "Trying toggle with 3 functions, attempt 1 yields 1"); @@ -903,11 +979,14 @@ test("toggle(Function, Function, ...)", function() { equals( turn, 1, "Trying toggle with 3 functions, attempt 4 yields 1"); $div.click(); equals( turn, 2, "Trying toggle with 3 functions, attempt 5 yields 2"); - + $div.unbind('click',fns[0]); - var data = jQuery.data( $div[0], 'events' ); + var data = jQuery._data( $div[0], 'events' ); ok( !data, "Unbinding one function from toggle unbinds them all"); + // manually clean up detached elements + $div.remove(); + // Test Multi-Toggles var a = [], b = []; $div = jQuery("
      "); @@ -923,6 +1002,9 @@ test("toggle(Function, Function, ...)", function() { $div.click(); same( a, [1,2,1], "Check that a click worked with a second toggle, second click." ); same( b, [1,2], "Check that a click worked with a second toggle, second click." ); + + // manually clean up detached elements + $div.remove(); }); test(".live()/.die()", function() { @@ -1023,7 +1105,7 @@ test(".live()/.die()", function() { equals( clicked, 2, "live with a context" ); // Make sure the event is actually stored on the context - ok( jQuery.data(container, "events").live, "live with a context" ); + ok( jQuery._data(container, "events").live, "live with a context" ); // Test unbinding with a different context jQuery("#foo", container).die("click"); @@ -1045,7 +1127,7 @@ test(".live()/.die()", function() { // Test binding with different this object, event data, and trigger data jQuery("#foo").live("click", true, jQuery.proxy(function(e, data){ equals( e.data, true, "live with with different this object, event data, and trigger data" ); - equals( this.foo, "bar", "live with with different this object, event data, and trigger data" ); + equals( this.foo, "bar", "live with with different this object, event data, and trigger data" ); equals( data, true, "live with with different this object, event data, and trigger data") }, { foo: "bar" })); jQuery("#foo").trigger("click", true).die("click"); @@ -1106,25 +1188,25 @@ test(".live()/.die()", function() { // Cleanup jQuery("#nothiddendiv").die("foo", callback); - + // Make sure we don't loose the target by DOM modifications // after the bubble already reached the liveHandler var livec = 0, elemDiv = jQuery("#nothiddendivchild").html('').get(0); - + jQuery("#nothiddendivchild").live("click", function(e){ jQuery("#nothiddendivchild").html(''); }); jQuery("#nothiddendivchild").live("click", function(e){ if(e.target) {livec++;} }); - + jQuery("#nothiddendiv span").click(); equals( jQuery("#nothiddendiv span").length, 0, "Verify that first handler occurred and modified the DOM." ); equals( livec, 1, "Verify that second handler occurred even with nuked target." ); - + // Cleanup jQuery("#nothiddendivchild").die("click"); // Verify that .live() ocurs and cancel buble in the same order as // we would expect .bind() and .click() without delegation var lived = 0, livee = 0; - + // bind one pair in one order jQuery('span#liveSpan1 a').live('click', function(){ lived++; return false; }); jQuery('span#liveSpan1').live('click', function(){ livee++; }); @@ -1142,22 +1224,22 @@ test(".live()/.die()", function() { jQuery('span#liveSpan2 a').click(); equals( lived, 1, "Verify that only one first handler occurred." ); equals( livee, 0, "Verify that second handler doesn't." ); - + // Cleanup jQuery("span#liveSpan1 a").die("click") jQuery("span#liveSpan1").die("click"); jQuery("span#liveSpan2 a").die("click"); jQuery("span#liveSpan2").die("click"); - + // Test this, target and currentTarget are correct - jQuery('span#liveSpan1').live('click', function(e){ + jQuery('span#liveSpan1').live('click', function(e){ equals( this.id, 'liveSpan1', 'Check the this within a live handler' ); equals( e.currentTarget.id, 'liveSpan1', 'Check the event.currentTarget within a live handler' ); equals( e.target.nodeName.toUpperCase(), 'A', 'Check the event.target within a live handler' ); }); - + jQuery('span#liveSpan1 a').click(); - + jQuery('span#liveSpan1').die('click'); // Work with deep selectors @@ -1233,6 +1315,9 @@ test("live with multiple events", function(){ div.trigger("submit"); equals( count, 2, "Make sure both the click and submit were triggered." ); + + // manually clean up events from elements outside the fixture + div.die(); }); test("live with namespaces", function(){ @@ -1291,18 +1376,18 @@ test("live with change", function(){ expect(8); var selectChange = 0, checkboxChange = 0; - + var select = jQuery("select[name='S1']") select.live("change", function() { selectChange++; }); - - var checkbox = jQuery("#check2"), + + var checkbox = jQuery("#check2"), checkboxFunction = function(){ checkboxChange++; } checkbox.live("change", checkboxFunction); - + // test click on select // second click that changed it @@ -1310,17 +1395,17 @@ test("live with change", function(){ select[0].selectedIndex = select[0].selectedIndex ? 0 : 1; select.trigger("change"); equals( selectChange, 1, "Change on click." ); - + // test keys on select selectChange = 0; select[0].selectedIndex = select[0].selectedIndex ? 0 : 1; select.trigger("change"); equals( selectChange, 1, "Change on keyup." ); - + // test click on checkbox checkbox.trigger("change"); equals( checkboxChange, 1, "Change on checkbox." ); - + // test blur/focus on text var text = jQuery("#name"), textChange = 0, oldTextVal = text.val(); text.live("change", function() { @@ -1333,7 +1418,7 @@ test("live with change", function(){ text.val(oldTextVal); text.die("change"); - + // test blur/focus on password var password = jQuery("#name"), passwordChange = 0, oldPasswordVal = password.val(); password.live("change", function() { @@ -1346,9 +1431,9 @@ test("live with change", function(){ password.val(oldPasswordVal); password.die("change"); - + // make sure die works - + // die all changes selectChange = 0; select.die("change"); @@ -1360,7 +1445,7 @@ test("live with change", function(){ select[0].selectedIndex = select[0].selectedIndex ? 0 : 1; select.trigger("change"); equals( selectChange, 0, "Die on keyup works." ); - + // die specific checkbox checkbox.die("change", checkboxFunction); checkbox.trigger("change"); @@ -1369,7 +1454,7 @@ test("live with change", function(){ test("live with submit", function() { var count1 = 0, count2 = 0; - + jQuery("#testForm").live("submit", function(ev) { count1++; ev.preventDefault(); @@ -1383,7 +1468,7 @@ test("live with submit", function() { jQuery("#testForm input[name=sub1]").submit(); equals( count1, 1, "Verify form submit." ); equals( count2, 1, "Verify body submit." ); - + jQuery("#testForm").die("submit"); jQuery("body").die("submit"); }); @@ -1536,7 +1621,7 @@ test(".delegate()/.undelegate()", function() { equals( clicked, 2, "delegate with a context" ); // Make sure the event is actually stored on the context - ok( jQuery.data(container, "events").live, "delegate with a context" ); + ok( jQuery._data(container, "events").live, "delegate with a context" ); // Test unbinding with a different context jQuery("#main").undelegate("#foo", "click"); @@ -1561,7 +1646,7 @@ test(".delegate()/.undelegate()", function() { // Test binding with different this object, event data, and trigger data jQuery("#body").delegate("#foo", "click", true, jQuery.proxy(function(e, data){ equals( e.data, true, "delegate with with different this object, event data, and trigger data" ); - equals( this.foo, "bar", "delegate with with different this object, event data, and trigger data" ); + equals( this.foo, "bar", "delegate with with different this object, event data, and trigger data" ); equals( data, true, "delegate with with different this object, event data, and trigger data") }, { foo: "bar" })); jQuery("#foo").trigger("click", true); @@ -1623,25 +1708,25 @@ test(".delegate()/.undelegate()", function() { // Cleanup jQuery("#body").undelegate("#nothiddendiv", "foo", callback); - + // Make sure we don't loose the target by DOM modifications // after the bubble already reached the liveHandler var livec = 0, elemDiv = jQuery("#nothiddendivchild").html('').get(0); - + jQuery("#body").delegate("#nothiddendivchild", "click", function(e){ jQuery("#nothiddendivchild").html(''); }); jQuery("#body").delegate("#nothiddendivchild", "click", function(e){ if(e.target) {livec++;} }); - + jQuery("#nothiddendiv span").click(); equals( jQuery("#nothiddendiv span").length, 0, "Verify that first handler occurred and modified the DOM." ); equals( livec, 1, "Verify that second handler occurred even with nuked target." ); - + // Cleanup jQuery("#body").undelegate("#nothiddendivchild", "click"); // Verify that .live() ocurs and cancel buble in the same order as // we would expect .bind() and .click() without delegation var lived = 0, livee = 0; - + // bind one pair in one order jQuery("#body").delegate('span#liveSpan1 a', 'click', function(){ lived++; return false; }); jQuery("#body").delegate('span#liveSpan1', 'click', function(){ livee++; }); @@ -1659,19 +1744,19 @@ test(".delegate()/.undelegate()", function() { jQuery('span#liveSpan2 a').click(); equals( lived, 1, "Verify that only one first handler occurred." ); equals( livee, 0, "Verify that second handler doesn't." ); - + // Cleanup jQuery("#body").undelegate("click"); - + // Test this, target and currentTarget are correct - jQuery("#body").delegate('span#liveSpan1', 'click', function(e){ + jQuery("#body").delegate('span#liveSpan1', 'click', function(e){ equals( this.id, 'liveSpan1', 'Check the this within a delegate handler' ); equals( e.currentTarget.id, 'liveSpan1', 'Check the event.currentTarget within a delegate handler' ); equals( e.target.nodeName.toUpperCase(), 'A', 'Check the event.target within a delegate handler' ); }); - + jQuery('span#liveSpan1 a').click(); - + jQuery("#body").undelegate('span#liveSpan1', 'click'); // Work with deep selectors @@ -1747,18 +1832,18 @@ test("delegate with change", function(){ expect(8); var selectChange = 0, checkboxChange = 0; - + var select = jQuery("select[name='S1']"); jQuery("#body").delegate("select[name='S1']", "change", function() { selectChange++; }); - - var checkbox = jQuery("#check2"), + + var checkbox = jQuery("#check2"), checkboxFunction = function(){ checkboxChange++; } jQuery("#body").delegate("#check2", "change", checkboxFunction); - + // test click on select // second click that changed it @@ -1766,17 +1851,17 @@ test("delegate with change", function(){ select[0].selectedIndex = select[0].selectedIndex ? 0 : 1; select.trigger("change"); equals( selectChange, 1, "Change on click." ); - + // test keys on select selectChange = 0; select[0].selectedIndex = select[0].selectedIndex ? 0 : 1; select.trigger("change"); equals( selectChange, 1, "Change on keyup." ); - + // test click on checkbox checkbox.trigger("change"); equals( checkboxChange, 1, "Change on checkbox." ); - + // test blur/focus on text var text = jQuery("#name"), textChange = 0, oldTextVal = text.val(); jQuery("#body").delegate("#name", "change", function() { @@ -1789,7 +1874,7 @@ test("delegate with change", function(){ text.val(oldTextVal); jQuery("#body").die("change"); - + // test blur/focus on password var password = jQuery("#name"), passwordChange = 0, oldPasswordVal = password.val(); jQuery("#body").delegate("#name", "change", function() { @@ -1802,9 +1887,9 @@ test("delegate with change", function(){ password.val(oldPasswordVal); jQuery("#body").undelegate("#name", "change"); - + // make sure die works - + // die all changes selectChange = 0; jQuery("#body").undelegate("select[name='S1']", "change"); @@ -1816,7 +1901,7 @@ test("delegate with change", function(){ select[0].selectedIndex = select[0].selectedIndex ? 0 : 1; select.trigger("change"); equals( selectChange, 0, "Die on keyup works." ); - + // die specific checkbox jQuery("#body").undelegate("#check2", "change", checkboxFunction); checkbox.trigger("change"); @@ -1825,7 +1910,7 @@ test("delegate with change", function(){ test("delegate with submit", function() { var count1 = 0, count2 = 0; - + jQuery("#body").delegate("#testForm", "submit", function(ev) { count1++; ev.preventDefault(); @@ -1839,7 +1924,7 @@ test("delegate with submit", function() { jQuery("#testForm input[name=sub1]").submit(); equals( count1, 1, "Verify form submit." ); equals( count2, 1, "Verify body submit." ); - + jQuery("#body").undelegate(); jQuery(document).undelegate(); }); @@ -1865,7 +1950,7 @@ test("window resize", function() { ok( true, "Resize event fired." ); }).resize().unbind("resize"); - ok( !jQuery(window).data("__events__"), "Make sure all the events are gone." ); + ok( !jQuery._data(window, "__events__"), "Make sure all the events are gone." ); }); test("focusin bubbles", function() { diff --git a/test/unit/manipulation.js b/test/unit/manipulation.js index d4c4348..37234d8 100644 --- a/test/unit/manipulation.js +++ b/test/unit/manipulation.js @@ -1,4 +1,7 @@ -module("manipulation"); +module("manipulation", { teardown: moduleTeardown }); + +// Ensure that an extended Array prototype doesn't break jQuery +Array.prototype.arrayProtoFn = function(arg) { throw("arrayProtoFn should not be called"); }; var bareObj = function(value) { return value; }; var functionReturningObj = function(value) { return (function() { return value; }); }; @@ -37,21 +40,21 @@ test("text(Function)", function() { test("text(Function) with incoming value", function() { expect(2); - + var old = "This link has class=\"blog\": Simon Willison's Weblog"; - + jQuery('#sap').text(function(i, val) { equals( val, old, "Make sure the incoming value is correct." ); return "foobar"; }); - + equals( jQuery("#sap").text(), "foobar", 'Check for merged text of more then one element.' ); - + QUnit.reset(); }); var testWrap = function(val) { - expect(18); + expect(19); var defaultText = 'Try them out:' var result = jQuery('#first').wrap(val( '
      ' )).text(); equals( defaultText, result, 'Check for wrapping of on-the-fly html' ); @@ -80,10 +83,20 @@ var testWrap = function(val) { equals( jQuery("#nonnodes > i").text(), j.text(), "Check node,textnode,comment wraps doesn't hurt text" ); // Try wrapping a disconnected node + var cacheLength = 0; + for (var i in jQuery.cache) { + cacheLength++; + } + j = jQuery("