From f89941412aeb26d46fcbdcec75b9f2ff9066de94 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Sat, 11 Jan 2014 20:36:01 -0800 Subject: [PATCH 01/67] Smooth over some cross-browser CSP differences. * Adding "foo.com" to your CSP via browser-policy now adds both "http://foo.com" and "https://foo.com". This smooths over the fact that some browsers interpret "foo.com" as "http://foo.com" and some interpret it as http AND https. * Trim trailing slashes from origins. Firefox does not allow content from foo.com if you add "foo.com/" to your CSP. --- docs/client/packages/browser-policy.html | 7 +- .../browser-policy-content.js | 69 ++++++++++++++----- .../browser-policy/browser-policy-test.js | 24 +++++++ 3 files changed, 81 insertions(+), 19 deletions(-) diff --git a/docs/client/packages/browser-policy.html b/docs/client/packages/browser-policy.html index cc46e01f3a..19d531af40 100644 --- a/docs/client/packages/browser-policy.html +++ b/docs/client/packages/browser-policy.html @@ -119,8 +119,11 @@ Allows this type of content to be loaded from the given origin. `origin` is a string and can include an optional scheme (such as `http` or `https`), an optional wildcard at the beginning, and an optional port which can be a wildcard. Examples include `example.com`, `https://*.example.com`, and -`example.com:*`. You can call these functions multiple times with different -origins to specify a whitelist of allowed origins. +`example.com:*`. You can call these functions multiple times with +different origins to specify a whitelist of allowed origins. Origins +that don't specify a protocol will allow content over both HTTP and +HTTPS: passing `example.com` will allow content from both +`http://example.com` and `https://example.com`. {{/dtdd}} {{#dtdd "BrowserPolicy.content.allow<ContentType>DataUrl()"}} diff --git a/packages/browser-policy-content/browser-policy-content.js b/packages/browser-policy-content/browser-policy-content.js index b9ef18c27b..52d9c898e2 100644 --- a/packages/browser-policy-content/browser-policy-content.js +++ b/packages/browser-policy-content/browser-policy-content.js @@ -37,10 +37,12 @@ var cspSrcs; var cachedCsp; // Avoid constructing the header out of cspSrcs when possible. // CSP keywords have to be single-quoted. -var unsafeInline = "'unsafe-inline'"; -var unsafeEval = "'unsafe-eval'"; -var selfKeyword = "'self'"; -var noneKeyword = "'none'"; +var keywords = { + unsafeInline: "'unsafe-inline'", + unsafeEval: "'unsafe-eval'", + self: "'self'", + none: "'none'" +}; BrowserPolicy.content = {}; @@ -52,7 +54,7 @@ var parseCsp = function (csp) { policy = policy.substring(0, policy.length - 1); var srcs = policy.split(" "); var directive = srcs[0]; - if (_.indexOf(srcs, noneKeyword) !== -1) + if (_.indexOf(srcs, keywords.none) !== -1) cspSrcs[directive] = null; else cspSrcs[directive] = srcs.slice(1); @@ -81,6 +83,39 @@ var prepareForCspDirective = function (directive) { cspSrcs[directive] = _.clone(cspSrcs["default-src"]); }; +// Add `src` to the list of allowed sources for `directive`, with the +// following modifications if `src` is an origin: +// - If `src` does not have a protocol specified, then add both +// http:// and https://. This is to mask differing +// cross-browser behavior; some browsers interpret an origin without a +// protocol as http:// and some interpret it as both http:// +// and https:// +// - Trim trailing slashes from `src`. +var addSourceForDirective = function (directive, src) { + if (_.contains(_.values(keywords), src)) { + cspSrcs[directive].push(src); + } else { + src = src.toLowerCase(); + + // Trim trailing slashes. + while (src.charAt(src.length - 1) === "/") { + src = src.substring(0, src.length - 1); + } + + var toAdd = []; + // If there is no protocol, add both http:// and https://. + if (! /^([a-z0-9.+-]+:)/.test(src)) { + toAdd.push("http://" + src); + toAdd.push("https://" + src); + } else { + toAdd.push(src); + } + _.each(toAdd, function (s) { + cspSrcs[directive].push(s); + }); + } +}; + var setDefaultPolicy = function () { // By default, unsafe inline scripts and styles are allowed, since we expect // many apps will use them for analytics, etc. Unsafe eval is disallowed, and @@ -111,7 +146,7 @@ _.extend(BrowserPolicy.content, { var header = _.map(cspSrcs, function (srcs, directive) { srcs = srcs || []; if (_.isEmpty(srcs)) - srcs = [noneKeyword]; + srcs = [keywords.none]; var directiveCsp = _.uniq(srcs).join(" "); return directive + " " + directiveCsp + ";"; }); @@ -129,7 +164,7 @@ _.extend(BrowserPolicy.content, { cachedCsp = null; parseCsp(csp); setWebAppInlineScripts( - BrowserPolicy.content._keywordAllowed("script-src", unsafeInline) + BrowserPolicy.content._keywordAllowed("script-src", keywords.unsafeInline) ); }, @@ -142,34 +177,34 @@ _.extend(BrowserPolicy.content, { allowInlineScripts: function () { prepareForCspDirective("script-src"); - cspSrcs["script-src"].push(unsafeInline); + cspSrcs["script-src"].push(keywords.unsafeInline); setWebAppInlineScripts(true); }, disallowInlineScripts: function () { prepareForCspDirective("script-src"); - removeCspSrc("script-src", unsafeInline); + removeCspSrc("script-src", keywords.unsafeInline); setWebAppInlineScripts(false); }, allowEval: function () { prepareForCspDirective("script-src"); - cspSrcs["script-src"].push(unsafeEval); + cspSrcs["script-src"].push(keywords.unsafeEval); }, disallowEval: function () { prepareForCspDirective("script-src"); - removeCspSrc("script-src", unsafeEval); + removeCspSrc("script-src", keywords.unsafeEval); }, allowInlineStyles: function () { prepareForCspDirective("style-src"); - cspSrcs["style-src"].push(unsafeInline); + cspSrcs["style-src"].push(keywords.unsafeInline); }, disallowInlineStyles: function () { prepareForCspDirective("style-src"); - removeCspSrc("style-src", unsafeInline); + removeCspSrc("style-src", keywords.unsafeInline); }, // Functions for setting defaults allowSameOriginForAll: function () { - BrowserPolicy.content.allowOriginForAll(selfKeyword); + BrowserPolicy.content.allowOriginForAll(keywords.self); }, allowDataUrlForAll: function () { BrowserPolicy.content.allowOriginForAll("data:"); @@ -177,7 +212,7 @@ _.extend(BrowserPolicy.content, { allowOriginForAll: function (origin) { prepareForCspDirective("default-src"); _.each(_.keys(cspSrcs), function (directive) { - cspSrcs[directive].push(origin); + addSourceForDirective(directive, origin); }); }, disallowAll: function () { @@ -214,7 +249,7 @@ _.each(["script", "object", "img", "media", BrowserPolicy.content[allowMethodName] = function (src) { prepareForCspDirective(directive); - cspSrcs[directive].push(src); + addSourceForDirective(directive, src); }; if (resource === "script") { BrowserPolicy.content[disallowMethodName] = function () { @@ -230,7 +265,7 @@ _.each(["script", "object", "img", "media", }; BrowserPolicy.content[allowSelfMethodName] = function () { prepareForCspDirective(directive); - cspSrcs[directive].push(selfKeyword); + cspSrcs[directive].push(keywords.self); }; }); diff --git a/packages/browser-policy/browser-policy-test.js b/packages/browser-policy/browser-policy-test.js index e8b00b20a1..b51320eed5 100644 --- a/packages/browser-policy/browser-policy-test.js +++ b/packages/browser-policy/browser-policy-test.js @@ -112,6 +112,30 @@ Tinytest.add("browser-policy - csp", function (test) { BrowserPolicy.content.disallowObject(); test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(), "default-src 'self'; object-src 'none';")); + + // Allow foo.com; it should allow both http://foo.com and + // https://foo.com. + BrowserPolicy.content.allowImageOrigin("foo.com"); + test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(), + "default-src 'self'; object-src 'none'; " + + "img-src 'self' http://foo.com https://foo.com;")); + // "Disallow all " followed by "allow foo.com for all" results + // in srcs from foo.com. + BrowserPolicy.content.allowOriginForAll("foo.com"); + test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(), + "default-src 'self' http://foo.com https://foo.com; " + + "object-src http://foo.com https://foo.com; " + + "img-src 'self' http://foo.com https://foo.com;")); + + // Check that trailing slashes are trimmed from origins. + BrowserPolicy.content.disallowAll(); + BrowserPolicy.content.allowScriptOrigin("https://foo.com/"); + test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(), + "default-src 'none'; script-src https://foo.com;")); + BrowserPolicy.content.allowObjectOrigin("foo.com//"); + test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(), + "default-src 'none'; script-src https://foo.com; " + + "object-src http://foo.com https://foo.com;")); }); Tinytest.add("browser-policy - x-frame-options", function (test) { From 189845f1fba51d291484efeff9a99944380adba1 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Sat, 11 Jan 2014 20:43:36 -0800 Subject: [PATCH 02/67] Add frame-src to browser-policy-content. --- docs/client/packages/browser-policy.html | 8 +++++++- packages/browser-policy-content/browser-policy-content.js | 2 +- packages/browser-policy/browser-policy-test.js | 6 +++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/client/packages/browser-policy.html b/docs/client/packages/browser-policy.html index 19d531af40..5bede3d5b2 100644 --- a/docs/client/packages/browser-policy.html +++ b/docs/client/packages/browser-policy.html @@ -111,7 +111,7 @@ Disallows inline CSS. Finally, you can configure a whitelist of allowed requests that various types of content can make. The following functions are defined for the content types -script, object, image, media, font, and connect. +script, object, image, media, font, frame, and connect.
{{#dtdd "BrowserPolicy.content.allow<ContentType>Origin(origin)"}} @@ -162,6 +162,12 @@ allows images to have their `src` attributes point to images served from `https://example.com`. * `BrowserPolicy.content.allowConnectOrigin("https://example.com")` allows XMLHttpRequest and WebSocket connections to `https://example.com`. +* `BrowserPolicy.content.allowFrameOrigin("https://example.com")` allows +your site to load the origin `https://example.com` in a frame or +iframe. The `BrowserPolicy.framing` API allows you to control which +sites can frame your site, while +`BrowserPolicy.content.allowFrameOrigin` allows you to control which +sites can be loaded inside frames on your site. {{/better_markdown}} diff --git a/packages/browser-policy-content/browser-policy-content.js b/packages/browser-policy-content/browser-policy-content.js index 52d9c898e2..328a7b5833 100644 --- a/packages/browser-policy-content/browser-policy-content.js +++ b/packages/browser-policy-content/browser-policy-content.js @@ -227,7 +227,7 @@ _.extend(BrowserPolicy.content, { // allowOrigin, allowData, allowself, and // disallow methods for each type of resource. _.each(["script", "object", "img", "media", - "font", "connect", "style"], + "font", "connect", "style", "frame"], function (resource) { var directive = resource + "-src"; var methodResource; diff --git a/packages/browser-policy/browser-policy-test.js b/packages/browser-policy/browser-policy-test.js index b51320eed5..48f999c9ba 100644 --- a/packages/browser-policy/browser-policy-test.js +++ b/packages/browser-policy/browser-policy-test.js @@ -129,12 +129,12 @@ Tinytest.add("browser-policy - csp", function (test) { // Check that trailing slashes are trimmed from origins. BrowserPolicy.content.disallowAll(); - BrowserPolicy.content.allowScriptOrigin("https://foo.com/"); + BrowserPolicy.content.allowFrameOrigin("https://foo.com/"); test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(), - "default-src 'none'; script-src https://foo.com;")); + "default-src 'none'; frame-src https://foo.com;")); BrowserPolicy.content.allowObjectOrigin("foo.com//"); test.isTrue(cspsEqual(BrowserPolicy.content._constructCsp(), - "default-src 'none'; script-src https://foo.com; " + + "default-src 'none'; frame-src https://foo.com; " + "object-src http://foo.com https://foo.com;")); }); From bdf6fd248574997f2d5da462d031a57139d135bd Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 13 Jan 2014 18:27:58 -0800 Subject: [PATCH 03/67] Use regex to replace trailing slashes from browser-policy URLs. Thanks glasser! --- packages/browser-policy-content/browser-policy-content.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/browser-policy-content/browser-policy-content.js b/packages/browser-policy-content/browser-policy-content.js index 328a7b5833..569bf9c4d0 100644 --- a/packages/browser-policy-content/browser-policy-content.js +++ b/packages/browser-policy-content/browser-policy-content.js @@ -90,7 +90,8 @@ var prepareForCspDirective = function (directive) { // cross-browser behavior; some browsers interpret an origin without a // protocol as http:// and some interpret it as both http:// // and https:// -// - Trim trailing slashes from `src`. +// - Trim trailing slashes from `src`, since some browsers interpret +// "foo.com/" as "foo.com" and some don't. var addSourceForDirective = function (directive, src) { if (_.contains(_.values(keywords), src)) { cspSrcs[directive].push(src); @@ -98,9 +99,7 @@ var addSourceForDirective = function (directive, src) { src = src.toLowerCase(); // Trim trailing slashes. - while (src.charAt(src.length - 1) === "/") { - src = src.substring(0, src.length - 1); - } + src = src.replace(/\/+$/, ''); var toAdd = []; // If there is no protocol, add both http:// and https://. From 0f3dff7866228c81d721d859a6f376eff97dca4c Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Wed, 15 Jan 2014 13:05:43 -0800 Subject: [PATCH 04/67] Update History.md for bp-improvements --- History.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/History.md b/History.md index d567729837..472159a56f 100644 --- a/History.md +++ b/History.md @@ -17,6 +17,10 @@ * Upgrade `jquery-waypoints` package from 1.1.7 to 2.0.3. (Contains backward-incompatible changes). +* Add `frame-src` to `browser-policy-content` and account for + cross-browser CSP disparities. + + ## v0.7.0.1 * Two fixes to `meteor run` Mongo startup bugs that could lead to hangs with the From 4cf8b41d5df73b4b51bf8921070720a856e8efc5 Mon Sep 17 00:00:00 2001 From: Nathan Le Ray Date: Mon, 13 Jan 2014 20:01:31 +0100 Subject: [PATCH 05/67] Fix a typo in the API doc --- docs/client/api.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index 1e8fcaf4b0..ec6d589e46 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1937,7 +1937,7 @@ effects of `created`. It fires once and is the last callback to fire. {{> api_box template_events}} -Declare event handers for instances of this template. Multiple calls add +Declare event handlers for instances of this template. Multiple calls add new event handlers in addition to the existing ones. See [Event Maps](#eventmaps) for a detailed description of the event From c8acc109f8f41d572e8745f16e542454bbd486c7 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Wed, 15 Jan 2014 15:50:17 -0800 Subject: [PATCH 06/67] Pass through the return value for update and remove on validated operations. Fixes #1759. --- packages/mongo-livedata/allow_tests.js | 18 ++++++++++++++++++ packages/mongo-livedata/collection.js | 8 ++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/mongo-livedata/allow_tests.js b/packages/mongo-livedata/allow_tests.js index f30dfc17fe..17439173b4 100644 --- a/packages/mongo-livedata/allow_tests.js +++ b/packages/mongo-livedata/allow_tests.js @@ -421,6 +421,7 @@ if (Meteor.isClient) { {$set: {updated: true}}, expect(function (err, res) { test.isFalse(err); + test.equal(res, 1); test.equal(collection.find({updated: true}).count(), 1); })); }, @@ -431,6 +432,7 @@ if (Meteor.isClient) { {$set: {updated: true}}, expect(function (err, res) { test.isFalse(err); + test.equal(res, 1); test.equal(collection.find({updated: true}).count(), 2); })); }, @@ -603,6 +605,7 @@ if (Meteor.isClient) { canUpdateId, {$set: {"dotted.field": 1}}, expect(function (err, res) { test.isFalse(err); + test.equal(res, 1); test.equal(collection.findOne(canUpdateId).dotted.field, 1); })); }, @@ -622,6 +625,7 @@ if (Meteor.isClient) { {$set: {updated: true}}, expect(function (err, res) { test.isFalse(err); + test.equal(res, 0); // nothing has changed test.equal(collection.find().count(), 3); test.equal(collection.find({updated: true}).count(), 0); @@ -670,6 +674,7 @@ if (Meteor.isClient) { {$set: {updated: true}}, expect(function (err, res) { test.isFalse(err); + test.equal(res, 1); test.equal(collection.find({updated: true}).count(), 1); })); }, @@ -701,6 +706,7 @@ if (Meteor.isClient) { {$set: {cantRemove: false, canUpdate2: true}}, expect(function (err, res) { test.isFalse(err); + test.equal(res, 1); test.equal(collection.find({cantRemove: true}).count(), 0); })); }, @@ -710,11 +716,23 @@ if (Meteor.isClient) { collection.remove(canRemoveId, expect(function (err, res) { test.isFalse(err); + test.equal(res, 1); // successfully removed test.equal(collection.find().count(), 2); })); }, + // try to remove a doc that doesn't exist. see we remove no docs. + function (test, expect) { + collection.remove('some-random-id-that-never-matches', + expect(function (err, res) { + test.isFalse(err); + test.equal(res, 0); + // nothing removed + test.equal(collection.find().count(), 2); + })); + }, + // methods can still bypass restrictions function (test, expect) { collection.callClearMethod( diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index 0c91c52a42..0fbadc0ae3 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -780,7 +780,7 @@ Meteor.Collection.prototype._validatedUpdate = function( var doc = self._collection.findOne(selector, findOptions); if (!doc) // none satisfied! - return; + return 0; var factoriedDoc; @@ -813,7 +813,7 @@ Meteor.Collection.prototype._validatedUpdate = function( // avoid races, but since selector is guaranteed to already just be an ID, we // don't have to any more. - self._collection.update.call( + return self._collection.update.call( self._collection, selector, mutator, options); }; @@ -843,7 +843,7 @@ Meteor.Collection.prototype._validatedRemove = function(userId, selector) { var doc = self._collection.findOne(selector, findOptions); if (!doc) - return; + return 0; // call user validators. // Any deny returns true means denied. @@ -864,5 +864,5 @@ Meteor.Collection.prototype._validatedRemove = function(userId, selector) { // Mongo to avoid races, but since selector is guaranteed to already just be // an ID, we don't have to any more. - self._collection.remove.call(self._collection, selector); + return self._collection.remove.call(self._collection, selector); }; From 9513c4229144691988debf4cfee15c26c6986c16 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 16 Jan 2014 16:56:54 -0800 Subject: [PATCH 07/67] Change many "maps from stringified ID" to _IdMap This greatly reduces the number of places in the code where "stringified IDs" are passed around. Specifically, changed these maps into _IdMaps: - minimongo - the main LocalCollection docs map - LocalCollection._savedOriginals - unordered query results, in several places: - query.results - query.results_snapshot - return value from _getRawObjects - as passed to _diffQueryChanges - livedata - Connection._serverDocuments[collection] - mongo-livedata - SynchronousCursor._visitedIds - return value from SynchronousCursor.getRawObjects - PollingObserveDriver._results (when unordered) --- packages/livedata/livedata_connection.js | 69 +++++----- packages/minimongo/diff.js | 27 ++-- packages/minimongo/id_map.js | 28 +++- packages/minimongo/minimongo.js | 122 ++++++++++-------- packages/minimongo/minimongo_tests.js | 22 ++-- packages/mongo-livedata/mongo_driver.js | 13 +- .../mongo-livedata/polling_observe_driver.js | 4 +- 7 files changed, 163 insertions(+), 122 deletions(-) diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 40f21bae09..1d8dbd19d1 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -93,7 +93,7 @@ var Connection = function (url, options) { // methods whose stub wrote at least one document, and whose data-done message // has not yet been received. self._documentsWrittenByStub = {}; - // collection -> id -> "server document" object. A "server document" has: + // collection -> IdMap of "server document" object. A "server document" has: // - "document": the version of the document according the // server (ie, the snapshot before a stub wrote it, amended by any changes // received from the server) @@ -762,11 +762,14 @@ _.extend(Connection.prototype, { var docsWritten = []; _.each(self._stores, function (s, collection) { var originals = s.retrieveOriginals(); - _.each(originals, function (doc, id) { - if (typeof id !== 'string') - throw new Error("id is not a string"); + // not all stores define retrieveOriginals + if (!originals) + return; + originals.forEach(function (doc, id) { docsWritten.push({collection: collection, id: id}); - var serverDoc = Meteor._ensure(self._serverDocuments, collection, id); + if (!_.has(self._serverDocuments, collection)) + self._serverDocuments[collection] = new LocalCollection._IdMap; + var serverDoc = self._serverDocuments[collection].setDefault(id, {}); if (serverDoc.writtenByStubs) { // We're not the first stub to write this doc. Just add our method ID // to the record. @@ -1054,17 +1057,26 @@ _.extend(Connection.prototype, { updates[collection].push(msg); }, + _getServerDoc: function (collection, id) { + var self = this; + if (!_.has(self._serverDocuments, collection)) + return null; + var serverDocsForCollection = self._serverDocuments[collection]; + if (!serverDocsForCollection.has(id)) + return null; + return serverDocsForCollection.get(id); + }, + _process_added: function (msg, updates) { var self = this; - var serverDoc = Meteor._get(self._serverDocuments, msg.collection, msg.id); + var id = LocalCollection._idParse(msg.id); + var serverDoc = self._getServerDoc(msg.collection, id); if (serverDoc) { // Some outstanding stub wrote here. - if (serverDoc.document !== undefined) { - throw new Error("It doesn't make sense to be adding something we know exists: " - + msg.id); - } + if (serverDoc.document !== undefined) + throw new Error("Server sent add for existing id: " + msg.id); serverDoc.document = msg.fields || {}; - serverDoc.document._id = LocalCollection._idParse(msg.id); + serverDoc.document._id = id; } else { self._pushUpdate(updates, msg.collection, msg); } @@ -1072,12 +1084,11 @@ _.extend(Connection.prototype, { _process_changed: function (msg, updates) { var self = this; - var serverDoc = Meteor._get(self._serverDocuments, msg.collection, msg.id); + var serverDoc = self._getServerDoc( + msg.collection, LocalCollection._idParse(msg.id)); if (serverDoc) { - if (serverDoc.document === undefined) { - throw new Error("It doesn't make sense to be changing something we don't think exists: " - + msg.id); - } + if (serverDoc.document === undefined) + throw new Error("Server sent changed for nonexisting id: " + msg.id); LocalCollection._applyChanges(serverDoc.document, msg.fields); } else { self._pushUpdate(updates, msg.collection, msg); @@ -1086,14 +1097,12 @@ _.extend(Connection.prototype, { _process_removed: function (msg, updates) { var self = this; - var serverDoc = Meteor._get( - self._serverDocuments, msg.collection, msg.id); + var serverDoc = self._getServerDoc( + msg.collection, LocalCollection._idParse(msg.id)); if (serverDoc) { // Some outstanding stub wrote here. - if (serverDoc.document === undefined) { - throw new Error("It doesn't make sense to be deleting something we don't know exists: " - + msg.id); - } + if (serverDoc.document === undefined) + throw new Error("Server sent removed for nonexisting id:" + msg.id); serverDoc.document = undefined; } else { self._pushUpdate(updates, msg.collection, { @@ -1109,8 +1118,7 @@ _.extend(Connection.prototype, { // Process "method done" messages. _.each(msg.methods, function (methodId) { _.each(self._documentsWrittenByStub[methodId], function (written) { - var serverDoc = Meteor._get(self._serverDocuments, - written.collection, written.id); + var serverDoc = self._getServerDoc(written.collection, written.id); if (!serverDoc) throw new Error("Lost serverDoc for " + JSON.stringify(written)); if (!serverDoc.writtenByStubs[methodId]) @@ -1123,11 +1131,12 @@ _.extend(Connection.prototype, { // change if the server did not write to this object, or applying the // server's writes if it did). - // This is a fake ddp 'replace' message. It's just for talking between - // livedata connections and minimongo. + // This is a fake ddp 'replace' message. It's just for talking + // between livedata connections and minimongo. (We have to stringify + // the ID because it's supposed to look like a wire message.) self._pushUpdate(updates, written.collection, { msg: 'replace', - id: written.id, + id: LocalCollection._idStringify(written.id), replace: serverDoc.document }); // Call all flush callbacks. @@ -1136,9 +1145,9 @@ _.extend(Connection.prototype, { }); // Delete this completed serverDocument. Don't bother to GC empty - // objects inside self._serverDocuments, since there probably aren't + // IdMaps inside self._serverDocuments, since there probably aren't // many collections and they'll be written repeatedly. - delete self._serverDocuments[written.collection][written.id]; + self._serverDocuments[written.collection].remove(written.id); } }); delete self._documentsWrittenByStub[methodId]; @@ -1192,7 +1201,7 @@ _.extend(Connection.prototype, { } }; _.each(self._serverDocuments, function (collectionDocs) { - _.each(collectionDocs, function (serverDoc) { + collectionDocs.forEach(function (serverDoc) { var writtenByStubForAMethodWithSentMessage = _.any( serverDoc.writtenByStubs, function (dummy, methodId) { var invoker = self._methodInvokers[methodId]; diff --git a/packages/minimongo/diff.js b/packages/minimongo/diff.js index 4b97628582..6fe8d4f411 100644 --- a/packages/minimongo/diff.js +++ b/packages/minimongo/diff.js @@ -2,7 +2,7 @@ // ordered: bool. // old_results and new_results: collections of documents. // if ordered, they are arrays. -// if unordered, they are maps {_id: doc}. +// if unordered, they are IdMaps LocalCollection._diffQueryChanges = function (ordered, oldResults, newResults, observer) { if (ordered) @@ -14,28 +14,31 @@ LocalCollection._diffQueryChanges = function (ordered, oldResults, newResults, }; LocalCollection._diffQueryUnorderedChanges = function (oldResults, newResults, - observer) { + observer) { if (observer.movedBefore) { throw new Error("_diffQueryUnordered called with a movedBefore observer!"); } - _.each(newResults, function (newDoc) { - if (_.has(oldResults, newDoc._id)) { - var oldDoc = oldResults[newDoc._id]; - if (observer.changed && !EJSON.equals(oldDoc, newDoc)) { - observer.changed(newDoc._id, LocalCollection._makeChangedFields(newDoc, oldDoc)); + newResults.forEach(function (newDoc, id) { + if (oldResults.has(id)) { + if (observer.changed) { + var oldDoc = oldResults.get(id); + if (!EJSON.equals(oldDoc, newDoc)) { + observer.changed( + id, LocalCollection._makeChangedFields(newDoc, oldDoc)); + } } - } else { + } else if (observer.added) { var fields = EJSON.clone(newDoc); delete fields._id; - observer.added && observer.added(newDoc._id, fields); + observer.added(newDoc._id, fields); } }); if (observer.removed) { - _.each(oldResults, function (oldDoc) { - if (!_.has(newResults, oldDoc._id)) - observer.removed(oldDoc._id); + oldResults.forEach(function (oldDoc, id) { + if (!newResults.has(id)) + observer.removed(id); }); } }; diff --git a/packages/minimongo/id_map.js b/packages/minimongo/id_map.js index c759df528d..5c2b628155 100644 --- a/packages/minimongo/id_map.js +++ b/packages/minimongo/id_map.js @@ -37,14 +37,22 @@ _.extend(LocalCollection._IdMap.prototype, { var self = this; self._map = {}; }, + // Iterates over the items in the map. Return `false` to break the loop. forEach: function (iterator) { var self = this; - _.each(self._map, function (value, key, obj) { - var context = this; - iterator.call(context, value, LocalCollection._idParse(key), obj); - }); + // don't use _.each, because we can't break out of it. + var keys = _.keys(self._map); + for (var i = 0; i < keys.length; i++) { + var breakIfFalse = iterator.call(null, self._map[keys[i]], + LocalCollection._idParse(keys[i])); + if (breakIfFalse === false) + return; + } + }, + size: function () { + var self = this; + return _.size(self._map); }, - // XXX used? setDefault: function (id, def) { var self = this; var key = LocalCollection._idStringify(id); @@ -52,5 +60,15 @@ _.extend(LocalCollection._IdMap.prototype, { return self._map[key]; self._map[key] = def; return def; + }, + // Assumes that values are EJSON-cloneable, and that we don't need to clone + // IDs (ie, that nobody is going to mutate an ObjectId). + clone: function () { + var self = this; + var clone = new LocalCollection._IdMap; + self.forEach(function (value, id) { + clone.set(id, EJSON.clone(value)); + }); + return clone; } }); diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index aa5a8b60cd..f8beb60353 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -8,27 +8,30 @@ // ObserveHandle: the return value of a live query. LocalCollection = function (name) { - this.name = name; - this.docs = {}; // _id -> document (also containing id) + var self = this; + self.name = name; + // _id -> document (also containing id) + self._docs = new LocalCollection._IdMap; - this._observeQueue = new Meteor._SynchronousQueue(); + self._observeQueue = new Meteor._SynchronousQueue(); - this.next_qid = 1; // live query id generator + self.next_qid = 1; // live query id generator // qid -> live query object. keys: // ordered: bool. ordered queries have addedBefore/movedBefore callbacks. // results: array (ordered) or object (unordered) of current results + // (aliased with self._docs!) // results_snapshot: snapshot of results. null if not paused. // cursor: Cursor object for the query. // selector, sorter, (callbacks): functions - this.queries = {}; + self.queries = {}; - // null if not saving originals; a map from id to original document value if + // null if not saving originals; an IdMap from id to original document value if // saving originals. See comments before saveOriginals(). - this._savedOriginals = null; + self._savedOriginals = null; // True when observers are paused and we should not send callbacks. - this.paused = false; + self.paused = false; }; Minimongo = {}; @@ -90,11 +93,11 @@ LocalCollection.Cursor = function (collection, selector, options) { if (LocalCollection._selectorIsId(selector)) { // stash for fast path - self.selector_id = LocalCollection._idStringify(selector); + self._selectorId = selector; self.matcher = new Minimongo.Matcher(selector, self); self.sorter = undefined; } else { - self.selector_id = undefined; + self._selectorId = undefined; self.matcher = new Minimongo.Matcher(selector, self); self.sorter = (self.matcher.hasGeoQuery() || options.sort) ? new Sorter(options.sort || []) : null; @@ -111,8 +114,8 @@ LocalCollection.Cursor = function (collection, selector, options) { else self._transform = options.transform; - // db_objects is a list of the objects that match the cursor. (It's always a - // list, never an object: LocalCollection.Cursor is always ordered.) + // db_objects is an array of the objects that match the cursor. (It's always + // an array, never an IdMap: LocalCollection.Cursor is always ordered.) self.db_objects = null; self.cursor_pos = 0; @@ -267,6 +270,10 @@ _.extend(LocalCollection.Cursor.prototype, { var ordered = LocalCollection._observeChangesCallbacksAreOrdered(options); + // there are several places that assume you aren't combining skip/limit with + // unordered observe. eg, update's EJSON.clone, and the "there are several" + // comment in _modifyAndNotify + // XXX allow skip/limit with unordered observe if (!options._allow_unordered && !ordered && (self.skip || self.limit)) throw new Error("must use ordered observe with skip or limit"); @@ -295,7 +302,7 @@ _.extend(LocalCollection.Cursor.prototype, { } query.results = self._getRawObjects(ordered, query.distances); if (self.collection.paused) - query.results_snapshot = (ordered ? [] : {}); + query.results_snapshot = (ordered ? [] : new LocalCollection._IdMap); // wrap callbacks we were passed. callbacks only fire when not paused and // are never undefined @@ -333,7 +340,11 @@ _.extend(LocalCollection.Cursor.prototype, { } if (!options._suppress_initial && !self.collection.paused) { - _.each(query.results, function (doc, i) { + // XXX unify ordered and unordered interface + var each = ordered + ? _.bind(_.each, null, query.results) + : _.bind(query.results.forEach, query.results); + each(function (doc) { var fields = EJSON.clone(doc); delete fields._id; @@ -389,22 +400,24 @@ LocalCollection.Cursor.prototype._getRawObjects = function (ordered, distances) { var self = this; - var results = ordered ? [] : {}; + // XXX use OrderedDict instead of array, and make IdMap and OrderedDict + // compatible + var results = ordered ? [] : new LocalCollection._IdMap; // fast path for single ID value - if (self.selector_id) { + if (self._selectorId !== undefined) { // If you have non-zero skip and ask for a single id, you get // nothing. This is so it matches the behavior of the '{_id: foo}' // path. if (self.skip) return results; - if (_.has(self.collection.docs, self.selector_id)) { - var selectedDoc = self.collection.docs[self.selector_id]; + if (self.collection._docs.has(self._selectorId)) { + var selectedDoc = self.collection._docs.get(self._selectorId); if (ordered) results.push(selectedDoc); else - results[self.selector_id] = selectedDoc; + results.set(self._selectorId, selectedDoc); } return results; } @@ -421,9 +434,7 @@ LocalCollection.Cursor.prototype._getRawObjects = function (ordered, distances = new LocalCollection._IdMap(); } - for (var idStringified in self.collection.docs) { - var doc = self.collection.docs[idStringified]; - var id = LocalCollection._idParse(idStringified); // XXX use more idmaps + self.collection._docs.forEach(function (doc, id) { var matchResult = self.matcher.documentMatches(doc); if (matchResult.result) { if (ordered) { @@ -431,14 +442,16 @@ LocalCollection.Cursor.prototype._getRawObjects = function (ordered, if (distances && matchResult.distance !== undefined) distances.set(id, matchResult.distance); } else { - results[idStringified] = doc; + results.set(id, doc); } } // Fast path for limited unsorted queries. + // XXX 'length' check here seems wrong for ordered if (self.limit && !self.skip && !self.sorter && results.length === self.limit) - return results; - } + return false; // break + return true; // continue + }); if (!ordered) return results; @@ -492,13 +505,13 @@ LocalCollection.prototype.insert = function (doc, callback) { doc._id = LocalCollection._useOID ? new LocalCollection._ObjectID() : Random.id(); } - var id = LocalCollection._idStringify(doc._id); + var id = doc._id; - if (_.has(self.docs, id)) - throw MinimongoError("Duplicate _id '" + doc._id + "'"); + if (self._docs.has(id)) + throw MinimongoError("Duplicate _id '" + id + "'"); self._saveOriginal(id, undefined); - self.docs[id] = doc; + self._docs.set(id, doc); var queriesToRecompute = []; // trigger live queries that match @@ -507,7 +520,7 @@ LocalCollection.prototype.insert = function (doc, callback) { var matchResult = query.matcher.documentMatches(doc); if (matchResult.result) { if (query.distances && matchResult.distance !== undefined) - query.distances.set(doc._id, matchResult.distance); + query.distances.set(id, matchResult.distance); if (query.cursor.skip || query.cursor.limit) queriesToRecompute.push(qid); else @@ -525,9 +538,9 @@ LocalCollection.prototype.insert = function (doc, callback) { // immediately. if (callback) Meteor.defer(function () { - callback(null, doc._id); + callback(null, id); }); - return doc._id; + return id; }; LocalCollection.prototype.remove = function (selector, callback) { @@ -541,26 +554,24 @@ LocalCollection.prototype.remove = function (selector, callback) { var specificIds = LocalCollection._idsMatchedBySelector(selector); if (specificIds) { _.each(specificIds, function (id) { - var strId = LocalCollection._idStringify(id); // We still have to run matcher, in case it's something like // {_id: "X", a: 42} - if (_.has(self.docs, strId) - && matcher.documentMatches(self.docs[strId]).result) - remove.push(strId); + if (self._docs.has(id) + && matcher.documentMatches(self._docs.get(id)).result) + remove.push(id); }); } else { - for (var strId in self.docs) { - var doc = self.docs[strId]; + self._docs.forEach(function (doc, id) { if (matcher.documentMatches(doc).result) { - remove.push(strId); + remove.push(id); } - } + }); } var queryRemove = []; for (var i = 0; i < remove.length; i++) { var removeId = remove[i]; - var removeDoc = self.docs[removeId]; + var removeDoc = self._docs.get(removeId); _.each(self.queries, function (query, qid) { if (query.matcher.documentMatches(removeDoc).result) { if (query.cursor.skip || query.cursor.limit) @@ -570,7 +581,7 @@ LocalCollection.prototype.remove = function (selector, callback) { } }); self._saveOriginal(removeId, removeDoc); - delete self.docs[removeId]; + self._docs.remove(removeId); } // run live query callbacks _after_ we've removed the documents. @@ -614,6 +625,8 @@ LocalCollection.prototype.update = function (selector, mod, options, callback) { // _recomputeResults.) var qidToOriginalResults = {}; _.each(self.queries, function (query, qid) { + // XXX for now, skip/limit implies ordered observe, so query.results is + // always an array if ((query.cursor.skip || query.cursor.limit) && !query.paused) qidToOriginalResults[qid] = EJSON.clone(query.results); }); @@ -621,8 +634,7 @@ LocalCollection.prototype.update = function (selector, mod, options, callback) { var updateCount = 0; - for (var id in self.docs) { - var doc = self.docs[id]; + self._docs.forEach(function (doc, id) { var queryResult = matcher.documentMatches(doc); if (queryResult.result) { // XXX Should we save the original even if mod ends up being a no-op? @@ -630,9 +642,10 @@ LocalCollection.prototype.update = function (selector, mod, options, callback) { self._modifyAndNotify(doc, mod, recomputeQids, queryResult.arrayIndex); ++updateCount; if (!options.multi) - break; + return false; // break } - } + return true; + }); _.each(recomputeQids, function (dummy, qid) { var query = self.queries[qid]; @@ -703,8 +716,7 @@ LocalCollection.prototype._modifyAndNotify = function ( } else { // Because we don't support skip or limit (yet) in unordered queries, we // can just do a direct lookup. - matched_before[qid] = _.has(query.results, - LocalCollection._idStringify(doc._id)); + matched_before[qid] = query.results.has(doc._id); } } @@ -767,7 +779,7 @@ LocalCollection._insertInResults = function (query, doc) { query.added(doc._id, fields); } else { query.added(doc._id, fields); - query.results[LocalCollection._idStringify(doc._id)] = doc; + query.results.set(doc._id, doc); } }; @@ -777,9 +789,9 @@ LocalCollection._removeFromResults = function (query, doc) { query.removed(doc._id); query.results.splice(i, 1); } else { - var id = LocalCollection._idStringify(doc._id); // in case callback mutates doc + var id = doc._id; // in case callback mutates doc query.removed(doc._id); - delete query.results[id]; + query.results.remove(id); } }; @@ -790,7 +802,7 @@ LocalCollection._updateInResults = function (query, doc, old_doc) { if (!query.ordered) { if (!_.isEmpty(changedFields)) { query.changed(doc._id, changedFields); - query.results[LocalCollection._idStringify(doc._id)] = doc; + query.results.set(doc._id, doc); } return; } @@ -888,7 +900,7 @@ LocalCollection.prototype.saveOriginals = function () { var self = this; if (self._savedOriginals) throw new Error("Called saveOriginals twice without retrieveOriginals"); - self._savedOriginals = {}; + self._savedOriginals = new LocalCollection._IdMap; }; LocalCollection.prototype.retrieveOriginals = function () { var self = this; @@ -908,9 +920,9 @@ LocalCollection.prototype._saveOriginal = function (id, doc) { // Have we previously mutated the original (and so 'doc' is not actually // original)? (Note the 'has' check rather than truth: we store undefined // here for inserted docs!) - if (_.has(self._savedOriginals, id)) + if (self._savedOriginals.has(id)) return; - self._savedOriginals[id] = EJSON.clone(doc); + self._savedOriginals.set(id, EJSON.clone(doc)); }; // Pause the observers. No callbacks from observers will fire until diff --git a/packages/minimongo/minimongo_tests.js b/packages/minimongo/minimongo_tests.js index e4ed903a33..9977fe8ae3 100644 --- a/packages/minimongo/minimongo_tests.js +++ b/packages/minimongo/minimongo_tests.js @@ -2451,15 +2451,15 @@ Tinytest.add("minimongo - saveOriginals", function (test) { // Verify the originals. var originals = c.retrieveOriginals(); var affected = ['bar', 'baz', 'quux', 'whoa', 'hooray']; - test.equal(_.size(originals), _.size(affected)); + test.equal(originals.size(), _.size(affected)); _.each(affected, function (id) { - test.isTrue(_.has(originals, id)); + test.isTrue(originals.has(id)); }); - test.equal(originals.bar, {_id: 'bar', x: 'updateme'}); - test.equal(originals.baz, {_id: 'baz', x: 'updateme'}); - test.equal(originals.quux, {_id: 'quux', y: 'removeme'}); - test.equal(originals.whoa, {_id: 'whoa', y: 'removeme'}); - test.equal(originals.hooray, undefined); + test.equal(originals.get('bar'), {_id: 'bar', x: 'updateme'}); + test.equal(originals.get('baz'), {_id: 'baz', x: 'updateme'}); + test.equal(originals.get('quux'), {_id: 'quux', y: 'removeme'}); + test.equal(originals.get('whoa'), {_id: 'whoa', y: 'removeme'}); + test.equal(originals.get('hooray'), undefined); // Verify that changes actually occured. test.equal(c.find().count(), 4); @@ -2472,16 +2472,16 @@ Tinytest.add("minimongo - saveOriginals", function (test) { c.saveOriginals(); originals = c.retrieveOriginals(); test.isTrue(originals); - test.isTrue(_.isEmpty(originals)); + test.isTrue(originals.empty()); // Insert and remove a document during the period. c.saveOriginals(); c.insert({_id: 'temp', q: 8}); c.remove('temp'); originals = c.retrieveOriginals(); - test.equal(_.size(originals), 1); - test.isTrue(_.has(originals, 'temp')); - test.equal(originals.temp, undefined); + test.equal(originals.size(), 1); + test.isTrue(originals.has('temp')); + test.equal(originals.get('temp'), undefined); }); Tinytest.add("minimongo - saveOriginals errors", function (test) { diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index a22d365244..11a978147d 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -792,7 +792,7 @@ var SynchronousCursor = function (dbCursor, cursorDescription, options) { self._synchronousNextObject = Future.wrap( dbCursor.nextObject.bind(dbCursor), 0); self._synchronousCount = Future.wrap(dbCursor.count.bind(dbCursor)); - self._visitedIds = {}; + self._visitedIds = new LocalCollection._IdMap; }; _.extend(SynchronousCursor.prototype, { @@ -812,9 +812,8 @@ _.extend(SynchronousCursor.prototype, { // because we want to maintain O(1) memory usage. And if there isn't _id // for some reason (maybe it's the oplog), then we don't do this either. // (Be careful to do this for falsey but existing _id, though.) - var strId = LocalCollection._idStringify(doc._id); - if (self._visitedIds[strId]) continue; - self._visitedIds[strId] = true; + if (self._visitedIds.has(doc._id)) continue; + self._visitedIds.set(doc._id, true); } if (self._transform) @@ -854,7 +853,7 @@ _.extend(SynchronousCursor.prototype, { // known to be synchronous self._dbCursor.rewind(); - self._visitedIds = {}; + self._visitedIds = new LocalCollection._IdMap; }, // Mostly usable for tailable cursors. @@ -880,9 +879,9 @@ _.extend(SynchronousCursor.prototype, { if (ordered) { return self.fetch(); } else { - var results = {}; + var results = new LocalCollection._IdMap; self.forEach(function (doc) { - results[doc._id] = doc; + results.set(doc._id, doc); }); return results; } diff --git a/packages/mongo-livedata/polling_observe_driver.js b/packages/mongo-livedata/polling_observe_driver.js index 82020f2147..aa17477c33 100644 --- a/packages/mongo-livedata/polling_observe_driver.js +++ b/packages/mongo-livedata/polling_observe_driver.js @@ -129,8 +129,8 @@ _.extend(PollingObserveDriver.prototype, { var first = false; if (!self._results) { first = true; - // XXX maybe use _IdMap/OrderedDict instead? - self._results = self._ordered ? [] : {}; + // XXX maybe use OrderedDict instead? + self._results = self._ordered ? [] : new LocalCollection._IdMap; } self._testOnlyPollCallback && self._testOnlyPollCallback(); From 3d8add8d9d94b98e4cb67c49e3f61a0c695ada20 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Mon, 6 Jan 2014 14:38:47 -0800 Subject: [PATCH 08/67] Tests for "can selector become true by modifier" for simple $-operators --- packages/minimongo/minimongo_server_tests.js | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/minimongo/minimongo_server_tests.js b/packages/minimongo/minimongo_server_tests.js index 90e797c0aa..48d7439165 100644 --- a/packages/minimongo/minimongo_server_tests.js +++ b/packages/minimongo/minimongo_server_tests.js @@ -465,5 +465,28 @@ Tinytest.add("minimongo - selector and projection combination", function (test) F({ 'a.b.c': 1 }, { $set: { 'a.b': 222 } }, "a simple scalar selector and simple set a wrong type"); }); + Tinytest.add("minimongo - can selector become true by modifier - simple selectors and simple tests", function (t) { + test = t; + T({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { c: 4 } } }, "nested $lt"); + F({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { c: 5 } } }, "nested $lt"); + F({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { c: 6 } } }, "nested $lt"); + T({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { d: 7 } } }, "nested $lt, the change doesn't matter"); + T({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { d: 7, c: -1 } } }, "nested $lt"); + F({ a: { $lt: 10, $gt: 3 } }, { $unset: { 'a': 1 } }, "unset $lt"); + T({ a: { $lt: 10, $gt: 3 } }, { $set: { 'a': 4 } }, "set between x and y"); + F({ a: { $lt: 10, $gt: 3 } }, { $set: { 'a': 3 } }, "set between x and y"); + F({ a: { $lt: 10, $gt: 3 } }, { $set: { 'a': 10 } }, "set between x and y"); + F({ a: { $gt: 10, $lt: 3 } }, { $set: { 'a': 9 } }, "impossible statement"); + T({ a: { $lte: 10, $gte: 3 } }, { $set: { 'a': 3 } }, "set between x and y"); + T({ a: { $lte: 10, $gte: 3 } }, { $set: { 'a': 10 } }, "set between x and y"); + F({ a: { $lte: 10, $gte: 3 } }, { $set: { 'a': -10 } }, "set between x and y"); + T({ a: { $ne: 5 } }, { $unset: { a: 1 } }, "unset of $ne"); + T({ a: { $ne: 5 } }, { $set: { a: 1 } }, "set of $ne"); + T({ a: { $ne: 5 } }, { $set: { a: -10 } }, "set of $ne"); + T({ a: { $ne: { x: 5 } } }, { $set: { 'a.x': 3 } }, "set of $ne"); + F({ a: { $ne: { x: 5 } } }, { $set: { 'a.x': 5 } }, "set of $ne"); + T({ a: { $in: [1, 3, 5, 7] } }, { $set: { a: 5 } }, "$in checks"); + F({ a: { $in: [1, 3, 5, 7] } }, { $set: { a: -5 } }, "$in checks"); + }); })(); From 80b8fcae2afc3d805220ba37a298a51748ad6cd8 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Thu, 9 Jan 2014 14:29:35 -0800 Subject: [PATCH 09/67] Fix wrong tests, separate "complex" tests to a separate scope. --- packages/minimongo/minimongo_server_tests.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/minimongo/minimongo_server_tests.js b/packages/minimongo/minimongo_server_tests.js index 48d7439165..73102fb5c4 100644 --- a/packages/minimongo/minimongo_server_tests.js +++ b/packages/minimongo/minimongo_server_tests.js @@ -465,12 +465,13 @@ Tinytest.add("minimongo - selector and projection combination", function (test) F({ 'a.b.c': 1 }, { $set: { 'a.b': 222 } }, "a simple scalar selector and simple set a wrong type"); }); - Tinytest.add("minimongo - can selector become true by modifier - simple selectors and simple tests", function (t) { + Tinytest.add("minimongo - can selector become true by modifier - $-scalar selectors and simple tests", function (t) { test = t; T({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { c: 4 } } }, "nested $lt"); F({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { c: 5 } } }, "nested $lt"); F({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { c: 6 } } }, "nested $lt"); - T({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { d: 7 } } }, "nested $lt, the change doesn't matter"); + F({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b.d': 7 } }, "nested $lt, the change doesn't matter"); + F({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { d: 7 } } }, "nested $lt, the key disappears"); T({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { d: 7, c: -1 } } }, "nested $lt"); F({ a: { $lt: 10, $gt: 3 } }, { $unset: { 'a': 1 } }, "unset $lt"); T({ a: { $lt: 10, $gt: 3 } }, { $set: { 'a': 4 } }, "set between x and y"); @@ -483,10 +484,18 @@ Tinytest.add("minimongo - selector and projection combination", function (test) T({ a: { $ne: 5 } }, { $unset: { a: 1 } }, "unset of $ne"); T({ a: { $ne: 5 } }, { $set: { a: 1 } }, "set of $ne"); T({ a: { $ne: 5 } }, { $set: { a: -10 } }, "set of $ne"); - T({ a: { $ne: { x: 5 } } }, { $set: { 'a.x': 3 } }, "set of $ne"); - F({ a: { $ne: { x: 5 } } }, { $set: { 'a.x': 5 } }, "set of $ne"); T({ a: { $in: [1, 3, 5, 7] } }, { $set: { a: 5 } }, "$in checks"); F({ a: { $in: [1, 3, 5, 7] } }, { $set: { a: -5 } }, "$in checks"); }); + + Tinytest.add("minimongo - can selector become true by modifier - $-nonscalar selectors and simple tests", function (t) { + test = t; + T({ a: { $ne: { x: 5 } } }, { $set: { 'a.x': 3 } }, "set of $ne"); + // XXX this test should be F, but it is not implemented yet + T({ a: { $ne: { x: 5 } } }, { $set: { 'a.x': 5 } }, "set of $ne"); + T({ a: { $in: [{ b: 1 }, { b: 3 }] } }, { $set: { a: { b: 3 } } }, "$in checks"); + // XXX this test should be F, but it is not implemented yet + T({ a: { $in: [{ b: 1 }, { b: 3 }] } }, { $set: { a: { v: 3 } } }, "$in checks"); + }); })(); From d02617589403feca368f15532b5f6f21d25d6352 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Thu, 9 Jan 2014 14:30:18 -0800 Subject: [PATCH 10/67] Now Matcher.canBecomeTrueByModifier deals with simple $-operators with scalars: - $gt, $gte, $lt, $lte - $ne - $nin, $in --- packages/minimongo/selector.js | 21 ++++++----- packages/minimongo/selector_modifier.js | 49 +++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/packages/minimongo/selector.js b/packages/minimongo/selector.js index f308d5160e..368d9766a8 100644 --- a/packages/minimongo/selector.js +++ b/packages/minimongo/selector.js @@ -30,7 +30,7 @@ Minimongo.Matcher = function (selector) { self._hasWhere = false; // Set to false if compilation finds anything other than a simple equality on // some fields. - self._isEquality = true; + self._isSimple = true; // A clone of the original selector. Used by canBecomeTrueByModifier. self._selector = null; self._docMatcher = self._compileSelector(selector); @@ -46,8 +46,8 @@ _.extend(Minimongo.Matcher.prototype, { hasWhere: function () { return this._hasWhere; }, - isEquality: function () { - return this._isEquality; + isSimple: function () { + return this._isSimple; }, // Given a selector, return a function that takes one argument, a @@ -56,7 +56,7 @@ _.extend(Minimongo.Matcher.prototype, { var self = this; // you can pass a literal function instead of a selector if (selector instanceof Function) { - self._isEquality = false; + self._isSimple = false; self._selector = selector; self._recordPathUsed(''); return function (doc) { @@ -77,7 +77,7 @@ _.extend(Minimongo.Matcher.prototype, { // likely programmer error, and not what you want, particularly for // destructive operations. if (!selector || (('_id' in selector) && !selector._id)) { - self._isEquality = null; + self._isSimple = null; return nothingMatcher; } @@ -116,7 +116,7 @@ var compileDocumentSelector = function (docSelector, matcher, options) { // this function), or $where. if (!_.has(LOGICAL_OPERATORS, key)) throw new Error("Unrecognized logical operator: " + key); - matcher._isEquality = false; + matcher._isSimple = false; docMatchers.push(LOGICAL_OPERATORS[key](subSelector, matcher, options.inElemMatch)); } else { @@ -144,7 +144,7 @@ var compileDocumentSelector = function (docSelector, matcher, options) { // [branched value]->result object. var compileValueSelector = function (valueSelector, matcher, isRoot) { if (valueSelector instanceof RegExp) { - matcher._isEquality = false; + matcher._isSimple = false; return convertElementMatcherToBranchedMatcher( regexpElementMatcher(valueSelector)); } else if (isOperatorObject(valueSelector)) { @@ -235,8 +235,11 @@ var operatorBranchedMatcher = function (valueSelector, matcher, isRoot) { var operatorMatchers = []; _.each(valueSelector, function (operand, operator) { // XXX we should actually implement $eq, which is new in 2.6 - if (operator !== '$eq') - matcher._isEquality = false; + if (operator !== '$eq' && + !_.contains(['$lt', '$lte', '$gt', '$gte'], operator) && + (operator !== '$ne' || _.isObject(operand)) && + (!_.contains(['$in', '$nin'], operator) || !_.isArray(operand) || _.any(operand, _.isObject))) + matcher._isSimple = false; if (_.has(VALUE_OPERATORS, operator)) { operatorMatchers.push( diff --git a/packages/minimongo/selector_modifier.js b/packages/minimongo/selector_modifier.js index 100ac007ac..766b6aa741 100644 --- a/packages/minimongo/selector_modifier.js +++ b/packages/minimongo/selector_modifier.js @@ -48,6 +48,9 @@ Minimongo.Matcher.prototype.affectedByModifier = function (modifier) { // only. (assumed to come from oplog) // @returns - Boolean: if after applying the modifier, selector can start // accepting the modified value. +// NOTE: assumes that document affected by modifier didn't match this Matcher +// before, so if modifier can't convince selector in a positive change it would +// stay 'false'. // Currently doesn't support $-operators and numeric indices precisely. Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { var self = this; @@ -56,7 +59,7 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { modifier = _.extend({$set:{}, $unset:{}}, modifier); - if (!self.isEquality()) + if (!self.isSimple()) return true; if (_.any(self._getPaths(), pathHasNumericKeys) || @@ -64,12 +67,52 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { _.any(_.keys(modifier.$set), pathHasNumericKeys)) return true; + // A helper to ensure object has only certain keys + var onlyContainsKeys = function (obj, keys) { + return _.every(_.keys(obj), _.bind(_.contains, null, keys)); + }; + // convert a selector into an object matching the selector // { 'a.b': { ans: 42 }, 'foo.bar': null, 'foo.baz': "something" } // => { a: { b: { ans: 42 } }, foo: { bar: null, baz: "something" } } + var failed = false; var doc = pathsToTree(self._getPaths(), - function (path) { return self._selector[path]; }, - _.identity /*conflict resolution is no resolution*/); + function (path) { + var valueSelector = self._selector[path]; + if (isOperatorObject(valueSelector)) { + // XXX check if there is a $set or $unset that indicates something is an + // object rather than a scalar in the actual object + + // if there is a strict equality, there is a good + // chance we can use one of those as "matching" + // dummy value + if (valueSelector.$in) { + var matches = _.bind(self.documentMatches, self); + return _.find(valueSelector.$in, matches); + } else if (onlyContainsKeys(valueSelector, ['$gt', '$gte', '$lt', '$lte'])) { + var lowerBound = -Infinity, upperBound = Infinity; + _.each(['$lte', '$lt'], function (op) { + if (_.has(valueSelector, op) && valueSelector[op] < upperBound) + upperBound = valueSelector[op]; + }); + _.each(['$gte', '$gt'], function (op) { + if (_.has(valueSelector, op) && valueSelector[op] > lowerBound) + lowerBound = valueSelector[op]; + }); + + return (lowerBound + upperBound) / 2; + } else if (onlyContainsKeys(valueSelector, ['$nin',' $ne'])) { + return {}; + } else { + failed = true; + } + } + return self._selector[path]; + }, + _.identity /*conflict resolution is no resolution*/); + + if (failed) + return true; try { LocalCollection._modify(doc, modifier); From 4f0d2c9a04a2b11d0332fb3da506a658d44eefdc Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Thu, 9 Jan 2014 15:53:11 -0800 Subject: [PATCH 11/67] More tests --- packages/minimongo/minimongo_server_tests.js | 35 +++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/packages/minimongo/minimongo_server_tests.js b/packages/minimongo/minimongo_server_tests.js index 73102fb5c4..f52f2884d1 100644 --- a/packages/minimongo/minimongo_server_tests.js +++ b/packages/minimongo/minimongo_server_tests.js @@ -473,19 +473,35 @@ Tinytest.add("minimongo - selector and projection combination", function (test) F({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b.d': 7 } }, "nested $lt, the change doesn't matter"); F({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { d: 7 } } }, "nested $lt, the key disappears"); T({ 'a.b.c': { $lt: 5 } }, { $set: { 'a.b': { d: 7, c: -1 } } }, "nested $lt"); - F({ a: { $lt: 10, $gt: 3 } }, { $unset: { 'a': 1 } }, "unset $lt"); - T({ a: { $lt: 10, $gt: 3 } }, { $set: { 'a': 4 } }, "set between x and y"); - F({ a: { $lt: 10, $gt: 3 } }, { $set: { 'a': 3 } }, "set between x and y"); - F({ a: { $lt: 10, $gt: 3 } }, { $set: { 'a': 10 } }, "set between x and y"); - F({ a: { $gt: 10, $lt: 3 } }, { $set: { 'a': 9 } }, "impossible statement"); - T({ a: { $lte: 10, $gte: 3 } }, { $set: { 'a': 3 } }, "set between x and y"); - T({ a: { $lte: 10, $gte: 3 } }, { $set: { 'a': 10 } }, "set between x and y"); - F({ a: { $lte: 10, $gte: 3 } }, { $set: { 'a': -10 } }, "set between x and y"); + F({ a: { $lt: 10, $gt: 3 } }, { $unset: { a: 1 } }, "unset $lt"); + T({ a: { $lt: 10, $gt: 3 } }, { $set: { a: 4 } }, "set between x and y"); + F({ a: { $lt: 10, $gt: 3 } }, { $set: { a: 3 } }, "set between x and y"); + F({ a: { $lt: 10, $gt: 3 } }, { $set: { a: 10 } }, "set between x and y"); + F({ a: { $gt: 10, $lt: 3 } }, { $set: { a: 9 } }, "impossible statement"); + T({ a: { $lte: 10, $gte: 3 } }, { $set: { a: 3 } }, "set between x and y"); + T({ a: { $lte: 10, $gte: 3 } }, { $set: { a: 10 } }, "set between x and y"); + F({ a: { $lte: 10, $gte: 3 } }, { $set: { a: -10 } }, "set between x and y"); + T({ a: { $lte: 10, $gte: 3, $gt: 3, $lt: 10 } }, { $set: { a: 4 } }, "set between x and y"); + F({ a: { $lte: 10, $gte: 3, $gt: 3, $lt: 10 } }, { $set: { a: 3 } }, "set between x and y"); + F({ a: { $lte: 10, $gte: 3, $gt: 3, $lt: 10 } }, { $set: { a: 10 } }, "set between x and y"); + F({ a: { $lte: 10, $gte: 3, $gt: 3, $lt: 10 } }, { $set: { a: Infinity } }, "set between x and y"); + T({ a: { $lte: 10, $gte: 3, $gt: 3, $lt: 10 }, x: 1 }, { $set: { x: 1 } }, "set between x and y - dummy"); + F({ a: { $lte: 10, $gte: 13, $gt: 3, $lt: 9 }, x: 1 }, { $set: { x: 1 } }, "set between x and y - dummy - impossible"); + F({ a: { $lte: 10 } }, { $set: { a: Infinity } }, "Infinity <= 10?"); + T({ a: { $lte: 10 } }, { $set: { a: -Infinity } }, "-Infinity <= 10?"); + // XXX is this sufficient? + T({ a: { $gt: 9.99999999999999, $lt: 10 }, x: 1 }, { $set: { x: 1 } }, "very close $gt and $lt"); T({ a: { $ne: 5 } }, { $unset: { a: 1 } }, "unset of $ne"); T({ a: { $ne: 5 } }, { $set: { a: 1 } }, "set of $ne"); + T({ a: { $ne: "some string" }, x: 1 }, { $set: { x: 1 } }, "$ne dummy"); + T({ a: { $ne: true }, x: 1 }, { $set: { x: 1 } }, "$ne dummy"); + T({ a: { $ne: false }, x: 1 }, { $set: { x: 1 } }, "$ne dummy"); + T({ a: { $ne: null }, x: 1 }, { $set: { x: 1 } }, "$ne dummy"); + T({ a: { $ne: Infinity }, x: 1 }, { $set: { x: 1 } }, "$ne dummy"); T({ a: { $ne: 5 } }, { $set: { a: -10 } }, "set of $ne"); T({ a: { $in: [1, 3, 5, 7] } }, { $set: { a: 5 } }, "$in checks"); F({ a: { $in: [1, 3, 5, 7] } }, { $set: { a: -5 } }, "$in checks"); + T({ a: { $in: [1, 3, 5, 7], $gt: 6 }, x: 1 }, { $set: { x: 1 } }, "$in combination with $gt"); }); Tinytest.add("minimongo - can selector become true by modifier - $-nonscalar selectors and simple tests", function (t) { @@ -496,6 +512,9 @@ Tinytest.add("minimongo - selector and projection combination", function (test) T({ a: { $in: [{ b: 1 }, { b: 3 }] } }, { $set: { a: { b: 3 } } }, "$in checks"); // XXX this test should be F, but it is not implemented yet T({ a: { $in: [{ b: 1 }, { b: 3 }] } }, { $set: { a: { v: 3 } } }, "$in checks"); + T({ a: { $ne: { a: 2 } }, x: 1 }, { $set: { x: 1 } }, "$ne dummy"); + // XXX this test should be F, but it is not implemented yet + T({ a: { $ne: { a: 2 } } }, { $set: { a: { a: 2 } } }, "$ne object"); }); })(); From cebd83e1fa333e74565a3e9d729469051dfaaeb8 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Thu, 9 Jan 2014 15:53:42 -0800 Subject: [PATCH 12/67] Fix the way $in analysis worked --- packages/minimongo/selector_modifier.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/minimongo/selector_modifier.js b/packages/minimongo/selector_modifier.js index 766b6aa741..68287bf83e 100644 --- a/packages/minimongo/selector_modifier.js +++ b/packages/minimongo/selector_modifier.js @@ -87,8 +87,10 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { // chance we can use one of those as "matching" // dummy value if (valueSelector.$in) { - var matches = _.bind(self.documentMatches, self); - return _.find(valueSelector.$in, matches); + var matcher = new Minimongo.Matcher({ placeholder: valueSelector }); + return _.find(valueSelector.$in, function (x) { + return matcher.documentMatches({ placeholder: x }).result; + }); } else if (onlyContainsKeys(valueSelector, ['$gt', '$gte', '$lt', '$lte'])) { var lowerBound = -Infinity, upperBound = Infinity; _.each(['$lte', '$lt'], function (op) { From 3470d00dbc2951fe1467bc850a444aaf79bc47c1 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Thu, 9 Jan 2014 20:33:55 -0800 Subject: [PATCH 13/67] Catch if modifier surely modifies object when scalar expected --- packages/minimongo/minimongo_server_tests.js | 4 ++++ packages/minimongo/selector_modifier.js | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/minimongo/minimongo_server_tests.js b/packages/minimongo/minimongo_server_tests.js index f52f2884d1..73b2b144ee 100644 --- a/packages/minimongo/minimongo_server_tests.js +++ b/packages/minimongo/minimongo_server_tests.js @@ -502,6 +502,10 @@ Tinytest.add("minimongo - selector and projection combination", function (test) T({ a: { $in: [1, 3, 5, 7] } }, { $set: { a: 5 } }, "$in checks"); F({ a: { $in: [1, 3, 5, 7] } }, { $set: { a: -5 } }, "$in checks"); T({ a: { $in: [1, 3, 5, 7], $gt: 6 }, x: 1 }, { $set: { x: 1 } }, "$in combination with $gt"); + F({ a: { $lte: 10, $gte: 3 } }, { $set: { 'a.b': -10 } }, "sel between x and y, set its subfield"); + F({ b: { $in: [1, 3, 5, 7] } }, { $set: { 'b.c': 2 } }, "sel $in, set subfield"); + T({ b: { $in: [1, 3, 5, 7] } }, { $set: { 'bd.c': 2, b: 3 } }, "sel $in, set similar subfield"); + F({ 'b.c': { $in: [1, 3, 5, 7] } }, { $set: { b: 2 } }, "sel subfield of set scalar"); }); Tinytest.add("minimongo - can selector become true by modifier - $-nonscalar selectors and simple tests", function (t) { diff --git a/packages/minimongo/selector_modifier.js b/packages/minimongo/selector_modifier.js index 68287bf83e..8b3e5050fd 100644 --- a/packages/minimongo/selector_modifier.js +++ b/packages/minimongo/selector_modifier.js @@ -58,15 +58,25 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { return false; modifier = _.extend({$set:{}, $unset:{}}, modifier); + var modifierPaths = _.keys(modifier.$set).concat(_.keys(modifier.$unset)); if (!self.isSimple()) return true; if (_.any(self._getPaths(), pathHasNumericKeys) || - _.any(_.keys(modifier.$unset), pathHasNumericKeys) || - _.any(_.keys(modifier.$set), pathHasNumericKeys)) + _.any(modifierPaths, pathHasNumericKeys)) return true; + // check if there is a $set or $unset that indicates something is an + // object rather than a scalar in the actual object where we saw $-operator + // NOTE: it is correct since we allow only scalars in $-operators + if (_.any(_.filter(self._getPaths(), isOperatorObject), function (sel, path) { + return _.any(modifierPaths, function (modifierPath) { + return !modifierPath.indexOf(path) && modifierPath[path.length] === '.'; + }); + })) return false; + + // A helper to ensure object has only certain keys var onlyContainsKeys = function (obj, keys) { return _.every(_.keys(obj), _.bind(_.contains, null, keys)); @@ -80,9 +90,6 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { function (path) { var valueSelector = self._selector[path]; if (isOperatorObject(valueSelector)) { - // XXX check if there is a $set or $unset that indicates something is an - // object rather than a scalar in the actual object - // if there is a strict equality, there is a good // chance we can use one of those as "matching" // dummy value From 0cfefa63d1320d1f5c25255eefd3296fbba04527 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Thu, 9 Jan 2014 20:41:54 -0800 Subject: [PATCH 14/67] Update the comment in tests section --- packages/minimongo/minimongo_server_tests.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/minimongo/minimongo_server_tests.js b/packages/minimongo/minimongo_server_tests.js index 73b2b144ee..afb85fe8bf 100644 --- a/packages/minimongo/minimongo_server_tests.js +++ b/packages/minimongo/minimongo_server_tests.js @@ -343,6 +343,12 @@ Tinytest.add("minimongo - selector and projection combination", function (test) // (are absent) // - tests with $-operators in the selector (are incomplete and test "not // ideal" implementation) + // * gives up on $-operators with non-scalar values ({$ne: {x: 1}}) + // * analyses $in + // * analyses $nin/$ne + // * analyses $gt, $gte, $lt, $lte + // * gives up on a combination of $gt/$gte/$lt/$lte and $ne/$nin + // * doesn't support $eq properly var test = null; // set this global in the beginning of every test // T - should return true From ccdbe31356c675c0f66084cd8ed99dceb1e4ce65 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Fri, 10 Jan 2014 17:51:54 -0800 Subject: [PATCH 15/67] Fallback to "YES" in a case of noticeable floating point numbers underflow --- packages/minimongo/minimongo_server_tests.js | 3 +++ packages/minimongo/selector_modifier.js | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/minimongo/minimongo_server_tests.js b/packages/minimongo/minimongo_server_tests.js index afb85fe8bf..85faa01615 100644 --- a/packages/minimongo/minimongo_server_tests.js +++ b/packages/minimongo/minimongo_server_tests.js @@ -497,6 +497,9 @@ Tinytest.add("minimongo - selector and projection combination", function (test) T({ a: { $lte: 10 } }, { $set: { a: -Infinity } }, "-Infinity <= 10?"); // XXX is this sufficient? T({ a: { $gt: 9.99999999999999, $lt: 10 }, x: 1 }, { $set: { x: 1 } }, "very close $gt and $lt"); + // XXX this test should be F, but since it is so hard to be precise in + // floating point math, the current implementation falls back to T + T({ a: { $gt: 9.999999999999999, $lt: 10 }, x: 1 }, { $set: { x: 1 } }, "very close $gt and $lt"); T({ a: { $ne: 5 } }, { $unset: { a: 1 } }, "unset of $ne"); T({ a: { $ne: 5 } }, { $set: { a: 1 } }, "set of $ne"); T({ a: { $ne: "some string" }, x: 1 }, { $set: { x: 1 } }, "$ne dummy"); diff --git a/packages/minimongo/selector_modifier.js b/packages/minimongo/selector_modifier.js index 8b3e5050fd..1491371926 100644 --- a/packages/minimongo/selector_modifier.js +++ b/packages/minimongo/selector_modifier.js @@ -85,7 +85,7 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { // convert a selector into an object matching the selector // { 'a.b': { ans: 42 }, 'foo.bar': null, 'foo.baz': "something" } // => { a: { b: { ans: 42 } }, foo: { bar: null, baz: "something" } } - var failed = false; + var fallback = false; var doc = pathsToTree(self._getPaths(), function (path) { var valueSelector = self._selector[path]; @@ -109,18 +109,26 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { lowerBound = valueSelector[op]; }); - return (lowerBound + upperBound) / 2; + var middle = (lowerBound + upperBound) / 2; + var matcher = new Minimongo.Matcher({ placeholder: valueSelector }); + if (!matcher.documentMatches({ placeholder: middle }).result && + (middle === lowerBound || middle === upperBound)) + fallback = true; + + return middle; } else if (onlyContainsKeys(valueSelector, ['$nin',' $ne'])) { return {}; } else { - failed = true; + fallback = true; } } return self._selector[path]; }, _.identity /*conflict resolution is no resolution*/); - if (failed) + // If the analysis of this selector is too hard for our implementation + // fallback to "YES" + if (fallback) return true; try { From 7a528be92d884e73b0f528725412d3c199564350 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Wed, 15 Jan 2014 16:09:10 -0800 Subject: [PATCH 16/67] Polish canBecomeTrueByModifier operators optimization - Fix the pruning `expectedScalarIsObject` - Limit $gt,$lt,... to numbers only (since there needs to be more logic to support dates and strings later) - Add comments on dubious parts --- packages/minimongo/minimongo_server_tests.js | 3 +++ packages/minimongo/selector.js | 20 ++++++++++------ packages/minimongo/selector_modifier.js | 25 ++++++++++++++++---- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/packages/minimongo/minimongo_server_tests.js b/packages/minimongo/minimongo_server_tests.js index 85faa01615..efd7808b4c 100644 --- a/packages/minimongo/minimongo_server_tests.js +++ b/packages/minimongo/minimongo_server_tests.js @@ -515,6 +515,9 @@ Tinytest.add("minimongo - selector and projection combination", function (test) F({ b: { $in: [1, 3, 5, 7] } }, { $set: { 'b.c': 2 } }, "sel $in, set subfield"); T({ b: { $in: [1, 3, 5, 7] } }, { $set: { 'bd.c': 2, b: 3 } }, "sel $in, set similar subfield"); F({ 'b.c': { $in: [1, 3, 5, 7] } }, { $set: { b: 2 } }, "sel subfield of set scalar"); + // If modifier tries to set a sub-field of a path expected to be a scalar. + F({ 'a.b': { $gt: 5, $lt: 7}, x: 1 }, { $set: { 'a.b.c': 3, x: 1 } }, "set sub-field of $gt,$lt operator (scalar expected)"); + F({ 'a.b': { $gt: 5, $lt: 7}, x: 1 }, { $set: { x: 1 }, $unset: { 'a.b.c': 1 } }, "unset sub-field of $gt,$lt operator (scalar expected)"); }); Tinytest.add("minimongo - can selector become true by modifier - $-nonscalar selectors and simple tests", function (t) { diff --git a/packages/minimongo/selector.js b/packages/minimongo/selector.js index 368d9766a8..277194f502 100644 --- a/packages/minimongo/selector.js +++ b/packages/minimongo/selector.js @@ -28,8 +28,9 @@ Minimongo.Matcher = function (selector) { self._hasGeoQuery = false; // Set to true if compilation finds a $where. self._hasWhere = false; - // Set to false if compilation finds anything other than a simple equality on - // some fields. + // Set to false if compilation finds anything other than a simple equality or + // one or more of '$gt', '$gte', '$lt', '$lte', '$ne', '$in', '$nin' used with + // scalars as operands. self._isSimple = true; // A clone of the original selector. Used by canBecomeTrueByModifier. self._selector = null; @@ -77,7 +78,7 @@ _.extend(Minimongo.Matcher.prototype, { // likely programmer error, and not what you want, particularly for // destructive operations. if (!selector || (('_id' in selector) && !selector._id)) { - self._isSimple = null; + self._isSimple = false; return nothingMatcher; } @@ -235,11 +236,16 @@ var operatorBranchedMatcher = function (valueSelector, matcher, isRoot) { var operatorMatchers = []; _.each(valueSelector, function (operand, operator) { // XXX we should actually implement $eq, which is new in 2.6 - if (operator !== '$eq' && - !_.contains(['$lt', '$lte', '$gt', '$gte'], operator) && - (operator !== '$ne' || _.isObject(operand)) && - (!_.contains(['$in', '$nin'], operator) || !_.isArray(operand) || _.any(operand, _.isObject))) + var supportedRange = _.contains(['$lt', '$lte', '$gt', '$gte'], operator) && + _.isNumber(operand); + var supportedInequality = operator === '$ne' && !_.isObject(operand); + var supportedInclusion = _.contains(['$in', '$nin'], operator) && + _.isArray(operand) && !_.any(operand, _.isObject); + + if (! (operator === '$eq' || supportedRange || + supportedInclusion || supportedInequality)) { matcher._isSimple = false; + } if (_.has(VALUE_OPERATORS, operator)) { operatorMatchers.push( diff --git a/packages/minimongo/selector_modifier.js b/packages/minimongo/selector_modifier.js index 1491371926..447375239c 100644 --- a/packages/minimongo/selector_modifier.js +++ b/packages/minimongo/selector_modifier.js @@ -70,21 +70,31 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { // check if there is a $set or $unset that indicates something is an // object rather than a scalar in the actual object where we saw $-operator // NOTE: it is correct since we allow only scalars in $-operators - if (_.any(_.filter(self._getPaths(), isOperatorObject), function (sel, path) { + var expectedScalarIsObject = _.any(self._selector, function (sel, path) { + if (! isOperatorObject(sel)) + return false; return _.any(modifierPaths, function (modifierPath) { return !modifierPath.indexOf(path) && modifierPath[path.length] === '.'; }); - })) return false; + }); + + if (expectedScalarIsObject) + return false; // A helper to ensure object has only certain keys var onlyContainsKeys = function (obj, keys) { - return _.every(_.keys(obj), _.bind(_.contains, null, keys)); + return _.all(obj, function (v, k) { + return _.contains(keys, k); + }); }; // convert a selector into an object matching the selector // { 'a.b': { ans: 42 }, 'foo.bar': null, 'foo.baz': "something" } // => { a: { b: { ans: 42 } }, foo: { bar: null, baz: "something" } } + + // If the analysis of this selector is too hard for our implementation + // fallback to "YES" var fallback = false; var doc = pathsToTree(self._getPaths(), function (path) { @@ -95,6 +105,10 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { // dummy value if (valueSelector.$in) { var matcher = new Minimongo.Matcher({ placeholder: valueSelector }); + + // Return anything from $in that matches the whole selector for this + // path. If nothing matches, returns `undefined` as nothing can make + // this selector into `true`. return _.find(valueSelector.$in, function (x) { return matcher.documentMatches({ placeholder: x }).result; }); @@ -117,6 +131,9 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { return middle; } else if (onlyContainsKeys(valueSelector, ['$nin',' $ne'])) { + // Since self._isSimple makes sure $nin and $ne are not combined with + // objects or arrays, we can confidently return an empty object as it + // never matches any scalar. return {}; } else { fallback = true; @@ -126,8 +143,6 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { }, _.identity /*conflict resolution is no resolution*/); - // If the analysis of this selector is too hard for our implementation - // fallback to "YES" if (fallback) return true; From 45ccc12970d4ff8efe8c0042b5c4f255950f885b Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Fri, 17 Jan 2014 15:37:12 -0800 Subject: [PATCH 17/67] Make default accounts-ui styling simpler/flatter --- .../accounts-ui-viewer.less | 2 - packages/accounts-ui/login_buttons.less | 101 ++++++++---------- 2 files changed, 42 insertions(+), 61 deletions(-) diff --git a/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.less b/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.less index e7db3c5eff..51890f7cf3 100644 --- a/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.less +++ b/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.less @@ -1,5 +1,3 @@ - -* { padding: 0; margin: 0; } html, body { height: 100%; } #controlpane { diff --git a/packages/accounts-ui/login_buttons.less b/packages/accounts-ui/login_buttons.less index 35d58d2898..f9fe8fcc55 100644 --- a/packages/accounts-ui/login_buttons.less +++ b/packages/accounts-ui/login_buttons.less @@ -32,43 +32,6 @@ -webkit-box-shadow: @arguments; // For Android } -////////// Display: Inline-block - -.display-inline-block () { - display: inline-block; - - // IE 7 hacks (disabled) - //*display: inline; - //*zoom: 1; -} - -////////// Gradients - -.vertical-gradient (@topColor: #fff, @bottomColor: #000) { - // Fallback in absence of gradients - background-color: mix(@topColor, @bottomColor, 60%); - // FF 3.6+ - background-image: -moz-linear-gradient(top, @topColor, @bottomColor); - // Safari 4+, Chrome 2+ - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@topColor), to(@bottomColor)); - // Safari 5.1+, Chrome 10+ - background-image: -webkit-linear-gradient(top, @topColor, @bottomColor); - // Opera 11.10 - background-image: -o-linear-gradient(top, @topColor, @bottomColor); - // Standard, IE10 - background-image: linear-gradient(to bottom, @topColor, @bottomColor); - background-repeat: repeat-x; - // IE9 and down - // XXX This gradient hack causes gradients to overflow the rounded corners - // in IE9. We make the same call as Bootstrap here: keep the rounded - // corners and withhold the gradients. - // filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@topColor),argb(@bottomColor))); -} - -.reset-ie-gradient () { - filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); -} - ////////// Unselectable .unselectable () { @@ -86,9 +49,19 @@ //////////////////// LOGIN BUTTONS @login-buttons-accounts-dialog-width: 198px; +@login-buttons-color: #596595; +@login-buttons-color-border: darken(@login-buttons-color, 10%); +@login-buttons-color-active: lighten(@login-buttons-color, 10%); +@login-buttons-color-active-border: darken(@login-buttons-color-active, 10%); + +@login-buttons-config-color: darken(#f53, 10%); +@login-buttons-config-color-border: darken(@login-buttons-config-color, 10%); +@login-buttons-config-color-active: lighten(@login-buttons-config-color, 10%); +@login-buttons-config-color-active-border: darken(@login-buttons-config-color-active, 10%); + #login-buttons { - .display-inline-block(); + display: inline-block; margin-right: 0.2px; // Fixes display on IE8: http://www.compsoft.co.uk/Blog/2009/11/inline-block-not-quite-inline-blocking.html // This seems to keep the height of the line from @@ -107,6 +80,7 @@ display: inline-block; } } + .login-display-name { display: inline-block; padding-right: 2px; @@ -133,26 +107,25 @@ text-align: center; color: #fff; - text-shadow: 0px -1px 1px rgba(0, 0, 0, 0.5); - @topColor: #a5acc9; - @bottomColor: darken(@topColor, 25%); + background: @login-buttons-color; + border: 1px solid @login-buttons-color-border; - .vertical-gradient(@topColor, @bottomColor); border-radius: 4px; - border: 1px solid mix(@bottomColor, #000, 30%); - .box-shadow(0 1px 3px rgba(0,0,0,0.5)); + &:hover { + background: @login-buttons-color-active; + } &:active { - .box-shadow(none); - .vertical-gradient(mix(@bottomColor, @topColor, 30%), - mix(@bottomColor, #000, 80%)); + background: @login-buttons-color-active; + .box-shadow(0 2px 3px 0 rgba(0, 0, 0, 0.2) inset); } &.login-button-disabled, &.login-button-disabled:active { color: #ddd; + background: #aaa; + border: 1px solid lighten(#aaa, 10%); .box-shadow(none); - background: #999; } } @@ -173,6 +146,8 @@ line-height: inherit; color: inherit; font: inherit; + + font-family: 'Helvetica Neue', Helvetica, Arial, default; } .accounts-dialog .login-button { @@ -187,12 +162,15 @@ } .login-display-name { margin-right: 4px; } + .configure-button { + background: @login-buttons-config-color; + border-color: @login-buttons-config-color-border; - .vertical-gradient(#f53, darken(#f53, 15%)); - .box-shadow(0 1px 3px rgba(0,0,0,0.5)); - - &:active { background: #b10; .box-shadow(0 1px 3px rgba(0,0,0,0.5) inset); } + &:active, &:hover { + background: @login-buttons-config-color-active; + border-color: @login-buttons-config-color-active-border; + } } .login-image { @@ -254,11 +232,9 @@ @meteor-accounts-dialog-border-width: 1px; .accounts-dialog { - border: @meteor-accounts-dialog-border-width solid #777; + border: @meteor-accounts-dialog-border-width solid #ccc; z-index: 1000; background: white; - - .box-shadow(0 3px 6px 1px rgba(0, 0, 0, 0.3)); border-radius: 4px; padding: 8px 12px; @@ -266,6 +242,8 @@ width: @login-buttons-accounts-dialog-width; + .box-shadow(0 0 3px 0 rgba(0, 0, 0, 0.2)); + // Labels and links inherit app's font with this line commented out: //font-family: 'Helvetica Neue', Helvetica, Arial, default; font-size: 16px; @@ -276,11 +254,16 @@ // the "Close" link, which we want to have the same line-height // as the "Sign in" link. & > * { line-height: 1.6; } - & > .login-close-text { line-height: inherit; } + & > .login-close-text { + line-height: inherit; + font-size: inherit; + font-family: inherit; + } label, .title { - font-weight: bold; font-size: 80%; + margin-top: 7px; + margin-bottom: -2px; } input { // Be pixel-accurate in IE 8+ regardless of our borders and @@ -302,7 +285,7 @@ } .login-button-form-submit { margin-top: 8px; } - .message { font-size: 80%; margin-top: 2px; line-height: 1.3; } + .message { font-size: 80%; margin-top: 8px; line-height: 1.3; } .error-message { color: red; } .info-message { color: green; } .additional-link { font-size: 75%; } @@ -418,7 +401,7 @@ #login-buttons, .accounts-dialog { input[type=text], input[type=email], input[type=password] { padding: 4px; - border: 1px solid #999; + border: 1px solid #aaa; border-radius: 3px; line-height: 1; } From 1c7e222c638b3ac94522b5ddeceb3f2f685693f5 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Tue, 21 Jan 2014 14:02:23 -0800 Subject: [PATCH 18/67] Refactor matchingDocument into separate method on Matcher's prototype, cache it. --- packages/minimongo/selector_modifier.js | 80 +++++++++++++++---------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/packages/minimongo/selector_modifier.js b/packages/minimongo/selector_modifier.js index 447375239c..2732cfa8e6 100644 --- a/packages/minimongo/selector_modifier.js +++ b/packages/minimongo/selector_modifier.js @@ -81,22 +81,51 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { if (expectedScalarIsObject) return false; + // See if we can apply the modifier on the ideally matching object. If it + // still matches the selector, then the modifier could have turned the real + // object in the database into something matching. + var matchingDocument = EJSON.clone(self.matchingDocument()); - // A helper to ensure object has only certain keys - var onlyContainsKeys = function (obj, keys) { - return _.all(obj, function (v, k) { - return _.contains(keys, k); - }); - }; + // The selector is too complex, anything can happen. + if (matchingDocument === null) + return true; - // convert a selector into an object matching the selector - // { 'a.b': { ans: 42 }, 'foo.bar': null, 'foo.baz': "something" } - // => { a: { b: { ans: 42 } }, foo: { bar: null, baz: "something" } } + try { + LocalCollection._modify(matchingDocument, modifier); + } catch (e) { + // Couldn't set a property on a field which is a scalar or null in the + // selector. + // Example: + // real document: { 'a.b': 3 } + // selector: { 'a': 12 } + // converted selector (ideal document): { 'a': 12 } + // modifier: { $set: { 'a.b': 4 } } + // We don't know what real document was like but from the error raised by + // $set on a scalar field we can reason that the structure of real document + // is completely different. + if (e.name === "MinimongoError" && e.setPropertyError) + return false; + throw e; + } + + return self.documentMatches(matchingDocument).result; +}; + +// Returns an object that would match the selector if possible or null if the +// selector is too complex for us to analyze +// { 'a.b': { ans: 42 }, 'foo.bar': null, 'foo.baz': "something" } +// => { a: { b: { ans: 42 } }, foo: { bar: null, baz: "something" } } +Minimongo.Matcher.prototype.matchingDocument = function () { + var self = this; + + // check if it was computed before + if (self._matchingDocument !== undefined) + return self._matchingDocument; // If the analysis of this selector is too hard for our implementation // fallback to "YES" var fallback = false; - var doc = pathsToTree(self._getPaths(), + self._matchingDocument = pathsToTree(self._getPaths(), function (path) { var valueSelector = self._selector[path]; if (isOperatorObject(valueSelector)) { @@ -144,27 +173,9 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { _.identity /*conflict resolution is no resolution*/); if (fallback) - return true; + self._matchingDocument = null; - try { - LocalCollection._modify(doc, modifier); - } catch (e) { - // Couldn't set a property on a field which is a scalar or null in the - // selector. - // Example: - // real document: { 'a.b': 3 } - // selector: { 'a': 12 } - // converted selector (ideal document): { 'a': 12 } - // modifier: { $set: { 'a.b': 4 } } - // We don't know what real document was like but from the error raised by - // $set on a scalar field we can reason that the structure of real document - // is completely different. - if (e.name === "MinimongoError" && e.setPropertyError) - return false; - throw e; - } - - return self.documentMatches(doc).result; + return self._matchingDocument; }; var getPaths = function (sel) { @@ -181,7 +192,14 @@ var getPaths = function (sel) { }).flatten().uniq().value(); }; -function pathHasNumericKeys (path) { +// A helper to ensure object has only certain keys +var onlyContainsKeys = function (obj, keys) { + return _.all(obj, function (v, k) { + return _.contains(keys, k); + }); +}; + +var pathHasNumericKeys = function (path) { return _.any(path.split('.'), isNumericKey); } From 16b23ecb3862ff4b8bbe3e0f8bc42cbad7923493 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 21 Jan 2014 18:11:53 -0800 Subject: [PATCH 19/67] Close proxy/server websockets on client errors Previously, certain errors on the client/proxy socket would result in the proxy keeping the websocket to the server open forever rather than closing it. For example, disconnecting the client from the internet without a graceful shutdown. This could easily be reproduced with any app using `facts`, eg https://github.com/tarangp/test-observers. Run the app, connect locally with one browser, connect from a second computer, observe sessions going to 2, disconnect the second computer. With this commit, sessions will go back to 1 in about a minute. Without it, it never will. This particular fix is not very compelling, since it uses undocumented features of the stream interface. I will file an issue with the node-http-proxy project tonight asking how we're supposed to do this. This addresses #1769. I'll close that once we have a more compelling fix, and once the similar bug is fixed in the proxies used in the `meteor deploy` server and in Galaxy. --- tools/run.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tools/run.js b/tools/run.js index 8cc0b34922..c3cd6c6276 100644 --- a/tools/run.js +++ b/tools/run.js @@ -154,6 +154,41 @@ var startProxy = function (outerPort, innerPort, callback) { // requests server.on('upgrade', function(req, socket, head) { var proxyIt = function () { + socket.on('error', function (e) { + // The http-proxy:outgoing:ws:error handler below only manages to detect + // errors between the proxy and the server. We apparently need to put in + // our own error handler to react to errors on the client/proxy + // socket. What we want to do in this error handler is destroy the + // proxy/server socket, but http-proxy doesn't actually ever hand that + // socket (which it calls `proxySocket`) to us! + // + // The good news is, `socket` is currently piped into + // `proxySocket`. (The reverse used to be true, but as soon as this + // error event fired, the pipe logic undid the reverse pipe.) So all we + // need to do to end `proxySocket` is to make `socket` emit an 'end' + // event. + // + // You might think that the right way to do that would be to call + // `socket.end()`. But `end()` is a method on writable sockets, and that + // call just means "close the half of the TCP socket leading from the + // proxy to the client", which certainly should not be echoed to the + // proxy/server connection! + // + // You might also think that `socket.destroy()` would cause the read + // half (client->proxy) of `socket` to be closed (which would then be + // piped to `proxySocket`, which is what we want), but for some reason + // it doesn't actually seem to! + // + // The best way we could find to make `socket` emit 'end' is + // `unshift(null)`. This doesn't seem great, since `unshift` is + // documented (well, commented in the source) as something you should + // only call with data you just pulled from `read()`. But it does fix + // the issue for now. + // + // XXX file an issue with http-proxy and add a link here + socket.unshift(null); + socket.destroy(); + }); proxy.ws(req, socket, head, { target: 'http://127.0.0.1:' + innerPort}); }; if (Status.listening) { From 4c3c1389eff6fa57e6f8e1eca6789c5e3a396c76 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Tue, 21 Jan 2014 17:29:53 -0800 Subject: [PATCH 20/67] Some additional facts about oplog-driver --- .../mongo-livedata/oplog_observe_driver.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/mongo-livedata/oplog_observe_driver.js b/packages/mongo-livedata/oplog_observe_driver.js index fc0d47c012..5e787cbc77 100644 --- a/packages/mongo-livedata/oplog_observe_driver.js +++ b/packages/mongo-livedata/oplog_observe_driver.js @@ -29,6 +29,7 @@ OplogObserveDriver = function (options) { "mongo-livedata", "observe-drivers-oplog", 1); self._phase = PHASE.QUERYING; + self._registerPhaseChange("querying"); self._published = new LocalCollection._IdMap; var selector = self._cursorDescription.selector; @@ -156,6 +157,7 @@ _.extend(OplogObserveDriver.prototype, { _fetchModifiedDocuments: function () { var self = this; self._phase = PHASE.FETCHING; + self._registerPhaseChange("fetching"); while (!self._stopped && !self._needToFetch.empty()) { if (self._phase !== PHASE.FETCHING) throw new Error("phase in fetchModifiedDocuments: " + self._phase); @@ -204,6 +206,7 @@ _.extend(OplogObserveDriver.prototype, { _beSteady: function () { var self = this; self._phase = PHASE.STEADY; + self._registerPhaseChange("steady"); var writes = self._writesToCommitWhenWeReachSteady; self._writesToCommitWhenWeReachSteady = []; self._multiplexer.onFlush(function () { @@ -313,6 +316,7 @@ _.extend(OplogObserveDriver.prototype, { self._currentlyFetching = null; ++self._fetchGeneration; // ignore any in-flight fetches self._phase = PHASE.QUERYING; + self._registerPhaseChange("querying"); // Defer so that we don't block. Meteor.defer(function () { @@ -453,6 +457,20 @@ _.extend(OplogObserveDriver.prototype, { Package.facts && Package.facts.Facts.incrementServerFact( "mongo-livedata", "observe-drivers-oplog", -1); + }, + + _registerPhaseChange: function (phase) { + var self = this; + var now = new Date; + + if (self._lastPhase) { + var timeDiff = now - self._lastPhaseStartTime; + Package.facts && Package.facts.Facts.incrementServerFact( + "mongo-livedata", "time-spent-in-" + self._lastPhase + "-phase", timeDiff); + } + + self._lastPhase = phase; + self._lastPhaseStartTime = now; } }); From 1be7e5a900642683b5fd3da94247c8650253640e Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Wed, 22 Jan 2014 14:17:19 -0800 Subject: [PATCH 21/67] Initialize Matcher._matchingDocument in the constructor. To declare it explicitly to humans and JITs. --- packages/minimongo/selector.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/minimongo/selector.js b/packages/minimongo/selector.js index 277194f502..e083ec5544 100644 --- a/packages/minimongo/selector.js +++ b/packages/minimongo/selector.js @@ -32,6 +32,9 @@ Minimongo.Matcher = function (selector) { // one or more of '$gt', '$gte', '$lt', '$lte', '$ne', '$in', '$nin' used with // scalars as operands. self._isSimple = true; + // Set to a dummy document which always matches this Matcher. Or set to null + // if such document is too hard to find. + self._matchingDocument = undefined; // A clone of the original selector. Used by canBecomeTrueByModifier. self._selector = null; self._docMatcher = self._compileSelector(selector); From 9971e5b298fc85911af2db5fc63b25e037fdc7b3 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Wed, 22 Jan 2014 15:29:13 -0800 Subject: [PATCH 22/67] Glasser's comments - use startsWith for strings rather than x.indexOf(y) && x[y.length] === '.' - more comments with examples - consistent naming isSimple => simpleRange, simpleInequality, etc --- packages/minimongo/selector.js | 10 +++++----- packages/minimongo/selector_modifier.js | 10 +++++++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/minimongo/selector.js b/packages/minimongo/selector.js index e083ec5544..87abd1ca27 100644 --- a/packages/minimongo/selector.js +++ b/packages/minimongo/selector.js @@ -239,14 +239,14 @@ var operatorBranchedMatcher = function (valueSelector, matcher, isRoot) { var operatorMatchers = []; _.each(valueSelector, function (operand, operator) { // XXX we should actually implement $eq, which is new in 2.6 - var supportedRange = _.contains(['$lt', '$lte', '$gt', '$gte'], operator) && + var simpleRange = _.contains(['$lt', '$lte', '$gt', '$gte'], operator) && _.isNumber(operand); - var supportedInequality = operator === '$ne' && !_.isObject(operand); - var supportedInclusion = _.contains(['$in', '$nin'], operator) && + var simpleInequality = operator === '$ne' && !_.isObject(operand); + var simpleInclusion = _.contains(['$in', '$nin'], operator) && _.isArray(operand) && !_.any(operand, _.isObject); - if (! (operator === '$eq' || supportedRange || - supportedInclusion || supportedInequality)) { + if (! (operator === '$eq' || simpleRange || + simpleInclusion || simpleInequality)) { matcher._isSimple = false; } diff --git a/packages/minimongo/selector_modifier.js b/packages/minimongo/selector_modifier.js index 2732cfa8e6..d7b913d841 100644 --- a/packages/minimongo/selector_modifier.js +++ b/packages/minimongo/selector_modifier.js @@ -70,11 +70,13 @@ Minimongo.Matcher.prototype.canBecomeTrueByModifier = function (modifier) { // check if there is a $set or $unset that indicates something is an // object rather than a scalar in the actual object where we saw $-operator // NOTE: it is correct since we allow only scalars in $-operators + // Example: for selector {'a.b': {$gt: 5}} the modifier {'a.b.c':7} would + // definitely set the result to false as 'a.b' appears to be an object. var expectedScalarIsObject = _.any(self._selector, function (sel, path) { if (! isOperatorObject(sel)) return false; return _.any(modifierPaths, function (modifierPath) { - return !modifierPath.indexOf(path) && modifierPath[path.length] === '.'; + return startsWith(modifierPath, path + '.'); }); }); @@ -203,3 +205,9 @@ var pathHasNumericKeys = function (path) { return _.any(path.split('.'), isNumericKey); } +// XXX from Underscore.String (http://epeli.github.com/underscore.string/) +var startsWith = function(str, starts) { + return str.length >= starts.length && + str.substring(0, starts.length) === starts; +}; + From 8b28f1fab629b0950dfec412dd57033be78687d0 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Wed, 22 Jan 2014 17:45:24 -0800 Subject: [PATCH 23/67] Change phase and register a fact in the same place --- .../mongo-livedata/oplog_observe_driver.js | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/mongo-livedata/oplog_observe_driver.js b/packages/mongo-livedata/oplog_observe_driver.js index 5e787cbc77..f2a0b07116 100644 --- a/packages/mongo-livedata/oplog_observe_driver.js +++ b/packages/mongo-livedata/oplog_observe_driver.js @@ -2,9 +2,9 @@ var Fiber = Npm.require('fibers'); var Future = Npm.require('fibers/future'); var PHASE = { - QUERYING: 1, - FETCHING: 2, - STEADY: 3 + QUERYING: "QUERYING", + FETCHING: "FETCHING", + STEADY: "STEADY" }; // OplogObserveDriver is an alternative to PollingObserveDriver which follows @@ -28,8 +28,7 @@ OplogObserveDriver = function (options) { Package.facts && Package.facts.Facts.incrementServerFact( "mongo-livedata", "observe-drivers-oplog", 1); - self._phase = PHASE.QUERYING; - self._registerPhaseChange("querying"); + self._registerPhaseChange(PHASE.QUERYING); self._published = new LocalCollection._IdMap; var selector = self._cursorDescription.selector; @@ -156,8 +155,7 @@ _.extend(OplogObserveDriver.prototype, { }, _fetchModifiedDocuments: function () { var self = this; - self._phase = PHASE.FETCHING; - self._registerPhaseChange("fetching"); + self._registerPhaseChange(PHASE.FETCHING); while (!self._stopped && !self._needToFetch.empty()) { if (self._phase !== PHASE.FETCHING) throw new Error("phase in fetchModifiedDocuments: " + self._phase); @@ -205,8 +203,7 @@ _.extend(OplogObserveDriver.prototype, { }, _beSteady: function () { var self = this; - self._phase = PHASE.STEADY; - self._registerPhaseChange("steady"); + self._registerPhaseChange(PHASE.STEADY); var writes = self._writesToCommitWhenWeReachSteady; self._writesToCommitWhenWeReachSteady = []; self._multiplexer.onFlush(function () { @@ -315,8 +312,7 @@ _.extend(OplogObserveDriver.prototype, { self._needToFetch = new LocalCollection._IdMap; self._currentlyFetching = null; ++self._fetchGeneration; // ignore any in-flight fetches - self._phase = PHASE.QUERYING; - self._registerPhaseChange("querying"); + self._registerPhaseChange(PHASE.QUERYING); // Defer so that we don't block. Meteor.defer(function () { @@ -463,14 +459,14 @@ _.extend(OplogObserveDriver.prototype, { var self = this; var now = new Date; - if (self._lastPhase) { - var timeDiff = now - self._lastPhaseStartTime; + if (self._phase) { + var timeDiff = now - self._phaseStartTime; Package.facts && Package.facts.Facts.incrementServerFact( - "mongo-livedata", "time-spent-in-" + self._lastPhase + "-phase", timeDiff); + "mongo-livedata", "time-spent-in-" + self._phase + "-phase", timeDiff); } - self._lastPhase = phase; - self._lastPhaseStartTime = now; + self._phase = phase; + self._phaseStartTime = now; } }); From 4f34ac0d6e90064a226751339b4cb253ffbbf750 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 22 Jan 2014 12:35:38 -0800 Subject: [PATCH 24/67] Various dev bundle upgrades * http-proxy: 1.0.1 (from a pre-release fork of 1.0) * Node: 0.10.24 (from 0.10.22) * semver: 2.2.1 (from 2.1.0) * request: 2.33.0 (from 2.27.0) * fstream: 0.1.25 (from 0.1.24) * tar: 0.1.19 (from 0.1.18) * eachline: 2.4.0 (from 2.3.3) * source-map: 0.1.31 (from 0.1.30) * source-map-support: 0.2.5 (from 0.2.3) * mongo: 2.4.9 (from 2.4.8) * openssl in mongo: 1.0.1f (from 1.0.1e) Have not yet updated the various minimum Node version requirements. --- scripts/generate-dev-bundle.sh | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index ec2c7af7ac..c312ed4ee5 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -76,7 +76,7 @@ cd node # When upgrading node versions, also update the values of MIN_NODE_VERSION at # the top of tools/meteor.js and tools/server/boot.js, and the text in # docs/client/concepts.html and the README in tools/bundler.js. -git checkout v0.10.22 +git checkout v0.10.24 ./configure --prefix="$DIR" make -j4 @@ -98,23 +98,19 @@ which npm cd "$DIR/lib/node_modules" npm install optimist@0.6.0 -npm install semver@2.1.0 -npm install request@2.27.0 +npm install semver@2.2.1 +npm install request@2.33.0 npm install keypress@0.2.1 npm install underscore@1.5.2 -npm install fstream@0.1.24 -npm install tar@0.1.18 +npm install fstream@0.1.25 +npm install tar@0.1.19 npm install kexec@0.1.1 npm install shell-quote@0.0.1 # now at 1.3.3, which adds plenty of options to parse but doesn't change quote -npm install eachline@2.3.3 -npm install source-map@0.1.30 -npm install source-map-support@0.2.3 +npm install eachline@2.4.0 +npm install source-map@0.1.31 +npm install source-map-support@0.2.5 npm install bcrypt@0.7.7 - -# Using the unreleased "caronte" branch rewrite of http-proxy (which will become -# 1.0.0), plus this PR: -# https://github.com/nodejitsu/node-http-proxy/pull/495 -npm install https://github.com/meteor/node-http-proxy/tarball/f17186f781c6f00b359d25df424ad74922cd1977 +npm install http-proxy@1.0.1 # Using the unreleased 1.1 branch. We can probably switch to a built NPM version # when it gets released. @@ -141,7 +137,7 @@ cd ../.. # particular version of openssl on the host system. cd "$DIR/build" -OPENSSL="openssl-1.0.1e" +OPENSSL="openssl-1.0.1f" OPENSSL_URL="http://www.openssl.org/source/$OPENSSL.tar.gz" wget $OPENSSL_URL || curl -O $OPENSSL_URL tar xzf $OPENSSL.tar.gz @@ -160,7 +156,7 @@ make install # click 'changelog' under the current version, then 'release notes' in # the upper right. cd "$DIR/build" -MONGO_VERSION="2.4.8" +MONGO_VERSION="2.4.9" # We use Meteor fork since we added some changes to the building script. # Our patches allow us to link most of the libraries statically. From 8354fe403b92328688a7c6cb735ca64737590a2e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 22 Jan 2014 16:12:33 -0800 Subject: [PATCH 25/67] Bump dev bundle version. --- meteor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor b/meteor index 54260b22d9..7d854adce0 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/bin/bash -BUNDLE_VERSION=0.3.26 +BUNDLE_VERSION=0.3.27 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. From a7d10d650d856b1a1c18e9b5cddb72d42679e418 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 23 Jan 2014 12:15:29 -0800 Subject: [PATCH 26/67] Update Node to 0.10.25. Remove bug workaround. --- docs/client/concepts.html | 9 +- packages/meteor/node-issue-6506-workaround.js | 120 ------------------ packages/meteor/package.js | 3 - scripts/admin/bless-release.js | 4 +- scripts/generate-dev-bundle.sh | 2 +- tools/bundler.js | 4 +- tools/meteor.js | 2 +- tools/server/boot.js | 2 +- 8 files changed, 10 insertions(+), 136 deletions(-) delete mode 100644 packages/meteor/node-issue-6506-workaround.js diff --git a/docs/client/concepts.html b/docs/client/concepts.html index 0475ddb6e5..a0e7d1e914 100644 --- a/docs/client/concepts.html +++ b/docs/client/concepts.html @@ -826,10 +826,11 @@ To get started, run This command will generate a fully-contained Node.js application in the form of a tarball. To run this application, you need to provide Node.js 0.10 and a MongoDB server. (The current release of Meteor has been tested with Node -0.10.22, and is recommended for use with 0.10.22 through 0.10.24 only.) You can -then run the application by invoking node, specifying the HTTP port for the -application to listen on, and the MongoDB endpoint. If you don't already have a -MongoDB server, we can recommend our friends at [MongoHQ](http://mongohq.com). +0.10.25; older versions contain a serious bug that can cause production servers +to stall.) You can then run the application by invoking node, specifying the +HTTP port for the application to listen on, and the MongoDB endpoint. If +you don't already have a MongoDB server, we can recommend our friends at +[MongoHQ](http://mongohq.com). $ PORT=3000 MONGO_URL=mongodb://localhost:27017/myapp node bundle/main.js diff --git a/packages/meteor/node-issue-6506-workaround.js b/packages/meteor/node-issue-6506-workaround.js deleted file mode 100644 index bd41b7c384..0000000000 --- a/packages/meteor/node-issue-6506-workaround.js +++ /dev/null @@ -1,120 +0,0 @@ -// Temporary workaround for https://github.com/joyent/node/issues/6506 -// Our fix involves replicating a bunch of functions in order to change -// a single line. - -var PATCH_VERSIONS = ['v0.10.22', 'v0.10.23', 'v0.10.24']; - -if (!_.contains(PATCH_VERSIONS, process.version)) { - if (!process.env.DISABLE_WEBSOCKETS) { - console.error("This version of Meteor contains a patch for a bug in Node v0.10."); - console.error("The patch is against only versions 0.10.22 through 0.10.24."); - console.error("You are using version " + process.version + " instead, so we cannot apply the patch."); - console.error("To mitigate the most common effect of the bug, websockets will be disabled."); - console.error("To enable websockets, use Node v0.10.22 through v0.10.24, or upgrade to a later version of Meteor (if available)."); - process.env.DISABLE_WEBSOCKETS = 't'; - } -} else { - // This code is all copied from Node's lib/_stream_writable.js, git tag - // v0.10.22, with one change (see "BUGFIX"). - var Writable = Npm.require('_stream_writable'); - var Duplex = Npm.require('_stream_duplex'); - - Writable.prototype.write = function(chunk, encoding, cb) { - var state = this._writableState; - var ret = false; - - if (typeof encoding === 'function') { - cb = encoding; - encoding = null; - } - - if (Buffer.isBuffer(chunk)) - encoding = 'buffer'; - else if (!encoding) - encoding = state.defaultEncoding; - - if (typeof cb !== 'function') - cb = function() {}; - - if (state.ended) - writeAfterEnd(this, state, cb); - else if (validChunk(this, state, chunk, cb)) - ret = writeOrBuffer(this, state, chunk, encoding, cb); - - return ret; - }; - - // Duplex doesn't directly inherit from Writable: it copies over this function - // explicitly. So we have to do it too. - Duplex.prototype.write = Writable.prototype.write; - - function writeAfterEnd(stream, state, cb) { - var er = new Error('write after end'); - // TODO: defer error events consistently everywhere, not just the cb - stream.emit('error', er); - process.nextTick(function() { - cb(er); - }); - } - - function validChunk(stream, state, chunk, cb) { - var valid = true; - if (!Buffer.isBuffer(chunk) && - 'string' !== typeof chunk && - chunk !== null && - chunk !== undefined && - !state.objectMode) { - var er = new TypeError('Invalid non-string/buffer chunk'); - stream.emit('error', er); - process.nextTick(function() { - cb(er); - }); - valid = false; - } - return valid; - } - - function writeOrBuffer(stream, state, chunk, encoding, cb) { - chunk = decodeChunk(state, chunk, encoding); - if (Buffer.isBuffer(chunk)) - encoding = 'buffer'; - var len = state.objectMode ? 1 : chunk.length; - - state.length += len; - - var ret = state.length < state.highWaterMark; - // This next line is the BUGFIX: - state.needDrain = state.needDrain || !ret; - - if (state.writing) - state.buffer.push(new WriteReq(chunk, encoding, cb)); - else - doWrite(stream, state, len, chunk, encoding, cb); - - return ret; - } - - function decodeChunk(state, chunk, encoding) { - if (!state.objectMode && - state.decodeStrings !== false && - typeof chunk === 'string') { - chunk = new Buffer(chunk, encoding); - } - return chunk; - } - - function WriteReq(chunk, encoding, cb) { - this.chunk = chunk; - this.encoding = encoding; - this.callback = cb; - } - - function doWrite(stream, state, len, chunk, encoding, cb) { - state.writelen = len; - state.writecb = cb; - state.writing = true; - state.sync = true; - stream._write(chunk, encoding, state.onwrite); - state.sync = false; - } -} diff --git a/packages/meteor/package.js b/packages/meteor/package.js index 5e16f7f9d6..439d80e6f2 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -15,9 +15,6 @@ Package.on_use(function (api) { api.export('Meteor'); - // Workaround for https://github.com/joyent/node/issues/6506 - api.add_files('node-issue-6506-workaround.js', 'server'); - api.add_files('client_environment.js', 'client'); api.add_files('server_environment.js', 'server'); api.add_files('helpers.js', ['client', 'server']); diff --git a/scripts/admin/bless-release.js b/scripts/admin/bless-release.js index 9fdc16e557..9a9b2d3247 100644 --- a/scripts/admin/bless-release.js +++ b/scripts/admin/bless-release.js @@ -78,9 +78,7 @@ var checkReleaseDoesNotExistYet = function (release) { // Writes out a JSON file, pretty-printed and read-only. var writeJSONFile = function (path, jsonObject) { - fs.writeFileSync(path, JSON.stringify(jsonObject, null, 2)); - // In 0.10 we can pass a mode to writeFileSync, but not yet... - fs.chmodSync(path, 0444); + fs.writeFileSync(path, JSON.stringify(jsonObject, null, 2), {mode: 0444}); }; var readJSONFile = function (path) { return JSON.parse(fs.readFileSync(path)); diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index c312ed4ee5..3fa8e2f9cd 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -76,7 +76,7 @@ cd node # When upgrading node versions, also update the values of MIN_NODE_VERSION at # the top of tools/meteor.js and tools/server/boot.js, and the text in # docs/client/concepts.html and the README in tools/bundler.js. -git checkout v0.10.24 +git checkout v0.10.25 ./configure --prefix="$DIR" make -j4 diff --git a/tools/bundler.js b/tools/bundler.js index 9d93ddd75f..8049026e98 100644 --- a/tools/bundler.js +++ b/tools/bundler.js @@ -1473,9 +1473,7 @@ var writeSiteArchive = function (targets, outputPath, options) { builder.write('README', { data: new Buffer( "This is a Meteor application bundle. It has only one dependency:\n" + -"Node.js 0.10 (with the 'fibers' package). The current release of Meteor\n" + -"has been tested with Node 0.10.22 and works best with 0.10.22 through\n" + -"0.10.24. To run the application:\n" + +"Node.js 0.10.25 or newer, plus the 'fibers' module. To run the application:\n" + "\n" + " $ rm -r programs/server/node_modules/fibers\n" + " $ npm install fibers@1.0.1\n" + diff --git a/tools/meteor.js b/tools/meteor.js index 053ec38e4d..c8b61fe29a 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -24,7 +24,7 @@ Fiber(function () { var Future = require('fibers/future'); // This code is duplicated in tools/server/boot.js. - var MIN_NODE_VERSION = 'v0.10.22'; + var MIN_NODE_VERSION = 'v0.10.25'; if (require('semver').lt(process.version, MIN_NODE_VERSION)) { process.stderr.write( 'Meteor requires Node ' + MIN_NODE_VERSION + ' or later.\n'); diff --git a/tools/server/boot.js b/tools/server/boot.js index dff293fa89..51f1a06ac4 100644 --- a/tools/server/boot.js +++ b/tools/server/boot.js @@ -6,7 +6,7 @@ var _ = require('underscore'); var sourcemap_support = require('source-map-support'); // This code is duplicated in tools/meteor.js. -var MIN_NODE_VERSION = 'v0.10.22'; +var MIN_NODE_VERSION = 'v0.10.25'; if (require('semver').lt(process.version, MIN_NODE_VERSION)) { process.stderr.write( From d4e0cff766ac0309df44f33ed0b90d6d7fb7a98e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 23 Jan 2014 12:23:16 -0800 Subject: [PATCH 27/67] Use a fork of http-proxy with two PRs --- scripts/generate-dev-bundle.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 3fa8e2f9cd..3edad979c6 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -110,7 +110,11 @@ npm install eachline@2.4.0 npm install source-map@0.1.31 npm install source-map-support@0.2.5 npm install bcrypt@0.7.7 -npm install http-proxy@1.0.1 + +# Based on 1.0.1; includes our PRs +# https://github.com/nodejitsu/node-http-proxy/pull/561 and +# https://github.com/nodejitsu/node-http-proxy/pull/560 +npm install https://github.com/meteor/node-http-proxy/tarball/d8ea687936d6bed0f3e99849695cab2dcdccd6f4 # Using the unreleased 1.1 branch. We can probably switch to a built NPM version # when it gets released. From 90b7489e7f0c2d126ba33f5a5b8da6c3a866731d Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 23 Jan 2014 13:40:02 -0800 Subject: [PATCH 28/67] Add $USE_TEST_DEV_BUNDLE_SERVER --- meteor | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/meteor b/meteor index 7d854adce0..40904b7bea 100755 --- a/meteor +++ b/meteor @@ -51,15 +51,25 @@ function install_dev_bundle { rm -rf "$BUNDLE_TMPDIR" mkdir "$BUNDLE_TMPDIR" + # fyi: URL duplicated in packages/dev-bundle-fetcher/dev-bundle + DEV_BUNDLE_URL_ROOT="https://d3sqy0vbqsdhku.cloudfront.net/" + # If you set $USE_TEST_DEV_BUNDLE_SERVER then we will download + # dev bundles copied by copy-dev-bundle-from-jenkins.sh without --prod. + # It still only does this if the version number has changed + # (setting it won't cause it to automatically delete a prod dev bundle). + if [ -n "$USE_TEST_DEV_BUNDLE_SERVER" ] ; then + DEV_BUNDLE_URL_ROOT="https://com.meteor.static.s3.amazonaws.com/test/" + fi + if [ -f "$SCRIPT_DIR/$TARBALL" ] ; then echo "Skipping download and installing kit from $SCRIPT_DIR/$TARBALL" >&2 tar -xzf "$SCRIPT_DIR/$TARBALL" -C "$BUNDLE_TMPDIR" elif [ -n "$SAVE_DEV_BUNDLE_TARBALL" ] ; then # URL duplicated in tools/server/target.sh.in - curl -# "https://d3sqy0vbqsdhku.cloudfront.net/$TARBALL" >"$SCRIPT_DIR/$TARBALL" + curl -# "$DEV_BUNDLE_URL_ROOT$TARBALL" >"$SCRIPT_DIR/$TARBALL" tar -xzf "$SCRIPT_DIR/$TARBALL" -C "$BUNDLE_TMPDIR" else - curl -# "https://d3sqy0vbqsdhku.cloudfront.net/$TARBALL" | tar -xzf - -C "$BUNDLE_TMPDIR" + curl -# "$DEV_BUNDLE_URL_ROOT$TARBALL" | tar -xzf - -C "$BUNDLE_TMPDIR" fi test -x "${BUNDLE_TMPDIR}/bin/node" # bomb out if it didn't work, eg no net From 97ec2d39481a962f168b7e788f0ae4643c436102 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 23 Jan 2014 14:40:21 -0800 Subject: [PATCH 29/67] Update to newer http-proxy API Take out workaround that's been incorporated into the branch we're using --- tools/run.js | 66 +++++++++++++++------------------------------------- 1 file changed, 19 insertions(+), 47 deletions(-) diff --git a/tools/run.js b/tools/run.js index c3cd6c6276..423b60e4ba 100644 --- a/tools/run.js +++ b/tools/run.js @@ -110,14 +110,15 @@ var startProxy = function (outerPort, innerPort, callback) { callback = callback || function () {}; var http = require('http'); - // Note: this uses the pre-release 1.0.0 API. + var net = require('net'); var httpProxy = require('http-proxy'); var proxy = httpProxy.createProxyServer({ // agent is required to handle keep-alive, and http-proxy 1.0 is a little // buggy without it: https://github.com/nodejitsu/node-http-proxy/pull/488 agent: new http.Agent({maxSockets: 100}), - xfwd: true + xfwd: true, + target: 'http://127.0.0.1:' + innerPort }); var server = http.createServer(function (req, res) { @@ -140,7 +141,7 @@ var startProxy = function (outerPort, innerPort, callback) { return; } var proxyIt = function () { - proxy.web(req, res, {target: 'http://127.0.0.1:' + innerPort}); + proxy.web(req, res); }; if (Status.listening) { // server is listening. things are hunky dory! @@ -154,42 +155,7 @@ var startProxy = function (outerPort, innerPort, callback) { // requests server.on('upgrade', function(req, socket, head) { var proxyIt = function () { - socket.on('error', function (e) { - // The http-proxy:outgoing:ws:error handler below only manages to detect - // errors between the proxy and the server. We apparently need to put in - // our own error handler to react to errors on the client/proxy - // socket. What we want to do in this error handler is destroy the - // proxy/server socket, but http-proxy doesn't actually ever hand that - // socket (which it calls `proxySocket`) to us! - // - // The good news is, `socket` is currently piped into - // `proxySocket`. (The reverse used to be true, but as soon as this - // error event fired, the pipe logic undid the reverse pipe.) So all we - // need to do to end `proxySocket` is to make `socket` emit an 'end' - // event. - // - // You might think that the right way to do that would be to call - // `socket.end()`. But `end()` is a method on writable sockets, and that - // call just means "close the half of the TCP socket leading from the - // proxy to the client", which certainly should not be echoed to the - // proxy/server connection! - // - // You might also think that `socket.destroy()` would cause the read - // half (client->proxy) of `socket` to be closed (which would then be - // piped to `proxySocket`, which is what we want), but for some reason - // it doesn't actually seem to! - // - // The best way we could find to make `socket` emit 'end' is - // `unshift(null)`. This doesn't seem great, since `unshift` is - // documented (well, commented in the source) as something you should - // only call with data you just pulled from `read()`. But it does fix - // the issue for now. - // - // XXX file an issue with http-proxy and add a link here - socket.unshift(null); - socket.destroy(); - }); - proxy.ws(req, socket, head, { target: 'http://127.0.0.1:' + innerPort}); + proxy.ws(req, socket, head); }; if (Status.listening) { // server is listening. things are hunky dory! @@ -218,14 +184,20 @@ var startProxy = function (outerPort, innerPort, callback) { // don't crash if the app doesn't respond. instead return an error // immediately. This shouldn't happen much since we try to not send requests // if the app is down. - proxy.ee.on('http-proxy:outgoing:web:error', function (err, req, res) { - res.writeHead(503, { - 'Content-Type': 'text/plain' - }); - res.end('Unexpected error.'); - }); - proxy.ee.on('http-proxy:outgoing:ws:error', function (err, req, socket) { - socket.end(); + // + // Currently, this error is emitted if the proxy->server connection has an + // error (whether in HTTP or websocket proxying). It is not emitted if the + // client->proxy connection has an error, though this may change; see + // discussion at https://github.com/nodejitsu/node-http-proxy/pull/488 + proxy.on('error', function (err, req, resOrSocket) { + if (resOrSocket instanceof http.ServerResponse) { + resOrSocket.writeHead(503, { + 'Content-Type': 'text/plain' + }); + resOrSocket.end('Unexpected error.'); + } else if (resOrSocket instanceof net.Socket) { + resOrSocket.end(); + } }); server.listen(outerPort, callback); From 8eca012c32802abb7f877809c7ae83ad6e0eca41 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 23 Jan 2014 21:28:21 -0800 Subject: [PATCH 30/67] Minor optimizations to new _IdMaps --- packages/livedata/livedata_connection.js | 4 +--- packages/minimongo/diff.js | 12 +++++------- packages/minimongo/minimongo.js | 8 ++++---- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index 1d8dbd19d1..21ca620a36 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -1062,9 +1062,7 @@ _.extend(Connection.prototype, { if (!_.has(self._serverDocuments, collection)) return null; var serverDocsForCollection = self._serverDocuments[collection]; - if (!serverDocsForCollection.has(id)) - return null; - return serverDocsForCollection.get(id); + return serverDocsForCollection.get(id) || null; }, _process_added: function (msg, updates) { diff --git a/packages/minimongo/diff.js b/packages/minimongo/diff.js index 6fe8d4f411..1ccfa282a3 100644 --- a/packages/minimongo/diff.js +++ b/packages/minimongo/diff.js @@ -20,13 +20,11 @@ LocalCollection._diffQueryUnorderedChanges = function (oldResults, newResults, } newResults.forEach(function (newDoc, id) { - if (oldResults.has(id)) { - if (observer.changed) { - var oldDoc = oldResults.get(id); - if (!EJSON.equals(oldDoc, newDoc)) { - observer.changed( - id, LocalCollection._makeChangedFields(newDoc, oldDoc)); - } + var oldDoc = oldResults.get(id); + if (oldDoc) { + if (observer.changed && !EJSON.equals(oldDoc, newDoc)) { + observer.changed( + id, LocalCollection._makeChangedFields(newDoc, oldDoc)); } } else if (observer.added) { var fields = EJSON.clone(newDoc); diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index f8beb60353..01e6cef137 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -412,8 +412,8 @@ LocalCollection.Cursor.prototype._getRawObjects = function (ordered, if (self.skip) return results; - if (self.collection._docs.has(self._selectorId)) { - var selectedDoc = self.collection._docs.get(self._selectorId); + var selectedDoc = self.collection._docs.get(self._selectorId); + if (selectedDoc) { if (ordered) results.push(selectedDoc); else @@ -556,8 +556,8 @@ LocalCollection.prototype.remove = function (selector, callback) { _.each(specificIds, function (id) { // We still have to run matcher, in case it's something like // {_id: "X", a: 42} - if (self._docs.has(id) - && matcher.documentMatches(self._docs.get(id)).result) + var doc = self._docs.get(id); + if (doc && matcher.documentMatches(doc).result) remove.push(id); }); } else { From d868325b8339c64b56b236f903f7a4f5d4c7a24b Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 23 Jan 2014 21:45:57 -0800 Subject: [PATCH 31/67] Don't yield in oplog entry handler --- .../mongo-livedata/oplog_observe_driver.js | 119 +++++++++--------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/packages/mongo-livedata/oplog_observe_driver.js b/packages/mongo-livedata/oplog_observe_driver.js index f2a0b07116..9f23ab84e3 100644 --- a/packages/mongo-livedata/oplog_observe_driver.js +++ b/packages/mongo-livedata/oplog_observe_driver.js @@ -51,19 +51,21 @@ OplogObserveDriver = function (options) { forEachTrigger(self._cursorDescription, function (trigger) { self._stopHandles.push(self._mongoHandle._oplogHandle.onOplogEntry( trigger, function (notification) { - var op = notification.op; - if (notification.dropCollection) { - // Note: this call is not allowed to block on anything (especially on - // waiting for oplog entries to catch up) because that will block - // onOplogEntry! - self._needToPollQuery(); - } else { - // All other operators should be handled depending on phase - if (self._phase === PHASE.QUERYING) - self._handleOplogEntryQuerying(op); - else - self._handleOplogEntrySteadyOrFetching(op); - } + Meteor._noYieldsAllowed(function () { + var op = notification.op; + if (notification.dropCollection) { + // Note: this call is not allowed to block on anything (especially + // on waiting for oplog entries to catch up) because that will block + // onOplogEntry! + self._needToPollQuery(); + } else { + // All other operators should be handled depending on phase + if (self._phase === PHASE.QUERYING) + self._handleOplogEntryQuerying(op); + else + self._handleOplogEntrySteadyOrFetching(op); + } + }); } )); }); @@ -156,50 +158,55 @@ _.extend(OplogObserveDriver.prototype, { _fetchModifiedDocuments: function () { var self = this; self._registerPhaseChange(PHASE.FETCHING); - while (!self._stopped && !self._needToFetch.empty()) { - if (self._phase !== PHASE.FETCHING) - throw new Error("phase in fetchModifiedDocuments: " + self._phase); + // Defer, because nothing called from the oplog entry handler may yield, but + // fetch() yields. + Meteor.defer(function () { + while (!self._stopped && !self._needToFetch.empty()) { + if (self._phase !== PHASE.FETCHING) + throw new Error("phase in fetchModifiedDocuments: " + self._phase); - self._currentlyFetching = self._needToFetch; - var thisGeneration = ++self._fetchGeneration; - self._needToFetch = new LocalCollection._IdMap; - var waiting = 0; - var anyError = null; - var fut = new Future; - // This loop is safe, because _currentlyFetching will not be updated - // during this loop (in fact, it is never mutated). - self._currentlyFetching.forEach(function (cacheKey, id) { - waiting++; - self._mongoHandle._docFetcher.fetch( - self._cursorDescription.collectionName, id, cacheKey, - function (err, doc) { - if (err) { - if (!anyError) - anyError = err; - } else if (!self._stopped && self._phase === PHASE.FETCHING - && self._fetchGeneration === thisGeneration) { - // We re-check the generation in case we've had an explicit - // _pollQuery call which should effectively cancel this round of - // fetches. (_pollQuery increments the generation.) - self._handleDoc(id, doc); - } - waiting--; - // Because fetch() never calls its callback synchronously, this is - // safe (ie, we won't call fut.return() before the forEach is done). - if (waiting === 0) - fut.return(); - }); - }); - fut.wait(); - // XXX do this even if we've switched to PHASE.QUERYING? - if (anyError) - throw anyError; - // Exit now if we've had a _pollQuery call. - if (self._phase === PHASE.QUERYING) - return; - self._currentlyFetching = null; - } - self._beSteady(); + self._currentlyFetching = self._needToFetch; + var thisGeneration = ++self._fetchGeneration; + self._needToFetch = new LocalCollection._IdMap; + var waiting = 0; + var anyError = null; + var fut = new Future; + // This loop is safe, because _currentlyFetching will not be updated + // during this loop (in fact, it is never mutated). + self._currentlyFetching.forEach(function (cacheKey, id) { + waiting++; + self._mongoHandle._docFetcher.fetch( + self._cursorDescription.collectionName, id, cacheKey, + function (err, doc) { + if (err) { + if (!anyError) + anyError = err; + } else if (!self._stopped && self._phase === PHASE.FETCHING + && self._fetchGeneration === thisGeneration) { + // We re-check the generation in case we've had an explicit + // _pollQuery call which should effectively cancel this round of + // fetches. (_pollQuery increments the generation.) + self._handleDoc(id, doc); + } + waiting--; + // Because fetch() never calls its callback synchronously, this is + // safe (ie, we won't call fut.return() before the forEach is + // done). + if (waiting === 0) + fut.return(); + }); + }); + fut.wait(); + // XXX do this even if we've switched to PHASE.QUERYING? + if (anyError) + throw anyError; + // Exit now if we've had a _pollQuery call. + if (self._phase === PHASE.QUERYING) + return; + self._currentlyFetching = null; + } + self._beSteady(); + }); }, _beSteady: function () { var self = this; From fca2966676e45e126d36534c9541c58527fa3efc Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 23 Jan 2014 23:13:42 -0800 Subject: [PATCH 32/67] Simplify crossbar by making it synchronous All existing listener callbacks were already calling complete() synchronously, so this should not be a functional change. This allows us to also eliminate the callback from crossbar.fire(). This in turn allows us to eliminate the `proxy_write` in Meteor.refresh. The only purpose of that write was to keep the current write fence open until fire's async callback got called; now that fire works synchronously, it's not necessary! (This relies on the fact that write fences never get armed while they are the current write fence, which now has an assertion.) --- packages/livedata/crossbar.js | 38 +++++++------------ packages/livedata/server_convenience.js | 11 +----- packages/livedata/writefence.js | 2 + .../mongo-livedata/oplog_observe_driver.js | 7 +--- packages/mongo-livedata/oplog_tailing.js | 12 ++---- .../mongo-livedata/polling_observe_driver.js | 3 +- 6 files changed, 22 insertions(+), 51 deletions(-) diff --git a/packages/livedata/crossbar.js b/packages/livedata/crossbar.js index 1600b4379b..631fefe6f6 100644 --- a/packages/livedata/crossbar.js +++ b/packages/livedata/crossbar.js @@ -1,6 +1,4 @@ // A "crossbar" is a class that provides structured notification registration. -// The "invalidation crossbar" is a specific instance used by the DDP server to -// implement write fence notifications. DDPServer._Crossbar = function (options) { var self = this; @@ -17,10 +15,8 @@ DDPServer._Crossbar = function (options) { _.extend(DDPServer._Crossbar.prototype, { // Listen for notification that match 'trigger'. A notification // matches if it has the key-value pairs in trigger as a - // subset. When a notification matches, call 'callback', passing two - // arguments, the actual notification and an acknowledgement - // function. The callback should call the acknowledgement function - // when it is finished processing the notification. + // subset. When a notification matches, call 'callback', passing + // the actual notification. // // Returns a listen handle, which is an object with a method // stop(). Call stop() to stop listening. @@ -48,14 +44,13 @@ _.extend(DDPServer._Crossbar.prototype, { // Fire the provided 'notification' (an object whose attribute // values are all JSON-compatibile) -- inform all matching listeners - // (registered with listen()), and once they have all acknowledged - // the notification, call onComplete with no arguments. + // (registered with listen()). // // If fire() is called inside a write fence, then each of the // listener callbacks will be called inside the write fence as well. // // The listeners may be invoked in parallel, rather than serially. - fire: function (notification, onComplete) { + fire: function (notification) { var self = this; var callbacks = []; // XXX consider refactoring to "index" on "collection" @@ -64,22 +59,10 @@ _.extend(DDPServer._Crossbar.prototype, { callbacks.push(l.callback); }); - if (onComplete) - onComplete = Meteor.bindEnvironment( - onComplete, - "Crossbar fire complete callback"); - - var outstanding = callbacks.length; - if (!outstanding) - onComplete && onComplete(); - else { - _.each(callbacks, function (c) { - c(notification, function () { - if (--outstanding === 0) - onComplete && onComplete(); - }); - }); - } + _.each(callbacks, function (c) { + // XXX don't call c if it's been stopped already + c(notification); + }); }, // A notification matches a trigger if all keys that exist in both are equal. @@ -107,6 +90,11 @@ _.extend(DDPServer._Crossbar.prototype, { } }); +// The "invalidation crossbar" is a specific instance used by the DDP server to +// implement write fence notifications. Listener callbacks on this crossbar +// should call beginWrite on the current write fence before they return, if they +// want to delay the write fence from firing (ie, the DDP method-data-updated +// message from being sent). DDPServer._InvalidationCrossbar = new DDPServer._Crossbar({ factName: "invalidation-crossbar-listeners" }); diff --git a/packages/livedata/server_convenience.js b/packages/livedata/server_convenience.js index ac20e68fae..1e482b95f0 100644 --- a/packages/livedata/server_convenience.js +++ b/packages/livedata/server_convenience.js @@ -10,16 +10,7 @@ if (Package.webapp) { Meteor.server = new Server; Meteor.refresh = function (notification) { - var fence = DDPServer._CurrentWriteFence.get(); - if (fence) { - // Block the write fence until all of the invalidations have - // landed. - var proxy_write = fence.beginWrite(); - } - DDPServer._InvalidationCrossbar.fire(notification, function () { - if (proxy_write) - proxy_write.committed(); - }); + DDPServer._InvalidationCrossbar.fire(notification); }; // Proxy the public methods of Meteor.server so they can diff --git a/packages/livedata/writefence.js b/packages/livedata/writefence.js index 3315cbfe3c..359fe32a99 100644 --- a/packages/livedata/writefence.js +++ b/packages/livedata/writefence.js @@ -53,6 +53,8 @@ _.extend(DDPServer._WriteFence.prototype, { // uncommitted writes, it will activate. arm: function () { var self = this; + if (self === DDPServer._CurrentWriteFence.get()) + throw Error("Can't arm the current fence"); self.armed = true; self._maybeFire(); }, diff --git a/packages/mongo-livedata/oplog_observe_driver.js b/packages/mongo-livedata/oplog_observe_driver.js index 9f23ab84e3..363ac59277 100644 --- a/packages/mongo-livedata/oplog_observe_driver.js +++ b/packages/mongo-livedata/oplog_observe_driver.js @@ -72,13 +72,11 @@ OplogObserveDriver = function (options) { // XXX ordering w.r.t. everything else? self._stopHandles.push(listenAll( - self._cursorDescription, function (notification, complete) { + self._cursorDescription, function (notification) { // If we're not in a write fence, we don't have to do anything. var fence = DDPServer._CurrentWriteFence.get(); - if (!fence) { - complete(); + if (!fence) return; - } var write = fence.beginWrite(); // This write cannot complete until we've caught up to "this point" in the // oplog, and then made it back to the steady state. @@ -98,7 +96,6 @@ OplogObserveDriver = function (options) { self._writesToCommitWhenWeReachSteady.push(write); } }); - complete(); } )); diff --git a/packages/mongo-livedata/oplog_tailing.js b/packages/mongo-livedata/oplog_tailing.js index a04fb0eee9..b8c044ce3e 100644 --- a/packages/mongo-livedata/oplog_tailing.js +++ b/packages/mongo-livedata/oplog_tailing.js @@ -74,13 +74,9 @@ _.extend(OplogHandle.prototype, { self._readyFuture.wait(); var originalCallback = callback; - callback = Meteor.bindEnvironment(function (notification, onComplete) { + callback = Meteor.bindEnvironment(function (notification) { // XXX can we avoid this clone by making oplog.js careful? - try { - originalCallback(EJSON.clone(notification)); - } finally { - onComplete(); - } + originalCallback(EJSON.clone(notification)); }, function (err) { Meteor._debug("Error in oplog callback", err.stack); }); @@ -208,9 +204,7 @@ _.extend(OplogHandle.prototype, { trigger.id = idForOp(doc); } - var f = new Future; - self._crossbar.fire(trigger, f.resolver()); - f.wait(); + self._crossbar.fire(trigger); // Now that we've processed this operation, process pending sequencers. if (!doc.ts) diff --git a/packages/mongo-livedata/polling_observe_driver.js b/packages/mongo-livedata/polling_observe_driver.js index aa17477c33..c1c700d0c1 100644 --- a/packages/mongo-livedata/polling_observe_driver.js +++ b/packages/mongo-livedata/polling_observe_driver.js @@ -34,7 +34,7 @@ PollingObserveDriver = function (options) { self._taskQueue = new Meteor._SynchronousQueue(); var listenersHandle = listenAll( - self._cursorDescription, function (notification, complete) { + self._cursorDescription, function (notification) { // When someone does a transaction that might affect us, schedule a poll // of the database. If that transaction happens inside of a write fence, // block the fence until we've polled and notified observers. @@ -46,7 +46,6 @@ PollingObserveDriver = function (options) { // lead to us calling it unnecessarily in 50ms). if (self._pollsScheduledButNotStarted === 0) self._ensurePollIsScheduled(); - complete(); } ); self._stopCallbacks.push(function () { listenersHandle.stop(); }); From d30bb79498c2167f4b2b69590fe6dee51dc594da Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 23 Jan 2014 23:23:30 -0800 Subject: [PATCH 33/67] Ensure crossbar callbacks not called after stop Hopefully fixes #1767. --- packages/livedata/crossbar.js | 16 ++++++++++------ packages/livedata/crossbar_tests.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/packages/livedata/crossbar.js b/packages/livedata/crossbar.js index 631fefe6f6..4c49fd259e 100644 --- a/packages/livedata/crossbar.js +++ b/packages/livedata/crossbar.js @@ -52,16 +52,20 @@ _.extend(DDPServer._Crossbar.prototype, { // The listeners may be invoked in parallel, rather than serially. fire: function (notification) { var self = this; - var callbacks = []; + // Listener callbacks can yield, so we need to first find all the ones that + // match in a single iteration over self.listeners (which can't be mutated + // during this iteration), and then invoke the matching callbacks, checking + // before each call to ensure they are still in self.listeners. + var matchingCallbacks = {}; // XXX consider refactoring to "index" on "collection" - _.each(self.listeners, function (l) { + _.each(self.listeners, function (l, id) { if (self._matches(notification, l.trigger)) - callbacks.push(l.callback); + matchingCallbacks[id] = l.callback; }); - _.each(callbacks, function (c) { - // XXX don't call c if it's been stopped already - c(notification); + _.each(matchingCallbacks, function (c, id) { + if (_.has(self.listeners, id)) + c(notification); }); }, diff --git a/packages/livedata/crossbar_tests.js b/packages/livedata/crossbar_tests.js index 2eefa6bdf5..cf42351798 100644 --- a/packages/livedata/crossbar_tests.js +++ b/packages/livedata/crossbar_tests.js @@ -18,4 +18,32 @@ Tinytest.add('livedata - crossbar', function (test) { test.isFalse(crossbar._matches({collection: "C", id: "X"}, {collection: "C", id: "Y"})); + + // Test that stopped listens definitely don't fire. + var calledFirst = false; + var calledSecond = false; + var trigger = {collection: "C"}; + var secondHandle; + crossbar.listen(trigger, function (notification) { + // This test assumes that listeners will be called in the order + // registered. It's not wrong for the crossbar to do something different, + // but the test won't be valid in that case, so make it fail so we notice. + calledFirst = true; + if (calledSecond) { + test.fail({ + type: "test_assumption_failed", + message: "test assumed that listeners would be called in the order registered" + }); + } else { + secondHandle.stop(); + } + }); + secondHandle = crossbar.listen(trigger, function (notification) { + // This should not get invoked, because it should be stopped by the other + // listener! + calledSecond = true; + }); + crossbar.fire(trigger); + test.isTrue(calledFirst); + test.isFalse(calledSecond); }); From 291c2ecad72fb15c555186061f4987b8616a44c8 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 27 Jan 2014 09:52:50 -0800 Subject: [PATCH 34/67] Upgrade kexec, drop shell-quote We only used shell-quote to workaround kexec's lack of a feature added in this version. --- meteor | 2 +- scripts/generate-dev-bundle.sh | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/meteor b/meteor index 40904b7bea..95a9676e78 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/bin/bash -BUNDLE_VERSION=0.3.27 +BUNDLE_VERSION=0.3.28 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. diff --git a/scripts/generate-dev-bundle.sh b/scripts/generate-dev-bundle.sh index 3edad979c6..bbee7da6e6 100755 --- a/scripts/generate-dev-bundle.sh +++ b/scripts/generate-dev-bundle.sh @@ -104,8 +104,7 @@ npm install keypress@0.2.1 npm install underscore@1.5.2 npm install fstream@0.1.25 npm install tar@0.1.19 -npm install kexec@0.1.1 -npm install shell-quote@0.0.1 # now at 1.3.3, which adds plenty of options to parse but doesn't change quote +npm install kexec@0.2.0 npm install eachline@2.4.0 npm install source-map@0.1.31 npm install source-map-support@0.2.5 From 8f4963ecd202635610272ffdf8bff2a1b6ea0806 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 27 Jan 2014 13:52:54 -0800 Subject: [PATCH 35/67] Use new exec --- LICENSE.txt | 1 - tools/meteor.js | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 420c8c773e..0f25743305 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -285,7 +285,6 @@ optimist: https://github.com/substack/node-optimist mkdirp: https://github.com/substack/node-mkdirp wordwrap: https://github.com/substack/node-wordwrap archy: https://github.com/substack/node-archy -shell-quote: https://github.com/substack/node-shell-quote deep-equal: https://github.com/substack/node-deep-equal editor: https://github.com/substack/node-editor minimist: https://github.com/substack/minimist diff --git a/tools/meteor.js b/tools/meteor.js index c8b61fe29a..4457941d63 100644 --- a/tools/meteor.js +++ b/tools/meteor.js @@ -1371,10 +1371,8 @@ Fiber(function () { if (extraArgs) newArgv.push.apply(newArgv, extraArgs); - // Now shell quote this (because kexec wants to use /bin/sh -c) and execvp. - // XXX fork kexec and make it take an array instead of using shell - var quotedArgv = require('shell-quote').quote(newArgv); - require('kexec')(quotedArgv); + // Now exec; we're not coming back. + require('kexec')(newArgv[0], newArgv); }; // Implements --version. Note that we only print to stdout and exit 0 if From 6300a723818b107464a9223b3f8b5e27bb626504 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Sat, 7 Dec 2013 12:39:32 -0800 Subject: [PATCH 36/67] add missing semicolon just happened to notice it --- packages/coffeescript/plugin/compile-coffeescript.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/coffeescript/plugin/compile-coffeescript.js b/packages/coffeescript/plugin/compile-coffeescript.js index dd712b634f..d7b835e071 100644 --- a/packages/coffeescript/plugin/compile-coffeescript.js +++ b/packages/coffeescript/plugin/compile-coffeescript.js @@ -156,9 +156,8 @@ var handler = function (compileStep, isLiterate) { var literateHandler = function (compileStep) { return handler(compileStep, true); -} +}; Plugin.registerSourceHandler("coffee", handler); Plugin.registerSourceHandler("litcoffee", literateHandler); Plugin.registerSourceHandler("coffee.md", literateHandler); - From 30374854f08924480f9ece16f0ab788d4ec9cc64 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Mon, 26 Aug 2013 22:38:06 -0700 Subject: [PATCH 37/67] jquery -> v1.10.2 --- packages/jquery/jquery.js | 9369 +++++++++++++++++++------------------ 1 file changed, 4859 insertions(+), 4510 deletions(-) diff --git a/packages/jquery/jquery.js b/packages/jquery/jquery.js index d4f3bb38cd..c5c648255c 100644 --- a/packages/jquery/jquery.js +++ b/packages/jquery/jquery.js @@ -1,28 +1,38 @@ /*! - * jQuery JavaScript Library v1.8.2 + * jQuery JavaScript Library v1.10.2 * http://jquery.com/ * * Includes Sizzle.js * http://sizzlejs.com/ * - * Copyright 2012 jQuery Foundation and other contributors + * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors * Released under the MIT license * http://jquery.org/license * - * Date: Thu Sep 20 2012 21:13:05 GMT-0400 (Eastern Daylight Time) + * Date: 2013-07-03T13:48Z */ (function( window, undefined ) { -var - // A central reference to the root jQuery(document) - rootjQuery, +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +//"use strict"; +var // The deferred used on DOM ready readyList, + // A central reference to the root jQuery(document) + rootjQuery, + + // Support: IE<10 + // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined` + core_strundefined = typeof undefined, + // Use the correct document accordingly with window argument (sandbox) - document = window.document, location = window.location, - navigator = window.navigator, + document = window.document, + docElem = document.documentElement, // Map over jQuery in case of overwrite _jQuery = window.jQuery, @@ -30,13 +40,22 @@ var // Map over the $ in case of overwrite _$ = window.$, + // [[Class]] -> type pairs + class2type = {}, + + // List of deleted data cache ids, so we can reuse them + core_deletedIds = [], + + core_version = "1.10.2", + // Save a reference to some core methods - core_push = Array.prototype.push, - core_slice = Array.prototype.slice, - core_indexOf = Array.prototype.indexOf, - core_toString = Object.prototype.toString, - core_hasOwn = Object.prototype.hasOwnProperty, - core_trim = String.prototype.trim, + core_concat = core_deletedIds.concat, + core_push = core_deletedIds.push, + core_slice = core_deletedIds.slice, + core_indexOf = core_deletedIds.indexOf, + core_toString = class2type.toString, + core_hasOwn = class2type.hasOwnProperty, + core_trim = core_version.trim, // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -45,18 +64,18 @@ var }, // Used for matching numbers - core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source, + core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, - // Used for detecting and trimming whitespace - core_rnotwhite = /\S/, - core_rspace = /\s+/, + // Used for splitting on whitespace + core_rnotwhite = /\S+/g, // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, // A simple way to check for HTML strings // Prioritize #id over to avoid XSS via location.hash (#9521) - rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, // Match a standalone tag rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, @@ -65,7 +84,7 @@ var rvalidchars = /^[\],:{}\s]*$/, rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, - rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, // Matches dashed string for camelizing rmsPrefix = /^-ms-/, @@ -73,42 +92,43 @@ var // Used by jQuery.camelCase as callback to replace() fcamelCase = function( all, letter ) { - return ( letter + "" ).toUpperCase(); + return letter.toUpperCase(); }, - // The ready event handler and self cleanup method - DOMContentLoaded = function() { - if ( document.addEventListener ) { - document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - jQuery.ready(); - } else if ( document.readyState === "complete" ) { - // we're here because readyState === "complete" in oldIE - // which is good enough for us to call the dom ready! - document.detachEvent( "onreadystatechange", DOMContentLoaded ); + // The ready event handler + completed = function( event ) { + + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); jQuery.ready(); } }, + // Clean-up method for dom ready events + detach = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); - // [[Class]] -> type pairs - class2type = {}; + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } + }; jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: core_version, + constructor: jQuery, init: function( selector, context, rootjQuery ) { - var match, elem, ret, doc; + var match, elem; - // Handle $(""), $(null), $(undefined), $(false) + // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } - // Handle $(DOMElement) - if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - } - // Handle HTML strings if ( typeof selector === "string" ) { if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { @@ -125,15 +145,29 @@ jQuery.fn = jQuery.prototype = { // HANDLE: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; - doc = ( context && context.nodeType ? context.ownerDocument || context : document ); // scripts is true for back-compat - selector = jQuery.parseHTML( match[1], doc, true ); + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { - this.attr.call( selector, context, true ); + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } } - return jQuery.merge( this, selector ); + return this; // HANDLE: $(#id) } else { @@ -168,6 +202,12 @@ jQuery.fn = jQuery.prototype = { return this.constructor( context ).find( selector ); } + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) { @@ -185,17 +225,9 @@ jQuery.fn = jQuery.prototype = { // Start with an empty selector selector: "", - // The current version of jQuery being used - jquery: "1.8.2", - // The default length of a jQuery object is 0 length: 0, - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - toArray: function() { return core_slice.call( this ); }, @@ -214,22 +246,15 @@ jQuery.fn = jQuery.prototype = { // Take an array of elements and push it onto the stack // (returning the new matched element set) - pushStack: function( elems, name, selector ) { + pushStack: function( elems ) { // Build a new jQuery matched element set var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; - ret.context = this.context; - if ( name === "find" ) { - ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; - } else if ( name ) { - ret.selector = this.selector + "." + name + "(" + selector + ")"; - } - // Return the newly-formed element set return ret; }, @@ -248,11 +273,8 @@ jQuery.fn = jQuery.prototype = { return this; }, - eq: function( i ) { - i = +i; - return i === -1 ? - this.slice( i ) : - this.slice( i, i + 1 ); + slice: function() { + return this.pushStack( core_slice.apply( this, arguments ) ); }, first: function() { @@ -263,9 +285,10 @@ jQuery.fn = jQuery.prototype = { return this.eq( -1 ); }, - slice: function() { - return this.pushStack( core_slice.apply( this, arguments ), - "slice", core_slice.call(arguments).join(",") ); + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); }, map: function( callback ) { @@ -289,7 +312,7 @@ jQuery.fn = jQuery.prototype = { jQuery.fn.init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, + var src, copyIsArray, copy, name, options, clone, target = arguments[0] || {}, i = 1, length = arguments.length, @@ -353,6 +376,10 @@ jQuery.extend = jQuery.fn.extend = function() { }; jQuery.extend({ + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), + noConflict: function( deep ) { if ( window.$ === jQuery ) { window.$ = _$; @@ -391,7 +418,7 @@ jQuery.extend({ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( !document.body ) { - return setTimeout( jQuery.ready, 1 ); + return setTimeout( jQuery.ready ); } // Remember that the DOM is ready @@ -423,6 +450,7 @@ jQuery.extend({ }, isWindow: function( obj ) { + /* jshint eqeqeq: false */ return obj != null && obj == obj.window; }, @@ -431,12 +459,17 @@ jQuery.extend({ }, type: function( obj ) { - return obj == null ? - String( obj ) : - class2type[ core_toString.call(obj) ] || "object"; + if ( obj == null ) { + return String( obj ); + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ core_toString.call(obj) ] || "object" : + typeof obj; }, isPlainObject: function( obj ) { + var key; + // Must be an Object. // Because of IE, we also have to check the presence of the constructor property. // Make sure that DOM nodes and window objects don't pass through, as well @@ -456,10 +489,16 @@ jQuery.extend({ return false; } + // Support: IE<9 + // Handle iteration over inherited properties before own properties. + if ( jQuery.support.ownLast ) { + for ( key in obj ) { + return core_hasOwn.call( obj, key ); + } + } + // 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 || core_hasOwn.call( obj, key ); @@ -479,50 +518,59 @@ jQuery.extend({ // data: string of html // context (optional): If specified, the fragment will be created in this context, defaults to document - // scripts (optional): If true, will include scripts passed in the html string - parseHTML: function( data, context, scripts ) { - var parsed; + // keepScripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, keepScripts ) { if ( !data || typeof data !== "string" ) { return null; } if ( typeof context === "boolean" ) { - scripts = context; - context = 0; + keepScripts = context; + context = false; } context = context || document; + var parsed = rsingleTag.exec( data ), + scripts = !keepScripts && []; + // Single tag - if ( (parsed = rsingleTag.exec( data )) ) { + if ( parsed ) { return [ context.createElement( parsed[1] ) ]; } - parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] ); - return jQuery.merge( [], - (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes ); + parsed = jQuery.buildFragment( [ data ], context, scripts ); + if ( scripts ) { + jQuery( scripts ).remove(); + } + return jQuery.merge( [], parsed.childNodes ); }, parseJSON: function( data ) { - if ( !data || typeof data !== "string") { - return null; - } - - // Make sure leading/trailing whitespace is removed (IE can't handle it) - data = jQuery.trim( data ); - // Attempt to parse using the native JSON parser first if ( window.JSON && window.JSON.parse ) { return window.JSON.parse( data ); } - // Make sure the incoming data is actual JSON - // Logic borrowed from http://json.org/json2.js - if ( rvalidchars.test( data.replace( rvalidescape, "@" ) - .replace( rvalidtokens, "]" ) - .replace( rvalidbraces, "")) ) { - - return ( new Function( "return " + data ) )(); - + if ( data === null ) { + return data; } + + if ( typeof data === "string" ) { + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + if ( data ) { + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + } + } + } + jQuery.error( "Invalid JSON: " + data ); }, @@ -556,7 +604,7 @@ jQuery.extend({ // Workarounds based on findings by Jim Driscoll // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context globalEval: function( data ) { - if ( data && core_rnotwhite.test( data ) ) { + if ( data && jQuery.trim( data ) ) { // We use execScript on Internet Explorer // We use an anonymous function so that context is window // rather than jQuery in Firefox @@ -578,21 +626,25 @@ jQuery.extend({ // args is for internal usage only each: function( obj, callback, args ) { - var name, + var value, i = 0, length = obj.length, - isObj = length === undefined || jQuery.isFunction( obj ); + isArray = isArraylike( obj ); if ( args ) { - if ( isObj ) { - for ( name in obj ) { - if ( callback.apply( obj[ name ], args ) === false ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { break; } } } else { - for ( ; i < length; ) { - if ( callback.apply( obj[ i++ ], args ) === false ) { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { break; } } @@ -600,15 +652,19 @@ jQuery.extend({ // A special, fast, case for the most common use of each } else { - if ( isObj ) { - for ( name in obj ) { - if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { break; } } } else { - for ( ; i < length; ) { - if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { break; } } @@ -635,18 +691,16 @@ jQuery.extend({ // results is for internal usage only makeArray: function( arr, results ) { - var type, - ret = results || []; + var ret = results || []; if ( arr != null ) { - // The window, strings (and functions) also have 'length' - // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 - type = jQuery.type( arr ); - - if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) { - core_push.call( ret, arr ); + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); } else { - jQuery.merge( ret, arr ); + core_push.call( ret, arr ); } } @@ -684,7 +738,6 @@ jQuery.extend({ for ( ; j < l; j++ ) { first[ i++ ] = second[ j ]; } - } else { while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; @@ -717,12 +770,11 @@ jQuery.extend({ // arg is for internal usage only map: function( elems, callback, arg ) { - var value, key, - ret = [], + var value, i = 0, length = elems.length, - // jquery objects are treated as arrays - isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + isArray = isArraylike( elems ), + ret = []; // Go through the array, translating each of the items to their if ( isArray ) { @@ -736,8 +788,8 @@ jQuery.extend({ // Go through every key on the object, } else { - for ( key in elems ) { - value = callback( elems[ key ], key, arg ); + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); if ( value != null ) { ret[ ret.length ] = value; @@ -746,7 +798,7 @@ jQuery.extend({ } // Flatten any nested arrays - return ret.concat.apply( [], ret ); + return core_concat.apply( [], ret ); }, // A global GUID counter for objects @@ -755,7 +807,7 @@ jQuery.extend({ // Bind a function to a context, optionally partially applying any // arguments. proxy: function( fn, context ) { - var tmp, args, proxy; + var args, proxy, tmp; if ( typeof context === "string" ) { tmp = fn[ context ]; @@ -772,7 +824,7 @@ jQuery.extend({ // Simulated bind args = core_slice.call( arguments, 2 ); proxy = function() { - return fn.apply( context, args.concat( core_slice.call( arguments ) ) ); + return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); }; // Set the guid of unique handler to the same of original handler, so it can be removed @@ -783,46 +835,46 @@ jQuery.extend({ // Multifunctional method to get and set values of a collection // The value/s can optionally be executed if it's a function - access: function( elems, fn, key, value, chainable, emptyGet, pass ) { - var exec, - bulk = key == null, - i = 0, - length = elems.length; + access: function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; // Sets many values - if ( key && typeof key === "object" ) { + if ( jQuery.type( key ) === "object" ) { + chainable = true; for ( i in key ) { - jQuery.access( elems, fn, i, key[i], 1, emptyGet, value ); + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); } - chainable = 1; // Sets one value } else if ( value !== undefined ) { - // Optionally, function values get executed if exec is true - exec = pass === undefined && jQuery.isFunction( value ); + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } if ( bulk ) { - // Bulk operations only iterate when executing function values - if ( exec ) { - exec = fn; - fn = function( elem, key, value ) { - return exec.call( jQuery( elem ), value ); - }; - - // Otherwise they run against the entire set - } else { + // Bulk operations run against the entire set + if ( raw ) { fn.call( elems, value ); fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; } } if ( fn ) { - for (; i < length; i++ ) { - fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); } } - - chainable = 1; } return chainable ? @@ -836,6 +888,29 @@ jQuery.extend({ now: function() { return ( new Date() ).getTime(); + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations. + // Note: this method belongs to the css module but it's needed here for the support module. + // If support gets modularized, this method should be moved back to the css module. + swap: function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; } }); @@ -849,23 +924,23 @@ jQuery.ready.promise = function( obj ) { // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready - setTimeout( jQuery.ready, 1 ); + setTimeout( jQuery.ready ); // Standards-based browsers support DOMContentLoaded } else if ( document.addEventListener ) { // Use the handy event callback - document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + document.addEventListener( "DOMContentLoaded", completed, false ); // A fallback to window.onload, that will always work - window.addEventListener( "load", jQuery.ready, false ); + window.addEventListener( "load", completed, false ); // If IE event model is used } else { // Ensure firing before onload, maybe late but safe also for iframes - document.attachEvent( "onreadystatechange", DOMContentLoaded ); + document.attachEvent( "onreadystatechange", completed ); // A fallback to window.onload, that will always work - window.attachEvent( "onload", jQuery.ready ); + window.attachEvent( "onload", completed ); // If IE and not a frame // continually check to see if the document is ready @@ -887,6 +962,9 @@ jQuery.ready.promise = function( obj ) { return setTimeout( doScrollCheck, 50 ); } + // detach all dom ready events + detach(); + // and execute any waiting functions jQuery.ready(); } @@ -898,19 +976,2015 @@ jQuery.ready.promise = function( obj ) { }; // Populate the class2type map -jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || type !== "function" && + ( length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj ); +} + // All jQuery objects should point back to these rootjQuery = jQuery(document); +/*! + * Sizzle CSS Selector Engine v1.10.2 + * http://sizzlejs.com/ + * + * Copyright 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2013-07-03 + */ +(function( window, undefined ) { + +var i, + support, + cachedruns, + Expr, + getText, + isXML, + compile, + outermostContext, + sortInput, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + hasDuplicate = false, + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + return 0; + }, + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rsibling = new RegExp( whitespace + "*[+~]" ), + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + // BMP codepoint + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( documentIsHTML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && context.parentNode || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key += " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Detect xml + * @param {Element|Object} elem An element or a document + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var doc = node ? node.ownerDocument || node : preferredDoc, + parent = doc.defaultView; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsHTML = !isXML( doc ); + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent.attachEvent && parent !== parent.top ) { + parent.attachEvent( "onbeforeunload", function() { + setDocument(); + }); + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if getElementsByClassName can be trusted + support.getElementsByClassName = assert(function( div ) { + div.innerHTML = "
"; + + // Support: Safari<4 + // Catch class over-caching + div.firstChild.className = "i"; + // Support: Opera<10 + // Catch gEBCN failure to find non-leading classes + return div.getElementsByClassName("i").length === 2; + }); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + + // Support: Opera 10-12/IE8 + // ^= $= *= and empty values + // Should not select anything + // Support: Windows 8 Native Apps + // The type attribute is restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "t", "" ); + + if ( div.querySelectorAll("[t^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = docElem.compareDocumentPosition ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b ); + + if ( compare ) { + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } + + // Not directly comparable, sort on existence of method + return a.compareDocumentPosition ? -1 : 1; + } : + function( a, b ) { + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Parentless nodes are either documents or disconnected + } else if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val === undefined ? + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null : + val; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + for ( ; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] && match[4] !== undefined ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( tokens = [] ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var data, cache, outerCache, + dirkey = dirruns + " " + doneName; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { + if ( (data = cache[1]) === true || data === cachedruns ) { + return data === true; + } + } else { + cache = outerCache[ dir ] = [ dirkey ]; + cache[1] = matcher( elem, context, xml ) || cachedruns; + if ( cache[1] === true ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + // A counter to specify which element is currently being matched + var matcherCachedRuns = 0, + bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, expandContext ) { + var elem, j, matcher, + setMatched = [], + matchedCount = 0, + i = "0", + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + // We must always have either seed elements or context + elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); + + if ( outermost ) { + outermostContext = context !== document && context; + cachedruns = matcherCachedRuns; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + for ( ; (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + cachedruns = ++matcherCachedRuns; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + } + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && context.parentNode || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) + ); + return results; +} + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome<14 +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + elem[ name ] === true ? name.toLowerCase() : null; + } + }); +} + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})( window ); // String to Object options format cache var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache function createOptions( options ) { var object = optionsCache[ options ] = {}; - jQuery.each( options.split( core_rspace ), function( _, flag ) { + jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { object[ flag ] = true; }); return object; @@ -946,18 +3020,18 @@ jQuery.Callbacks = function( options ) { ( optionsCache[ options ] || createOptions( options ) ) : jQuery.extend( {}, options ); - var // Last fire value (for non-forgettable lists) + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) memory, // Flag to know if list was already fired fired, - // Flag to know if list is currently firing - firing, - // First callback to fire (used internally by add and fireWith) - firingStart, // End of the loop when firing firingLength, // Index of currently firing callback (modified by remove if needed) firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, // Actual callback list list = [], // Stack of fire calls for repeatable lists @@ -999,8 +3073,10 @@ jQuery.Callbacks = function( options ) { (function add( args ) { jQuery.each( args, function( _, arg ) { var type = jQuery.type( arg ); - if ( type === "function" && ( !options.unique || !self.has( arg ) ) ) { - list.push( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } } else if ( arg && arg.length && type !== "string" ) { // Inspect recursively add( arg ); @@ -1041,13 +3117,15 @@ jQuery.Callbacks = function( options ) { } return this; }, - // Control if a given callback is in the list + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. has: function( fn ) { - return jQuery.inArray( fn, list ) > -1; + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); }, // Remove all callbacks from the list empty: function() { list = []; + firingLength = 0; return this; }, // Have the list do nothing anymore @@ -1073,9 +3151,9 @@ jQuery.Callbacks = function( options ) { }, // Call all callbacks with the given context and arguments fireWith: function( context, args ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; if ( firing ) { stack.push( args ); } else { @@ -1120,22 +3198,19 @@ jQuery.extend({ return jQuery.Deferred(function( newDefer ) { jQuery.each( tuples, function( i, tuple ) { var action = tuple[ 0 ], - fn = fns[ i ]; + fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; // deferred[ done | fail | progress ] for forwarding actions to newDefer - deferred[ tuple[1] ]( jQuery.isFunction( fn ) ? - function() { - var returned = fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .done( newDefer.resolve ) - .fail( newDefer.reject ) - .progress( newDefer.notify ); - } else { - newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); - } - } : - newDefer[ action ] - ); + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); }); fns = null; }).promise(); @@ -1169,8 +3244,11 @@ jQuery.extend({ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); } - // deferred[ resolve | reject | notify ] = list.fire - deferred[ tuple[0] ] = list.fire; + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; deferred[ tuple[0] + "With" ] = list.fireWith; }); @@ -1238,102 +3316,82 @@ jQuery.extend({ return deferred.promise(); } }); -jQuery.support = (function() { +jQuery.support = (function( support ) { - var support, - all, - a, - select, - opt, - input, - fragment, - eventName, - i, - isSupported, - clickFn, + var all, a, input, select, fragment, opt, eventName, isSupported, i, div = document.createElement("div"); - // Preliminary tests + // Setup div.setAttribute( "className", "t" ); div.innerHTML = "
a"; - all = div.getElementsByTagName("*"); + // Finish early in limited (non-browser) environments + all = div.getElementsByTagName("*") || []; a = div.getElementsByTagName("a")[ 0 ]; - a.style.cssText = "top:1px;float:left;opacity:.5"; - - // Can't get basic test support - if ( !all || !all.length ) { - return {}; + if ( !a || !a.style || !all.length ) { + return support; } - // First batch of supports tests + // First batch of tests select = document.createElement("select"); opt = select.appendChild( document.createElement("option") ); input = div.getElementsByTagName("input")[ 0 ]; - support = { - // IE strips leading whitespace when .innerHTML is used - leadingWhitespace: ( div.firstChild.nodeType === 3 ), + a.style.cssText = "top:1px;float:left;opacity:.5"; - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - tbody: !div.getElementsByTagName("tbody").length, + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + support.getSetAttribute = div.className !== "t"; - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - htmlSerialize: !!div.getElementsByTagName("link").length, + // IE strips leading whitespace when .innerHTML is used + support.leadingWhitespace = div.firstChild.nodeType === 3; - // Get the style information from getAttribute - // (IE uses .cssText instead) - style: /top/.test( a.getAttribute("style") ), + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + support.tbody = !div.getElementsByTagName("tbody").length; - // Make sure that URLs aren't manipulated - // (IE normalizes it by default) - hrefNormalized: ( a.getAttribute("href") === "/a" ), + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + support.htmlSerialize = !!div.getElementsByTagName("link").length; - // Make sure that element opacity exists - // (IE uses filter instead) - // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.5/.test( a.style.opacity ), + // Get the style information from getAttribute + // (IE uses .cssText instead) + support.style = /top/.test( a.getAttribute("style") ); - // Verify style float existence - // (IE uses styleFloat instead of cssFloat) - cssFloat: !!a.style.cssFloat, + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + support.hrefNormalized = a.getAttribute("href") === "/a"; - // Make sure that if no value is specified for a checkbox - // that it defaults to "on". - // (WebKit defaults to "" instead) - checkOn: ( input.value === "on" ), + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + support.opacity = /^0.5/.test( a.style.opacity ); - // Make sure that a selected-by-default option has a working selected property. - // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) - optSelected: opt.selected, + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + support.cssFloat = !!a.style.cssFloat; - // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) - getSetAttribute: div.className !== "t", + // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) + support.checkOn = !!input.value; - // Tests for enctype support on a form(#6743) - enctype: !!document.createElement("form").enctype, + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + support.optSelected = opt.selected; - // Makes sure cloning an html5 element does not cause problems - // Where outerHTML is undefined, this still works - html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + // Tests for enctype support on a form (#6743) + support.enctype = !!document.createElement("form").enctype; - // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode - boxModel: ( document.compatMode === "CSS1Compat" ), + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + support.html5Clone = document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>"; - // Will be defined later - submitBubbles: true, - changeBubbles: true, - focusinBubbles: false, - deleteExpando: true, - noCloneEvent: true, - inlineBlockNeedsLayout: false, - shrinkWrapBlocks: false, - reliableMarginRight: true, - boxSizingReliable: true, - pixelPosition: false - }; + // Will be defined later + support.inlineBlockNeedsLayout = false; + support.shrinkWrapBlocks = false; + support.pixelPosition = false; + support.deleteExpando = true; + support.noCloneEvent = true; + support.reliableMarginRight = true; + support.boxSizingReliable = true; // Make sure checked status is properly cloned input.checked = true; @@ -1344,76 +3402,71 @@ jQuery.support = (function() { select.disabled = true; support.optDisabled = !opt.disabled; - // Test to see if it's possible to delete an expando from an element - // Fails in Internet Explorer + // Support: IE<9 try { delete div.test; } catch( e ) { support.deleteExpando = false; } - if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { - div.attachEvent( "onclick", clickFn = function() { - // Cloning a node shouldn't copy over any - // bound event handlers (IE does this) - support.noCloneEvent = false; - }); - div.cloneNode( true ).fireEvent("onclick"); - div.detachEvent( "onclick", clickFn ); - } - - // Check if a radio maintains its value - // after being appended to the DOM + // Check if we can trust getAttribute("value") input = document.createElement("input"); + input.setAttribute( "value", "" ); + support.input = input.getAttribute( "value" ) === ""; + + // Check if an input maintains its value after becoming a radio input.value = "t"; input.setAttribute( "type", "radio" ); support.radioValue = input.value === "t"; - input.setAttribute( "checked", "checked" ); - // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "checked", "t" ); input.setAttribute( "name", "t" ); - div.appendChild( input ); fragment = document.createDocumentFragment(); - fragment.appendChild( div.lastChild ); - - // WebKit doesn't clone checked state correctly in fragments - support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + fragment.appendChild( input ); // Check if a disconnected checkbox will retain its checked // value of true after appended to the DOM (IE6/7) support.appendChecked = input.checked; - fragment.removeChild( input ); - fragment.appendChild( div ); + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; - // Technique from Juriy Zaytsev - // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ - // We only care about the case where non-standard event systems - // are used, namely in IE. Short-circuiting here helps us to - // avoid an eval call (in setAttribute) which can cause CSP - // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() if ( div.attachEvent ) { - for ( i in { - submit: true, - change: true, - focusin: true - }) { - eventName = "on" + i; - isSupported = ( eventName in div ); - if ( !isSupported ) { - div.setAttribute( eventName, "return;" ); - isSupported = ( typeof div[ eventName ] === "function" ); - } - support[ i + "Bubbles" ] = isSupported; - } + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); } + // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + for ( i in { submit: true, change: true, focusin: true }) { + div.setAttribute( eventName = "on" + i, "t" ); + + support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + // Support: IE<9 + // Iteration over object's inherited properties before its own. + for ( i in jQuery( support ) ) { + break; + } + support.ownLast = i !== "0"; + // Run tests that need a body at doc ready jQuery(function() { - var container, div, tds, marginDiv, - divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;", + var container, marginDiv, tds, + divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", body = document.getElementsByTagName("body")[0]; if ( !body ) { @@ -1422,20 +3475,17 @@ jQuery.support = (function() { } container = document.createElement("div"); - container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px"; - body.insertBefore( container, body.firstChild ); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; - // Construct the test element - div = document.createElement("div"); - container.appendChild( div ); + body.appendChild( container ).appendChild( div ); + // Support: IE8 // Check if table cells still have offsetWidth/Height when they are set // to display:none and there are still other visible table cells in a // table row; if so, offsetWidth/Height are not reliable for use when // determining if an element has been hidden directly using // display:none (it is still safe to use offsets if a parent element is // hidden; don safety goggles and see bug #4512 for more information). - // (only IE 8 fails this test) div.innerHTML = "
t
"; tds = div.getElementsByTagName("td"); tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; @@ -1444,89 +3494,266 @@ jQuery.support = (function() { tds[ 0 ].style.display = ""; tds[ 1 ].style.display = "none"; + // Support: IE8 // Check if empty table cells still have offsetWidth/Height - // (IE <= 8 fail this test) support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); - // Check box-sizing and margin behavior + // Check box-sizing and margin behavior. div.innerHTML = ""; div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; - support.boxSizing = ( div.offsetWidth === 4 ); - support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); - // NOTE: To any future maintainer, we've window.getComputedStyle - // because jsdom on node.js will break without it. + // Workaround failing boxSizing test due to offsetWidth returning wrong value + // with some non-1 values of body zoom, ticket #13543 + jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() { + support.boxSizing = div.offsetWidth === 4; + }); + + // Use window.getComputedStyle because jsdom on node.js will break without it. if ( window.getComputedStyle ) { support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. For more - // info see bug #3333 + // gets computed margin-right based on width of container. (#3333) // Fails in WebKit before Feb 2011 nightlies // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - marginDiv = document.createElement("div"); + marginDiv = div.appendChild( document.createElement("div") ); marginDiv.style.cssText = div.style.cssText = divReset; marginDiv.style.marginRight = marginDiv.style.width = "0"; div.style.width = "1px"; - div.appendChild( marginDiv ); + support.reliableMarginRight = !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); } - if ( typeof div.style.zoom !== "undefined" ) { + if ( typeof div.style.zoom !== core_strundefined ) { + // Support: IE<8 // Check if natively block-level elements act like inline-block // elements when setting their display to 'inline' and giving // them layout - // (IE < 8 does this) div.innerHTML = ""; div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + // Support: IE6 // Check if elements with layout shrink-wrap their children - // (IE 6 does this) div.style.display = "block"; - div.style.overflow = "visible"; div.innerHTML = "
"; div.firstChild.style.width = "5px"; support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); - container.style.zoom = 1; + if ( support.inlineBlockNeedsLayout ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } } - // Null elements to avoid leaks in IE body.removeChild( container ); + + // Null elements to avoid leaks in IE container = div = tds = marginDiv = null; }); // Null elements to avoid leaks in IE - fragment.removeChild( div ); - all = a = select = opt = input = fragment = div = null; + all = select = fragment = opt = a = input = null; return support; -})(); +})({}); + var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, rmultiDash = /([A-Z])/g; +function internalData( elem, name, data, pvt /* Internal Use Only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var ret, thisCache, + internalKey = jQuery.expando, + + // 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, + + // 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[ internalKey ] : elem[ internalKey ] && internalKey; + + // 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 || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + // Avoid exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + cache[ id ] = isNode ? {} : { toJSON: jQuery.noop }; + } + + // 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" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( typeof name === "string" ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + 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 ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + i = name.length; + while ( i-- ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + /* jshint eqeqeq: false */ + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + /* jshint eqeqeq: true */ + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + jQuery.extend({ cache: {}, - deletedIds: [], - - // Remove at next major release (1.9/2.0) - uuid: 0, - - // 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. noData: { + "applet": true, "embed": true, // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" }, hasData: function( elem ) { @@ -1534,187 +3761,30 @@ jQuery.extend({ return !!elem && !isEmptyDataObject( elem ); }, - data: function( elem, name, data, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, ret, - internalKey = jQuery.expando, - getByName = typeof name === "string", - - // 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, - - // 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[ internalKey ] : elem[ internalKey ] && internalKey; - - // 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 || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { - return; - } - - 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[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++; - } else { - id = internalKey; - } - } - - if ( !cache[ id ] ) { - cache[ id ] = {}; - - // Avoids exposing jQuery metadata on plain JS objects when the object - // is serialized using JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } - } - - // 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" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ] = jQuery.extend( cache[ id ], name ); - } else { - cache[ id ].data = jQuery.extend( cache[ id ].data, name ); - } - } - - thisCache = cache[ id ]; - - // jQuery data() is stored in a separate object inside the object's internal data - // cache in order to avoid key collisions between internal data and user-defined - // data. - if ( !pvt ) { - if ( !thisCache.data ) { - thisCache.data = {}; - } - - thisCache = thisCache.data; - } - - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( getByName ) { - - // First Try to find as-is property data - ret = thisCache[ name ]; - - // Test for null|undefined property data - if ( ret == null ) { - - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; - } - } else { - ret = thisCache; - } - - return ret; + data: function( elem, name, data ) { + return internalData( elem, name, data ); }, - removeData: function( elem, name, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, i, l, - - isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - 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 ( name ) { - - thisCache = pvt ? cache[ id ] : cache[ id ].data; - - if ( thisCache ) { - - // Support array or space separated string names for data keys - if ( !jQuery.isArray( name ) ) { - - // try the string as a key before any manipulation - if ( name in thisCache ) { - name = [ name ]; - } else { - - // split the camel cased version by spaces unless a key with the spaces exists - name = jQuery.camelCase( name ); - if ( name in thisCache ) { - name = [ name ]; - } else { - name = name.split(" "); - } - } - } - - for ( i = 0, l = name.length; i < l; i++ ) { - delete thisCache[ name[i] ]; - } - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { - return; - } - } - } - - // See jQuery.data for more information - if ( !pvt ) { - delete cache[ id ].data; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject( cache[ id ] ) ) { - return; - } - } - - // Destroy the cache - if ( isNode ) { - jQuery.cleanData( [ elem ], true ); - - // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) - } else if ( jQuery.support.deleteExpando || cache != cache.window ) { - delete cache[ id ]; - - // When all else fails, null - } else { - cache[ id ] = null; - } + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); }, // For internal use only. _data: function( elem, name, data ) { - return jQuery.data( elem, name, data, true ); + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); }, // A method for determining if a DOM node can handle the data expando acceptData: function( elem ) { + // Do not set data on non-element because it will not be cleared (#8335). + if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { + return false; + } + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; // nodes accept data unless otherwise specified; rejection can be conditional @@ -1724,10 +3794,13 @@ jQuery.extend({ jQuery.fn.extend({ data: function( key, value ) { - var parts, part, attr, name, l, - elem = this[0], + var attrs, name, + data = null, i = 0, - data = null; + elem = this[0]; + + // Special expections of .data basically thwart jQuery.access, + // so implement the relevant behavior ourselves // Gets all values if ( key === undefined ) { @@ -1735,12 +3808,12 @@ jQuery.fn.extend({ data = jQuery.data( elem ); if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { - attr = elem.attributes; - for ( l = attr.length; i < l; i++ ) { - name = attr[i].name; + attrs = elem.attributes; + for ( ; i < attrs.length; i++ ) { + name = attrs[i].name; - if ( !name.indexOf( "data-" ) ) { - name = jQuery.camelCase( name.substring(5) ); + if ( name.indexOf("data-") === 0 ) { + name = jQuery.camelCase( name.slice(5) ); dataAttr( elem, name, data[ name ] ); } @@ -1759,35 +3832,16 @@ jQuery.fn.extend({ }); } - parts = key.split( ".", 2 ); - parts[1] = parts[1] ? "." + parts[1] : ""; - part = parts[1] + "!"; + return arguments.length > 1 ? - return jQuery.access( this, function( value ) { - - if ( value === undefined ) { - data = this.triggerHandler( "getData" + part, [ parts[0] ] ); - - // Try to fetch any internally stored data first - if ( data === undefined && elem ) { - data = jQuery.data( elem, key ); - data = dataAttr( elem, key, data ); - } - - return data === undefined && parts[1] ? - this.data( parts[0] ) : - data; - } - - parts[1] = value; + // Sets one value this.each(function() { - var self = jQuery( this ); - - self.triggerHandler( "setData" + part, parts ); jQuery.data( this, key, value ); - self.triggerHandler( "changeData" + part, parts ); - }); - }, null, value, arguments.length > 1, null, false ); + }) : + + // Gets one value + // Try to fetch any internally stored data first + elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; }, removeData: function( key ) { @@ -1809,12 +3863,12 @@ function dataAttr( elem, key, data ) { if ( typeof data === "string" ) { try { data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; } catch( e ) {} // Make sure we set the data so it isn't changed later @@ -1904,8 +3958,8 @@ jQuery.extend({ var key = type + "queueHooks"; return jQuery._data( elem, key ) || jQuery._data( elem, key, { empty: jQuery.Callbacks("once memory").add(function() { - jQuery.removeData( elem, type + "queue", true ); - jQuery.removeData( elem, key, true ); + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); }) }); } @@ -1990,14 +4044,14 @@ jQuery.fn.extend({ return defer.promise( obj ); } }); -var nodeHook, boolHook, fixSpecified, - rclass = /[\t\r\n]/g, +var nodeHook, boolHook, + rclass = /[\t\r\n\f]/g, rreturn = /\r/g, - rtype = /^(?:button|input)$/i, - rfocusable = /^(?:button|input|object|select|textarea)$/i, - rclickable = /^a(?:rea|)$/i, - rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, - getSetAttribute = jQuery.support.getSetAttribute; + rfocusable = /^(?:input|select|textarea|button|object)$/i, + rclickable = /^(?:a|area)$/i, + ruseDefault = /^(?:checked|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + getSetInput = jQuery.support.input; jQuery.fn.extend({ attr: function( name, value ) { @@ -2026,35 +4080,37 @@ jQuery.fn.extend({ }, addClass: function( value ) { - var classNames, i, l, elem, - setClass, c, cl; + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = typeof value === "string" && value; if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { - jQuery( this ).addClass( value.call(this, j, this.className) ); + jQuery( this ).addClass( value.call( this, j, this.className ) ); }); } - if ( value && typeof value === "string" ) { - classNames = value.split( core_rspace ); + if ( proceed ) { + // The disjunction here is for better compressibility (see removeClass) + classes = ( value || "" ).match( core_rnotwhite ) || []; - for ( i = 0, l = this.length; i < l; i++ ) { + for ( ; i < len; i++ ) { elem = this[ i ]; + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + " " + ); - if ( elem.nodeType === 1 ) { - if ( !elem.className && classNames.length === 1 ) { - elem.className = value; - - } else { - setClass = " " + elem.className + " "; - - for ( c = 0, cl = classNames.length; c < cl; c++ ) { - if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) { - setClass += classNames[ c ] + " "; - } + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; } - elem.className = jQuery.trim( setClass ); } + elem.className = jQuery.trim( cur ); + } } } @@ -2063,30 +4119,36 @@ jQuery.fn.extend({ }, removeClass: function( value ) { - var removes, className, elem, c, cl, i, l; + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = arguments.length === 0 || typeof value === "string" && value; if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { - jQuery( this ).removeClass( value.call(this, j, this.className) ); + jQuery( this ).removeClass( value.call( this, j, this.className ) ); }); } - if ( (value && typeof value === "string") || value === undefined ) { - removes = ( value || "" ).split( core_rspace ); + if ( proceed ) { + classes = ( value || "" ).match( core_rnotwhite ) || []; - for ( i = 0, l = this.length; i < l; i++ ) { + for ( ; i < len; i++ ) { elem = this[ i ]; - if ( elem.nodeType === 1 && elem.className ) { + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + "" + ); - className = (" " + elem.className + " ").replace( rclass, " " ); - - // loop over each item in the removal list - for ( c = 0, cl = removes.length; c < cl; c++ ) { - // Remove until there is nothing to remove, - while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) { - className = className.replace( " " + removes[ c ] + " " , " " ); + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { + cur = cur.replace( " " + clazz + " ", " " ); } } - elem.className = value ? jQuery.trim( className ) : ""; + elem.className = value ? jQuery.trim( cur ) : ""; } } } @@ -2095,8 +4157,11 @@ jQuery.fn.extend({ }, toggleClass: function( value, stateVal ) { - var type = typeof value, - isBool = typeof stateVal === "boolean"; + var type = typeof value; + + if ( typeof stateVal === "boolean" && type === "string" ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } if ( jQuery.isFunction( value ) ) { return this.each(function( i ) { @@ -2110,22 +4175,28 @@ jQuery.fn.extend({ var className, i = 0, self = jQuery( this ), - state = stateVal, - classNames = value.split( core_rspace ); + classNames = value.match( core_rnotwhite ) || []; while ( (className = classNames[ i++ ]) ) { // check each className given, space separated list - state = isBool ? state : !self.hasClass( className ); - self[ state ? "addClass" : "removeClass" ]( className ); + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } } - } else if ( type === "undefined" || type === "boolean" ) { + // Toggle whole class name + } else if ( type === core_strundefined || type === "boolean" ) { if ( this.className ) { // store className if set jQuery._data( this, "__className__", this.className ); } - // toggle whole className + // If the element has a class name or if we're passed "false", + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; } }); @@ -2145,7 +4216,7 @@ jQuery.fn.extend({ }, val: function( value ) { - var hooks, ret, isFunction, + var ret, hooks, isFunction, elem = this[0]; if ( !arguments.length ) { @@ -2171,15 +4242,14 @@ jQuery.fn.extend({ isFunction = jQuery.isFunction( value ); return this.each(function( i ) { - var val, - self = jQuery(this); + var val; if ( this.nodeType !== 1 ) { return; } if ( isFunction ) { - val = value.call( this, i, self.val() ); + val = value.call( this, i, jQuery( this ).val() ); } else { val = value; } @@ -2209,34 +4279,34 @@ jQuery.extend({ valHooks: { option: { get: function( elem ) { - // attributes.value is undefined in Blackberry 4.7 but - // uses .value. See #6932 - var val = elem.attributes.value; - return !val || val.specified ? elem.value : elem.text; + // Use proper attribute retrieval(#6932, #12072) + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + elem.text; } }, select: { get: function( elem ) { - var value, i, max, option, - index = elem.selectedIndex, - values = [], + var value, option, options = elem.options, - one = elem.type === "select-one"; - - // Nothing was selected - if ( index < 0 ) { - return null; - } + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; // Loop through all the selected options - i = one ? index : 0; - max = one ? index + 1 : options.length; for ( ; i < max; i++ ) { 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) && - (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { // Get the specific value for the option value = jQuery( option ).val(); @@ -2251,22 +4321,24 @@ jQuery.extend({ } } - // Fixes Bug #2551 -- select.val() broken in IE after form.reset() - if ( one && !values.length && options.length ) { - return jQuery( options[ index ] ).val(); - } - return values; }, set: function( elem, value ) { - var values = jQuery.makeArray( value ); + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; - jQuery(elem).find("option").each(function() { - this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; - }); + while ( i-- ) { + option = options[ i ]; + if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) { + optionSet = true; + } + } - if ( !values.length ) { + // force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { elem.selectedIndex = -1; } return values; @@ -2274,11 +4346,8 @@ jQuery.extend({ } }, - // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9 - attrFn: {}, - - attr: function( elem, name, value, pass ) { - var ret, hooks, notxml, + attr: function( elem, name, value ) { + var hooks, ret, nType = elem.nodeType; // don't get/set attributes on text, comment and attribute nodes @@ -2286,31 +4355,25 @@ jQuery.extend({ return; } - if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) { - return jQuery( elem )[ name ]( value ); - } - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { + if ( typeof elem.getAttribute === core_strundefined ) { return jQuery.prop( elem, name, value ); } - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - // All attributes are lowercase // Grab necessary hook if one is defined - if ( notxml ) { + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { name = name.toLowerCase(); - hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + hooks = jQuery.attrHooks[ name ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook ); } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); - return; - } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { @@ -2318,47 +4381,46 @@ jQuery.extend({ return value; } - } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { return ret; } else { - - ret = elem.getAttribute( name ); + ret = jQuery.find.attr( elem, name ); // Non-existent attributes return null, we normalize to undefined - return ret === null ? + return ret == null ? undefined : ret; } }, removeAttr: function( elem, value ) { - var propName, attrNames, name, isBool, - i = 0; + var name, propName, + i = 0, + attrNames = value && value.match( core_rnotwhite ); - if ( value && elem.nodeType === 1 ) { + if ( attrNames && elem.nodeType === 1 ) { + while ( (name = attrNames[i++]) ) { + propName = jQuery.propFix[ name ] || name; - attrNames = value.split( core_rspace ); - - for ( ; i < attrNames.length; i++ ) { - name = attrNames[ i ]; - - if ( name ) { - propName = jQuery.propFix[ name ] || name; - isBool = rboolean.test( name ); - - // See #9699 for explanation of this approach (setting first, then removal) - // Do not do this for boolean attributes (see #10870) - if ( !isBool ) { - jQuery.attr( elem, name, "" ); - } - elem.removeAttribute( getSetAttribute ? name : propName ); - - // Set corresponding property to false for boolean attributes - if ( isBool && propName in elem ) { + // Boolean attributes get special treatment (#10870) + if ( jQuery.expr.match.bool.test( name ) ) { + // Set corresponding property to false + if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { elem[ propName ] = false; + // Support: IE<9 + // Also clear defaultChecked/defaultSelected (if appropriate) + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = + elem[ propName ] = false; } + + // See #9699 for explanation of this approach (setting first, then removal) + } else { + jQuery.attr( elem, name, "" ); } + + elem.removeAttribute( getSetAttribute ? name : propName ); } } }, @@ -2366,13 +4428,9 @@ jQuery.extend({ attrHooks: { type: { set: function( elem, value ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { // Setting the type on a radio button after the value resets the value in IE6-9 - // Reset value to it's default in case type is set after value - // This is for element creation + // Reset value to default in case type is set after value during creation var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { @@ -2381,41 +4439,12 @@ jQuery.extend({ return value; } } - }, - // Use the value property for back compat - // Use the nodeHook for button elements in IE6/7 (#1954) - value: { - get: function( elem, name ) { - if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { - return nodeHook.get( elem, name ); - } - return name in elem ? - elem.value : - null; - }, - set: function( elem, value, name ) { - if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { - return nodeHook.set( elem, value, name ); - } - // Does not return so that setAttribute is also used - elem.value = value; - } } }, propFix: { - tabindex: "tabIndex", - readonly: "readOnly", "for": "htmlFor", - "class": "className", - maxlength: "maxLength", - cellspacing: "cellSpacing", - cellpadding: "cellPadding", - rowspan: "rowSpan", - colspan: "colSpan", - usemap: "useMap", - frameborder: "frameBorder", - contenteditable: "contentEditable" + "class": "className" }, prop: function( elem, name, value ) { @@ -2436,20 +4465,14 @@ jQuery.extend({ } if ( value !== undefined ) { - if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { - return ret; - - } else { - return ( elem[ name ] = value ); - } + return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ? + ret : + ( elem[ name ] = value ); } else { - if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - return elem[ name ]; - } + return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ? + ret : + elem[ name ]; } }, @@ -2458,115 +4481,154 @@ jQuery.extend({ get: function( elem ) { // 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/ - var attributeNode = elem.getAttributeNode("tabindex"); + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); - return attributeNode && attributeNode.specified ? - parseInt( attributeNode.value, 10 ) : + return tabindex ? + parseInt( tabindex, 10 ) : rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? 0 : - undefined; + -1; } } } }); -// Hook for boolean attributes +// Hooks for boolean attributes boolHook = { - get: function( elem, name ) { - // Align boolean attributes with corresponding properties - // Fall back to attribute presence where some booleans are not supported - var attrNode, - property = jQuery.prop( elem, name ); - return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? - name.toLowerCase() : - undefined; - }, set: function( elem, value, name ) { - var propName; if ( value === false ) { // Remove boolean attributes when set to false jQuery.removeAttr( elem, name ); - } else { - // value is true since we know at this point it's type boolean and not false - // Set boolean attributes to the same name and set the DOM property - propName = jQuery.propFix[ name ] || name; - if ( propName in elem ) { - // Only set the IDL specifically if it already exists on the element - elem[ propName ] = true; - } + } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + // IE<8 needs the *property* name + elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); - elem.setAttribute( name, name.toLowerCase() ); + // Use defaultChecked and defaultSelected for oldIE + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; } + return name; } }; +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr; + + jQuery.expr.attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ? + function( elem, name, isXML ) { + var fn = jQuery.expr.attrHandle[ name ], + ret = isXML ? + undefined : + /* jshint eqeqeq: false */ + (jQuery.expr.attrHandle[ name ] = undefined) != + getter( elem, name, isXML ) ? + + name.toLowerCase() : + null; + jQuery.expr.attrHandle[ name ] = fn; + return ret; + } : + function( elem, name, isXML ) { + return isXML ? + undefined : + elem[ jQuery.camelCase( "default-" + name ) ] ? + name.toLowerCase() : + null; + }; +}); + +// fix oldIE attroperties +if ( !getSetInput || !getSetAttribute ) { + jQuery.attrHooks.value = { + set: function( elem, value, name ) { + if ( jQuery.nodeName( elem, "input" ) ) { + // Does not return so that setAttribute is also used + elem.defaultValue = value; + } else { + // Use nodeHook if defined (#1954); otherwise setAttribute is fine + return nodeHook && nodeHook.set( elem, value, name ); + } + } + }; +} // IE6/7 do not support getting/setting some attributes with get/setAttribute if ( !getSetAttribute ) { - fixSpecified = { - name: true, - id: true, - coords: true - }; - // Use this for any attribute in IE6/7 // This fixes almost every IE6/7 issue - nodeHook = jQuery.valHooks.button = { - get: function( elem, name ) { - var ret; - ret = elem.getAttributeNode( name ); - return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ? - ret.value : - undefined; - }, + nodeHook = { set: function( elem, value, name ) { // Set the existing or create a new attribute node var ret = elem.getAttributeNode( name ); if ( !ret ) { - ret = document.createAttribute( name ); - elem.setAttributeNode( ret ); + elem.setAttributeNode( + (ret = elem.ownerDocument.createAttribute( name )) + ); } - return ( ret.value = value + "" ); + + ret.value = value += ""; + + // Break association with cloned elements by also using setAttribute (#9646) + return name === "value" || value === elem.getAttribute( name ) ? + value : + undefined; + } + }; + jQuery.expr.attrHandle.id = jQuery.expr.attrHandle.name = jQuery.expr.attrHandle.coords = + // Some attributes are constructed with empty-string values when not defined + function( elem, name, isXML ) { + var ret; + return isXML ? + undefined : + (ret = elem.getAttributeNode( name )) && ret.value !== "" ? + ret.value : + null; + }; + jQuery.valHooks.button = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return ret && ret.specified ? + ret.value : + undefined; + }, + set: nodeHook.set + }; + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + set: function( elem, value, name ) { + nodeHook.set( elem, value === "" ? false : value, name ); } }; // Set width and height to auto instead of 0 on empty string( Bug #8150 ) // This is for removals jQuery.each([ "width", "height" ], function( i, name ) { - jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + jQuery.attrHooks[ name ] = { set: function( elem, value ) { if ( value === "" ) { elem.setAttribute( name, "auto" ); return value; } } - }); + }; }); - - // Set contenteditable to false on removals(#10429) - // Setting to empty string throws an error as an invalid value - jQuery.attrHooks.contenteditable = { - get: nodeHook.get, - set: function( elem, value, name ) { - if ( value === "" ) { - value = "false"; - } - nodeHook.set( elem, value, name ); - } - }; } // Some attributes require a special call on IE +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx if ( !jQuery.support.hrefNormalized ) { - jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { - jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + // href/src property should get the full normalized URL (#10299/#12915) + jQuery.each([ "href", "src" ], function( i, name ) { + jQuery.propHooks[ name ] = { get: function( elem ) { - var ret = elem.getAttribute( name, 2 ); - return ret === null ? undefined : ret; + return elem.getAttribute( name, 4 ); } - }); + }; }); } @@ -2574,8 +4636,9 @@ if ( !jQuery.support.style ) { jQuery.attrHooks.style = { get: function( elem ) { // Return undefined in the case of empty string - // Normalize to lowercase since IE uppercases css property names - return elem.style.cssText.toLowerCase() || undefined; + // Note: IE uppercases css property names, but if we were to .toLowerCase() + // .cssText, that would destroy case senstitivity in URL's, like in "background" + return elem.style.cssText || undefined; }, set: function( elem, value ) { return ( elem.style.cssText = value + "" ); @@ -2586,7 +4649,7 @@ if ( !jQuery.support.style ) { // Safari mis-reports the default selected property of an option // Accessing the parent's selectedIndex property fixes it if ( !jQuery.support.optSelected ) { - jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + jQuery.propHooks.selected = { get: function( elem ) { var parent = elem.parentNode; @@ -2600,43 +4663,65 @@ if ( !jQuery.support.optSelected ) { } return null; } - }); + }; } +jQuery.each([ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +}); + // IE6/7 call enctype encoding if ( !jQuery.support.enctype ) { jQuery.propFix.enctype = "encoding"; } // Radios and checkboxes getter/setter -if ( !jQuery.support.checkOn ) { - jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - get: function( elem ) { - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - return elem.getAttribute("value") === null ? "on" : elem.value; - } - }; - }); -} jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + jQuery.valHooks[ this ] = { set: function( elem, value ) { if ( jQuery.isArray( value ) ) { return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); } } - }); + }; + if ( !jQuery.support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + // Support: Webkit + // "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + }; + } }); -var rformElems = /^(?:textarea|input|select)$/i, - rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/, - rhoverHack = /(?:^|\s)hover(\.\S+|)\b/, +var rformElems = /^(?:input|select|textarea)$/i, rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|contextmenu)|click/, rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - hoverHack = function( events ) { - return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); - }; + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} /* * Helper functions for managing events -- not part of the public interface. @@ -2644,14 +4729,16 @@ var rformElems = /^(?:textarea|input|select)$/i, */ jQuery.event = { + global: {}, + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); - var elemData, eventHandle, events, - t, tns, type, namespaces, handleObj, - handleObjIn, handlers, special; - - // Don't attach events to noData or text/comment nodes (allow plain objects tho) - if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { return; } @@ -2668,16 +4755,14 @@ jQuery.event = { } // Init the element's event structure and main handler, if this is the first - events = elemData.events; - if ( !events ) { - elemData.events = events = {}; + if ( !(events = elemData.events) ) { + events = elemData.events = {}; } - eventHandle = elemData.handle; - if ( !eventHandle ) { - elemData.handle = eventHandle = function( e ) { + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; @@ -2686,13 +4771,17 @@ jQuery.event = { } // Handle multiple events separated by a space - // jQuery(...).bind("mouseover mouseout", fn); - types = jQuery.trim( hoverHack(types) ).split( " " ); - for ( t = 0; t < types.length; t++ ) { + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); - tns = rtypenamespace.exec( types[t] ) || []; - type = tns[1]; - namespaces = ( tns[2] || "" ).split( "." ).sort(); + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } // If event changes its type, use the special event handlers for the changed type special = jQuery.event.special[ type ] || {}; @@ -2706,7 +4795,7 @@ jQuery.event = { // handleObj is passed to all event handlers handleObj = jQuery.extend({ type: type, - origType: tns[1], + origType: origType, data: data, handler: handler, guid: handler.guid, @@ -2716,8 +4805,7 @@ jQuery.event = { }, handleObjIn ); // Init the event handler queue if we're the first - handlers = events[ type ]; - if ( !handlers ) { + if ( !(handlers = events[ type ]) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; @@ -2756,13 +4844,12 @@ jQuery.event = { elem = null; }, - global: {}, - // Detach an event or set of events from an element remove: function( elem, types, handler, selector, mappedTypes ) { - - var t, tns, type, origType, namespaces, origCount, - j, events, special, eventType, handleObj, + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, elemData = jQuery.hasData( elem ) && jQuery._data( elem ); if ( !elemData || !(events = elemData.events) ) { @@ -2770,11 +4857,12 @@ jQuery.event = { } // Once for each type.namespace in types; type may be omitted - types = jQuery.trim( hoverHack( types || "" ) ).split(" "); - for ( t = 0; t < types.length; t++ ) { - tns = rtypenamespace.exec( types[t] ) || []; - type = origType = tns[1]; - namespaces = tns[2]; + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); // Unbind all events (on this namespace, if provided) for the element if ( !type ) { @@ -2785,23 +4873,23 @@ jQuery.event = { } special = jQuery.event.special[ type ] || {}; - type = ( selector? special.delegateType : special.bindType ) || type; - eventType = events[ type ] || []; - origCount = eventType.length; - namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); // Remove matching events - for ( j = 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !namespaces || namespaces.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { - eventType.splice( j--, 1 ); + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); if ( handleObj.selector ) { - eventType.delegateCount--; + handlers.delegateCount--; } if ( special.remove ) { special.remove.call( elem, handleObj ); @@ -2811,7 +4899,7 @@ jQuery.event = { // Remove generic event handler if we removed something and no more handlers exist // (avoids potential for endless recursion during removal of special event handlers) - if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( origCount && !handlers.length ) { if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } @@ -2826,80 +4914,48 @@ jQuery.event = { // removeData also checks for emptiness and clears the expando if empty // so use it instead of delete - jQuery.removeData( elem, "events", true ); + jQuery._removeData( elem, "events" ); } }, - // Events that are safe to short-circuit if no handlers are attached. - // Native DOM events should not be added, they may have inline handlers. - customEvent: { - "getData": true, - "setData": true, - "changeData": true - }, - trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = core_hasOwn.call( event, "type" ) ? event.type : event, + namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + // Don't do events on text and comment nodes - if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } - // Event object or event type - var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType, - type = event.type || event, - namespaces = []; - // focus/blur morphs to focusin/out; ensure we're not firing them right now if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { return; } - if ( type.indexOf( "!" ) >= 0 ) { - // Exclusive events trigger only for the exact event (no namespaces) - type = type.slice(0, -1); - exclusive = true; - } - - if ( type.indexOf( "." ) >= 0 ) { + if ( type.indexOf(".") >= 0 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split("."); type = namespaces.shift(); namespaces.sort(); } + ontype = type.indexOf(":") < 0 && "on" + type; - if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { - // No jQuery handlers for this event type, and it can't have inline handlers - return; - } + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); - // Caller can pass in an Event, Object, or just an event type string - event = typeof event === "object" ? - // jQuery.Event object - event[ jQuery.expando ] ? event : - // Object literal - new jQuery.Event( type, event ) : - // Just the event type (string) - new jQuery.Event( type ); - - event.type = type; - event.isTrigger = true; - event.exclusive = exclusive; - event.namespace = namespaces.join( "." ); - event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null; - ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; - - // Handle a global trigger - if ( !elem ) { - - // TODO: Stop taunting the data cache; remove global events and always attach to document - cache = jQuery.cache; - for ( i in cache ) { - if ( cache[ i ].events && cache[ i ].events[ type ] ) { - jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); - } - } - return; - } + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; // Clean up the event in case it is being reused event.result = undefined; @@ -2908,44 +4964,50 @@ jQuery.event = { } // Clone any incoming data and prepend the event, creating the handler arg list - data = data != null ? jQuery.makeArray( data ) : []; - data.unshift( event ); + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); // Allow special events to draw outside the lines special = jQuery.event.special[ type ] || {}; - if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { return; } // Determine event propagation path in advance, per W3C events spec (#9951) // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - eventPath = [[ elem, special.bindType || type ]]; if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { bubbleType = special.delegateType || type; - cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; - for ( old = elem; cur; cur = cur.parentNode ) { - eventPath.push([ cur, bubbleType ]); - old = cur; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; } // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( old === (elem.ownerDocument || document) ) { - eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); } } // Fire handlers on the event path - for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { - cur = eventPath[i][0]; - event.type = eventPath[i][1]; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + // jQuery handler handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } - // Note that this is a bare JS function and not a jQuery handler + + // Native handler handle = ontype && cur[ ontype ]; if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { event.preventDefault(); @@ -2956,29 +5018,33 @@ jQuery.event = { // If nobody prevented the default action, do it now if ( !onlyHandlers && !event.isDefaultPrevented() ) { - if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && - !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { // Call a native DOM method on the target with the same name name as the event. // Can't use an .isFunction() check here because IE6/7 fails that test. // Don't do default actions on window, that's where global variables be (#6170) - // IE<9 dies on focus/blur to hidden element (#1486) - if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method - old = elem[ ontype ]; + tmp = elem[ ontype ]; - if ( old ) { + if ( tmp ) { elem[ ontype ] = null; } // Prevent re-triggering of the same event, since we already bubbled it above jQuery.event.triggered = type; - elem[ type ](); + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } jQuery.event.triggered = undefined; - if ( old ) { - elem[ ontype ] = old; + if ( tmp ) { + elem[ ontype ] = tmp; } } } @@ -2990,15 +5056,13 @@ jQuery.event = { dispatch: function( event ) { // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( event || window.event ); + event = jQuery.event.fix( event ); - var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related, - handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), - delegateCount = handlers.delegateCount, + var i, ret, handleObj, matched, j, + handlerQueue = [], args = core_slice.call( arguments ), - run_all = !event.exclusive && !event.namespace, - special = jQuery.event.special[ event.type ] || {}, - handlerQueue = []; + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event args[0] = event; @@ -3009,62 +5073,29 @@ jQuery.event = { return; } - // Determine handlers that should run if there are delegated events - // Avoid non-left-click bubbling in Firefox (#3861) - if ( delegateCount && !(event.button && event.type === "click") ) { - - for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { - - // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.disabled !== true || event.type !== "click" ) { - selMatch = {}; - matches = []; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - sel = handleObj.selector; - - if ( selMatch[ sel ] === undefined ) { - selMatch[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) >= 0 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( selMatch[ sel ] ) { - matches.push( handleObj ); - } - } - if ( matches.length ) { - handlerQueue.push({ elem: cur, matches: matches }); - } - } - } - } - - // Add the remaining (directly-bound) handlers - if ( handlers.length > delegateCount ) { - handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); - } + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); // Run delegates first; they may want to stop propagation beneath us - for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { - matched = handlerQueue[ i ]; + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { event.currentTarget = matched.elem; - for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { - handleObj = matched.matches[ j ]; + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { - // Triggered event must either 1) be non-exclusive and have no namespace, or + // Triggered event must either 1) have no namespace, or // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). - if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { - event.data = handleObj.data; event.handleObj = handleObj; + event.data = handleObj.data; ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) .apply( matched.elem, args ); if ( ret !== undefined ) { - event.result = ret; - if ( ret === false ) { + if ( (event.result = ret) === false ) { event.preventDefault(); event.stopPropagation(); } @@ -3081,9 +5112,103 @@ jQuery.event = { return event.result; }, + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + /* jshint eqeqeq: false */ + for ( ; cur != this; cur = cur.parentNode || this ) { + /* jshint eqeqeq: true */ + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + // Includes some event props shared by KeyEvent and MouseEvent - // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** - props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), fixHooks: {}, @@ -3103,7 +5228,7 @@ jQuery.event = { mouseHooks: { props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), filter: function( event, original ) { - var eventDoc, doc, body, + var body, eventDoc, doc, button = original.button, fromElement = original.fromElement; @@ -3132,64 +5257,57 @@ jQuery.event = { } }, - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, - originalEvent = event, - fixHook = jQuery.event.fixHooks[ event.type ] || {}, - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - - event = jQuery.Event( originalEvent ); - - for ( i = copy.length; i; ) { - prop = copy[ --i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) - if ( !event.target ) { - event.target = originalEvent.srcElement || document; - } - - // Target should not be a text node (#504, Safari) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8) - event.metaKey = !!event.metaKey; - - return fixHook.filter? fixHook.filter( event, originalEvent ) : event; - }, - special: { load: { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, - focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, delegateType: "focusin" }, blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, delegateType: "focusout" }, - - beforeunload: { - setup: function( data, namespaces, eventHandle ) { - // We only want to do this special case on windows - if ( jQuery.isWindow( this ) ) { - this.onbeforeunload = eventHandle; + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; } }, - teardown: function( namespaces, eventHandle ) { - if ( this.onbeforeunload === eventHandle ) { - this.onbeforeunload = null; + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Even when returnValue equals to undefined Firefox will still show alert + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; } } } @@ -3202,7 +5320,8 @@ jQuery.event = { var e = jQuery.extend( new jQuery.Event(), event, - { type: type, + { + type: type, isSimulated: true, originalEvent: {} } @@ -3218,10 +5337,6 @@ jQuery.event = { } }; -// Some plugins are using, but it's undocumented/deprecated and will be removed. -// The 1.7 special event interface should provide all the hooks needed now. -jQuery.event.handle = jQuery.event.dispatch; - jQuery.removeEvent = document.removeEventListener ? function( elem, type, handle ) { if ( elem.removeEventListener ) { @@ -3233,9 +5348,9 @@ jQuery.removeEvent = document.removeEventListener ? if ( elem.detachEvent ) { - // #8545, #7054, preventing memory leaks for custom events in IE6-8 – + // #8545, #7054, preventing memory leaks for custom events in IE6-8 // detachEvent needed property on element, by name of that event, to properly expose it to GC - if ( typeof elem[ name ] === "undefined" ) { + if ( typeof elem[ name ] === core_strundefined ) { elem[ name ] = null; } @@ -3276,54 +5391,51 @@ jQuery.Event = function( src, props ) { this[ jQuery.expando ] = true; }; -function returnFalse() { - return false; -} -function returnTrue() { - return true; -} - // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html jQuery.Event.prototype = { - preventDefault: function() { - this.isDefaultPrevented = returnTrue; + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + preventDefault: function() { var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; if ( !e ) { return; } - // if preventDefault exists run it on the original event + // If preventDefault exists, run it on the original event if ( e.preventDefault ) { e.preventDefault(); - // otherwise set the returnValue property of the original event to false (IE) + // Support: IE + // Otherwise set the returnValue property of the original event to false } else { e.returnValue = false; } }, stopPropagation: function() { - this.isPropagationStopped = returnTrue; - var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; if ( !e ) { return; } - // if stopPropagation exists run it on the original event + // If stopPropagation exists, run it on the original event if ( e.stopPropagation ) { e.stopPropagation(); } - // otherwise set the cancelBubble property of the original event to true (IE) + + // Support: IE + // Set the cancelBubble property of the original event to true e.cancelBubble = true; }, stopImmediatePropagation: function() { this.isImmediatePropagationStopped = returnTrue; this.stopPropagation(); - }, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse + } }; // Create mouseenter/leave events using mouseover/out and event-time checks @@ -3339,8 +5451,7 @@ jQuery.each({ var ret, target = this, related = event.relatedTarget, - handleObj = event.handleObj, - selector = handleObj.selector; + handleObj = event.handleObj; // For mousenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window @@ -3369,11 +5480,11 @@ if ( !jQuery.support.submitBubbles ) { // Node name check avoids a VML-related crash in IE (#9807) var elem = e.target, form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; - if ( form && !jQuery._data( form, "_submit_attached" ) ) { + if ( form && !jQuery._data( form, "submitBubbles" ) ) { jQuery.event.add( form, "submit._submit", function( event ) { event._submit_bubble = true; }); - jQuery._data( form, "_submit_attached", true ); + jQuery._data( form, "submitBubbles", true ); } }); // return undefined since we don't need an event listener @@ -3432,13 +5543,13 @@ if ( !jQuery.support.changeBubbles ) { jQuery.event.add( this, "beforeactivate._change", function( e ) { var elem = e.target; - if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) { + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { jQuery.event.add( elem, "change._change", function( event ) { if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { jQuery.event.simulate( "change", this.parentNode, event, true ); } }); - jQuery._data( elem, "_change_attached", true ); + jQuery._data( elem, "changeBubbles", true ); } }); }, @@ -3488,12 +5599,12 @@ if ( !jQuery.support.focusinBubbles ) { jQuery.fn.extend({ on: function( types, selector, data, fn, /*INTERNAL*/ one ) { - var origFn, type; + var type, origFn; // Types can be a map of types/handlers if ( typeof types === "object" ) { // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { // && selector != null + if ( typeof selector !== "string" ) { // ( types-Object, data ) data = data || selector; selector = undefined; @@ -3575,1772 +5686,20 @@ jQuery.fn.extend({ }); }, - bind: function( types, data, fn ) { - return this.on( types, null, data, fn ); - }, - unbind: function( types, fn ) { - return this.off( types, null, fn ); - }, - - live: function( types, data, fn ) { - jQuery( this.context ).on( types, this.selector, data, fn ); - return this; - }, - die: function( types, fn ) { - jQuery( this.context ).off( types, this.selector || "**", fn ); - return this; - }, - - delegate: function( selector, types, data, fn ) { - return this.on( types, selector, data, fn ); - }, - undelegate: function( selector, types, fn ) { - // ( namespace ) or ( selector, types [, fn] ) - return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); - }, - trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); }, triggerHandler: function( type, data ) { - if ( this[0] ) { - return jQuery.event.trigger( type, data, this[0], true ); + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); } - }, - - toggle: function( fn ) { - // Save reference to arguments for access in closure - var args = arguments, - guid = fn.guid || jQuery.guid++, - i = 0, - toggler = 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 ); - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[ lastToggle ].apply( this, arguments ) || false; - }; - - // link all the functions, so any of them can unbind this click handler - toggler.guid = guid; - while ( i < args.length ) { - args[ i++ ].guid = guid; - } - - return this.click( toggler ); - }, - - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); } }); - -jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - if ( fn == null ) { - fn = data; - data = null; - } - - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }; - - if ( rkeyEvent.test( name ) ) { - jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; - } - - if ( rmouseEvent.test( name ) ) { - jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; - } -}); -/*! - * Sizzle CSS Selector Engine - * Copyright 2012 jQuery Foundation and other contributors - * Released under the MIT license - * http://sizzlejs.com/ - */ -(function( window, undefined ) { - -var cachedruns, - assertGetIdNotName, - Expr, - getText, - isXML, - contains, - compile, - sortOrder, - hasDuplicate, - outermostContext, - - baseHasDuplicate = true, - strundefined = "undefined", - - expando = ( "sizcache" + Math.random() ).replace( ".", "" ), - - Token = String, - document = window.document, - docElem = document.documentElement, - dirruns = 0, - done = 0, - pop = [].pop, - push = [].push, - slice = [].slice, - // Use a stripped-down indexOf if a native one is unavailable - indexOf = [].indexOf || function( elem ) { - var i = 0, - len = this.length; - for ( ; i < len; i++ ) { - if ( this[i] === elem ) { - return i; - } - } - return -1; - }, - - // Augment a function for special use by Sizzle - markFunction = function( fn, value ) { - fn[ expando ] = value == null || value; - return fn; - }, - - createCache = function() { - var cache = {}, - keys = []; - - return markFunction(function( key, value ) { - // Only keep the most recent entries - if ( keys.push( key ) > Expr.cacheLength ) { - delete cache[ keys.shift() ]; - } - - return (cache[ key ] = value); - }, cache ); - }, - - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - - // Regex - - // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/css3-syntax/#characters - characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+", - - // Loosely modeled on CSS identifier characters - // An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors) - // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = characterEncoding.replace( "w", "w#" ), - - // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors - operators = "([*^$|!~]?=)", - attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + - "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", - - // Prefer arguments not in parens/brackets, - // then attribute selectors and non-pseudos (denoted by :), - // then anything else - // These preferences are here to reduce the number of selectors - // needing tokenize in the PSEUDO preFilter - pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:" + attributes + ")|[^:]|\\\\.)*|.*))\\)|)", - - // For matchExpr.POS and matchExpr.needsContext - pos = ":(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + - "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), - rpseudo = new RegExp( pseudos ), - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/, - - rnot = /^:not/, - rsibling = /[\x20\t\r\n\f]*[+~]/, - rendsWithNot = /:not\($/, - - rheader = /h\d/i, - rinputs = /input|select|textarea|button/i, - - rbackslash = /\\(?!\\)/g, - - matchExpr = { - "ID": new RegExp( "^#(" + characterEncoding + ")" ), - "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), - "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), - "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "POS": new RegExp( pos, "i" ), - "CHILD": new RegExp( "^:(only|nth|first|last)-child(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - // For use in libraries implementing .is() - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" ) - }, - - // Support - - // Used for testing something on an element - assert = function( fn ) { - var div = document.createElement("div"); - - try { - return fn( div ); - } catch (e) { - return false; - } finally { - // release memory in IE - div = null; - } - }, - - // Check if getElementsByTagName("*") returns only elements - assertTagNameNoComments = assert(function( div ) { - div.appendChild( document.createComment("") ); - return !div.getElementsByTagName("*").length; - }), - - // Check if getAttribute returns normalized href attributes - assertHrefNotNormalized = assert(function( div ) { - div.innerHTML = ""; - return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && - div.firstChild.getAttribute("href") === "#"; - }), - - // Check if attributes should be retrieved by attribute nodes - assertAttributes = assert(function( div ) { - div.innerHTML = ""; - var type = typeof div.lastChild.getAttribute("multiple"); - // IE8 returns a string for some attributes even when not present - return type !== "boolean" && type !== "string"; - }), - - // Check if getElementsByClassName can be trusted - assertUsableClassName = assert(function( div ) { - // Opera can't find a second classname (in 9.6) - div.innerHTML = ""; - if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) { - return false; - } - - // Safari 3.2 caches class attributes and doesn't catch changes - div.lastChild.className = "e"; - return div.getElementsByClassName("e").length === 2; - }), - - // Check if getElementById returns elements by name - // Check if getElementsByName privileges form controls or returns elements by ID - assertUsableName = assert(function( div ) { - // Inject content - div.id = expando + 0; - div.innerHTML = "
"; - docElem.insertBefore( div, docElem.firstChild ); - - // Test - var pass = document.getElementsByName && - // buggy browsers will return fewer than the correct 2 - document.getElementsByName( expando ).length === 2 + - // buggy browsers will return more than the correct 0 - document.getElementsByName( expando + 0 ).length; - assertGetIdNotName = !document.getElementById( expando ); - - // Cleanup - docElem.removeChild( div ); - - return pass; - }); - -// If slice is not available, provide a backup -try { - slice.call( docElem.childNodes, 0 )[0].nodeType; -} catch ( e ) { - slice = function( i ) { - var elem, - results = []; - for ( ; (elem = this[i]); i++ ) { - results.push( elem ); - } - return results; - }; -} - -function Sizzle( selector, context, results, seed ) { - results = results || []; - context = context || document; - var match, elem, xml, m, - nodeType = context.nodeType; - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - if ( nodeType !== 1 && nodeType !== 9 ) { - return []; - } - - xml = isXML( context ); - - if ( !xml && !seed ) { - if ( (match = rquickExpr.exec( selector )) ) { - // Speed-up: Sizzle("#ID") - if ( (m = match[1]) ) { - if ( nodeType === 9 ) { - elem = context.getElementById( m ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE, Opera, and Webkit return items - // by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - } else { - // Context is not a document - if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && - contains( context, elem ) && elem.id === m ) { - results.push( elem ); - return results; - } - } - - // Speed-up: Sizzle("TAG") - } else if ( match[2] ) { - push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) ); - return results; - - // Speed-up: Sizzle(".CLASS") - } else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) { - push.apply( results, slice.call(context.getElementsByClassName( m ), 0) ); - return results; - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed, xml ); -} - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - return Sizzle( expr, null, null, [ elem ] ).length > 0; -}; - -// Returns a function to use in pseudos for input types -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -// Returns a function to use in pseudos for buttons -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -// Returns a function to use in pseudos for positionals -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( nodeType ) { - if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (see #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - } else { - - // If no nodeType, this is expected to be an array - for ( ; (node = elem[i]); i++ ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } - return ret; -}; - -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -// Element contains another -contains = Sizzle.contains = docElem.contains ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) ); - } : - docElem.compareDocumentPosition ? - function( a, b ) { - return b && !!( a.compareDocumentPosition( b ) & 16 ); - } : - function( a, b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - return false; - }; - -Sizzle.attr = function( elem, name ) { - var val, - xml = isXML( elem ); - - if ( !xml ) { - name = name.toLowerCase(); - } - if ( (val = Expr.attrHandle[ name ]) ) { - return val( elem ); - } - if ( xml || assertAttributes ) { - return elem.getAttribute( name ); - } - val = elem.getAttributeNode( name ); - return val ? - typeof elem[ name ] === "boolean" ? - elem[ name ] ? name : null : - val.specified ? val.value : null : - null; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - // IE6/7 return a modified href - attrHandle: assertHrefNotNormalized ? - {} : - { - "href": function( elem ) { - return elem.getAttribute( "href", 2 ); - }, - "type": function( elem ) { - return elem.getAttribute("type"); - } - }, - - find: { - "ID": assertGetIdNotName ? - function( id, context, xml ) { - if ( typeof context.getElementById !== strundefined && !xml ) { - var m = context.getElementById( id ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - } : - function( id, context, xml ) { - if ( typeof context.getElementById !== strundefined && !xml ) { - var m = context.getElementById( id ); - - return m ? - m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? - [m] : - undefined : - []; - } - }, - - "TAG": assertTagNameNoComments ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== strundefined ) { - return context.getElementsByTagName( tag ); - } - } : - function( tag, context ) { - var results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - var elem, - tmp = [], - i = 0; - - for ( ; (elem = results[i]); i++ ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }, - - "NAME": assertUsableName && function( tag, context ) { - if ( typeof context.getElementsByName !== strundefined ) { - return context.getElementsByName( name ); - } - }, - - "CLASS": assertUsableClassName && function( className, context, xml ) { - if ( typeof context.getElementsByClassName !== strundefined && !xml ) { - return context.getElementsByClassName( className ); - } - } - }, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( rbackslash, "" ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 3 xn-component of xn+y argument ([+-]?\d*n|) - 4 sign of xn-component - 5 x of xn-component - 6 sign of y-component - 7 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1] === "nth" ) { - // nth-child requires argument - if ( !match[2] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) ); - match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" ); - - // other types prohibit arguments - } else if ( match[2] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var unquoted, excess; - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - if ( match[3] ) { - match[2] = match[3]; - } else if ( (unquoted = match[4]) ) { - // Only check arguments that contain a pseudo - if ( rpseudo.test(unquoted) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - unquoted = unquoted.slice( 0, excess ); - match[0] = match[0].slice( 0, excess ); - } - match[2] = unquoted; - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - "ID": assertGetIdNotName ? - function( id ) { - id = id.replace( rbackslash, "" ); - return function( elem ) { - return elem.getAttribute("id") === id; - }; - } : - function( id ) { - id = id.replace( rbackslash, "" ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); - return node && node.value === id; - }; - }, - - "TAG": function( nodeName ) { - if ( nodeName === "*" ) { - return function() { return true; }; - } - nodeName = nodeName.replace( rbackslash, "" ).toLowerCase(); - - return function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ expando ][ className ]; - if ( !pattern ) { - pattern = classCache( className, new RegExp("(^|" + whitespace + ")" + className + "(" + whitespace + "|$)") ); - } - return function( elem ) { - return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); - }; - }, - - "ATTR": function( name, operator, check ) { - return function( elem, context ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.substr( result.length - check.length ) === check : - operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, argument, first, last ) { - - if ( type === "nth" ) { - return function( elem ) { - var node, diff, - parent = elem.parentNode; - - if ( first === 1 && last === 0 ) { - return true; - } - - if ( parent ) { - diff = 0; - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - diff++; - if ( elem === node ) { - break; - } - } - } - } - - // Incorporate the offset (or cast to NaN), then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - }; - } - - return function( elem ) { - var node = elem; - - switch ( type ) { - case "only": - case "first": - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - if ( type === "first" ) { - return true; - } - - node = elem; - - /* falls through */ - case "last": - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - return true; - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf.call( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - "enabled": function( elem ) { - return elem.disabled === false; - }, - - "disabled": function( elem ) { - return elem.disabled === true; - }, - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), - // not comment, processing instructions, or others - // Thanks to Diego Perini for the nodeName shortcut - // Greater than "@" means alpha characters (specifically not starting with "#" or "?") - var nodeType; - elem = elem.firstChild; - while ( elem ) { - if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) { - return false; - } - elem = elem.nextSibling; - } - return true; - }, - - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "text": function( elem ) { - var type, attr; - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return elem.nodeName.toLowerCase() === "input" && - (type = elem.type) === "text" && - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type ); - }, - - // Input types - "radio": createInputPseudo("radio"), - "checkbox": createInputPseudo("checkbox"), - "file": createInputPseudo("file"), - "password": createInputPseudo("password"), - "image": createInputPseudo("image"), - - "submit": createButtonPseudo("submit"), - "reset": createButtonPseudo("reset"), - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "focus": function( elem ) { - var doc = elem.ownerDocument; - return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href); - }, - - "active": function( elem ) { - return elem === elem.ownerDocument.activeElement; - }, - - // Positional types - "first": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length, argument ) { - for ( var i = 0; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length, argument ) { - for ( var i = 1; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - for ( var i = argument < 0 ? argument + length : argument; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - for ( var i = argument < 0 ? argument + length : argument; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -function siblingCheck( a, b, ret ) { - if ( a === b ) { - return ret; - } - - var cur = a.nextSibling; - - while ( cur ) { - if ( cur === b ) { - return -1; - } - - cur = cur.nextSibling; - } - - return 1; -} - -sortOrder = docElem.compareDocumentPosition ? - function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - return ( !a.compareDocumentPosition || !b.compareDocumentPosition ? - a.compareDocumentPosition : - a.compareDocumentPosition(b) & 4 - ) ? -1 : 1; - } : - function( a, b ) { - // The nodes are identical, we can exit early - if ( a === b ) { - hasDuplicate = true; - return 0; - - // Fallback to using sourceIndex (in IE) if it's available on both nodes - } else if ( a.sourceIndex && b.sourceIndex ) { - return a.sourceIndex - b.sourceIndex; - } - - var al, bl, - ap = [], - bp = [], - aup = a.parentNode, - bup = b.parentNode, - cur = aup; - - // If the nodes are siblings (or identical) we can do a quick check - if ( aup === bup ) { - return siblingCheck( a, b ); - - // If no parents were found then the nodes are disconnected - } else if ( !aup ) { - return -1; - - } else if ( !bup ) { - return 1; - } - - // Otherwise they're somewhere else in the tree so we need - // to build up a full list of the parentNodes for comparison - while ( cur ) { - ap.unshift( cur ); - cur = cur.parentNode; - } - - cur = bup; - - while ( cur ) { - bp.unshift( cur ); - cur = cur.parentNode; - } - - al = ap.length; - bl = bp.length; - - // Start walking down the tree looking for a discrepancy - for ( var i = 0; i < al && i < bl; i++ ) { - if ( ap[i] !== bp[i] ) { - return siblingCheck( ap[i], bp[i] ); - } - } - - // We ended someplace up the tree so do a sibling check - return i === al ? - siblingCheck( a, bp[i], -1 ) : - siblingCheck( ap[i], b, 1 ); - }; - -// Always assume the presence of duplicates if sort doesn't -// pass them to our comparison function (as in Google Chrome). -[0, 0].sort( sortOrder ); -baseHasDuplicate = !hasDuplicate; - -// Document sorting and removing duplicates -Sizzle.uniqueSort = function( results ) { - var elem, - i = 1; - - hasDuplicate = baseHasDuplicate; - results.sort( sortOrder ); - - if ( hasDuplicate ) { - for ( ; (elem = results[i]); i++ ) { - if ( elem === results[ i - 1 ] ) { - results.splice( i--, 1 ); - } - } - } - - return results; -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -function tokenize( selector, parseOnly ) { - var matched, match, tokens, type, soFar, groups, preFilters, - cached = tokenCache[ expando ][ selector ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - soFar = soFar.slice( match[0].length ); - } - groups.push( tokens = [] ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - tokens.push( matched = new Token( match.shift() ) ); - soFar = soFar.slice( matched.length ); - - // Cast descendant combinators to space - matched.type = match[0].replace( rtrim, " " ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - // The last two arguments here are (context, xml) for backCompat - (match = preFilters[ type ]( match, document, true ))) ) { - - tokens.push( matched = new Token( match.shift() ) ); - soFar = soFar.slice( matched.length ); - matched.type = type; - matched.matches = match; - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - checkNonElements = base && combinator.dir === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( checkNonElements || elem.nodeType === 1 ) { - return matcher( elem, context, xml ); - } - } - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching - if ( !xml ) { - var cache, - dirkey = dirruns + " " + doneName + " ", - cachedkey = dirkey + cachedruns; - while ( (elem = elem[ dir ]) ) { - if ( checkNonElements || elem.nodeType === 1 ) { - if ( (cache = elem[ expando ]) === cachedkey ) { - return elem.sizset; - } else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) { - if ( elem.sizset ) { - return elem; - } - } else { - elem[ expando ] = cachedkey; - if ( matcher( elem, context, xml ) ) { - elem.sizset = true; - return elem; - } - elem.sizset = false; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( checkNonElements || elem.nodeType === 1 ) { - if ( matcher( elem, context, xml ) ) { - return elem; - } - } - } - } - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - // Positional selectors apply to seed elements, so it is invalid to follow them with relative ones - if ( seed && postFinder ) { - return; - } - - var i, elem, postFilterIn, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [], seed ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - postFilterIn = condense( matcherOut, postMap ); - postFilter( postFilterIn, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = postFilterIn.length; - while ( i-- ) { - if ( (elem = postFilterIn[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - // Keep seed and results synchronized - if ( seed ) { - // Ignore postFinder because it can't coexist with seed - i = preFilter && matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - seed[ preMap[i] ] = !(results[ preMap[i] ] = elem); - } - } - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf.call( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; - } else { - // The concatenated values are (context, xml) for backCompat - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && tokens.slice( 0, i - 1 ).join("").replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && tokens.join("") - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, expandContext ) { - var elem, j, matcher, - setMatched = [], - matchedCount = 0, - i = "0", - unmatched = seed && [], - outermost = expandContext != null, - contextBackup = outermostContext, - // We must always have either seed elements or context - elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), - // Nested matchers should use non-integer dirruns - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E); - - if ( outermost ) { - outermostContext = context !== document && context; - cachedruns = superMatcher.el; - } - - // Add elements passing elementMatchers directly to results - for ( ; (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - for ( j = 0; (matcher = elementMatchers[j]); j++ ) { - if ( matcher( elem, context, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - cachedruns = ++superMatcher.el; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // Apply set filters to unmatched elements - matchedCount += i; - if ( bySet && i !== matchedCount ) { - for ( j = 0; (matcher = setMatchers[j]); j++ ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - superMatcher.el = 0; - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ expando ][ selector ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !group ) { - group = tokenize( selector ); - } - i = group.length; - while ( i-- ) { - cached = matcherFromTokens( group[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - } - return cached; -}; - -function multipleContexts( selector, contexts, results, seed ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results, seed ); - } - return results; -} - -function select( selector, context, results, seed, xml ) { - var i, tokens, token, type, find, - match = tokenize( selector ), - j = match.length; - - if ( !seed ) { - // Try to minimize operations if there is only one group - if ( match.length === 1 ) { - - // Take a shortcut and set the context if the root selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && !xml && - Expr.relative[ tokens[1].type ] ) { - - context = Expr.find["ID"]( token.matches[0].replace( rbackslash, "" ), context, xml )[0]; - if ( !context ) { - return results; - } - - selector = selector.slice( tokens.shift().length ); - } - - // Fetch a seed set for right-to-left matching - for ( i = matchExpr["POS"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( rbackslash, "" ), - rsibling.test( tokens[0].type ) && context.parentNode || context, - xml - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && tokens.join(""); - if ( !selector ) { - push.apply( results, slice.call( seed, 0 ) ); - return results; - } - - break; - } - } - } - } - } - - // Compile and execute a filtering function - // Provide `match` to avoid retokenization if we modified the selector above - compile( selector, match )( - seed, - context, - xml, - results, - rsibling.test( selector ) - ); - return results; -} - -if ( document.querySelectorAll ) { - (function() { - var disconnectedMatch, - oldSelect = select, - rescape = /'|\\/g, - rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, - - // qSa(:focus) reports false when true (Chrome 21), - // A support test would require too much code (would include document ready) - rbuggyQSA = [":focus"], - - // matchesSelector(:focus) reports false when true (Chrome 21), - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - // A support test would require too much code (would include document ready) - // just skip matchesSelector for :active - rbuggyMatches = [ ":active", ":focus" ], - matches = docElem.matchesSelector || - docElem.mozMatchesSelector || - docElem.webkitMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector; - - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( div ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explictly - // setting a boolean content attribute, - // since its presence should be enough - // http://bugs.jquery.com/ticket/12359 - div.innerHTML = ""; - - // IE8 - Some boolean attributes are not treated correctly - if ( !div.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here (do not put tests after this one) - if ( !div.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - }); - - assert(function( div ) { - - // Opera 10-12/IE9 - ^= $= *= and empty values - // Should not select anything - div.innerHTML = "

"; - if ( div.querySelectorAll("[test^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here (do not put tests after this one) - div.innerHTML = ""; - if ( !div.querySelectorAll(":enabled").length ) { - rbuggyQSA.push(":enabled", ":disabled"); - } - }); - - // rbuggyQSA always contains :focus, so no need for a length check - rbuggyQSA = /* rbuggyQSA.length && */ new RegExp( rbuggyQSA.join("|") ); - - select = function( selector, context, results, seed, xml ) { - // Only use querySelectorAll when not filtering, - // when this is not xml, - // and when no QSA bugs apply - if ( !seed && !xml && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - var groups, i, - old = true, - nid = expando, - newContext = context, - newSelector = context.nodeType === 9 && selector; - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - groups = tokenize( selector ); - - if ( (old = context.getAttribute("id")) ) { - nid = old.replace( rescape, "\\$&" ); - } else { - context.setAttribute( "id", nid ); - } - nid = "[id='" + nid + "'] "; - - i = groups.length; - while ( i-- ) { - groups[i] = nid + groups[i].join(""); - } - newContext = rsibling.test( selector ) && context.parentNode || context; - newSelector = groups.join(","); - } - - if ( newSelector ) { - try { - push.apply( results, slice.call( newContext.querySelectorAll( - newSelector - ), 0 ) ); - return results; - } catch(qsaError) { - } finally { - if ( !old ) { - context.removeAttribute("id"); - } - } - } - } - - return oldSelect( selector, context, results, seed, xml ); - }; - - if ( matches ) { - assert(function( div ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - disconnectedMatch = matches.call( div, "div" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - try { - matches.call( div, "[test!='']:sizzle" ); - rbuggyMatches.push( "!=", pseudos ); - } catch ( e ) {} - }); - - // rbuggyMatches always contains :active and :focus, so no need for a length check - rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") ); - - Sizzle.matchesSelector = function( elem, expr ) { - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - // rbuggyMatches always contains :active, so no need for an existence check - if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && (!rbuggyQSA || !rbuggyQSA.test( expr )) ) { - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch(e) {} - } - - return Sizzle( expr, null, null, [ elem ] ).length > 0; - }; - } - })(); -} - -// Deprecated -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Back-compat -function setFilters() {} -Expr.filters = setFilters.prototype = Expr.pseudos; -Expr.setFilters = new setFilters(); - -// Override sizzle attribute retrieval -Sizzle.attr = jQuery.attr; -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.pseudos; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - -})( window ); -var runtil = /Until$/, +var isSimple = /^.[^:#\[\.,]*$/, rparentsprev = /^(?:parents|prev(?:Until|All))/, - isSimple = /^.[^:#\[\.,]*$/, rneedsContext = jQuery.expr.match.needsContext, // methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { @@ -5352,38 +5711,28 @@ var runtil = /Until$/, jQuery.fn.extend({ find: function( selector ) { - var i, l, length, n, r, ret, - self = this; + var i, + ret = [], + self = this, + len = self.length; if ( typeof selector !== "string" ) { - return jQuery( selector ).filter(function() { - for ( i = 0, l = self.length; i < l; i++ ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { if ( jQuery.contains( self[ i ], this ) ) { return true; } } - }); + }) ); } - ret = this.pushStack( "", "find", selector ); - - for ( i = 0, l = this.length; i < l; i++ ) { - length = ret.length; - jQuery.find( selector, this[i], ret ); - - if ( i > 0 ) { - // Make sure that the results are unique - for ( n = length; n < ret.length; n++ ) { - for ( r = 0; r < length; r++ ) { - if ( ret[r] === ret[n] ) { - ret.splice(n--, 1); - break; - } - } - } - } + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); } + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; return ret; }, @@ -5402,22 +5751,24 @@ jQuery.fn.extend({ }, not: function( selector ) { - return this.pushStack( winnow(this, selector, false), "not", selector); + return this.pushStack( winnow(this, selector || [], true) ); }, filter: function( selector ) { - return this.pushStack( winnow(this, selector, true), "filter", selector ); + return this.pushStack( winnow(this, selector || [], false) ); }, is: function( selector ) { - return !!selector && ( - typeof selector === "string" ? - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - rneedsContext.test( selector ) ? - jQuery( selector, this.context ).index( this[0] ) >= 0 : - jQuery.filter( selector, this ).length > 0 : - this.filter( selector ).length > 0 ); + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; }, closest: function( selectors, context ) { @@ -5430,20 +5781,22 @@ jQuery.fn.extend({ 0; for ( ; i < l; i++ ) { - cur = this[i]; + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : - while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { - if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { - ret.push( cur ); + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + cur = ret.push( cur ); break; } - cur = cur.parentNode; } } - ret = ret.length > 1 ? jQuery.unique( ret ) : ret; - - return this.pushStack( ret, "closest", selectors ); + return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); }, // Determine the position of an element within @@ -5452,7 +5805,7 @@ jQuery.fn.extend({ // No argument, return index in parent if ( !elem ) { - return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; } // index in selector @@ -5472,9 +5825,7 @@ jQuery.fn.extend({ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), all = jQuery.merge( this.get(), set ); - return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? - all : - jQuery.unique( all ) ); + return this.pushStack( jQuery.unique(all) ); }, addBack: function( selector ) { @@ -5484,14 +5835,6 @@ jQuery.fn.extend({ } }); -jQuery.fn.andSelf = jQuery.fn.addBack; - -// A painfully simple check to see if an element is disconnected -// from a document (should be improved, where feasible). -function isDisconnected( node ) { - return !node || !node.parentNode || node.parentNode.nodeType === 11; -} - function sibling( cur, dir ) { do { cur = cur[ dir ]; @@ -5544,7 +5887,7 @@ jQuery.each({ jQuery.fn[ name ] = function( until, selector ) { var ret = jQuery.map( this, fn, until ); - if ( !runtil.test( name ) ) { + if ( name.slice( -5 ) !== "Until" ) { selector = until; } @@ -5552,25 +5895,35 @@ jQuery.each({ ret = jQuery.filter( selector, ret ); } - ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + ret = jQuery.unique( ret ); + } - if ( this.length > 1 && rparentsprev.test( name ) ) { - ret = ret.reverse(); + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + ret = ret.reverse(); + } } - return this.pushStack( ret, name, core_slice.call( arguments ).join(",") ); + return this.pushStack( ret ); }; }); jQuery.extend({ filter: function( expr, elems, not ) { + var elem = elems[ 0 ]; + if ( not ) { expr = ":not(" + expr + ")"; } - return elems.length === 1 ? - jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : - jQuery.find.matches(expr, elems); + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); }, dir: function( elem, dir, until ) { @@ -5600,42 +5953,37 @@ jQuery.extend({ }); // Implement the identical functionality for filter and not -function winnow( elements, qualifier, keep ) { - - // Can't pass null or undefined to indexOf in Firefox 4 - // Set to 0 to skip string check - qualifier = qualifier || 0; - +function winnow( elements, qualifier, not ) { if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep(elements, function( elem, i ) { - var retVal = !!qualifier.call( elem, i, elem ); - return retVal === keep; + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; }); - } else if ( qualifier.nodeType ) { - return jQuery.grep(elements, function( elem, i ) { - return ( elem === qualifier ) === keep; - }); - - } else if ( typeof qualifier === "string" ) { - var filtered = jQuery.grep(elements, function( elem ) { - return elem.nodeType === 1; - }); - - if ( isSimple.test( qualifier ) ) { - return jQuery.filter(qualifier, filtered, !keep); - } else { - qualifier = jQuery.filter( qualifier, filtered ); - } } - return jQuery.grep(elements, function( elem, i ) { - return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( isSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not; }); } function createSafeFragment( document ) { var list = nodeNames.split( "|" ), - safeFrag = document.createDocumentFragment(); + safeFrag = document.createDocumentFragment(); if ( safeFrag.createElement ) { while ( list.length ) { @@ -5650,28 +5998,34 @@ function createSafeFragment( document ) { var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), rleadingWhitespace = /^\s+/, rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, rtagName = /<([\w:]+)/, rtbody = /]", "i"), - rcheckableType = /^(?:checkbox|radio)$/, + manipulation_rcheckableType = /^(?:checkbox|radio)$/i, // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - rscriptType = /\/(java|ecma)script/i, - rcleanScript = /^\s*\s*$/g, + rscriptType = /^$|\/(?:java|ecma)script/i, + rscriptTypeMasked = /^true\/(.*)/, + rcleanScript = /^\s*\s*$/g, + + // We have to close these tags to support XHTML (#13200) wrapMap = { option: [ 1, "" ], legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], thead: [ 1, "", "
" ], tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], col: [ 2, "", "
" ], - area: [ 1, "", "" ], - _default: [ 0, "", "" ] + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] }, safeFragment = createSafeFragment( document ), fragmentDiv = safeFragment.appendChild( document.createElement("div") ); @@ -5680,12 +6034,6 @@ wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; -// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, -// unless wrapped in a div with non-breaking characters in front of it. -if ( !jQuery.support.htmlSerialize ) { - wrapMap._default = [ 1, "X
", "
" ]; -} - jQuery.fn.extend({ text: function( value ) { return jQuery.access( this, function( value ) { @@ -5695,6 +6043,689 @@ jQuery.fn.extend({ }, null, value, arguments.length ); }, + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var + // Snapshot the DOM in case .domManip sweeps something relevant into its fragment + args = jQuery.map( this, function( elem ) { + return [ elem.nextSibling, elem.parentNode ]; + }), + i = 0; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + var next = args[ i++ ], + parent = args[ i++ ]; + + if ( parent ) { + // Don't use the snapshot next if it has moved (#13810) + if ( next && next.parentNode !== parent ) { + next = this.nextSibling; + } + jQuery( this ).remove(); + parent.insertBefore( elem, next ); + } + // Allow new content to include elements from the context set + }, true ); + + // Force removal if there was no new content (e.g., from empty arguments) + return i ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback, allowIntersection ) { + + // Flatten any nested arrays + args = core_concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, self.html() ); + } + self.domManip( args, callback, allowIntersection ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[i], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Hope ajax is available... + jQuery._evalUrl( node.src ); + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +// Support: IE<8 +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + // 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 + + 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.defaultSelected = 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; + } +} + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + core_push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( manipulation_rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( 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 ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // 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 ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== core_strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + core_deletedIds.push( id ); + } + } + } + } + }, + + _evalUrl: function( url ) { + return jQuery.ajax({ + url: url, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } +}); +jQuery.fn.extend({ wrapAll: function( html ) { if ( jQuery.isFunction( html ) ) { return this.each(function(i) { @@ -5758,773 +6789,11 @@ jQuery.fn.extend({ jQuery( this ).replaceWith( this.childNodes ); } }).end(); - }, - - append: function() { - return this.domManip(arguments, true, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 ) { - this.appendChild( elem ); - } - }); - }, - - prepend: function() { - return this.domManip(arguments, true, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 ) { - this.insertBefore( elem, this.firstChild ); - } - }); - }, - - before: function() { - if ( !isDisconnected( this[0] ) ) { - return this.domManip(arguments, false, function( elem ) { - this.parentNode.insertBefore( elem, this ); - }); - } - - if ( arguments.length ) { - var set = jQuery.clean( arguments ); - return this.pushStack( jQuery.merge( set, this ), "before", this.selector ); - } - }, - - after: function() { - if ( !isDisconnected( this[0] ) ) { - return this.domManip(arguments, false, function( elem ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - }); - } - - if ( arguments.length ) { - var set = jQuery.clean( arguments ); - return this.pushStack( jQuery.merge( this, set ), "after", this.selector ); - } - }, - - // keepData is for internal use only--do not document - remove: function( selector, keepData ) { - var elem, - i = 0; - - for ( ; (elem = this[i]) != null; i++ ) { - if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { - if ( !keepData && elem.nodeType === 1 ) { - jQuery.cleanData( elem.getElementsByTagName("*") ); - jQuery.cleanData( [ elem ] ); - } - - if ( elem.parentNode ) { - elem.parentNode.removeChild( elem ); - } - } - } - - return this; - }, - - empty: function() { - var elem, - i = 0; - - for ( ; (elem = this[i]) != null; i++ ) { - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( elem.getElementsByTagName("*") ); - } - - // Remove any remaining nodes - while ( elem.firstChild ) { - elem.removeChild( elem.firstChild ); - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function () { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - }); - }, - - html: function( value ) { - return jQuery.access( this, function( value ) { - var elem = this[0] || {}, - i = 0, - l = this.length; - - if ( value === undefined ) { - return elem.nodeType === 1 ? - elem.innerHTML.replace( rinlinejQuery, "" ) : - undefined; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && - ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && - !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { - - value = value.replace( rxhtmlTag, "<$1>" ); - - try { - for (; i < l; i++ ) { - // Remove element nodes and prevent memory leaks - elem = this[i] || {}; - if ( elem.nodeType === 1 ) { - jQuery.cleanData( elem.getElementsByTagName( "*" ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch(e) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function( value ) { - if ( !isDisconnected( this[0] ) ) { - // Make sure that the elements are removed from the DOM before they are inserted - // this can help fix replacing a parent with child elements - if ( jQuery.isFunction( value ) ) { - return this.each(function(i) { - var self = jQuery(this), old = self.html(); - self.replaceWith( value.call( this, i, old ) ); - }); - } - - if ( typeof value !== "string" ) { - value = jQuery( value ).detach(); - } - - return this.each(function() { - var next = this.nextSibling, - parent = this.parentNode; - - jQuery( this ).remove(); - - if ( next ) { - jQuery(next).before( value ); - } else { - jQuery(parent).append( value ); - } - }); - } - - return this.length ? - this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) : - this; - }, - - detach: function( selector ) { - return this.remove( selector, true ); - }, - - domManip: function( args, table, callback ) { - - // Flatten any nested arrays - args = [].concat.apply( [], args ); - - var results, first, fragment, iNoClone, - i = 0, - value = args[0], - scripts = [], - l = this.length; - - // We can't cloneNode fragments that contain checked, in WebKit - if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) { - return this.each(function() { - jQuery(this).domManip( args, table, callback ); - }); - } - - if ( jQuery.isFunction(value) ) { - return this.each(function(i) { - var self = jQuery(this); - args[0] = value.call( this, i, table ? self.html() : undefined ); - self.domManip( args, table, callback ); - }); - } - - if ( this[0] ) { - results = jQuery.buildFragment( args, this, scripts ); - fragment = results.fragment; - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - if ( first ) { - table = table && jQuery.nodeName( first, "tr" ); - - // Use the original fragment for the last item instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - // Fragments from the fragment cache must always be cloned and never used in place. - for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) { - callback.call( - table && jQuery.nodeName( this[i], "table" ) ? - findOrAppend( this[i], "tbody" ) : - this[i], - i === iNoClone ? - fragment : - jQuery.clone( fragment, true, true ) - ); - } - } - - // Fix #11809: Avoid leaking memory - fragment = first = null; - - if ( scripts.length ) { - jQuery.each( scripts, function( i, elem ) { - if ( elem.src ) { - if ( jQuery.ajax ) { - jQuery.ajax({ - url: elem.src, - type: "GET", - dataType: "script", - async: false, - global: false, - "throws": true - }); - } else { - jQuery.error("no ajax"); - } - } else { - jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) ); - } - - if ( elem.parentNode ) { - elem.parentNode.removeChild( elem ); - } - }); - } - } - - return this; } }); - -function findOrAppend( elem, tag ) { - return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); -} - -function cloneCopyEvent( src, dest ) { - - if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { - return; - } - - var type, i, l, - oldData = jQuery._data( src ), - curData = jQuery._data( dest, oldData ), - events = oldData.events; - - if ( events ) { - delete curData.handle; - curData.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - - // make the cloned public data object a copy from the original - if ( curData.data ) { - curData.data = jQuery.extend( {}, curData.data ); - } -} - -function cloneFixAttributes( src, dest ) { - var nodeName; - - // We do not need to do anything for non-Elements - if ( dest.nodeType !== 1 ) { - return; - } - - // clearAttributes removes the attributes, which we don't want, - // but also removes the attachEvent events, which we *do* want - if ( dest.clearAttributes ) { - dest.clearAttributes(); - } - - // mergeAttributes, in contrast, only merges back on the - // original attributes, not the events - if ( dest.mergeAttributes ) { - dest.mergeAttributes( src ); - } - - nodeName = dest.nodeName.toLowerCase(); - - if ( nodeName === "object" ) { - // IE6-10 improperly clones children of object elements using classid. - // IE10 throws NoModificationAllowedError if parent is null, #12132. - if ( dest.parentNode ) { - dest.outerHTML = src.outerHTML; - } - - // This path appears unavoidable for IE9. When cloning an object - // element in IE9, the outerHTML strategy above is not sufficient. - // If the src has innerHTML and the destination does not, - // copy the src.innerHTML into the dest.innerHTML. #10324 - if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) { - dest.innerHTML = src.innerHTML; - } - - } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - // 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 - - 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; - - // IE blanks contents when cloning scripts - } else if ( nodeName === "script" && dest.text !== src.text ) { - dest.text = src.text; - } - - // Event data gets referenced instead of copied if the expando - // gets copied too - dest.removeAttribute( jQuery.expando ); -} - -jQuery.buildFragment = function( args, context, scripts ) { - var fragment, cacheable, cachehit, - first = args[ 0 ]; - - // Set context from what may come in as undefined or a jQuery collection or a node - // Updated to fix #12266 where accessing context[0] could throw an exception in IE9/10 & - // also doubles as fix for #8950 where plain objects caused createDocumentFragment exception - context = context || document; - context = !context.nodeType && context[0] || context; - context = context.ownerDocument || context; - - // 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 - // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501 - if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document && - first.charAt(0) === "<" && !rnocache.test( first ) && - (jQuery.support.checkClone || !rchecked.test( first )) && - (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) { - - // Mark cacheable and look for a hit - cacheable = true; - fragment = jQuery.fragments[ first ]; - cachehit = fragment !== undefined; - } - - if ( !fragment ) { - fragment = context.createDocumentFragment(); - jQuery.clean( args, context, fragment, scripts ); - - // Update the cache, but only store false - // unless this is a second parsing of the same content - if ( cacheable ) { - jQuery.fragments[ first ] = cachehit && fragment; - } - } - - return { fragment: fragment, cacheable: cacheable }; -}; - -jQuery.fragments = {}; - -jQuery.each({ - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - i = 0, - ret = [], - insert = jQuery( selector ), - l = insert.length, - parent = this.length === 1 && this[0].parentNode; - - if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) { - insert[ original ]( this[0] ); - return this; - } else { - for ( ; i < l; i++ ) { - elems = ( i > 0 ? this.clone(true) : this ).get(); - jQuery( insert[i] )[ original ]( elems ); - ret = ret.concat( elems ); - } - - return this.pushStack( ret, name, insert.selector ); - } - }; -}); - -function getAll( elem ) { - if ( typeof elem.getElementsByTagName !== "undefined" ) { - return elem.getElementsByTagName( "*" ); - - } else if ( typeof elem.querySelectorAll !== "undefined" ) { - return elem.querySelectorAll( "*" ); - - } else { - return []; - } -} - -// Used in clean, fixes the defaultChecked property -function fixDefaultChecked( elem ) { - if ( rcheckableType.test( elem.type ) ) { - elem.defaultChecked = elem.checked; - } -} - -jQuery.extend({ - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var srcElements, - destElements, - i, - clone; - - if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { - clone = elem.cloneNode( true ); - - // IE<=8 does not properly clone detached, unknown element nodes - } else { - fragmentDiv.innerHTML = elem.outerHTML; - fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); - } - - if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && - (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { - // 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. - - cloneFixAttributes( elem, clone ); - - // Using Sizzle here is crazy slow, so we use getElementsByTagName instead - srcElements = getAll( elem ); - destElements = getAll( clone ); - - // 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 ( i = 0; srcElements[i]; ++i ) { - // Ensure that the destination node is not null; Fixes #9587 - if ( destElements[i] ) { - cloneFixAttributes( srcElements[i], destElements[i] ); - } - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - cloneCopyEvent( elem, clone ); - - if ( deepDataAndEvents ) { - srcElements = getAll( elem ); - destElements = getAll( clone ); - - for ( i = 0; srcElements[i]; ++i ) { - cloneCopyEvent( srcElements[i], destElements[i] ); - } - } - } - - srcElements = destElements = null; - - // Return the cloned set - return clone; - }, - - clean: function( elems, context, fragment, scripts ) { - var i, j, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags, - safe = context === document && safeFragment, - ret = []; - - // Ensure that context is a document - if ( !context || typeof context.createDocumentFragment === "undefined" ) { - context = document; - } - - // Use the already-created safe fragment if context permits - for ( i = 0; (elem = elems[i]) != null; i++ ) { - if ( typeof elem === "number" ) { - elem += ""; - } - - if ( !elem ) { - continue; - } - - // Convert html string into DOM nodes - if ( typeof elem === "string" ) { - if ( !rhtml.test( elem ) ) { - elem = context.createTextNode( elem ); - } else { - // Ensure a safe container in which to render the html - safe = safe || createSafeFragment( context ); - div = context.createElement("div"); - safe.appendChild( div ); - - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(rxhtmlTag, "<$1>"); - - // Go to html and back, then peel off extra wrappers - tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - depth = wrap[0]; - div.innerHTML = wrap[1] + elem + wrap[2]; - - // Move to the right depth - while ( depth-- ) { - div = div.lastChild; - } - - // Remove IE's autoinserted from table fragments - if ( !jQuery.support.tbody ) { - - // String was a
, *may* have spurious - hasBody = rtbody.test(elem); - tbody = tag === "table" && !hasBody ? - div.firstChild && div.firstChild.childNodes : - - // String was a bare or - wrap[1] === "
" && !hasBody ? - div.childNodes : - []; - - for ( j = tbody.length - 1; j >= 0 ; --j ) { - if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { - tbody[ j ].parentNode.removeChild( tbody[ j ] ); - } - } - } - - // IE completely kills leading whitespace when innerHTML is used - if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { - div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); - } - - elem = div.childNodes; - - // Take out of fragment container (we need a fresh div each time) - div.parentNode.removeChild( div ); - } - } - - if ( elem.nodeType ) { - ret.push( elem ); - } else { - jQuery.merge( ret, elem ); - } - } - - // Fix #11356: Clear elements from safeFragment - if ( div ) { - elem = div = safe = null; - } - - // Reset defaultChecked for any radios and checkboxes - // about to be appended to the DOM in IE 6/7 (#8060) - if ( !jQuery.support.appendChecked ) { - for ( i = 0; (elem = ret[i]) != null; i++ ) { - if ( jQuery.nodeName( elem, "input" ) ) { - fixDefaultChecked( elem ); - } else if ( typeof elem.getElementsByTagName !== "undefined" ) { - jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); - } - } - } - - // Append elements to a provided document fragment - if ( fragment ) { - // Special handling of each script element - handleScript = function( elem ) { - // Check if we consider it executable - if ( !elem.type || rscriptType.test( elem.type ) ) { - // Detach the script and store it in the scripts array (if provided) or the fragment - // Return truthy to indicate that it has been handled - return scripts ? - scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) : - fragment.appendChild( elem ); - } - }; - - for ( i = 0; (elem = ret[i]) != null; i++ ) { - // Check if we're done after handling an executable script - if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) { - // Append to fragment and handle embedded scripts - fragment.appendChild( elem ); - if ( typeof elem.getElementsByTagName !== "undefined" ) { - // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration - jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript ); - - // Splice the scripts into ret after their former ancestor and advance our index beyond them - ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) ); - i += jsTags.length; - } - } - } - } - - return ret; - }, - - cleanData: function( elems, /* internal */ acceptData ) { - var data, id, elem, type, - i = 0, - internalKey = jQuery.expando, - cache = jQuery.cache, - deleteExpando = jQuery.support.deleteExpando, - special = jQuery.event.special; - - for ( ; (elem = elems[i]) != null; i++ ) { - - if ( acceptData || jQuery.acceptData( elem ) ) { - - id = elem[ internalKey ]; - data = id && cache[ id ]; - - if ( data ) { - if ( data.events ) { - for ( 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 ); - } - } - } - - // Remove cache only if it was not already removed by jQuery.event.remove - if ( cache[ id ] ) { - - delete cache[ id ]; - - // 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 ( deleteExpando ) { - delete elem[ internalKey ]; - - } else if ( elem.removeAttribute ) { - elem.removeAttribute( internalKey ); - - } else { - elem[ internalKey ] = null; - } - - jQuery.deletedIds.push( id ); - } - } - } - } - } -}); -// Limit scope pollution from any deprecated API -(function() { - -var matched, browser; - -// Use of jQuery.browser is frowned upon. -// More details: http://api.jquery.com/jQuery.browser -// jQuery.uaMatch maintained for back-compat -jQuery.uaMatch = function( ua ) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) || - /(webkit)[ \/]([\w.]+)/.exec( ua ) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || - /(msie) ([\w.]+)/.exec( ua ) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; -}; - -matched = jQuery.uaMatch( navigator.userAgent ); -browser = {}; - -if ( matched.browser ) { - browser[ matched.browser ] = true; - browser.version = matched.version; -} - -// Chrome is Webkit, but Webkit is also Safari. -if ( browser.chrome ) { - browser.webkit = true; -} else if ( browser.webkit ) { - browser.safari = true; -} - -jQuery.browser = browser; - -jQuery.sub = function() { - function jQuerySub( selector, context ) { - return new jQuerySub.fn.init( selector, context ); - } - jQuery.extend( true, jQuerySub, this ); - jQuerySub.superclass = this; - jQuerySub.fn = jQuerySub.prototype = this(); - jQuerySub.fn.constructor = jQuerySub; - jQuerySub.sub = this.sub; - jQuerySub.fn.init = function init( selector, context ) { - if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { - context = jQuerySub( context ); - } - - return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); - }; - jQuerySub.fn.init.prototype = jQuerySub.fn; - var rootjQuerySub = jQuerySub(document); - return jQuerySub; -}; - -})(); -var curCSS, iframe, iframeDoc, +var iframe, getStyles, curCSS, ralpha = /alpha\([^)]*\)/i, - ropacity = /opacity=([^)]*)/, + ropacity = /opacity\s*=\s*([^)]*)/, rposition = /^(top|right|bottom|left)$/, // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display @@ -6532,8 +6801,8 @@ var curCSS, iframe, iframeDoc, rmargin = /^margin/, rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), - rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ), - elemdisplay = {}, + rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssNormalTransform = { @@ -6542,9 +6811,7 @@ var curCSS, iframe, iframeDoc, }, cssExpand = [ "Top", "Right", "Bottom", "Left" ], - cssPrefixes = [ "Webkit", "O", "Moz", "ms" ], - - eventsToggle = jQuery.fn.toggle; + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; // return a css property mapped to a potentially vendor prefixed property function vendorPropName( style, name ) { @@ -6570,12 +6837,14 @@ function vendorPropName( style, name ) { } function isHidden( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument elem = el || elem; return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); } function showHide( elements, show ) { - var elem, display, + var display, elem, hidden, values = [], index = 0, length = elements.length; @@ -6585,11 +6854,13 @@ function showHide( elements, show ) { if ( !elem.style ) { continue; } + values[ index ] = jQuery._data( elem, "olddisplay" ); + display = elem.style.display; if ( show ) { // Reset the inline display of this element to learn if it is // being hidden by cascaded rules or not - if ( !values[ index ] && elem.style.display === "none" ) { + if ( !values[ index ] && display === "none" ) { elem.style.display = ""; } @@ -6600,10 +6871,13 @@ function showHide( elements, show ) { values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); } } else { - display = curCSS( elem, "display" ); - if ( !values[ index ] && display !== "none" ) { - jQuery._data( elem, "olddisplay", display ); + if ( !values[ index ] ) { + hidden = isHidden( elem ); + + if ( display && display !== "none" || !hidden ) { + jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); + } } } } @@ -6626,6 +6900,21 @@ function showHide( elements, show ) { jQuery.fn.extend({ css: function( name, value ) { return jQuery.access( this, function( elem, name, value ) { + var len, styles, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + return value !== undefined ? jQuery.style( elem, name, value ) : jQuery.css( elem, name ); @@ -6637,15 +6926,13 @@ jQuery.fn.extend({ hide: function() { return showHide( this ); }, - toggle: function( state, fn2 ) { - var bool = typeof state === "boolean"; - - if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) { - return eventsToggle.apply( this, arguments ); + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); } return this.each(function() { - if ( bool ? state : isHidden( this ) ) { + if ( isHidden( this ) ) { jQuery( this ).show(); } else { jQuery( this ).hide(); @@ -6664,18 +6951,19 @@ jQuery.extend({ // We should always get a number back from opacity var ret = curCSS( elem, "opacity" ); return ret === "" ? "1" : ret; - } } } }, - // Exclude the following css properties to add px + // Don't automatically add "px" to these possibly-unitless properties cssNumber: { + "columnCount": true, "fillOpacity": true, "fontWeight": true, "lineHeight": true, "opacity": true, + "order": true, "orphans": true, "widows": true, "zIndex": true, @@ -6728,8 +7016,15 @@ jQuery.extend({ value += "px"; } + // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { + style[ name ] = "inherit"; + } + // If a hook was provided, use that value, otherwise just set the specified value if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided // Fixes bug #5509 try { @@ -6748,8 +7043,8 @@ jQuery.extend({ } }, - css: function( elem, name, numeric, extra ) { - var val, num, hooks, + css: function( elem, name, extra, styles ) { + var num, val, hooks, origName = jQuery.camelCase( name ); // Make sure that we're working with the right name @@ -6766,7 +7061,7 @@ jQuery.extend({ // Otherwise, if a way to get the computed value exists, use that if ( val === undefined ) { - val = curCSS( elem, name ); + val = curCSS( elem, name, styles ); } //convert "normal" to computed value @@ -6775,46 +7070,31 @@ jQuery.extend({ } // Return, converting to number if forced or a qualifier was provided and val looks numeric - if ( numeric || extra !== undefined ) { + if ( extra === "" || extra ) { num = parseFloat( val ); - return numeric || jQuery.isNumeric( num ) ? num || 0 : val; + return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; } return val; - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.call( elem ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; } }); -// NOTE: To any future maintainer, we've window.getComputedStyle +// NOTE: we've included the "window" in window.getComputedStyle // because jsdom on node.js will break without it. if ( window.getComputedStyle ) { - curCSS = function( elem, name ) { - var ret, width, minWidth, maxWidth, - computed = window.getComputedStyle( elem, null ), + getStyles = function( elem ) { + return window.getComputedStyle( elem, null ); + }; + + curCSS = function( elem, name, _computed ) { + var width, minWidth, maxWidth, + computed = _computed || getStyles( elem ), + + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, style = elem.style; if ( computed ) { - ret = computed[ name ]; if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { ret = jQuery.style( elem, name ); } @@ -6824,13 +7104,17 @@ if ( window.getComputedStyle ) { // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values width = style.width; minWidth = style.minWidth; maxWidth = style.maxWidth; + // Put in the new values to get a computed value out style.minWidth = style.maxWidth = style.width = ret; ret = computed.width; + // Revert the changed values style.width = width; style.minWidth = minWidth; style.maxWidth = maxWidth; @@ -6840,9 +7124,14 @@ if ( window.getComputedStyle ) { return ret; }; } else if ( document.documentElement.currentStyle ) { - curCSS = function( elem, name ) { - var left, rsLeft, - ret = elem.currentStyle && elem.currentStyle[ name ], + getStyles = function( elem ) { + return elem.currentStyle; + }; + + curCSS = function( elem, name, _computed ) { + var left, rs, rsLeft, + computed = _computed || getStyles( elem ), + ret = computed ? computed[ name ] : undefined, style = elem.style; // Avoid setting ret to empty string here @@ -6862,11 +7151,12 @@ if ( window.getComputedStyle ) { // Remember the original values left = style.left; - rsLeft = elem.runtimeStyle && elem.runtimeStyle.left; + rs = elem.runtimeStyle; + rsLeft = rs && rs.left; // Put in the new values to get a computed value out if ( rsLeft ) { - elem.runtimeStyle.left = elem.currentStyle.left; + rs.left = elem.currentStyle.left; } style.left = name === "fontSize" ? "1em" : ret; ret = style.pixelLeft + "px"; @@ -6874,7 +7164,7 @@ if ( window.getComputedStyle ) { // Revert the changed values style.left = left; if ( rsLeft ) { - elem.runtimeStyle.left = rsLeft; + rs.left = rsLeft; } } @@ -6885,11 +7175,12 @@ if ( window.getComputedStyle ) { function setPositiveNumber( elem, value, subtract ) { var matches = rnumsplit.exec( value ); return matches ? - Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : - value; + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; } -function augmentWidthOrHeight( elem, name, extra, isBorderBox ) { +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { var i = extra === ( isBorderBox ? "border" : "content" ) ? // If we already have the right measurement, avoid augmentation 4 : @@ -6901,29 +7192,26 @@ function augmentWidthOrHeight( elem, name, extra, isBorderBox ) { for ( ; i < 4; i += 2 ) { // both box models exclude margin, so add it if we want it if ( extra === "margin" ) { - // we use jQuery.css instead of curCSS here - // because of the reliableMarginRight CSS hook! - val += jQuery.css( elem, extra + cssExpand[ i ], true ); + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); } - // From this point on we use curCSS for maximum performance (relevant in animations) if ( isBorderBox ) { // border-box includes padding, so remove it if we want content if ( extra === "content" ) { - val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0; + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); } // at this point, extra isn't border nor margin, so remove border if ( extra !== "margin" ) { - val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0; + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } } else { // at this point, extra isn't content, so add padding - val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0; + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); // at this point, extra isn't content nor padding, so add border if ( extra !== "padding" ) { - val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0; + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } } } @@ -6934,16 +7222,17 @@ function augmentWidthOrHeight( elem, name, extra, isBorderBox ) { function getWidthOrHeight( elem, name, extra ) { // Start with offset property, which is equivalent to the border-box value - var val = name === "width" ? elem.offsetWidth : elem.offsetHeight, - valueIsBorderBox = true, - isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"; + var valueIsBorderBox = true, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles( elem ), + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; // some non-html elements return undefined for offsetWidth, so check for null/undefined // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 if ( val <= 0 || val == null ) { // Fall back to computed then uncomputed css if necessary - val = curCSS( elem, name ); + val = curCSS( elem, name, styles ); if ( val < 0 || val == null ) { val = elem.style[ name ]; } @@ -6967,52 +7256,49 @@ function getWidthOrHeight( elem, name, extra ) { elem, name, extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox + valueIsBorderBox, + styles ) ) + "px"; } - // Try to determine the default display value of an element function css_defaultDisplay( nodeName ) { - if ( elemdisplay[ nodeName ] ) { - return elemdisplay[ nodeName ]; - } + var doc = document, + display = elemdisplay[ nodeName ]; - var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ), - display = elem.css("display"); - elem.remove(); + if ( !display ) { + display = actualDisplay( nodeName, doc ); - // If the simple way fails, - // get element's real default display by attaching it to a temp iframe - if ( display === "none" || display === "" ) { - // Use the already-created iframe if possible - iframe = document.body.appendChild( - iframe || jQuery.extend( document.createElement("iframe"), { - frameBorder: 0, - width: 0, - height: 0 - }) - ); + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + // Use the already-created iframe if possible + iframe = ( iframe || + jQuery("