From 4af3116d3faa3c4416dcfa49e32ea1d73dc15dc5 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Thu, 6 Feb 2014 23:59:20 -0800 Subject: [PATCH 01/99] Bump docs to sso-16 --- docs/.meteor/release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.meteor/release b/docs/.meteor/release index 400877328a..0d518fd9aa 100644 --- a/docs/.meteor/release +++ b/docs/.meteor/release @@ -1 +1 @@ -sso-1 +sso-16 From cf2de810ce2daeaea88aea8b1dfe6687eba83ddd Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Fri, 14 Feb 2014 18:44:56 -0800 Subject: [PATCH 02/99] Remove stray %s in update message. --- tools/updater.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/updater.js b/tools/updater.js index 21000dd45f..baf79fdd22 100644 --- a/tools/updater.js +++ b/tools/updater.js @@ -105,6 +105,6 @@ var check = function (showBanner) { ! release.forced) { runLog.log( "=> Meteor " + localLatestRelease + - " %s is available. Update this project with 'meteor update'."); + " is available. Update this project with 'meteor update'."); } }; From 87f22606ff19adc610eab93d30bb2a8e07a7b0a3 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Sat, 15 Feb 2014 14:46:59 -0800 Subject: [PATCH 03/99] Factor our accounts command timeout in more places. We've been seeing flakiness on login tests on different internet connections, so now we can easily bump up the timeout (at the cost of slowing down test failures). --- tools/test-utils.js | 2 ++ tools/tests/claim.js | 23 +++++++----- tools/tests/deploy-auth.js | 66 ++++++++++++++++++++-------------- tools/tests/login.js | 17 +++++---- tools/tests/logs-mongo-auth.js | 13 ++++--- tools/tests/registration.js | 2 +- 6 files changed, 75 insertions(+), 48 deletions(-) diff --git a/tools/test-utils.js b/tools/test-utils.js index ebfbf4fd01..6b6a2190aa 100644 --- a/tools/test-utils.js +++ b/tools/test-utils.js @@ -102,3 +102,5 @@ exports.logout = function (s) { run.matchErr('Logged out'); run.expectExit(0); }; + +exports.accountsCommandTimeoutSecs = 15; diff --git a/tools/tests/claim.js b/tools/tests/claim.js index ad8139b4d0..ccd1a4140c 100644 --- a/tools/tests/claim.js +++ b/tools/tests/claim.js @@ -1,8 +1,9 @@ var selftest = require('../selftest.js'); var testUtils = require('../test-utils.js'); var Sandbox = selftest.Sandbox; +var files = require('../files.js'); -var commandTimeoutSecs = 15; +var commandTimeoutSecs = testUtils.commandTimeoutSecs; var loggedInError = selftest.markStack(function(run) { run.waitSecs(commandTimeoutSecs); @@ -46,6 +47,7 @@ selftest.define("claim", ['net', 'slow'], function () { testUtils.login(s, "test", "testtest"); run = s.run('authorized', appName, '--add', 'testtest'); run.waitSecs(commandTimeoutSecs); + run.match('added'); run.expectExit(0); testUtils.logout(s); testUtils.login(s, "testtest", "testtest"); @@ -55,13 +57,18 @@ selftest.define("claim", ['net', 'slow'], function () { testUtils.cleanUpApp(s, appName); // Legacy sites. - var sLegacy = new Sandbox({ - // Include a warehouse argument so that we can deploy apps with - // --release arguments. - warehouse: { - v1: { tools: 'tool1', latest: true } - } - }); + var sLegacy; + if (files.inCheckout()) { + sLegacy = new Sandbox({ + // Include a warehouse argument so that we can deploy apps with + // --release arguments. + warehouse: { + v1: { tools: 'tool1', latest: true } + } + }); + } else { + sLegacy = new Sandbox; + } // legacy w/pwd. var pwd = testUtils.randomString(10); diff --git a/tools/tests/deploy-auth.js b/tools/tests/deploy-auth.js index 6c3759e511..6630f7a637 100644 --- a/tools/tests/deploy-auth.js +++ b/tools/tests/deploy-auth.js @@ -1,22 +1,29 @@ var _ = require('underscore'); var selftest = require('../selftest.js'); var testUtils = require('../test-utils.js'); +var files = require('../files.js'); var Sandbox = selftest.Sandbox; +var commandTimeoutSecs = testUtils.accountsCommandTimeoutSecs; + selftest.define('deploy - logged in', ['net', 'slow'], function () { // Create two sandboxes: one with a warehouse so that we can run // --release, and one without a warehouse so that we run from the // checkout or release that we started from. // XXX Is having two sandboxes the only way to do this? - var sandboxWithWarehouse = new Sandbox({ - // Include a warehouse arugment so that we can deploy apps with - // --release arguments. - warehouse: { - v1: { tools: 'tool1', latest: true } - } - }); - var sandbox = new Sandbox; + var sandboxWithWarehouse; + if (files.inCheckout()) { + sandboxWithWarehouse = new Sandbox({ + // Include a warehouse arugment so that we can deploy apps with + // --release arguments. + warehouse: { + v1: { tools: 'tool1', latest: true } + } + }); + } else { + sandboxWithWarehouse = new Sandbox; + } sandbox.createApp('deployapp', 'empty'); sandbox.cd('deployapp'); @@ -35,7 +42,7 @@ selftest.define('deploy - logged in', ['net', 'slow'], function () { run.expectExit(0); // And we should have claimed the app by deploying to it. run = sandbox.run('claim', noPasswordLegacyApp); - run.waitSecs(20); + run.waitSecs(commandTimeoutSecs); run.matchErr('already belongs to you'); run.expectExit(1); // Clean up @@ -48,15 +55,15 @@ selftest.define('deploy - logged in', ['net', 'slow'], function () { ); // We shouldn't be able to deploy to this app without claiming it run = sandbox.run('deploy', passwordLegacyApp); - run.waitSecs(15); + run.waitSecs(commandTimeoutSecs); run.matchErr('meteor claim'); run.expectExit(1); // If we claim it, we should be able to deploy to it. run = sandbox.run('claim', passwordLegacyApp); - run.waitSecs(15); + run.waitSecs(commandTimeoutSecs); run.matchErr('Password:'); run.write('test\n'); - run.waitSecs(10); + run.waitSecs(commandTimeoutSecs); run.match('successfully transferred to your account'); run.expectExit(0); run = sandbox.run('deploy', passwordLegacyApp); @@ -75,7 +82,7 @@ selftest.define('deploy - logged in', ['net', 'slow'], function () { testUtils.logout(sandbox); testUtils.login(sandbox, 'testtest', 'testtest'); run = sandbox.run('deploy', appName); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr('belongs to a different user'); run.expectExit(1); @@ -89,9 +96,14 @@ selftest.define('deploy - logged in', ['net', 'slow'], function () { selftest.define('deploy - logged out', ['net', 'slow'], function () { var s = new Sandbox; - var sandboxWithWarehouse = new Sandbox({ - warehouse: { v1: { tools: 'tool1', latest: true } } - }); + var sandboxWithWarehouse; + if (files.inCheckout()) { + sandboxWithWarehouse = new Sandbox({ + warehouse: { v1: { tools: 'tool1', latest: true } } + }); + } else { + sandboxWithWarehouse = new Sandbox; + } testUtils.login(s, 'test', 'testtest'); var appName = testUtils.createAndDeployApp(s); @@ -100,10 +112,10 @@ selftest.define('deploy - logged out', ['net', 'slow'], function () { // Deploy when logged out. We should be prompted to log in and then // the deploy should succeed. var run = s.run('deploy', appName); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr('Email:'); run.write('test@test.com\n'); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr('Password:'); run.write('testtest\n'); run.waitSecs(90); @@ -119,10 +131,10 @@ selftest.define('deploy - logged out', ['net', 'slow'], function () { sandboxWithWarehouse ); run = s.run('deploy', legacyNoPassword); - run.waitSecs(15); + run.waitSecs(commandTimeoutSecs); run.matchErr('Email:'); run.write('test@test.com\n'); - run.waitSecs(15); + run.waitSecs(commandTimeoutSecs); run.matchErr('Password: '); run.write('testtest\n'); run.waitSecs(90); @@ -137,15 +149,15 @@ selftest.define('deploy - logged out', ['net', 'slow'], function () { 'test' ); run = s.run('deploy', legacyPassword); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr('Email:'); // Log in with a username here to test that the email prompt also // accepts emails. (We put an email in the email prompt above.) run.write('test\n'); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr('Password:'); run.write('testtest\n'); - run.waitSecs(15); + run.waitSecs(commandTimeoutSecs); run.matchErr('meteor claim'); run.expectExit(1); @@ -158,12 +170,12 @@ selftest.define('deploy - logged out', ['net', 'slow'], function () { appName = testUtils.randomAppName(); var email = testUtils.randomUserEmail(); run = s.run('deploy', appName); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr('Email:'); run.write(email + '\n'); run.waitSecs(90); run.match('Now serving'); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.expectExit(0); // Now that we've created an account with this email address, we // should be logged in as it and should be able to delete it. @@ -172,10 +184,10 @@ selftest.define('deploy - logged out', ['net', 'slow'], function () { // Now that we've created a user, try to deploy a new app. appName = testUtils.randomAppName(); run = s.run('deploy', appName); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr('Email:'); run.write(email + '\n'); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr('already in use'); run.matchErr('pick a password'); run.stop(); diff --git a/tools/tests/login.js b/tools/tests/login.js index 6862048982..b40b374ddf 100644 --- a/tools/tests/login.js +++ b/tools/tests/login.js @@ -1,5 +1,8 @@ var selftest = require('../selftest.js'); var Sandbox = selftest.Sandbox; +var testUtils = require('../test-utils.js'); + +var commandTimeoutSecs = testUtils.accountsCommandTimeoutSecs; selftest.define("login", ['net'], function () { var s = new Sandbox; @@ -16,7 +19,7 @@ selftest.define("login", ['net'], function () { run.write("test\n"); run.matchErr("Password:"); run.write("testtest\n"); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr("Logged in as test."); run.expectExit(0); @@ -28,12 +31,12 @@ selftest.define("login", ['net'], function () { run.expectExit(0); run = s.run("logout"); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr("Logged out"); run.expectExit(0); run = s.run("logout"); - run.waitSecs(1); + run.waitSecs(commandTimeoutSecs); run.matchErr("Not logged in"); run.expectExit(0); @@ -47,7 +50,7 @@ selftest.define("login", ['net'], function () { run.write("test\n"); run.matchErr("Password:"); run.write("badpassword\n"); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr("Login failed"); run.expectExit(1); @@ -58,12 +61,12 @@ selftest.define("login", ['net'], function () { run.write("TeSt\n"); run.matchErr("Password:"); run.write("testtest\n"); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr("Logged in as test."); run.expectExit(0); run = s.run("logout"); - run.waitSecs(2); + run.waitSecs(commandTimeoutSecs); run.matchErr("Logged out"); run.expectExit(0); @@ -74,7 +77,7 @@ selftest.define("login", ['net'], function () { run.write("test\n"); run.matchErr("Password:"); run.write("TesTTesT\n"); - run.waitSecs(5); + run.waitSecs(commandTimeoutSecs); run.matchErr("Login failed"); run.expectExit(1); }); diff --git a/tools/tests/logs-mongo-auth.js b/tools/tests/logs-mongo-auth.js index 47f5abf0ab..cd32bdf238 100644 --- a/tools/tests/logs-mongo-auth.js +++ b/tools/tests/logs-mongo-auth.js @@ -1,6 +1,7 @@ var _ = require('underscore'); var selftest = require('../selftest.js'); var Sandbox = selftest.Sandbox; +var testUtils = require('../test-utils.js'); // XXX need to make sure that mother doesn't clean up: // 'legacy-password-app-for-selftest' @@ -8,6 +9,8 @@ var Sandbox = selftest.Sandbox; // 'app-for-selftest-not-test-owned' // 'app-for-selftest-test-owned' +var commandTimeoutSecs = testUtils.accountsCommandTimeoutSecs; + // Run 'meteor logs' or 'meteor mongo' against an app. Options: // - legacy: boolean @@ -37,7 +40,7 @@ var logsOrMongoForApp = function (sandbox, command, appName, options) { } var run = sandbox.run.apply(sandbox, runArgs); - run.waitSecs(10); + run.waitSecs(commandTimeoutSecs); var expectSuccess = selftest.markStack(function () { run.match(matchString); @@ -72,7 +75,7 @@ var logsOrMongoForApp = function (sandbox, command, appName, options) { run.write((options.username || 'test') + '\n'); run.matchErr('Password: '); run.write((options.password || 'testtest') + '\n'); - run.waitSecs(15); + run.waitSecs(commandTimeoutSecs); if (options.authorized) { expectSuccess(); } else { @@ -92,12 +95,12 @@ _.each([false, true], function (loggedIn) { var s = new Sandbox; if (loggedIn) { var run = s.run('login'); - run.waitSecs(2); + run.waitSecs(commandTimeoutSecs); run.matchErr('Username:'); run.write('test\n'); run.matchErr('Password:'); run.write('testtest\n'); - run.waitSecs(15); + run.waitSecs(commandTimeoutSecs); run.matchErr('Logged in as test.'); run.expectExit(0); } @@ -126,7 +129,7 @@ _.each([false, true], function (loggedIn) { // We logged in as a result of running the previous command, // so log out again. run = s.run('logout'); - run.waitSecs(15); + run.waitSecs(commandTimeoutSecs); run.matchErr('Logged out'); run.expectExit(0); } diff --git a/tools/tests/registration.js b/tools/tests/registration.js index d740400608..8dc1d858e1 100644 --- a/tools/tests/registration.js +++ b/tools/tests/registration.js @@ -106,7 +106,7 @@ selftest.define('deferred registration', ['net'], function () { var username = testUtils.randomString(10); var appName = testUtils.randomAppName(); var run = s.run('deploy', appName); - run.waitSecs(5); + run.waitSecs(testUtils.accountsCommandTimeoutSecs); run.matchErr('Email:'); run.write(email + '\n'); run.waitSecs(90); From 9694b585df1eff7cca637fdb97732c2961af07e1 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Sat, 15 Feb 2014 15:19:18 -0800 Subject: [PATCH 04/99] Tweak npm self-test for update message on built release --- tools/tests/npm.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/tests/npm.js b/tools/tests/npm.js index f9998a6c53..e38fedc46f 100644 --- a/tools/tests/npm.js +++ b/tools/tests/npm.js @@ -25,7 +25,9 @@ selftest.define("npm", ["net"], function () { run.tellMongo(MONGO_LISTENING); if (i === 0) { run.waitSecs(2); - run.read( + // use match instead of read because on a built release we can + // also get an update message here. + run.match( "npm-test: updating npm dependencies -- meteor-test-executable...\n"); } run.waitSecs(15); From 4cfd34d1e973593401d0ae4ede5dfb8bbd7a461b Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Sun, 16 Feb 2014 17:58:15 -0800 Subject: [PATCH 05/99] Fix my tweak to the npm self-test --- tools/tests/npm.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/tests/npm.js b/tools/tests/npm.js index e38fedc46f..07a53f45ef 100644 --- a/tools/tests/npm.js +++ b/tools/tests/npm.js @@ -27,11 +27,11 @@ selftest.define("npm", ["net"], function () { run.waitSecs(2); // use match instead of read because on a built release we can // also get an update message here. - run.match( + run.read( "npm-test: updating npm dependencies -- meteor-test-executable...\n"); } run.waitSecs(15); - run.read("null; From shell script\n"); + run.match("null; From shell script\n"); run.expectEnd(); run.expectExit(0); }); From b4c78fce39e1ea1a30113e6f2d87733d46f3cf8e Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 17 Feb 2014 10:02:54 -0800 Subject: [PATCH 06/99] Bump apps to 0.7.1-rc0 --- docs/.meteor/release | 2 +- examples/leaderboard/.meteor/release | 2 +- examples/parties/.meteor/release | 2 +- examples/todos/.meteor/release | 2 +- examples/wordplay/.meteor/release | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/.meteor/release b/docs/.meteor/release index 400877328a..405b344e0e 100644 --- a/docs/.meteor/release +++ b/docs/.meteor/release @@ -1 +1 @@ -sso-1 +0.7.1-rc0 diff --git a/examples/leaderboard/.meteor/release b/examples/leaderboard/.meteor/release index b6e63167d2..405b344e0e 100644 --- a/examples/leaderboard/.meteor/release +++ b/examples/leaderboard/.meteor/release @@ -1 +1 @@ -0.7.0.1 +0.7.1-rc0 diff --git a/examples/parties/.meteor/release b/examples/parties/.meteor/release index b6e63167d2..405b344e0e 100644 --- a/examples/parties/.meteor/release +++ b/examples/parties/.meteor/release @@ -1 +1 @@ -0.7.0.1 +0.7.1-rc0 diff --git a/examples/todos/.meteor/release b/examples/todos/.meteor/release index b6e63167d2..405b344e0e 100644 --- a/examples/todos/.meteor/release +++ b/examples/todos/.meteor/release @@ -1 +1 @@ -0.7.0.1 +0.7.1-rc0 diff --git a/examples/wordplay/.meteor/release b/examples/wordplay/.meteor/release index b6e63167d2..405b344e0e 100644 --- a/examples/wordplay/.meteor/release +++ b/examples/wordplay/.meteor/release @@ -1 +1 @@ -0.7.0.1 +0.7.1-rc0 From 927b0a06fc9b78d8b8b032981619885050928d3d Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 17 Feb 2014 10:07:44 -0800 Subject: [PATCH 07/99] Update History.md header --- History.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index e9f4358d6e..62961e1ddb 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,4 @@ -## v.NEXT +## v.0.7.1 * Meteor developer accounts - `accounts-meteor-developer` package for OAuth support From bdd9e7e4b409268443796c1961ce7f7031b090b2 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 17 Feb 2014 10:52:42 -0800 Subject: [PATCH 08/99] Add test for deferred registration with API token URL --- tools/tests/registration.js | 86 ++++++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/tools/tests/registration.js b/tools/tests/registration.js index 8dc1d858e1..21bbf084d2 100644 --- a/tools/tests/registration.js +++ b/tools/tests/registration.js @@ -21,6 +21,30 @@ var ddpConnect = function (url) { return DDP.connect(url); }; +var registrationUrlRegexp = + /https:\/\/www\.meteor\.com\/setPassword\?([a-zA-Z0-9\+\/]+)/; + +// Given a registration token created by doing a deferred registration +// with `email`, makes a DDP connection to the accounts server and +// finishes the registration process. +var registerWithToken = function (token, username, password, email) { + // XXX It might make more sense to hard-code the DDP url to + // https://www.meteor.com, since that's who the sandboxes are talking + // to. + var accountsConn = ddpConnect(config.getAuthDDPUrl()); + var registrationTokenInfo = accountsConn.call('registrationTokenInfo', + token); + var registrationCode = registrationTokenInfo.code; + accountsConn.call('register', { + username: username, + password: password, + emails: [email], + token: token, + code: registrationCode + }); + accountsConn.close(); +}; + // Polls a guerrillamail.com inbox every 3 seconds looking for an email // that matches the given subject and body regexes. This could fail if // there is someone else polling this same inbox, so use a random email @@ -96,7 +120,7 @@ var waitForEmail = selftest.markStack(function (inbox, subjectRegExp, return match; }); -selftest.define('deferred registration', ['net'], function () { +selftest.define('deferred registration - email registration token', ['net', 'slow'], function () { var s = new Sandbox; s.createApp('deployapp', 'empty'); s.cd('deployapp'); @@ -112,7 +136,10 @@ selftest.define('deferred registration', ['net'], function () { run.waitSecs(90); // Check that we got a prompt to set a password on meteor.com. run.matchErr('set a password'); - run.matchErr('https://www.meteor.com'); + var urlMatch = run.matchErr(registrationUrlRegexp); + if (! urlMatch || ! urlMatch[1]) { + throw new Error("Missing registration URL"); + } run.expectExit(0); // Check that we got a registration email in our inbox. @@ -121,28 +148,13 @@ selftest.define('deferred registration', ['net'], function () { // Fish out the registration token and use to it to complete // registration. - var token = /\/setPassword\?([a-zA-Z0-9\+\/]+)/. - exec(registrationEmail.bodyPage); + var token = registrationUrlRegexp.exec(registrationEmail.bodyPage); if (! token || ! token[1]) { throw new Error("No registration token in email"); } token = token[1]; - // XXX It might make more sense to hard-code the DDP url to - // https://www.meteor.com, since that's who the sandboxes are talking - // to. - var accountsConn = ddpConnect(config.getAuthDDPUrl()); - var registrationTokenInfo = accountsConn.call('registrationTokenInfo', - token); - var registrationCode = registrationTokenInfo.code; - accountsConn.call('register', { - username: username, - password: 'testtest', - emails: [email], - token: token, - code: registrationCode - }); - accountsConn.close(); + registerWithToken(token, username, 'testtest', email); // Success! We should be able to log out and log back in with our new // password. @@ -153,9 +165,43 @@ selftest.define('deferred registration', ['net'], function () { // authorization to delete our app. testUtils.cleanUpApp(s, appName); - // XXX Test the api registration URLs + // XXX Test that registration can only be done once (after using email + // url, api url fails and vice versa, and after using each of them + // using it again fails) // XXX Test that registration URLs get printed when they should // XXX Test registration while the tool is waiting on a DDP method to // return (e.g. deploy and login with an existing username that // doesn't have a password set yet) }); + +selftest.define( + 'deferred registration - api registration token', + ['net', 'slow'], + function () { + var s = new Sandbox; + s.createApp('deployapp', 'empty'); + s.cd('deployapp'); + + // Deploy an app with a new email address. + var email = testUtils.randomUserEmail(); + var username = testUtils.randomString(10); + var appName = testUtils.randomAppName(); + var run = s.run('deploy', appName); + run.waitSecs(testUtils.accountsCommandTimeoutSecs); + run.matchErr('Email:'); + run.write(email + '\n'); + run.waitSecs(90); + // Check that we got a prompt to set a password on meteor.com. + run.matchErr('set a password'); + var urlMatch = run.matchErr(registrationUrlRegexp); + if (! urlMatch || ! urlMatch.length || ! urlMatch[1]) { + throw new Error("Missing registration token"); + } + var token = urlMatch[1]; + registerWithToken(token, username, 'testtest', email); + + testUtils.logout(s); + testUtils.login(s, username, 'testtest'); + testUtils.cleanUpApp(s, appName); + } +); From 2ef499f583b008363a1304426365efb50bd15b11 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 17 Feb 2014 11:10:53 -0800 Subject: [PATCH 09/99] Test that registration tokens are invalid after registration is completed --- tools/tests/registration.js | 90 +++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/tools/tests/registration.js b/tools/tests/registration.js index 21bbf084d2..e6390e63ab 100644 --- a/tools/tests/registration.js +++ b/tools/tests/registration.js @@ -45,6 +45,43 @@ var registerWithToken = function (token, username, password, email) { accountsConn.close(); }; +var expectInvalidToken = function (token) { + // Same XXX as above: should be hardcoded to https://www.meteor.com? + var accountsConn = ddpConnect(config.getAuthDDPUrl()); + var registrationTokenInfo = accountsConn.call('registrationTokenInfo', + token); + // We should not be able to get a registration code for an invalid + // token. + if (registrationTokenInfo.valid || registrationTokenInfo.code) { + throw new Error('Expected invalid token is valid!'); + } + accountsConn.close(); +}; + +// In the sandbox `s`, create and deploy a new app with an unregistered +// email address. Returns the registration token from the printed URL in +// the deploy message. +var deployWithNewEmail = function (s, email, appName) { + s.createApp('deployapp', 'empty'); + s.cd('deployapp'); + var run = s.run('deploy', appName); + run.waitSecs(testUtils.accountsCommandTimeoutSecs); + run.matchErr('Email:'); + run.write(email + '\n'); + run.waitSecs(90); + // Check that we got a prompt to set a password on meteor.com. + run.matchErr('set a password'); + var urlMatch = run.matchErr(registrationUrlRegexp); + if (! urlMatch || ! urlMatch.length || ! urlMatch[1]) { + throw new Error("Missing registration token"); + } + var token = urlMatch[1]; + + run.expectExit(0); + + return token; +}; + // Polls a guerrillamail.com inbox every 3 seconds looking for an email // that matches the given subject and body regexes. This could fail if // there is someone else polling this same inbox, so use a random email @@ -122,25 +159,11 @@ var waitForEmail = selftest.markStack(function (inbox, subjectRegExp, selftest.define('deferred registration - email registration token', ['net', 'slow'], function () { var s = new Sandbox; - s.createApp('deployapp', 'empty'); - s.cd('deployapp'); - - // Deploy an app with a new email address. var email = testUtils.randomUserEmail(); var username = testUtils.randomString(10); var appName = testUtils.randomAppName(); - var run = s.run('deploy', appName); - run.waitSecs(testUtils.accountsCommandTimeoutSecs); - run.matchErr('Email:'); - run.write(email + '\n'); - run.waitSecs(90); - // Check that we got a prompt to set a password on meteor.com. - run.matchErr('set a password'); - var urlMatch = run.matchErr(registrationUrlRegexp); - if (! urlMatch || ! urlMatch[1]) { - throw new Error("Missing registration URL"); - } - run.expectExit(0); + + var apiToken = deployWithNewEmail(s, email, appName); // Check that we got a registration email in our inbox. var registrationEmail = waitForEmail(email, /Set a password/, @@ -165,9 +188,10 @@ selftest.define('deferred registration - email registration token', ['net', 'slo // authorization to delete our app. testUtils.cleanUpApp(s, appName); - // XXX Test that registration can only be done once (after using email - // url, api url fails and vice versa, and after using each of them - // using it again fails) + // All the tokens we got should now be invalid. + expectInvalidToken(token); + expectInvalidToken(apiToken); + // XXX Test that registration URLs get printed when they should // XXX Test registration while the tool is waiting on a DDP method to // return (e.g. deploy and login with an existing username that @@ -179,29 +203,27 @@ selftest.define( ['net', 'slow'], function () { var s = new Sandbox; - s.createApp('deployapp', 'empty'); - s.cd('deployapp'); - // Deploy an app with a new email address. var email = testUtils.randomUserEmail(); var username = testUtils.randomString(10); var appName = testUtils.randomAppName(); - var run = s.run('deploy', appName); - run.waitSecs(testUtils.accountsCommandTimeoutSecs); - run.matchErr('Email:'); - run.write(email + '\n'); - run.waitSecs(90); - // Check that we got a prompt to set a password on meteor.com. - run.matchErr('set a password'); - var urlMatch = run.matchErr(registrationUrlRegexp); - if (! urlMatch || ! urlMatch.length || ! urlMatch[1]) { - throw new Error("Missing registration token"); - } - var token = urlMatch[1]; + var token = deployWithNewEmail(s, email, appName); registerWithToken(token, username, 'testtest', email); testUtils.logout(s); testUtils.login(s, username, 'testtest'); testUtils.cleanUpApp(s, appName); + + // All tokens we received should not be invalid. + expectInvalidToken(token); + var registrationEmail = waitForEmail(email, /Set a password/, + /set a password/, 60); + var emailToken = registrationUrlRegexp.exec( + registrationEmail.bodyPage + ); + if (! emailToken || ! emailToken[1]) { + throw new Error('No registration token in email'); + } + expectInvalidToken(emailToken[1]); } ); From 784a14de5b28158cef3bfc393d1acf95c93a1853 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 17 Feb 2014 11:47:58 -0800 Subject: [PATCH 10/99] Add test for registering after logging out --- tools/tests/registration.js | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tools/tests/registration.js b/tools/tests/registration.js index e6390e63ab..d47edbb20e 100644 --- a/tools/tests/registration.js +++ b/tools/tests/registration.js @@ -227,3 +227,58 @@ selftest.define( expectInvalidToken(emailToken[1]); } ); + +selftest.define( + 'deferred registration - register after logging out', + ['net', 'slow'], + function () { + var s = new Sandbox; + var email = testUtils.randomUserEmail(); + var username = testUtils.randomString(10); + var appName = testUtils.randomAppName(); + var token = deployWithNewEmail(s, email, appName); + testUtils.logout(s); + + // If we deploy again with the same email address after logging out, + // we should get a message telling us to check our email and + // register, and the tool should obediently wait for us to do that + // before doing the deploy. + s.createApp('deployapp2', 'empty'); + s.cd('deployapp2'); + var run = s.run('deploy', appName); + run.waitSecs(testUtils.accountsCommandTimeoutSecs); + run.matchErr('Email:'); + run.write(email + '\n'); + run.waitSecs(testUtils.accountsCommandTimeoutSecs); + run.matchErr('already in use'); + run.matchErr('come back here to deploy your app'); + + var registrationEmail = waitForEmail( + email, + /Set a password/, + /You previously created a Meteor developer account/, + 60 + ); + token = registrationUrlRegexp.exec( + registrationEmail.bodyPage + ); + if (! token || ! token[1]) { + throw new Error('No registration token in email'); + } + + registerWithToken(token[1], username, 'testtest', email); + run.waitSecs(testUtils.accountsCommandTimeoutSecs); + run.matchErr('log in with your new password'); + run.matchErr('Password:'); + run.write('testtest\n'); + run.waitSecs(90); + run.match('Now serving at'); + run.expectExit(0); + + run = s.run('whoami'); + run.read(username); + run.expectExit(0); + + testUtils.cleanUpApp(s, appName); + } +); From e6f6fce4a2d5da33ff03dd34dbea6d633a4475d6 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 17 Feb 2014 15:17:07 -0800 Subject: [PATCH 11/99] Clone DDP data as it goes into the merge box Fixes #1750. --- History.md | 2 ++ packages/livedata/livedata_server.js | 4 ++++ packages/livedata/livedata_tests.js | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/History.md b/History.md index 62961e1ddb..fe9bf61b5e 100644 --- a/History.md +++ b/History.md @@ -152,6 +152,8 @@ * User-supplied connect handlers now see the URL's full path, even if ROOT_URL contains a non-empty path. +* Don't cache direct references to the fields arguments to the subscription + `added` and `changed` methods. #1750 ## v0.7.0.1 diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index 77e7a1b0b4..95ddab9e6f 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -68,6 +68,10 @@ _.extend(SessionDocumentView.prototype, { // Publish API ignores _id if present in fields if (key === "_id") return; + + // Don't share state with the data passed in by the user. + value = EJSON.clone(value); + if (!_.has(self.dataByKey, key)) { self.dataByKey[key] = [{subscriptionHandle: subscriptionHandle, value: value}]; diff --git a/packages/livedata/livedata_tests.js b/packages/livedata/livedata_tests.js index 1fe7ce9702..65736d76f8 100644 --- a/packages/livedata/livedata_tests.js +++ b/packages/livedata/livedata_tests.js @@ -691,6 +691,31 @@ if (Meteor.isServer) { ]); })(); +if (Meteor.isServer) { + Meteor.publish("publisherCloning", function () { + var self = this; + var fields = {x: {y: 42}}; + self.added("publisherCloning", "a", fields); + fields.x.y = 43; + self.changed("publisherCloning", "a", fields); + self.ready(); + }); +} else { + var PublisherCloningCollection = new Meteor.Collection("publisherCloning"); + testAsyncMulti("livedata - publish callbacks clone", [ + function (test, expect) { + Meteor.subscribe("publisherCloning", {normal: 1}, { + onReady: expect(function () { + test.equal(PublisherCloningCollection.findOne(), { + _id: "a", + x: {y: 43}}); + }), + onError: failure() + }); + } + ]); +} + // XXX some things to test in greater detail: // staying in simulation mode From 418f560c4698fee423b44b254eebc76e60f459cd Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 17 Feb 2014 15:18:50 -0800 Subject: [PATCH 12/99] Tweak header in History --- History.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index fe9bf61b5e..abbb7def27 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,6 @@ -## v.0.7.1 +## v.NEXT + +## v0.7.1 * Meteor developer accounts - `accounts-meteor-developer` package for OAuth support From b0efd08c5ded16dd3c29a779588f7aeabe4822d0 Mon Sep 17 00:00:00 2001 From: Robert Dickert Date: Mon, 17 Feb 2014 15:35:50 -0800 Subject: [PATCH 13/99] Put `/node_modules` in package root on build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #1761. Package code pulled from a git repo does not contain the directory `/node_modules`, as these modules are downloaded during the build process. However, this can confuse NPM prior to build completion. If the package directory doesn’t have a '/node_modules' subdirectory, NPM will assume we are not at the package root and will search the directory's parents for one. It will then set the root to any ancestor that does have a '/node_modules' subdir. This can cause the build to fail for downloaded user packages and also Meteor itself if run from checkout. See also #927. --- History.md | 3 +++ tools/meteor-npm.js | 13 +++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index abbb7def27..e39f0691dd 100644 --- a/History.md +++ b/History.md @@ -113,6 +113,9 @@ * Do a better job of handling shrinkwrap files when a npm module depends on something that isn't a semver. #1684 +* Fix failures updating npm dependencies when a node_modules directory exists + above the project directory. #1761 + * In email package, print a message in dev mode when email is not sent. #1196 * Meteor accounts logins (or anything else using the `localstorage` package) no diff --git a/tools/meteor-npm.js b/tools/meteor-npm.js index cfd0b1062f..a137888836 100644 --- a/tools/meteor-npm.js +++ b/tools/meteor-npm.js @@ -195,7 +195,8 @@ var updateExistingNpmDirectory = function (packageName, newPackageNpmDir, // We need to rebuild all node modules when the Node version // changes, in case there are some binary ones. Technically this is // racey, but it shouldn't fail very often. - if (fs.existsSync(path.join(packageNpmDir, 'node_modules'))) { + var nodeModulesDir = path.join(packageNpmDir, 'node_modules'); + if (fs.existsSync(nodeModulesDir)) { var oldNodeVersion; try { oldNodeVersion = fs.readFileSync( @@ -209,9 +210,17 @@ var updateExistingNpmDirectory = function (packageName, newPackageNpmDir, } if (oldNodeVersion !== process.version) - files.rm_recursive(path.join(packageNpmDir, 'node_modules')); + files.rm_recursive(nodeModulesDir); } + // Make sure node_modules is present (fix for #1761). Prevents npm install + // from installing to an existing node_modules dir higher up in the + // filesystem. node_modules may be absent due to a change in Node version or + // when `meteor add`ing a cloned package for the first time (node_modules is + // excluded by .gitignore) + if (! fs.existsSync(nodeModulesDir)) + fs.mkdirSync(nodeModulesDir); + var installedDependencies = getInstalledDependencies(packageNpmDir); // If we already have the right things installed, life is good. From 3d5feb053549fda82f56684304c6fde87bc1821a Mon Sep 17 00:00:00 2001 From: Tim Haines Date: Mon, 17 Feb 2014 15:48:29 -0800 Subject: [PATCH 14/99] Allow _sockjsOptions to be provided on DDP.connect This is an unsupported API that may later be removed. Fixes #1762. --- packages/livedata/livedata_connection.js | 4 +++- packages/livedata/stream_client_common.js | 4 ++++ packages/livedata/stream_client_sockjs.js | 9 +++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index b6756ecae7..30ff1ef377 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -10,6 +10,7 @@ if (Meteor.isServer) { // reloadWithOutstanding: is it OK to reload if there are outstanding methods? // headers: extra headers to send on the websockets connection, for // server-to-server DDP only +// _sockjsOptions: Specifies options to pass through to the sockjs client // onDDPNegotiationVersionFailure: callback when version negotiation fails. // // XXX There should be a way to destroy a DDP connection, causing all @@ -47,7 +48,8 @@ var Connection = function (url, options) { } else { self._stream = new LivedataTest.ClientStream(url, { retry: options.retry, - headers: options.headers + headers: options.headers, + _sockjsOptions: options._sockjsOptions }); } diff --git a/packages/livedata/stream_client_common.js b/packages/livedata/stream_client_common.js index e4fde2f1ec..6f551ca424 100644 --- a/packages/livedata/stream_client_common.js +++ b/packages/livedata/stream_client_common.js @@ -137,6 +137,10 @@ _.extend(LivedataTest.ClientStream.prototype, { self._changeUrl(options.url); } + if (options._sockjsOptions) { + self.options._sockjsOptions = options._sockjsOptions; + } + if (self.currentStatus.connected) { if (options._force || options.url) { // force reconnect. diff --git a/packages/livedata/stream_client_sockjs.js b/packages/livedata/stream_client_sockjs.js index d99889f339..3f42a76371 100644 --- a/packages/livedata/stream_client_sockjs.js +++ b/packages/livedata/stream_client_sockjs.js @@ -147,13 +147,14 @@ _.extend(LivedataTest.ClientStream.prototype, { var self = this; self._cleanup(); // cleanup the old socket, if there was one. + var options = _.extend({ + protocols_whitelist:self._sockjsProtocolsWhitelist() + }, self.options._sockjsOptions); + // Convert raw URL to SockJS URL each time we open a connection, so that we // can connect to random hostnames and get around browser per-host // connection limits. - self.socket = new SockJS( - toSockjsUrl(self.rawUrl), undefined, { - debug: false, protocols_whitelist: self._sockjsProtocolsWhitelist() - }); + self.socket = new SockJS(toSockjsUrl(self.rawUrl), undefined, options); self.socket.onopen = function (data) { self._connected(); }; From 39b61b4a417df16415a2cdee330067e002371075 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 17 Feb 2014 15:53:23 -0800 Subject: [PATCH 15/99] Mention that run-command is gone --- History.md | 1 + 1 file changed, 1 insertion(+) diff --git a/History.md b/History.md index e39f0691dd..029de186d1 100644 --- a/History.md +++ b/History.md @@ -160,6 +160,7 @@ * Don't cache direct references to the fields arguments to the subscription `added` and `changed` methods. #1750 +* Remove undocumented run-command command. ## v0.7.0.1 From 8b25a5b045f244af9deeeb1ac28a0f9013b5c535 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 17 Feb 2014 19:47:27 -0800 Subject: [PATCH 16/99] Merge branch 'restore-run-command' into devel --- tools/commands.js | 61 +++++++++++++++++++ tools/help.txt | 19 ++++++ .../apps/with-command/.meteor/.gitignore | 1 + .../tests/apps/with-command/.meteor/packages | 4 ++ tools/tests/apps/with-command/.meteor/release | 1 + .../apps/with-command/packages/command/foo.js | 5 ++ .../with-command/packages/command/package.js | 4 ++ tools/tests/run-command.js | 18 ++++++ 8 files changed, 113 insertions(+) create mode 100644 tools/tests/apps/with-command/.meteor/.gitignore create mode 100644 tools/tests/apps/with-command/.meteor/packages create mode 100644 tools/tests/apps/with-command/.meteor/release create mode 100644 tools/tests/apps/with-command/packages/command/foo.js create mode 100644 tools/tests/apps/with-command/packages/command/package.js create mode 100644 tools/tests/run-command.js diff --git a/tools/commands.js b/tools/commands.js index 6b9a788193..5992657887 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -6,6 +6,7 @@ var files = require('./files.js'); var deploy = require('./deploy.js'); var library = require('./library.js'); var buildmessage = require('./buildmessage.js'); +var unipackage = require('./unipackage.js'); var project = require('./project.js'); var warehouse = require('./warehouse.js'); var auth = require('./auth.js'); @@ -1133,6 +1134,66 @@ main.registerCommand({ }); +/////////////////////////////////////////////////////////////////////////////// +// run-command +/////////////////////////////////////////////////////////////////////////////// + +main.registerCommand({ + name: 'run-command', + hidden: true, + minArgs: 1, + maxArgs: Infinity +}, function (options) { + var library = release.current.library; + + if (! fs.existsSync(options.args[0]) || + ! fs.statSync(options.args[0]).isDirectory()) { + process.stderr.write(options.args[0] + ": not a directory\n"); + return 1; + } + + // Build and load the package + var world, packageName; + var messages = buildmessage.capture( + { title: "building the program" }, function () { + // Make the directory visible as a package. Derive the last + // package name from the last component of the directory, and + // bail out if that creates a conflict. + var packageDir = path.resolve(options.args[0]); + packageName = path.basename(packageDir) + "-tool"; + if (library.get(packageName, false)) { + buildmessage.error("'" + packageName + + "' conflicts with the name " + + "of a package in the library"); + } + library.override(packageName, packageDir); + + world = unipackage.load({ + library: library, + packages: [ packageName ], + release: release.current.name + }); + }); + if (messages.hasMessages()) { + process.stderr.write(messages.formatMessages()); + return 1; + } + + if (typeof world[packageName].main !== "function") { + process.stderr.write("Package does not define a main() function.\n"); + return 1; + } + + var ret = world[packageName].main(options.args.slice(1)); + // let exceptions propagate and get printed by node + if (ret === undefined) + ret = 0; + if (typeof ret !== "number") + ret = 1; + ret = +ret; // cast to integer + return ret; +}); + /////////////////////////////////////////////////////////////////////////////// // login /////////////////////////////////////////////////////////////////////////////// diff --git a/tools/help.txt b/tools/help.txt index 4e9b57b8d4..068e489cba 100644 --- a/tools/help.txt +++ b/tools/help.txt @@ -264,6 +264,25 @@ You should never need to use this command. It is intended for use while debugging the Meteor packaging tools themselves. +>>> run-command +Build and run a command-line tool +Usage: meteor run-command [arguments..] + +Builds the provided directory as a package, then loads the package and +calls the main() function inside the package. The function will receive +any remaining arguments. The exit status will be the return value of +main() (which is called inside a fiber). + +The arguments will be parsed by Meteor's option parser, which means that +--release will be effective (but not passed to the command), and that it will be +an error to pass any unknown options. If you want to pass options to your tool, +place them after a '--' argument (which turns off option parsing for the rest of +the arguments). + +This command is for temporary, internal use, until we have a more mature +system for building standalone command-line programs with Meteor. + + >>> login Log in to your Meteor account Usage: meteor login [--email] [--galaxy ] diff --git a/tools/tests/apps/with-command/.meteor/.gitignore b/tools/tests/apps/with-command/.meteor/.gitignore new file mode 100644 index 0000000000..4083037423 --- /dev/null +++ b/tools/tests/apps/with-command/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/tools/tests/apps/with-command/.meteor/packages b/tools/tests/apps/with-command/.meteor/packages new file mode 100644 index 0000000000..301b942bde --- /dev/null +++ b/tools/tests/apps/with-command/.meteor/packages @@ -0,0 +1,4 @@ +# Meteor packages used by this project, one per line. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. diff --git a/tools/tests/apps/with-command/.meteor/release b/tools/tests/apps/with-command/.meteor/release new file mode 100644 index 0000000000..621e94f0ec --- /dev/null +++ b/tools/tests/apps/with-command/.meteor/release @@ -0,0 +1 @@ +none diff --git a/tools/tests/apps/with-command/packages/command/foo.js b/tools/tests/apps/with-command/packages/command/foo.js new file mode 100644 index 0000000000..0d1fd9f1b1 --- /dev/null +++ b/tools/tests/apps/with-command/packages/command/foo.js @@ -0,0 +1,5 @@ +// For testing meteor run-command. +main = function (argv) { + console.log("argv", argv); + return 17; +}; diff --git a/tools/tests/apps/with-command/packages/command/package.js b/tools/tests/apps/with-command/packages/command/package.js new file mode 100644 index 0000000000..1471b66aa2 --- /dev/null +++ b/tools/tests/apps/with-command/packages/command/package.js @@ -0,0 +1,4 @@ +Package.on_use(function (api) { + api.add_files('foo.js'); + api.export('main', 'server'); +}); diff --git a/tools/tests/run-command.js b/tools/tests/run-command.js new file mode 100644 index 0000000000..80cb8c2abd --- /dev/null +++ b/tools/tests/run-command.js @@ -0,0 +1,18 @@ +var selftest = require('../selftest.js'); +var Sandbox = selftest.Sandbox; + +selftest.define("run-command", function () { + var s = new Sandbox; + var run; + + s.createApp("myapp", "with-command"); + run = s.run("run-command", "myapp/packages/command"); + run.read("argv []\n"); + run.expectEnd(); + run.expectExit(17); + + run = s.run("run-command", "myapp/packages/command", "x", "--", "-f", "--bla"); + run.read("argv [ 'x', '-f', '--bla' ]\n"); + run.expectEnd(); + run.expectExit(17); +}); From 977156db7fa723d06f5532fc1e7977ecb2ef8732 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 17 Feb 2014 19:48:43 -0800 Subject: [PATCH 17/99] Remove removal from History --- History.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/History.md b/History.md index 029de186d1..0c95fd5ecf 100644 --- a/History.md +++ b/History.md @@ -160,8 +160,6 @@ * Don't cache direct references to the fields arguments to the subscription `added` and `changed` methods. #1750 -* Remove undocumented run-command command. - ## v0.7.0.1 * Two fixes to `meteor run` Mongo startup bugs that could lead to hangs with the From 64ce16830656b27830e55523bf3686adb96b70c5 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 17 Feb 2014 20:01:27 -0800 Subject: [PATCH 18/99] Allow setting nested field named _id Disallow any mod on (top-level) _id, not just $sets that change the _id. Fixes #1794. --- packages/minimongo/minimongo_tests.js | 5 +++++ packages/minimongo/modify.js | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/minimongo/minimongo_tests.js b/packages/minimongo/minimongo_tests.js index 4bb58d2b94..ef2fed60b1 100644 --- a/packages/minimongo/minimongo_tests.js +++ b/packages/minimongo/minimongo_tests.js @@ -1949,6 +1949,7 @@ Tinytest.add("minimongo - modify", function (test) { modify({a: [1, 2]}, {$inc: {'a.3': 10}}, {a: [1, 2, null, 10]}); modify({a: {b: 2}}, {$inc: {'a.b': 10}}, {a: {b: 12}}); modify({a: {b: 2}}, {$inc: {'a.c': 10}}, {a: {b: 2, c: 10}}); + exception({}, {$inc: {_id: 1}}); // $set modify({a: 1, b: 2}, {$set: {a: 10}}, {a: 10, b: 2}); @@ -1960,6 +1961,9 @@ Tinytest.add("minimongo - modify", function (test) { modify({a: [1], b: 2}, {$set: {'a.1': 9}}, {a: [1, 9], b: 2}); modify({a: [1], b: 2}, {$set: {'a.2': 9}}, {a: [1, null, 9], b: 2}); modify({a: {b: 1}}, {$set: {'a.c': 9}}, {a: {b: 1, c: 9}}); + modify({}, {$set: {'x._id': 4}}, {x: {_id: 4}}); + exception({}, {$set: {_id: 4}}); + exception({_id: 4}, {$set: {_id: 4}}); // even not-changing _id is bad // $unset modify({}, {$unset: {a: 1}}, {}); @@ -1977,6 +1981,7 @@ Tinytest.add("minimongo - modify", function (test) { modify({a: {b: 1}}, {$unset: {'a.b.c.d': 1}}, {a: {b: 1}}); modify({a: {b: 1}}, {$unset: {'a.x.c.d': 1}}, {a: {b: 1}}); modify({a: {b: {c: 1}}}, {$unset: {'a.b.c': 1}}, {a: {b: {}}}); + exception({}, {$unset: {_id: 1}}); // $push modify({}, {$push: {a: 1}}, {a: [1]}); diff --git a/packages/minimongo/modify.js b/packages/minimongo/modify.js index ec768fd11b..3b95e5363f 100644 --- a/packages/minimongo/modify.js +++ b/packages/minimongo/modify.js @@ -47,6 +47,9 @@ LocalCollection._modify = function (doc, mod, options) { throw MinimongoError( "Invalid mod field name, may not end in a period"); + if (keypath === '_id') + throw MinimongoError("Mod on _id not allowed"); + var keyparts = keypath.split('.'); var noCreate = _.has(NO_CREATE_MODIFIERS, op); var forbidArray = (op === "$rename"); @@ -194,9 +197,6 @@ var MODIFIERS = { e.setPropertyError = true; throw e; } - if (field === '_id' && !EJSON.equals(arg, target._id)) - throw MinimongoError("Cannot change the _id of a document"); - target[field] = EJSON.clone(arg); }, $setOnInsert: function (target, field, arg) { From a2355179dee1b4e1a3e32c1719742b16e83087d1 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 17 Feb 2014 20:03:24 -0800 Subject: [PATCH 19/99] History update for nested _id modifier --- History.md | 1 + 1 file changed, 1 insertion(+) diff --git a/History.md b/History.md index 0c95fd5ecf..ea3b9c3ef0 100644 --- a/History.md +++ b/History.md @@ -30,6 +30,7 @@ - {$type: 4} queries - optimize `remove({})` when observers are paused - make update-by-id constant time + - allow {$set: {'x._id': 1}} #1794 * Add `clientAddress` and `httpHeaders` to `this.connection` in method calls and publish functions. From b983537592f0471782a2d84eef21fbb9de32236c Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Tue, 4 Feb 2014 13:31:55 -0800 Subject: [PATCH 20/99] Revert "Do less deep copies" This reverts commit cb2f2adb1bd1a88a9fd651fe797592e2cd725941. --- packages/mongo-livedata/oplog_observe_driver.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/mongo-livedata/oplog_observe_driver.js b/packages/mongo-livedata/oplog_observe_driver.js index face62ccbb..d3d9796eda 100644 --- a/packages/mongo-livedata/oplog_observe_driver.js +++ b/packages/mongo-livedata/oplog_observe_driver.js @@ -144,7 +144,7 @@ _.extend(OplogObserveDriver.prototype, { + EJSON.stringify(self._cursorDescription)); } - var inCollection = !!self._collection.find(id).count(); + var inCollection = !!self._collection.findOne(id); if (matchesNow && !inCollection) { // It matches the selector and it isn't in our collection, so add it. @@ -250,7 +250,7 @@ _.extend(OplogObserveDriver.prototype, { if (op.op === 'd') { self._remove(id); } else if (op.op === 'i') { - if (self._collection.find(id).count()) + if (self._collection.findOne(id)) throw new Error("insert found for already-existing ID"); // XXX what if selector yields? for now it can't but later it could have @@ -279,9 +279,7 @@ _.extend(OplogObserveDriver.prototype, { // this directly. // XXX just send the modifier to _collection.update? but then // we don't necessarily get to GC - - // We can avoid another deep clone here since the findOne above would - // return a copy anyways + newDoc = EJSON.clone(newDoc); LocalCollection._modify(newDoc, op.o); self._handleDoc(id, newDoc); } else if (!canDirectlyModifyDoc || From 79b03cd8b4ed834d324bb7cde57565d2021f5a8c Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Tue, 4 Feb 2014 13:32:03 -0800 Subject: [PATCH 21/99] Revert "Merge branch 'oplog-localcollection' into devel" This reverts commit 297b97659b0ca71dd2065242e6d455c7581f4667, reversing changes made to 204959b5092265455abc2482d17622a8aa3d7b9c. --- packages/meteor/fiber_stubs_client.js | 68 ++++++++ packages/meteor/package.js | 1 - packages/meteor/unyielding_queue.js | 72 --------- packages/minimongo/minimongo.js | 31 +--- packages/minimongo/sort.js | 18 +-- .../mongo-livedata/local_collection_driver.js | 2 +- .../mongo-livedata/oplog_observe_driver.js | 151 +++++++++--------- 7 files changed, 158 insertions(+), 185 deletions(-) delete mode 100644 packages/meteor/unyielding_queue.js diff --git a/packages/meteor/fiber_stubs_client.js b/packages/meteor/fiber_stubs_client.js index 46163a26e1..3babd0e08f 100644 --- a/packages/meteor/fiber_stubs_client.js +++ b/packages/meteor/fiber_stubs_client.js @@ -6,3 +6,71 @@ Meteor._noYieldsAllowed = function (f) { return f(); }; + +// An even simpler queue of tasks than the fiber-enabled one. This one just +// runs all the tasks when you call runTask or flush, synchronously. +// +Meteor._SynchronousQueue = function () { + var self = this; + self._tasks = []; + self._running = false; +}; + +_.extend(Meteor._SynchronousQueue.prototype, { + runTask: function (task) { + var self = this; + if (!self.safeToRunTask()) + throw new Error("Could not synchronously run a task from a running task"); + self._tasks.push(task); + var tasks = self._tasks; + self._tasks = []; + self._running = true; + try { + while (!_.isEmpty(tasks)) { + var t = tasks.shift(); + try { + t(); + } catch (e) { + if (_.isEmpty(tasks)) { + // this was the last task, that is, the one we're calling runTask + // for. + throw e; + } else { + Meteor._debug("Exception in queued task: " + e.stack); + } + } + } + } finally { + self._running = false; + } + }, + + queueTask: function (task) { + var self = this; + var wasEmpty = _.isEmpty(self._tasks); + self._tasks.push(task); + // Intentionally not using Meteor.setTimeout, because it doesn't like runing + // in stubs for now. + if (wasEmpty) + setTimeout(_.bind(self.flush, self), 0); + }, + + flush: function () { + var self = this; + self.runTask(function () {}); + }, + + drain: function () { + var self = this; + if (!self.safeToRunTask()) + return; + while (!_.isEmpty(self._tasks)) { + self.flush(); + } + }, + + safeToRunTask: function () { + var self = this; + return !self._running; + } +}); diff --git a/packages/meteor/package.js b/packages/meteor/package.js index 9e2db6e68e..439d80e6f2 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -23,7 +23,6 @@ Package.on_use(function (api) { api.add_files('errors.js', ['client', 'server']); api.add_files('fiber_helpers.js', 'server'); api.add_files('fiber_stubs_client.js', 'client'); - api.add_files('unyielding_queue.js'); api.add_files('startup_client.js', ['client']); api.add_files('startup_server.js', ['server']); api.add_files('debug.js', ['client', 'server']); diff --git a/packages/meteor/unyielding_queue.js b/packages/meteor/unyielding_queue.js deleted file mode 100644 index 95c62b23a4..0000000000 --- a/packages/meteor/unyielding_queue.js +++ /dev/null @@ -1,72 +0,0 @@ -// A simpler version of Meteor._SynchronousQueue with the same external -// interface. It runs on both client and server, unlike _SynchronousQueue which -// only runs on the server. When used on the server, tasks may not yield. This -// one just runs all the tasks when you call runTask or flush, synchronously. -// It itself also does not yield. -// -Meteor._UnyieldingQueue = function () { - var self = this; - self._tasks = []; - self._running = false; -}; - -_.extend(Meteor._UnyieldingQueue.prototype, { - runTask: function (task) { - var self = this; - if (!self.safeToRunTask()) - throw new Error("Could not synchronously run a task from a running task"); - self._tasks.push(task); - var tasks = self._tasks; - self._tasks = []; - self._running = true; - try { - while (!_.isEmpty(tasks)) { - var t = tasks.shift(); - try { - Meteor._noYieldsAllowed(function () { - t(); - }); - } catch (e) { - if (_.isEmpty(tasks)) { - // this was the last task, that is, the one we're calling runTask - // for. - throw e; - } else { - Meteor._debug("Exception in queued task: " + e.stack); - } - } - } - } finally { - self._running = false; - } - }, - - queueTask: function (task) { - var self = this; - var wasEmpty = _.isEmpty(self._tasks); - self._tasks.push(task); - // Intentionally not using Meteor.setTimeout, because it doesn't like runing - // in stubs for now. - if (wasEmpty) - setTimeout(_.bind(self.flush, self), 0); - }, - - flush: function () { - var self = this; - self.runTask(function () {}); - }, - - drain: function () { - var self = this; - if (!self.safeToRunTask()) - return; - while (!_.isEmpty(self._tasks)) { - self.flush(); - } - }, - - safeToRunTask: function () { - var self = this; - return !self._running; - } -}); diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index aa99c091da..de7134fedb 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -7,34 +7,13 @@ // ObserveHandle: the return value of a live query. -LocalCollection = function (options) { +LocalCollection = function (name) { var self = this; - options = options || {}; - - self.name = options.name; + self.name = name; // _id -> document (also containing id) self._docs = new LocalCollection._IdMap; - // When writing to this collection, we batch all observeChanges callbacks - // until the end of the write, and run them at this point. On the server, we - // use a single SynchronousQueue to do so, so that we never deliver callbacks - // out of order even if other writes occur during a yield. On the client, or - // on the server if we promise that our callbacks will never yield via an - // undocumented option, we use the simpler UnyieldingQueue. - // - // (What is the _observeCallbacksWillNeverYield option for? In some cases, it - // can be nice (on the server) to be able to write to a LocalCollection - // without yielding (eg, in a _noYieldsAllowed block). It's necessary to - // provide non-yielding allow callbacks in that case, but just doing that - // wouldn't be good enough if we always used SynchronousQueue on the server, - // since it tends to yield in order to run even non-yielding callbacks.) - var queueClass; - if (Meteor._SynchronousQueue && !options._observeCallbacksWillNeverYield) { - queueClass = Meteor._SynchronousQueue; - } else { - queueClass = Meteor._UnyieldingQueue; - } - self._observeQueue = new queueClass(); + self._observeQueue = new Meteor._SynchronousQueue(); self.next_qid = 1; // live query id generator @@ -47,8 +26,8 @@ LocalCollection = function (options) { // selector, sorter, (callbacks): functions self.queries = {}; - // null if not saving originals; an IdMap from id to original document value - // if saving originals. See comments before saveOriginals(). + // null if not saving originals; an IdMap from id to original document value if + // saving originals. See comments before saveOriginals(). self._savedOriginals = null; // True when observers are paused and we should not send callbacks. diff --git a/packages/minimongo/sort.js b/packages/minimongo/sort.js index 02218593ec..718ec1537e 100644 --- a/packages/minimongo/sort.js +++ b/packages/minimongo/sort.js @@ -48,19 +48,11 @@ Sorter = function (spec) { // min/max.) // // XXX This is actually wrong! In fact, the whole attempt to compile sort - // functions independently of selectors is wrong. In MongoDB, if you have - // documents {_id: 'x', a: [1, 10]} and {_id: 'y', a: [5, 15]}, then - // C.find({}, {sort: {a: 1}}) puts x before y (1 comes before 5). But - // C.find({a: {$gt: 3}}, {sort: {a: 1}}) puts y before x (1 does not match - // the selector, and 5 comes before 10). - // - // The way this works is pretty subtle! For example, if the documents are - // instead {_id: 'x', a: [{x: 1}, {x: 10}]}) and - // {_id: 'y', a: [{x: 5}, {x: 15}]}), - // then C.find({'a.x': {$gt: 3}}, {sort: {'a.x': 1}}) and - // C.find({a: {$elemMatch: {x: {$gt: 3}}}}, {sort: {'a.x': 1}}) - // both follow this rule (y before x). ie, you do have to apply this - // through $elemMatch. + // functions independently of selectors is wrong. In MongoDB, if you have + // documents {_id: 'x', a: [1, 10]} and {_id: 'y', a: [5, 15]}, + // then C.find({}, {sort: {a: 1}}) puts x before y (1 comes before 5). + // But C.find({a: {$gt: 3}}, {sort: {a: 1}}) puts y before x (1 does not match + // the selector, and 5 comes before 10). var reduceValue = function (branchValues, findMin) { // Expand any leaf arrays that we find, and ignore those arrays themselves. branchValues = expandArraysInBranches(branchValues, true); diff --git a/packages/mongo-livedata/local_collection_driver.js b/packages/mongo-livedata/local_collection_driver.js index aa325979e5..ec78544154 100644 --- a/packages/mongo-livedata/local_collection_driver.js +++ b/packages/mongo-livedata/local_collection_driver.js @@ -5,7 +5,7 @@ LocalCollectionDriver = function () { var ensureCollection = function (name, collections) { if (!(name in collections)) - collections[name] = new LocalCollection({name: name}); + collections[name] = new LocalCollection(name); return collections[name]; }; diff --git a/packages/mongo-livedata/oplog_observe_driver.js b/packages/mongo-livedata/oplog_observe_driver.js index d3d9796eda..2cfa2a7775 100644 --- a/packages/mongo-livedata/oplog_observe_driver.js +++ b/packages/mongo-livedata/oplog_observe_driver.js @@ -30,24 +30,13 @@ OplogObserveDriver = function (options) { self._registerPhaseChange(PHASE.QUERYING); - // A minimongo LocalCollection containing the docs that match the selector, - // and maybe more. It is guaranteed to contain all the fields needed for the - // selector and the projection, and may have other fields too. (In the future - // we may try to make this collection be shared between multiple - // OplogObserveDrivers, but not currently.) - self._collection = - new LocalCollection({_observeCallbacksWillNeverYield: true}); - // XXX think about what all the options are - var minimongoCursor = self._collection.find( - self._cursorDescription.selector, self._cursorDescription.options); - self._stopHandles.push(minimongoCursor.observeChanges(self._multiplexer)); - + self._published = new LocalCollection._IdMap; var selector = self._cursorDescription.selector; self._matcher = options.matcher; - + var projection = self._cursorDescription.options.fields || {}; + self._projectionFn = LocalCollection._compileProjection(projection); // Projection function, result of combining important fields for selector and // existing fields projection - var projection = self._cursorDescription.options.fields || {}; self._sharedProjection = self._matcher.combineIntoProjection(projection); self._sharedProjectionFn = LocalCollection._compileProjection( self._sharedProjection); @@ -120,51 +109,47 @@ OplogObserveDriver = function (options) { _.extend(OplogObserveDriver.prototype, { _add: function (doc) { var self = this; - doc = self._sharedProjectionFn(doc); - // XXX does _sharedProjection always preserve id? - if (!_.has(doc, '_id')) - throw Error("Can't add doc without _id"); - self._collection.insert(doc); + var id = doc._id; + var fields = _.clone(doc); + delete fields._id; + if (self._published.has(id)) + throw Error("tried to add something already published " + id); + self._published.set(id, self._sharedProjectionFn(fields)); + self._multiplexer.added(id, self._projectionFn(fields)); }, - _remove: function (id, options) { + _remove: function (id) { var self = this; - options = options || {}; - var removed = self._collection.remove({_id: id}); - if (options.mustExist && removed !== 1) + if (!self._published.has(id)) throw Error("tried to remove something unpublished " + id); + self._published.remove(id); + self._multiplexer.removed(id); }, _handleDoc: function (id, newDoc, mustMatchNow) { var self = this; - newDoc = _.clone(newDoc); // *shallow* clone + newDoc = _.clone(newDoc); - // XXX this is just about "matching selector", not about skip/limit var matchesNow = newDoc && self._matcher.documentMatches(newDoc).result; if (mustMatchNow && !matchesNow) { throw Error("expected " + EJSON.stringify(newDoc) + " to match " + EJSON.stringify(self._cursorDescription)); } - var inCollection = !!self._collection.findOne(id); + var matchedBefore = self._published.has(id); - if (matchesNow && !inCollection) { - // It matches the selector and it isn't in our collection, so add it. - // XXX once we add skip/limit, this may not always send an added, and - // we may need to do some GC + if (matchesNow && !matchedBefore) { self._add(newDoc); - } else if (inCollection && !matchesNow) { - // We remove this from the collection to achieve two goals: (a) causing - // the observeChanges to fire removed() and (b) saving memory. That said, - // it would be legitimate (if !!newDoc) to update the collection instead - // of removing, if we thought we might need this doc again soon. - self._remove(id, {mustExist: true}); + } else if (matchedBefore && !matchesNow) { + self._remove(id); } else if (matchesNow) { - // Replace the doc inside our collection, which may trigger a changed - // callback. - newDoc = self._sharedProjectionFn(newDoc); - // XXX does _sharedProjection always preserve id? - if (!_.has(newDoc, '_id')) - throw Error("Can't add newDoc without _id"); - self._collection.update(id, newDoc); + var oldDoc = self._published.get(id); + if (!oldDoc) + throw Error("thought that " + id + " was there!"); + delete newDoc._id; + self._published.set(id, self._sharedProjectionFn(newDoc)); + var changed = LocalCollection._makeChangedFields(_.clone(newDoc), oldDoc); + changed = self._projectionFn(changed); + if (!_.isEmpty(changed)) + self._multiplexer.changed(id, changed); } }, _fetchModifiedDocuments: function () { @@ -248,9 +233,10 @@ _.extend(OplogObserveDriver.prototype, { } if (op.op === 'd') { - self._remove(id); + if (self._published.has(id)) + self._remove(id); } else if (op.op === 'i') { - if (self._collection.findOne(id)) + if (self._published.has(id)) throw new Error("insert found for already-existing ID"); // XXX what if selector yields? for now it can't but later it could have @@ -272,22 +258,18 @@ _.extend(OplogObserveDriver.prototype, { if (isReplace) { self._handleDoc(id, _.extend({_id: id}, op.o)); - } else { - var newDoc = self._collection.findOne(id); - if (newDoc && canDirectlyModifyDoc) { - // Oh great, we actually know what the document is, so we can apply - // this directly. - // XXX just send the modifier to _collection.update? but then - // we don't necessarily get to GC - newDoc = EJSON.clone(newDoc); - LocalCollection._modify(newDoc, op.o); - self._handleDoc(id, newDoc); - } else if (!canDirectlyModifyDoc || - self._matcher.canBecomeTrueByModifier(op.o)) { - self._needToFetch.set(id, op.ts.toString()); - if (self._phase === PHASE.STEADY) - self._fetchModifiedDocuments(); - } + } else if (self._published.has(id) && canDirectlyModifyDoc) { + // Oh great, we actually know what the document is, so we can apply + // this directly. + var newDoc = EJSON.clone(self._published.get(id)); + newDoc._id = id; + LocalCollection._modify(newDoc, op.o); + self._handleDoc(id, self._sharedProjectionFn(newDoc)); + } else if (!canDirectlyModifyDoc || + self._matcher.canBecomeTrueByModifier(op.o)) { + self._needToFetch.set(id, op.ts.toString()); + if (self._phase === PHASE.STEADY) + self._fetchModifiedDocuments(); } } else { throw Error("XXX SURPRISING OPERATION: " + op); @@ -336,19 +318,18 @@ _.extend(OplogObserveDriver.prototype, { self._currentlyFetching = null; ++self._fetchGeneration; // ignore any in-flight fetches self._registerPhaseChange(PHASE.QUERYING); - self._collection.pauseObservers(); - // XXX this won't be quite correct for skip/limit - self._collection.remove({}); // Defer so that we don't block. Meteor.defer(function () { - // Insert all the documents currently found by the query. - self._cursorForQuery().forEach(function (doc) { - self._collection.insert(doc); + // subtle note: _published does not contain _id fields, but newResults + // does + var newResults = new LocalCollection._IdMap; + var cursor = self._cursorForQuery(); + cursor.forEach(function (doc) { + newResults.set(doc._id, doc); }); - // Allow observe callbacks (ie multiplexer invocations) to fire. - self._collection.resumeObservers(); + self._publishNewResults(newResults); self._doneQuerying(); }); @@ -418,6 +399,34 @@ _.extend(OplogObserveDriver.prototype, { }, + // Replace self._published with newResults (both are IdMaps), invoking observe + // callbacks on the multiplexer. + // + // XXX This is very similar to LocalCollection._diffQueryUnorderedChanges. We + // should really: (a) Unify IdMap and OrderedDict into Unordered/OrderedDict (b) + // Rewrite diff.js to use these classes instead of arrays and objects. + _publishNewResults: function (newResults) { + var self = this; + + // First remove anything that's gone. Be careful not to modify + // self._published while iterating over it. + var idsToRemove = []; + self._published.forEach(function (doc, id) { + if (!newResults.has(id)) + idsToRemove.push(id); + }); + _.each(idsToRemove, function (id) { + self._remove(id); + }); + + // Now do adds and changes. + newResults.forEach(function (doc, id) { + // "true" here means to throw if we think this doc doesn't match the + // selector. + self._handleDoc(id, doc, true); + }); + }, + // This stop function is invoked from the onStop of the ObserveMultiplexer, so // it shouldn't actually be possible to call it until the multiplexer is // ready. @@ -441,6 +450,7 @@ _.extend(OplogObserveDriver.prototype, { self._writesToCommitWhenWeReachSteady = null; // Proactively drop references to potentially big things. + self._published = null; self._needToFetch = null; self._currentlyFetching = null; self._oplogEntryHandle = null; @@ -454,9 +464,6 @@ _.extend(OplogObserveDriver.prototype, { var self = this; var now = new Date; - if (phase === self._phase) - return; - if (self._phase) { var timeDiff = now - self._phaseStartTime; Package.facts && Package.facts.Facts.incrementServerFact( From b8250ecf66bcb5eec802c298f8e74af4857feafb Mon Sep 17 00:00:00 2001 From: David Glasser Date: Mon, 27 Jan 2014 21:55:36 -0800 Subject: [PATCH 22/99] More comments about our sort inconsistency (overzealously reverted) --- packages/minimongo/sort.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/minimongo/sort.js b/packages/minimongo/sort.js index 718ec1537e..02218593ec 100644 --- a/packages/minimongo/sort.js +++ b/packages/minimongo/sort.js @@ -48,11 +48,19 @@ Sorter = function (spec) { // min/max.) // // XXX This is actually wrong! In fact, the whole attempt to compile sort - // functions independently of selectors is wrong. In MongoDB, if you have - // documents {_id: 'x', a: [1, 10]} and {_id: 'y', a: [5, 15]}, - // then C.find({}, {sort: {a: 1}}) puts x before y (1 comes before 5). - // But C.find({a: {$gt: 3}}, {sort: {a: 1}}) puts y before x (1 does not match - // the selector, and 5 comes before 10). + // functions independently of selectors is wrong. In MongoDB, if you have + // documents {_id: 'x', a: [1, 10]} and {_id: 'y', a: [5, 15]}, then + // C.find({}, {sort: {a: 1}}) puts x before y (1 comes before 5). But + // C.find({a: {$gt: 3}}, {sort: {a: 1}}) puts y before x (1 does not match + // the selector, and 5 comes before 10). + // + // The way this works is pretty subtle! For example, if the documents are + // instead {_id: 'x', a: [{x: 1}, {x: 10}]}) and + // {_id: 'y', a: [{x: 5}, {x: 15}]}), + // then C.find({'a.x': {$gt: 3}}, {sort: {'a.x': 1}}) and + // C.find({a: {$elemMatch: {x: {$gt: 3}}}}, {sort: {'a.x': 1}}) + // both follow this rule (y before x). ie, you do have to apply this + // through $elemMatch. var reduceValue = function (branchValues, findMin) { // Expand any leaf arrays that we find, and ignore those arrays themselves. branchValues = expandArraysInBranches(branchValues, true); From 9905a881f1113bfcb5ed7f248d7fbad9c14cc85a Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 17 Feb 2014 21:47:32 -0800 Subject: [PATCH 23/99] Remove an obsolete XXX --- tools/tests/registration.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/tests/registration.js b/tools/tests/registration.js index d47edbb20e..0c2a8c5940 100644 --- a/tools/tests/registration.js +++ b/tools/tests/registration.js @@ -193,9 +193,6 @@ selftest.define('deferred registration - email registration token', ['net', 'slo expectInvalidToken(apiToken); // XXX Test that registration URLs get printed when they should - // XXX Test registration while the tool is waiting on a DDP method to - // return (e.g. deploy and login with an existing username that - // doesn't have a password set yet) }); selftest.define( From c06b991b9072702dc7a345f570679b24bde0c41e Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Tue, 18 Feb 2014 11:03:54 -0800 Subject: [PATCH 24/99] Fix undefined variable in galaxy error handling --- tools/deploy-galaxy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index 021c570051..46377b5559 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -32,14 +32,14 @@ var handleError = function (error, galaxyName, messages) { var Package = getPackage(); messages = messages || {}; - if (e instanceof Package.meteor.Meteor.Error) { + if (error instanceof Package.meteor.Meteor.Error) { var msg = messages[error.error]; if (msg) process.stderr.write(msg + "\n"); else if (error.message) process.stderr.write("Denied: " + error.message + "\n"); return 1; - } else if (e instanceof ConnectionTimeoutError) { + } else if (error instanceof ConnectionTimeoutError) { // If we have an http/https URL for a galaxyName instead of a // proper galaxyName (which is what the code in this file // currently passes), strip off the scheme and trailing slash. From c0d5fed96c8e976aa7565ccdae3246f07148bc4b Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Tue, 18 Feb 2014 14:03:06 -0800 Subject: [PATCH 25/99] help text tweaks --- tools/help.txt | 52 +++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/tools/help.txt b/tools/help.txt index 068e489cba..c8fd32c737 100644 --- a/tools/help.txt +++ b/tools/help.txt @@ -218,6 +218,32 @@ site into your account. If you had set a password on the site you will be prompted for it one last time. +>>> login +Log in to your Meteor developer account +Usage: meteor login [--email] + +Prompts for your username and password and logs you in to your Meteor +developer account. Pass --email to log in by email address rather than +by username. + + +>>> logout +Log out of your Meteor developer account +Usage: meteor logout + +Log out of your Meteor developer account. + + +>>> whoami +Prints the username of your Meteor developer account +Usage: meteor whoami + +Prints the username of the currently logged-in Meteor developer. + +See 'meteor login' to log into or 'meteor logout' to log out of your +Meteor developer account. + + >>> test-packages Test one or more packages Usage: meteor test-packages [--release ] [options] [package...] @@ -283,32 +309,6 @@ This command is for temporary, internal use, until we have a more mature system for building standalone command-line programs with Meteor. ->>> login -Log in to your Meteor account -Usage: meteor login [--email] [--galaxy ] - -Prompts for your username and password and logs you in to your -Meteor account. Pass --email to log in by email address instead of -username. Pass --galaxy to specify a galaxy to log in to. - - ->>> logout -Log out of your Meteor account -Usage: meteor logout - -Log out of your Meteor account. - - ->>> whoami -Prints the username of your Meteor developer account -Usage: meteor whoami - -Prints the username of the currently logged-in Meteor developer. - -See 'meteor login' to log into or 'meteor logout' to log out of of your -Meteor account. - - >>> self-test Run tests of the 'meteor' tool. Usage: meteor self-test [--changed] [--slow] [--force-online] [--history n] From dd2b5ce7299b6f0296122f66ed988955e5e942c1 Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Tue, 18 Feb 2014 14:17:41 -0800 Subject: [PATCH 26/99] 'login' without --galaxy always prompts for username/password --- tools/commands.js | 6 +++++- tools/tests/login.js | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/tools/commands.js b/tools/commands.js index 5992657887..6eff8b91aa 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -1202,10 +1202,14 @@ main.registerCommand({ name: 'login', options: { email: { type: String }, + // Undocumented: get credentials on a specific Galaxy. Do we still + // need this? galaxy: { type: String } } }, function (options) { - return auth.loginCommand(options); + return auth.loginCommand(_.extend({ + overwriteExistingToken: true + }, options)); }); diff --git a/tools/tests/login.js b/tools/tests/login.js index 6862048982..b0ae42c57a 100644 --- a/tools/tests/login.js +++ b/tools/tests/login.js @@ -11,6 +11,20 @@ selftest.define("login", ['net'], function () { // Username and password prompts happen on stderr so that scripts can // run commands that do login interactively and still save the command // output with the login prompts appearing in it. + // + // Do this twice to confirm that the login command prints a prompt + // even if you are already logged in. + for (var i = 0; i < 2; i++) { + run = s.run("login"); + run.matchErr("Username:"); + run.write("test\n"); + run.matchErr("Password:"); + run.write("testtest\n"); + run.waitSecs(5); + run.matchErr("Logged in as test."); + run.expectExit(0); + } + run = s.run("login"); run.matchErr("Username:"); run.write("test\n"); From 653045ffec060855ae9d01ddcedbe13216fddf3e Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Tue, 18 Feb 2014 14:18:07 -0800 Subject: [PATCH 27/99] In self-test, make '--tests' into an argument and document it. --- tools/commands.js | 9 +++++---- tools/help.txt | 6 +++++- tools/selftest.js | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/tools/commands.js b/tools/commands.js index 6eff8b91aa..d787019208 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -1256,12 +1256,13 @@ main.registerCommand({ main.registerCommand({ name: 'self-test', + minArgs: 0, + maxArgs: 1, options: { changed: { type: Boolean }, 'force-online': { type: Boolean }, slow: { type: Boolean }, history: { type: Number }, - tests: { type: String } }, hidden: true }, function (options) { @@ -1279,13 +1280,13 @@ main.registerCommand({ } var testRegexp = undefined; - if (options.tests) { + if (options.args.length) { try { - testRegexp = new RegExp(options.tests); + testRegexp = new RegExp(options.args[0]); } catch (e) { if (!(e instanceof SyntaxError)) throw e; - process.stderr.write("Bad regular expression: " + options.tests + "\n"); + process.stderr.write("Bad regular expression: " + options.args[0] + "\n"); return 1; } } diff --git a/tools/help.txt b/tools/help.txt index c8fd32c737..0c9a492294 100644 --- a/tools/help.txt +++ b/tools/help.txt @@ -311,10 +311,14 @@ system for building standalone command-line programs with Meteor. >>> self-test Run tests of the 'meteor' tool. -Usage: meteor self-test [--changed] [--slow] [--force-online] [--history n] +Usage: meteor self-test [pattern] [--changed] [--slow] + [--force-online] [--history n] Runs internal tests. Exits with status 0 on success. +If 'pattern' is provided, it should be a regular expression. Only +tests that match the regular expression will be run. + Pass --changed to run only tests that have changed since they last passed. This uses a really rough heuristic: A test has changed iff there has been any change to the file in the 'selftests' subdirectory diff --git a/tools/selftest.js b/tools/selftest.js index bfabb4db87..9c91fee132 100644 --- a/tools/selftest.js +++ b/tools/selftest.js @@ -976,7 +976,7 @@ var tagDescriptions = { // these last two are not actually test tags; they reflect the use of // --changed and --tests unchanged: 'unchanged since last pass', - misnamed: "don't match --tests argument" + 'non-matching': "don't match specified pattern" }; // options: onlyChanged, offline, includeSlowTests, historyLines, testRegexp @@ -1015,7 +1015,7 @@ var runTests = function (options) { tests = _.filter(tests, function (test) { return options.testRegexp.test(test.name); }); - skipCounts.misnamed = lengthBeforeTestRegexp - tests.length; + skipCounts['non-matching'] = lengthBeforeTestRegexp - tests.length; } if (options.onlyChanged) { From bee4fbbd247c0fcd5a5bb1d11ee19d24c5ab9419 Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Tue, 18 Feb 2014 14:48:29 -0800 Subject: [PATCH 28/99] Reprompt if they enter a blank username to logs or mongo. With test. --- tools/auth.js | 15 ++++++++++----- tools/tests/login.js | 17 ++++++++++++++--- tools/tests/logs-mongo-auth.js | 18 +++++++++++++++++- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/tools/auth.js b/tools/auth.js index b679086441..f2268f364b 100644 --- a/tools/auth.js +++ b/tools/auth.js @@ -584,13 +584,18 @@ var doInteractivePasswordLogin = function (options) { return true; }; -// options are the same as for doInteractivePasswordLogin, exept without +// options are the same as for doInteractivePasswordLogin, except without // username and email. exports.doUsernamePasswordLogin = function (options) { - var username = utils.readLine({ - prompt: "Username: ", - stream: process.stderr - }); + var username; + + do { + username = utils.readLine({ + prompt: "Username: ", + stream: process.stderr + }).trim(); + } while (username.length === 0); + return doInteractivePasswordLogin(_.extend({}, options, { username: username })); diff --git a/tools/tests/login.js b/tools/tests/login.js index b0ae42c57a..c4e04b5853 100644 --- a/tools/tests/login.js +++ b/tools/tests/login.js @@ -25,14 +25,25 @@ selftest.define("login", ['net'], function () { run.expectExit(0); } + // Leaving username blank, or getting the password wrong, doesn't + // reprompt. It also doesn't log you out. + run = s.run("login"); + run.matchErr("Username:"); + run.write("\n"); + run.matchErr("Password:"); + run.write("whatever\n"); + run.waitSecs(5); + run.matchErr("failed"); + run.expectExit(1); + run = s.run("login"); run.matchErr("Username:"); run.write("test\n"); run.matchErr("Password:"); - run.write("testtest\n"); + run.write("whatever\n"); run.waitSecs(5); - run.matchErr("Logged in as test."); - run.expectExit(0); + run.matchErr("failed"); + run.expectExit(1); // XXX test login by email diff --git a/tools/tests/logs-mongo-auth.js b/tools/tests/logs-mongo-auth.js index 47f5abf0ab..778306ab55 100644 --- a/tools/tests/logs-mongo-auth.js +++ b/tools/tests/logs-mongo-auth.js @@ -68,8 +68,23 @@ var logsOrMongoForApp = function (sandbox, command, appName, options) { } else { // If we are not logged in and this is not a legacy app, then we // expect a login prompt. + // + // (If testReprompt is true, try getting reprompted as a result + // of entering no username or a bad password.) + if (options.testReprompt) { + run.matchErr('Username: '); + run.write("\n"); + run.matchErr("Username:"); + run.write(" \n"); + } run.matchErr('Username: '); run.write((options.username || 'test') + '\n'); + if (options.testReprompt) { + run.matchErr("Password:"); + run.write("wrongpassword\n"); + run.waitSecs(15); + run.matchErr("failed"); + } run.matchErr('Password: '); run.write((options.password || 'testtest') + '\n'); run.waitSecs(15); @@ -119,7 +134,8 @@ _.each([false, true], function (loggedIn) { logsOrMongoForApp(s, command, 'app-for-selftest-not-test-owned', { loggedIn: loggedIn, - authorized: false + authorized: false, + testReprompt: true }); if (! loggedIn) { From 97d2264a3aebd0bf5aeea48cbb7ea11bd7f45fec Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Tue, 18 Feb 2014 15:38:26 -0800 Subject: [PATCH 29/99] Fix a "Future resolved more than once" error --- tools/auth.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/auth.js b/tools/auth.js index f2268f364b..395129324f 100644 --- a/tools/auth.js +++ b/tools/auth.js @@ -229,8 +229,13 @@ var currentUsername = function (data) { // username, then ask the server if we have one. var username = null; var fut = new Future(); + var resolved = false; var connection = loggedInAccountsConnection(sessionData.token); connection.call('getUsername', function (err, result) { + if (resolved) + return; + resolved = true; + if (err) { // If anything went wrong, return null just as we would have if // we hadn't bothered to ask the server. @@ -240,12 +245,16 @@ var currentUsername = function (data) { fut['return'](result); }); - setTimeout(inFiber(function () { - fut['return'](null); + var timer = setTimeout(inFiber(function () { + if (! resolved) { + fut['return'](null); + resolved = true; + } }), 5000); username = fut.wait(); connection.close(); + clearTimeout(timer); if (username) { writeMeteorAccountsUsername(username); } From ec63838822a00dcc4f7626a5a981f3ac62f5591d Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Tue, 18 Feb 2014 15:34:13 -0800 Subject: [PATCH 30/99] Change required number of deploy args. Add tests for commands that require a site name. --- tools/commands.js | 2 +- tools/tests/authorized.js | 3 +++ tools/tests/claim.js | 5 +++++ tools/tests/deploy-auth.js | 16 +++++++++++++++- tools/tests/logs-mongo-auth.js | 17 ++++++++++++++++- 5 files changed, 40 insertions(+), 3 deletions(-) diff --git a/tools/commands.js b/tools/commands.js index 5992657887..71d068a032 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -773,7 +773,7 @@ main.registerCommand({ main.registerCommand({ name: 'deploy', - minArgs: 0, + minArgs: 1, maxArgs: 1, options: { 'delete': { type: Boolean, short: 'D' }, diff --git a/tools/tests/authorized.js b/tools/tests/authorized.js index fa31d0e067..755690396c 100644 --- a/tools/tests/authorized.js +++ b/tools/tests/authorized.js @@ -24,6 +24,9 @@ selftest.define("authorized", ['net', 'slow'], function () { run.matchErr("You must be logged in for that."); run.expectExit(1); + run = s.run("authorized"); + run.matchErr("not enough arguments"); + run = s.run("authorized", appName, "--remove", "bob"); run.waitSecs(commandTimeoutSecs); run.matchErr("You must be logged in for that."); diff --git a/tools/tests/claim.js b/tools/tests/claim.js index ccd1a4140c..a7a076c4cb 100644 --- a/tools/tests/claim.js +++ b/tools/tests/claim.js @@ -25,6 +25,11 @@ selftest.define("claim", ['net', 'slow'], function () { var run = s.run('claim', testUtils.randomAppName(20)); loggedInError(run); + // Can't claim sites without specifying a site + run = s.run('claim'); + run.matchErr('not enough arguments'); + run.expectExit(1); + // Existing site. run = s.run('claim', 'mother-test'); loggedInError(run); diff --git a/tools/tests/deploy-auth.js b/tools/tests/deploy-auth.js index 6630f7a637..245bef6a73 100644 --- a/tools/tests/deploy-auth.js +++ b/tools/tests/deploy-auth.js @@ -6,6 +6,20 @@ var Sandbox = selftest.Sandbox; var commandTimeoutSecs = testUtils.accountsCommandTimeoutSecs; +selftest.define('deploy - bad arguments', [], function () { + var s = new Sandbox; + + // Deploy with no app name should fail + var run = s.run('deploy'); + run.matchErr('not enough arguments'); + run.expectExit(1); + + // Deploy outside of an app directory + run = s.run('deploy', testUtils.randomAppName()); + run.matchErr('not in a Meteor project directory'); + run.expectExit(1); +}); + selftest.define('deploy - logged in', ['net', 'slow'], function () { // Create two sandboxes: one with a warehouse so that we can run // --release, and one without a warehouse so that we run from the @@ -36,7 +50,7 @@ selftest.define('deploy - logged in', ['net', 'slow'], function () { // Now, with our logged in current release, we should be able to // deploy to the legacy app. - var run = sandbox.run('deploy', noPasswordLegacyApp); + run = sandbox.run('deploy', noPasswordLegacyApp); run.waitSecs(90); run.match('Now serving at ' + noPasswordLegacyApp + '.meteor.com'); run.expectExit(0); diff --git a/tools/tests/logs-mongo-auth.js b/tools/tests/logs-mongo-auth.js index cd32bdf238..3a8de6fa60 100644 --- a/tools/tests/logs-mongo-auth.js +++ b/tools/tests/logs-mongo-auth.js @@ -93,8 +93,9 @@ _.each([false, true], function (loggedIn) { ['net'], function () { var s = new Sandbox; + var run; if (loggedIn) { - var run = s.run('login'); + run = s.run('login'); run.waitSecs(commandTimeoutSecs); run.matchErr('Username:'); run.write('test\n'); @@ -105,6 +106,20 @@ _.each([false, true], function (loggedIn) { run.expectExit(0); } + // Running 'meteor logs' without an app name should fail. + if (command === 'logs') { + run = s.run(command); + run.matchErr('not enough arguments'); + run.expectExit(1); + } + // Running 'meteor mongo' without an app name and not in an app + // dir should fail. + if (command === 'mongo') { + run = s.run('mongo'); + run.matchErr('not in a Meteor project directory'); + run.expectExit(1); + } + logsOrMongoForApp(s, command, 'legacy-no-password-app-for-selftest', { legacy: true, From 96e3273f284d175142c786a024afbbceef6e83b1 Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Tue, 18 Feb 2014 15:43:55 -0800 Subject: [PATCH 31/99] make account setup link even more handy --- tools/auth.js | 24 ++++++++++++++++++++++++ tools/commands.js | 36 ++++++++++++++---------------------- tools/deploy.js | 5 +++-- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/tools/auth.js b/tools/auth.js index 395129324f..6c1ab6a3a6 100644 --- a/tools/auth.js +++ b/tools/auth.js @@ -835,6 +835,30 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) { } }); +// options: firstTime, leadingNewline +exports.maybePrintRegistrationLink = function (options) { + options = options || {}; + + var registrationUrl = auth.registrationUrl(); + if (registrationUrl && ! auth.currentUsername()) { + if (options.leadingNewline) + process.stderr.write("\n"); + if (! options.firstTime) { + // If they've already been prompted to set a password then this + // is more of a friendly reminder, so we word it slightly + // differently than the first time they're being shown a + // registration url. + process.stderr.write( +"You should set a password on your Meteor developer account. It takes\n" + +"about a minute at: " + registrationUrl + "\n\n"); + } else { + process.stderr.write( +"You can set a password on your account or change your email address at:\n" + +registrationUrl + "\n\n"); + } + } +}; + exports.tryRevokeOldTokens = tryRevokeOldTokens; exports.getSessionId = function (domain) { diff --git a/tools/commands.js b/tools/commands.js index d787019208..39c26a6cf9 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -678,6 +678,7 @@ main.registerCommand({ } }, function (options) { var mongoUrl; + var usedMeteorAccount = false; if (options.args.length === 0) { // localhost mode @@ -708,6 +709,7 @@ main.registerCommand({ mongoUrl = deployGalaxy.temporaryMongoUrl(site); } else { mongoUrl = deploy.temporaryMongoUrl(site); + usedMeteorAccount = true; } if (! mongoUrl) @@ -717,6 +719,8 @@ main.registerCommand({ if (options.url) { console.log(mongoUrl); } else { + if (usedMeteorAccount) + auth.maybePrintRegistrationLink(); process.stdin.pause(); var runMongo = require('./run-mongo.js'); runMongo.runMongoShell(mongoUrl); @@ -862,26 +866,16 @@ main.registerCommand({ }); } - var registrationUrl = auth.registrationUrl(); - if (registrationUrl && - deployResult === 0 && - ! auth.currentUsername()) { - process.stderr.write("\n"); - if (loggedIn) { + if (deployResult === 0) { + auth.maybePrintRegistrationLink({ + leadingNewline: true, // If the user was already logged in at the beginning of the // deploy, then they've already been prompted to set a password - // and this is more of a friendly reminder to set their password, - // so we word it slightly differently than the first time they're - // being shown a registration url. - process.stderr.write( -"You should set a password on your Meteor developer account. It takes\n" + -"about a minute at: " + registrationUrl + "\n\n"); - } else { - process.stderr.write( -"You can set a password on your account or change your email address at:\n" + -registrationUrl + "\n\n"); - } + // at least once before, so we use a slightly different message. + firstTime: ! loggedIn + }); } + return deployResult; }); @@ -979,12 +973,10 @@ main.registerCommand({ var site = qualifySitename(options.args[0]); if (! auth.isLoggedIn()) { - // XXX meteor.com/create-account or something should have a nice - // registration form process.stderr.write( -"\nYou must be logged in to claim sites. Use 'meteor login' to log in.\n" + -"If you don't have a Meteor developer account yet, you can quickly\n" + -"create one at www.meteor.com.\n\n"); +"You must be logged in to claim sites. Use 'meteor login' to log in.\n" + +"If you don't have a Meteor developer account yet, create one by clicking\n" + +"'Sign in' and then 'Create account' at www.meteor.com.\n\n"); return 1; } diff --git a/tools/deploy.js b/tools/deploy.js index 4524014edb..236d2b3b02 100644 --- a/tools/deploy.js +++ b/tools/deploy.js @@ -539,6 +539,7 @@ var logs = function (site) { return 1; } else { process.stdout.write(result.message); + auth.maybePrintRegistrationLink({ leadingNewline: true }); return 0; } }; @@ -662,8 +663,8 @@ var claim = function (site) { if (! auth.currentUsername() && auth.registrationUrl()) { process.stderr.write( -"\nBefore you can claim existing sites, you need to set a password on\n" + -"your Meteor developer account. You can do that here in under a minute:\n\n" + +"You need to set a password on your Meteor developer account before\n" + +"you can claim sites. You can do that here in under a minute:\n\n" + auth.registrationUrl() + "\n\n"); } else { process.stderr.write("Couldn't claim site: " + From d3291f723431aec31adec8ce6a00399559de7458 Mon Sep 17 00:00:00 2001 From: Geoff Schmidt Date: Tue, 18 Feb 2014 15:44:09 -0800 Subject: [PATCH 32/99] one space after period --- tools/deploy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/deploy.js b/tools/deploy.js index 236d2b3b02..64251f55eb 100644 --- a/tools/deploy.js +++ b/tools/deploy.js @@ -341,7 +341,7 @@ var bundleAndDeploy = function (options) { var buildDir = path.join(options.appDir, '.meteor', 'local', 'build_tar'); var bundlePath = path.join(buildDir, 'bundle'); - process.stdout.write('Deploying to ' + site + '. Bundling...\n'); + process.stdout.write('Deploying to ' + site + '. Bundling...\n'); var settings = null; var messages = buildmessage.capture({ From 5308ee7fbb1d1c843bd2dafd8a9c6b54aa4fb3e0 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Tue, 18 Feb 2014 15:38:34 -0800 Subject: [PATCH 33/99] Upgrade jQuery to 0.11.0 --- History.md | 6 +- packages/jquery/jquery.js | 8346 ++++++++++++++++++++----------------- 2 files changed, 4450 insertions(+), 3902 deletions(-) diff --git a/History.md b/History.md index ea3b9c3ef0..78473a3617 100644 --- a/History.md +++ b/History.md @@ -78,9 +78,9 @@ * Upgraded dependencies: - node from 0.10.22 to 0.10.25 (removed workaround from 0.7.0 -- now support 0.10.25+) - - Upgrade jQuery from 1.8.2 to 1.10.2. - XXX see http://jquery.com/upgrade-guide/1.9/ for incompatibilities - XXX consider taking 1.11 instead, which was released this week + - Upgrade jQuery from 1.8.2 to 1.11.0 + XXX see http://jquery.com/upgrade-guide/1.9/ for incompatibilities (maybe + goes in notices?) - source-map from 0.3.30 to 0.3.32 #1782 - websocket-driver from 0.3.1 to 0.3.2 - http-proxy: 1.0.2 (from a pre-release fork of 1.0) diff --git a/packages/jquery/jquery.js b/packages/jquery/jquery.js index c5c648255c..3c88fa8b7f 100644 --- a/packages/jquery/jquery.js +++ b/packages/jquery/jquery.js @@ -1,91 +1,83 @@ /*! - * jQuery JavaScript Library v1.10.2 + * jQuery JavaScript Library v1.11.0 * http://jquery.com/ * * Includes Sizzle.js * http://sizzlejs.com/ * - * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors * Released under the MIT license * http://jquery.org/license * - * Date: 2013-07-03T13:48Z + * Date: 2014-01-23T21:02Z */ -(function( window, undefined ) { + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper window is present, + // execute the factory and get jQuery + // For environments that do not inherently posses a window with a document + // (such as Node.js), expose a jQuery-making factory as module.exports + // This accentuates the need for the creation of a real window + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { // 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 deletedIds = []; + +var slice = deletedIds.slice; + +var concat = deletedIds.concat; + +var push = deletedIds.push; + +var indexOf = deletedIds.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var trim = "".trim; + +var support = {}; + + + 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) - location = window.location, - document = window.document, - docElem = document.documentElement, - - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - - // 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_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, + version = "1.11.0", // Define a local copy of jQuery jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context, rootjQuery ); + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); }, - // Used for matching numbers - core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, - - // 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) - // Strict HTML recognition (#11290: must start with <) - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, - - // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, - - // JSON RegExp - rvalidchars = /^[\],:{}\s]*$/, - rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, - rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, - rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, - // Matches dashed string for camelizing rmsPrefix = /^-ms-/, rdashAlpha = /-([\da-z])/gi, @@ -93,134 +85,13 @@ var // Used by jQuery.camelCase as callback to replace() fcamelCase = function( all, letter ) { return letter.toUpperCase(); - }, - - // 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 ); - - } else { - document.detachEvent( "onreadystatechange", completed ); - window.detachEvent( "onload", completed ); - } }; jQuery.fn = jQuery.prototype = { // The current version of jQuery being used - jquery: core_version, + jquery: version, constructor: jQuery, - init: function( selector, context, rootjQuery ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - context = context instanceof jQuery ? context[0] : context; - - // scripts is true for back-compat - 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 ) ) { - 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 this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[2] ); - - // 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 and Opera return items - // by name instead of ID - if ( elem.id !== match[2] ) { - return rootjQuery.find( selector ); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || rootjQuery ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - 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 ) ) { - return rootjQuery.ready( selector ); - } - - if ( selector.selector !== undefined ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }, // Start with an empty selector selector: "", @@ -229,19 +100,19 @@ jQuery.fn = jQuery.prototype = { length: 0, toArray: function() { - return core_slice.call( this ); + return slice.call( this ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { - return num == null ? + return num != null ? // Return a 'clean' array - this.toArray() : + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : // Return just the object - ( num < 0 ? this[ this.length + num ] : this[ num ] ); + slice.call( this ); }, // Take an array of elements and push it onto the stack @@ -266,15 +137,14 @@ jQuery.fn = jQuery.prototype = { return jQuery.each( this, callback, args ); }, - ready: function( fn ) { - // Add the callback - jQuery.ready.promise().done( fn ); - - return this; + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); }, slice: function() { - return this.pushStack( core_slice.apply( this, arguments ) ); + return this.pushStack( slice.apply( this, arguments ) ); }, first: function() { @@ -291,26 +161,17 @@ jQuery.fn = jQuery.prototype = { return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); }, - map: function( callback ) { - return this.pushStack( jQuery.map(this, function( elem, i ) { - return callback.call( elem, i, elem ); - })); - }, - end: function() { return this.prevObject || this.constructor(null); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. - push: core_push, - sort: [].sort, - splice: [].splice + push: push, + sort: deletedIds.sort, + splice: deletedIds.splice }; -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - jQuery.extend = jQuery.fn.extend = function() { var src, copyIsArray, copy, name, options, clone, target = arguments[0] || {}, @@ -321,9 +182,10 @@ jQuery.extend = jQuery.fn.extend = function() { // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; - target = arguments[1] || {}; + // skip the boolean and the target - i = 2; + target = arguments[ i ] || {}; + i++; } // Handle case when target is a string or something (possible in deep copy) @@ -332,9 +194,9 @@ jQuery.extend = jQuery.fn.extend = function() { } // extend jQuery itself if only one argument is passed - if ( length === i ) { + if ( i === length ) { target = this; - --i; + i--; } for ( ; i < length; i++ ) { @@ -377,66 +239,16 @@ 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, "" ), + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - noConflict: function( deep ) { - if ( window.$ === jQuery ) { - window.$ = _$; - } + // Assume jQuery is ready without the ready module + isReady: true, - if ( deep && window.jQuery === jQuery ) { - window.jQuery = _jQuery; - } - - return jQuery; + error: function( msg ) { + throw new Error( msg ); }, - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( !document.body ) { - return setTimeout( jQuery.ready ); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger("ready").off("ready"); - } - }, + noop: function() {}, // See test/unit/core.js for details concerning isFunction. // Since version 1.3, DOM methods and functions like alert @@ -455,16 +267,18 @@ jQuery.extend({ }, isNumeric: function( obj ) { - return !isNaN( parseFloat(obj) ) && isFinite( obj ); + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + return obj - parseFloat( obj ) >= 0; }, - type: function( obj ) { - if ( obj == null ) { - return String( obj ); + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; } - return typeof obj === "object" || typeof obj === "function" ? - class2type[ core_toString.call(obj) ] || "object" : - typeof obj; + return true; }, isPlainObject: function( obj ) { @@ -480,8 +294,8 @@ jQuery.extend({ try { // Not own constructor property must be Object if ( obj.constructor && - !core_hasOwn.call(obj, "constructor") && - !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } } catch ( e ) { @@ -491,9 +305,9 @@ jQuery.extend({ // Support: IE<9 // Handle iteration over inherited properties before own properties. - if ( jQuery.support.ownLast ) { + if ( support.ownLast ) { for ( key in obj ) { - return core_hasOwn.call( obj, key ); + return hasOwn.call( obj, key ); } } @@ -501,105 +315,18 @@ jQuery.extend({ // if last one is own, then all properties are own. for ( key in obj ) {} - return key === undefined || core_hasOwn.call( obj, key ); + return key === undefined || hasOwn.call( obj, key ); }, - isEmptyObject: function( obj ) { - var name; - for ( name in obj ) { - return false; + type: function( obj ) { + if ( obj == null ) { + return obj + ""; } - return true; + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; }, - error: function( msg ) { - throw new Error( msg ); - }, - - // data: string of html - // context (optional): If specified, the fragment will be created in this context, defaults to document - // 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" ) { - keepScripts = context; - context = false; - } - context = context || document; - - var parsed = rsingleTag.exec( data ), - scripts = !keepScripts && []; - - // Single tag - if ( parsed ) { - return [ context.createElement( parsed[1] ) ]; - } - - parsed = jQuery.buildFragment( [ data ], context, scripts ); - if ( scripts ) { - jQuery( scripts ).remove(); - } - return jQuery.merge( [], parsed.childNodes ); - }, - - parseJSON: function( data ) { - // Attempt to parse using the native JSON parser first - if ( window.JSON && window.JSON.parse ) { - return window.JSON.parse( 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 ); - }, - - // Cross-browser xml parsing - parseXML: function( data ) { - var xml, tmp; - if ( !data || typeof data !== "string" ) { - return null; - } - try { - if ( window.DOMParser ) { // Standard - tmp = new DOMParser(); - xml = tmp.parseFromString( data , "text/xml" ); - } else { // IE - xml = new ActiveXObject( "Microsoft.XMLDOM" ); - xml.async = "false"; - xml.loadXML( data ); - } - } catch( e ) { - xml = undefined; - } - if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; - }, - - noop: function() {}, - // Evaluates a script in a global context // Workarounds based on findings by Jim Driscoll // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context @@ -675,11 +402,11 @@ jQuery.extend({ }, // Use native String.trim function wherever possible - trim: core_trim && !core_trim.call("\uFEFF\xA0") ? + trim: trim && !trim.call("\uFEFF\xA0") ? function( text ) { return text == null ? "" : - core_trim.call( text ); + trim.call( text ); } : // Otherwise use our own trimming functionality @@ -700,7 +427,7 @@ jQuery.extend({ [ arr ] : arr ); } else { - core_push.call( ret, arr ); + push.call( ret, arr ); } } @@ -711,8 +438,8 @@ jQuery.extend({ var len; if ( arr ) { - if ( core_indexOf ) { - return core_indexOf.call( arr, elem, i ); + if ( indexOf ) { + return indexOf.call( arr, elem, i ); } len = arr.length; @@ -730,15 +457,17 @@ jQuery.extend({ }, merge: function( first, second ) { - var l = second.length, - i = first.length, - j = 0; + var len = +second.length, + j = 0, + i = first.length; - if ( typeof l === "number" ) { - for ( ; j < l; j++ ) { - first[ i++ ] = second[ j ]; - } - } else { + while ( j < len ) { + first[ i++ ] = second[ j++ ]; + } + + // Support: IE<9 + // Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists) + if ( len !== len ) { while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; } @@ -749,23 +478,23 @@ jQuery.extend({ return first; }, - grep: function( elems, callback, inv ) { - var retVal, - ret = [], + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], i = 0, - length = elems.length; - inv = !!inv; + length = elems.length, + callbackExpect = !invert; // Go through the array, only saving the items // that pass the validator function for ( ; i < length; i++ ) { - retVal = !!callback( elems[ i ], i ); - if ( inv !== retVal ) { - ret.push( elems[ i ] ); + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); } } - return ret; + return matches; }, // arg is for internal usage only @@ -776,13 +505,13 @@ jQuery.extend({ isArray = isArraylike( elems ), ret = []; - // Go through the array, translating each of the items to their + // Go through the array, translating each of the items to their new values if ( isArray ) { for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { - ret[ ret.length ] = value; + ret.push( value ); } } @@ -792,13 +521,13 @@ jQuery.extend({ value = callback( elems[ i ], i, arg ); if ( value != null ) { - ret[ ret.length ] = value; + ret.push( value ); } } } // Flatten any nested arrays - return core_concat.apply( [], ret ); + return concat.apply( [], ret ); }, // A global GUID counter for objects @@ -822,9 +551,9 @@ jQuery.extend({ } // Simulated bind - args = core_slice.call( arguments, 2 ); + args = slice.call( arguments, 2 ); proxy = function() { - return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); }; // Set the guid of unique handler to the same of original handler, so it can be removed @@ -833,148 +562,15 @@ jQuery.extend({ return proxy; }, - // 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, raw ) { - var i = 0, - length = elems.length, - bulk = key == null; - - // Sets many values - if ( jQuery.type( key ) === "object" ) { - chainable = true; - for ( i in key ) { - jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !jQuery.isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - // 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, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); - } - } - } - - return chainable ? - elems : - - // Gets - bulk ? - fn.call( elems ) : - length ? fn( elems[0], key ) : emptyGet; - }, - now: function() { - return ( new Date() ).getTime(); + return +( new Date() ); }, - // 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; - } + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support }); -jQuery.ready.promise = function( obj ) { - if ( !readyList ) { - - readyList = jQuery.Deferred(); - - // Catch cases where $(document).ready() is called after the browser event has already occurred. - // we once tried to use readyState "interactive" here, but it caused issues like the one - // 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 ); - - // Standards-based browsers support DOMContentLoaded - } else if ( document.addEventListener ) { - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed, false ); - - // A fallback to window.onload, that will always work - 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", completed ); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", completed ); - - // If IE and not a frame - // continually check to see if the document is ready - var top = false; - - try { - top = window.frameElement == null && document.documentElement; - } catch(e) {} - - if ( top && top.doScroll ) { - (function doScrollCheck() { - if ( !jQuery.isReady ) { - - try { - // Use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - top.doScroll("left"); - } catch(e) { - return setTimeout( doScrollCheck, 50 ); - } - - // detach all dom ready events - detach(); - - // and execute any waiting functions - jQuery.ready(); - } - })(); - } - } - } - return readyList.promise( obj ); -}; - // Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); @@ -984,7 +580,7 @@ function isArraylike( obj ) { var length = obj.length, type = jQuery.type( obj ); - if ( jQuery.isWindow( obj ) ) { + if ( type === "function" || jQuery.isWindow( obj ) ) { return false; } @@ -992,34 +588,31 @@ function isArraylike( obj ) { return true; } - return type === "array" || type !== "function" && - ( length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj ); + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; } - -// All jQuery objects should point back to these -rootjQuery = jQuery(document); +var Sizzle = /*! - * Sizzle CSS Selector Engine v1.10.2 + * Sizzle CSS Selector Engine v1.10.16 * http://sizzlejs.com/ * * Copyright 2013 jQuery Foundation, Inc. and other contributors * Released under the MIT license * http://jquery.org/license * - * Date: 2013-07-03 + * Date: 2014-01-13 */ -(function( window, undefined ) { +(function( window ) { var i, support, - cachedruns, Expr, getText, isXML, compile, outermostContext, sortInput, + hasDuplicate, // Local document vars setDocument, @@ -1039,11 +632,9 @@ var i, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), - hasDuplicate = false, sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; - return 0; } return 0; }, @@ -1103,8 +694,7 @@ var i, rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - rsibling = new RegExp( whitespace + "*[+~]" ), - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*)" + whitespace + "*\\]", "g" ), + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), @@ -1125,14 +715,15 @@ var i, whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/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, - + rsibling = /[+~]/, rescape = /'|\\/g, // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters @@ -1144,8 +735,8 @@ var i, // Workaround erroneous numeric interpretation of +"0x" return high !== high || escapedWhitespace ? escaped : - // BMP codepoint high < 0 ? + // BMP codepoint String.fromCharCode( high + 0x10000 ) : // Supplemental Plane codepoint (surrogate pair) String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); @@ -1209,7 +800,7 @@ function Sizzle( selector, context, results, seed ) { 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 + // nodes that are no longer in the document (jQuery #6963) if ( elem && elem.parentNode ) { // Handle the case where IE, Opera, and Webkit return items // by name instead of ID @@ -1265,7 +856,7 @@ function Sizzle( selector, context, results, seed ) { while ( i-- ) { groups[i] = nid + toSelector( groups[i] ); } - newContext = rsibling.test( selector ) && context.parentNode || context; + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; newSelector = groups.join(","); } @@ -1300,11 +891,11 @@ function createCache() { function cache( key, value ) { // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key += " " ) > Expr.cacheLength ) { + if ( keys.push( key + " " ) > Expr.cacheLength ) { // Only keep the most recent entries delete cache[ keys.shift() ]; } - return (cache[ key ] = value); + return (cache[ key + " " ] = value); } return cache; } @@ -1427,8 +1018,21 @@ function createPositionalPseudo( fn ) { } /** - * Detect xml + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== strundefined && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node */ isXML = Sizzle.isXML = function( elem ) { // documentElement is verified for cases where it doesn't yet exist @@ -1437,16 +1041,14 @@ isXML = Sizzle.isXML = function( elem ) { 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, + var hasCompare, + doc = node ? node.ownerDocument || node : preferredDoc, parent = doc.defaultView; // If no document and documentElement is available, return @@ -1465,10 +1067,17 @@ setDocument = Sizzle.setDocument = function( node ) { // 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(); - }); + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", function() { + setDocument(); + }, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", function() { + setDocument(); + }); + } } /* Attributes @@ -1491,7 +1100,7 @@ setDocument = Sizzle.setDocument = function( node ) { }); // Check if getElementsByClassName can be trusted - support.getElementsByClassName = assert(function( div ) { + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { div.innerHTML = "
"; // Support: Safari<4 @@ -1598,7 +1207,13 @@ setDocument = Sizzle.setDocument = function( node ) { // setting a boolean content attribute, // since its presence should be enough // http://bugs.jquery.com/ticket/12359 - div.innerHTML = ""; + div.innerHTML = ""; + + // Support: IE8, Opera 10-12 + // Nothing should be selected when empty strings follow ^= or $= or *= + if ( div.querySelectorAll("[t^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } // Support: IE8 // Boolean attributes and "value" are not treated correctly @@ -1615,18 +1230,16 @@ setDocument = Sizzle.setDocument = function( node ) { }); 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 + // The type and name attributes are restricted during .innerHTML assignment var input = doc.createElement("input"); input.setAttribute( "type", "hidden" ); - div.appendChild( input ).setAttribute( "t", "" ); + div.appendChild( input ).setAttribute( "name", "D" ); - if ( div.querySelectorAll("[t^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) @@ -1663,11 +1276,12 @@ setDocument = Sizzle.setDocument = function( node ) { /* Contains ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); // Element contains another // Purposefully does not implement inclusive descendent // As in, an element does not contain itself - contains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ? + contains = hasCompare || rnative.test( docElem.contains ) ? function( a, b ) { var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; @@ -1692,7 +1306,7 @@ setDocument = Sizzle.setDocument = function( node ) { ---------------------------------------------------------------------- */ // Document order sorting - sortOrder = docElem.compareDocumentPosition ? + sortOrder = hasCompare ? function( a, b ) { // Flag for duplicate removal @@ -1701,34 +1315,46 @@ setDocument = Sizzle.setDocument = function( node ) { return 0; } - var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b ); - + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; 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; + return compare; } - // Not directly comparable, sort on existence of method - return a.compareDocumentPosition ? -1 : 1; + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // 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 || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; } : function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + var cur, i = 0, aup = a.parentNode, @@ -1736,13 +1362,8 @@ setDocument = Sizzle.setDocument = function( node ) { 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 ) { + if ( !aup || !bup ) { return a === doc ? -1 : b === doc ? 1 : aup ? -1 : @@ -1837,13 +1458,13 @@ Sizzle.attr = function( elem, name ) { fn( elem, name, !documentIsHTML ) : undefined; - return val === undefined ? + return val !== undefined ? + val : support.attributes || !documentIsHTML ? elem.getAttribute( name ) : (val = elem.getAttributeNode(name)) && val.specified ? val.value : - null : - val; + null; }; Sizzle.error = function( msg ) { @@ -1876,6 +1497,10 @@ Sizzle.uniqueSort = function( results ) { } } + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + return results; }; @@ -1891,13 +1516,13 @@ getText = Sizzle.getText = function( elem ) { if ( !nodeType ) { // If no nodeType, this is expected to be an array - for ( ; (node = elem[i]); i++ ) { + while ( (node = elem[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) + // innerText usage removed for consistency of new lines (jQuery #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { @@ -2294,12 +1919,11 @@ Expr = Sizzle.selectors = { // 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 "?") + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { + if ( elem.nodeType < 6 ) { return false; } } @@ -2326,11 +1950,12 @@ Expr = Sizzle.selectors = { "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 ); + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); }, // Position-in-collection @@ -2416,7 +2041,7 @@ function tokenize( selector, parseOnly ) { // Don't consume trailing commas as valid soFar = soFar.slice( match[0].length ) || soFar; } - groups.push( tokens = [] ); + groups.push( (tokens = []) ); } matched = false; @@ -2489,8 +2114,8 @@ function addCombinator( matcher, combinator, base ) { // Check against all ancestor/preceding elements function( elem, context, xml ) { - var data, cache, outerCache, - dirkey = dirruns + " " + doneName; + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching if ( xml ) { @@ -2505,14 +2130,17 @@ function addCombinator( matcher, combinator, base ) { 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; - } + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); } else { - cache = outerCache[ dir ] = [ dirkey ]; - cache[1] = matcher( elem, context, xml ) || cachedruns; - if ( cache[1] === true ) { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { return true; } } @@ -2706,31 +2334,30 @@ function matcherFromTokens( tokens ) { } function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - // A counter to specify which element is currently being matched - var matcherCachedRuns = 0, - bySet = setMatchers.length > 0, + var bySet = setMatchers.length > 0, byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, expandContext ) { + superMatcher = function( seed, context, xml, results, outermost ) { var elem, j, matcher, - setMatched = [], matchedCount = 0, i = "0", unmatched = seed && [], - outermost = expandContext != null, + setMatched = [], contextBackup = outermostContext, - // We must always have either seed elements or context - elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; 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++ ) { + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { if ( byElement && elem ) { j = 0; while ( (matcher = elementMatchers[j++]) ) { @@ -2741,7 +2368,6 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { } if ( outermost ) { dirruns = dirrunsUnique; - cachedruns = ++matcherCachedRuns; } } @@ -2876,7 +2502,7 @@ function select( selector, context, results, seed ) { // Search, expanding context for leading sibling combinators if ( (seed = find( token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && context.parentNode || context + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context )) ) { // If seed is empty or no tokens remain, we can return early @@ -2901,7 +2527,7 @@ function select( selector, context, results, seed ) { context, !documentIsHTML, results, - rsibling.test( selector ) + rsibling.test( selector ) && testContext( context.parentNode ) || context ); return results; } @@ -2913,7 +2539,7 @@ 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; +support.detectDuplicates = !!hasDuplicate; // Initialize against the default document setDocument(); @@ -2961,13 +2587,20 @@ if ( !assert(function( div ) { 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; + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; } }); } +return Sizzle; + +})( window ); + + + jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; jQuery.expr[":"] = jQuery.expr.pseudos; @@ -2977,14 +2610,432 @@ jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains; -})( window ); + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.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; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + 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; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + ret = [], + self = this, + len = self.length; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + 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; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + 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; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + 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 ) ) { + 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 this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // 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 and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + 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 ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + ret = jQuery.unique( ret ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + } + + return this.pushStack( ret ); + }; +}); +var rnotwhite = (/\S+/g); + + + // 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.match( core_rnotwhite ) || [], function( _, flag ) { + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { object[ flag ] = true; }); return object; @@ -3101,7 +3152,7 @@ jQuery.Callbacks = function( options ) { if ( list ) { jQuery.each( arguments, function( _, arg ) { var index; - while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { list.splice( index, 1 ); // Handle firing indexes if ( firing ) { @@ -3175,6 +3226,8 @@ jQuery.Callbacks = function( options ) { return self; }; + + jQuery.extend({ Deferred: function( func ) { @@ -3197,8 +3250,7 @@ jQuery.extend({ var fns = arguments; return jQuery.Deferred(function( newDefer ) { jQuery.each( tuples, function( i, tuple ) { - var action = tuple[ 0 ], - fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; // deferred[ done | fail | progress ] for forwarding actions to newDefer deferred[ tuple[1] ](function() { var returned = fn && fn.apply( this, arguments ); @@ -3208,7 +3260,7 @@ jQuery.extend({ .fail( newDefer.reject ) .progress( newDefer.notify ); } else { - newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); } }); }); @@ -3267,7 +3319,7 @@ jQuery.extend({ // Deferred helper when: function( subordinate /* , ..., subordinateN */ ) { var i = 0, - resolveValues = core_slice.call( arguments ), + resolveValues = slice.call( arguments ), length = resolveValues.length, // the count of uncompleted subordinates @@ -3280,10 +3332,11 @@ jQuery.extend({ updateFunc = function( i, contexts, values ) { return function( value ) { contexts[ i ] = this; - values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; - if( values === progressValues ) { + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { deferred.notifyWith( contexts, values ); - } else if ( !( --remaining ) ) { + + } else if ( !(--remaining) ) { deferred.resolveWith( contexts, values ); } }; @@ -3316,256 +3369,299 @@ jQuery.extend({ return deferred.promise(); } }); -jQuery.support = (function( support ) { - var all, a, input, select, fragment, opt, eventName, isSupported, i, - div = document.createElement("div"); - // Setup - div.setAttribute( "className", "t" ); - div.innerHTML = "
a"; +// The deferred used on DOM ready +var readyList; - // Finish early in limited (non-browser) environments - all = div.getElementsByTagName("*") || []; - a = div.getElementsByTagName("a")[ 0 ]; - if ( !a || !a.style || !all.length ) { - return support; - } +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); - // First batch of tests - select = document.createElement("select"); - opt = select.appendChild( document.createElement("option") ); - input = div.getElementsByTagName("input")[ 0 ]; + return this; +}; - a.style.cssText = "top:1px;float:left;opacity:.5"; +jQuery.extend({ + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, - // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) - support.getSetAttribute = div.className !== "t"; + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, - // IE strips leading whitespace when .innerHTML is used - support.leadingWhitespace = div.firstChild.nodeType === 3; + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - support.tbody = !div.getElementsByTagName("tbody").length; + // Handle when the DOM is ready + ready: function( wait ) { - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - support.htmlSerialize = !!div.getElementsByTagName("link").length; - - // Get the style information from getAttribute - // (IE uses .cssText instead) - support.style = /top/.test( a.getAttribute("style") ); - - // Make sure that URLs aren't manipulated - // (IE normalizes it by default) - support.hrefNormalized = a.getAttribute("href") === "/a"; - - // 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 ); - - // Verify style float existence - // (IE uses styleFloat instead of cssFloat) - support.cssFloat = !!a.style.cssFloat; - - // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) - support.checkOn = !!input.value; - - // 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; - - // Tests for enctype support on a form (#6743) - support.enctype = !!document.createElement("form").enctype; - - // 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 - 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; - support.noCloneChecked = input.cloneNode( true ).checked; - - // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as disabled) - select.disabled = true; - support.optDisabled = !opt.disabled; - - // Support: IE<9 - try { - delete div.test; - } catch( e ) { - support.deleteExpando = false; - } - - // 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"; - - // #11217 - WebKit loses check when the name is after the checked attribute - input.setAttribute( "checked", "t" ); - input.setAttribute( "name", "t" ); - - fragment = document.createDocumentFragment(); - 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; - - // WebKit doesn't clone checked state correctly in fragments - support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // 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 ) { - 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, 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 ) { - // Return for frameset docs that don't have a body + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { return; } - container = document.createElement("div"); - container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; - - 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). - div.innerHTML = "
t
"; - tds = div.getElementsByTagName("td"); - tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; - isSupported = ( tds[ 0 ].offsetHeight === 0 ); - - tds[ 0 ].style.display = ""; - tds[ 1 ].style.display = "none"; - - // Support: IE8 - // Check if empty table cells still have offsetWidth/Height - support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); - - // 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%;"; - - // 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. (#3333) - // Fails in WebKit before Feb 2011 nightlies - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - marginDiv = div.appendChild( document.createElement("div") ); - marginDiv.style.cssText = div.style.cssText = divReset; - marginDiv.style.marginRight = marginDiv.style.width = "0"; - div.style.width = "1px"; - - support.reliableMarginRight = - !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); } - 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 - div.innerHTML = ""; - div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; - support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + // Remember that the DOM is ready + jQuery.isReady = true; - // Support: IE6 - // Check if elements with layout shrink-wrap their children - div.style.display = "block"; - div.innerHTML = "
"; - div.firstChild.style.width = "5px"; - support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } - 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; + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + } +}); + +/** + * Clean-up method for dom ready events + */ +function detach() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } +} + +/** + * The ready event handler and self cleanup method + */ +function completed() { + // 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(); + } +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // 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 ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + 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", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); } } + } + return readyList.promise( obj ); +}; - body.removeChild( container ); - // Null elements to avoid leaks in IE - container = div = tds = marginDiv = null; - }); +var strundefined = typeof undefined; + + + +// Support: IE<9 +// Iteration over object's inherited properties before its own +var i; +for ( i in jQuery( support ) ) { + break; +} +support.ownLast = i !== "0"; + +// Note: most support tests are defined in their respective modules. +// false until the test is run +support.inlineBlockNeedsLayout = false; + +jQuery(function() { + // We need to execute this one support test ASAP because we need to know + // if body.style.zoom needs to be set. + + var container, div, + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + // Setup + container = document.createElement( "div" ); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; + + div = document.createElement( "div" ); + body.appendChild( container ).appendChild( div ); + + if ( typeof div.style.zoom !== 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 + div.style.cssText = "border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1"; + + if ( (support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 )) ) { + // 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; + } + } + + body.removeChild( container ); // Null elements to avoid leaks in IE - all = select = fragment = opt = a = input = null; + container = div = null; +}); - return support; -})({}); -var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + + +(function() { + var div = document.createElement( "div" ); + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( elem ) { + var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ], + nodeType = +elem.nodeType || 1; + + // Do not set data on non-element DOM nodes because it will not be cleared (#8335). + return nodeType !== 1 && nodeType !== 9 ? + false : + + // Nodes accept data unless otherwise specified; rejection can be conditional + !noData || noData !== true && elem.getAttribute("classid") === noData; +}; + + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, rmultiDash = /([A-Z])/g; -function internalData( elem, name, data, pvt /* Internal Use Only */ ){ +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + 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; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + +function internalData( elem, name, data, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } @@ -3595,7 +3691,7 @@ function internalData( elem, name, data, pvt /* Internal Use Only */ ){ // 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++; + id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++; } else { id = internalKey; } @@ -3734,7 +3830,7 @@ function internalRemoveData( elem, name, pvt ) { // 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 ) { + } else if ( support.deleteExpando || cache != cache.window ) { /* jshint eqeqeq: true */ delete cache[ id ]; @@ -3747,13 +3843,13 @@ function internalRemoveData( elem, name, pvt ) { jQuery.extend({ cache: {}, - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. + // The following elements (space-suffixed to avoid Object.prototype collisions) + // throw uncatchable exceptions if you attempt to set expando properties noData: { - "applet": true, - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" + "applet ": true, + "embed ": true, + // ...but Flash objects (which have this classid) *can* handle expandos + "object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" }, hasData: function( elem ) { @@ -3776,28 +3872,14 @@ jQuery.extend({ _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 - return !noData || noData !== true && elem.getAttribute("classid") === noData; } }); jQuery.fn.extend({ data: function( key, value ) { - var attrs, name, - data = null, - i = 0, - elem = this[0]; + var i, name, data, + elem = this[0], + attrs = elem && elem.attributes; // Special expections of .data basically thwart jQuery.access, // so implement the relevant behavior ourselves @@ -3808,8 +3890,8 @@ jQuery.fn.extend({ data = jQuery.data( elem ); if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { - attrs = elem.attributes; - for ( ; i < attrs.length; i++ ) { + i = attrs.length; + while ( i-- ) { name = attrs[i].name; if ( name.indexOf("data-") === 0 ) { @@ -3841,7 +3923,7 @@ jQuery.fn.extend({ // Gets one value // Try to fetch any internally stored data first - elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined; }, removeData: function( key ) { @@ -3851,53 +3933,7 @@ jQuery.fn.extend({ } }); -function dataAttr( elem, key, data ) { - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - - data = elem.getAttribute( name ); - - 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; - } catch( e ) {} - - // Make sure we set the data so it isn't changed later - jQuery.data( elem, key, data ); - - } else { - data = undefined; - } - } - - return data; -} - -// checks a cache object for emptiness -function isEmptyDataObject( obj ) { - var name; - for ( name in obj ) { - - // if the public data object is empty, the private is still empty - if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { - continue; - } - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} jQuery.extend({ queue: function( elem, type, data ) { var queue; @@ -3997,19 +4033,6 @@ jQuery.fn.extend({ jQuery.dequeue( this, type ); }); }, - // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ - delay: function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = setTimeout( next, time ); - hooks.stop = function() { - clearTimeout( timeout ); - }; - }); - }, clearQueue: function( type ) { return this.queue( type || "fx", [] ); }, @@ -4033,7 +4056,7 @@ jQuery.fn.extend({ } type = type || "fx"; - while( i-- ) { + while ( i-- ) { tmp = jQuery._data( elements[ i ], type + "queueHooks" ); if ( tmp && tmp.empty ) { count++; @@ -4044,665 +4067,168 @@ jQuery.fn.extend({ return defer.promise( obj ); } }); -var nodeHook, boolHook, - rclass = /[\t\r\n\f]/g, - rreturn = /\r/g, - rfocusable = /^(?:input|select|textarea|button|object)$/i, - rclickable = /^(?:a|area)$/i, - ruseDefault = /^(?:checked|selected)$/i, - getSetAttribute = jQuery.support.getSetAttribute, - getSetInput = jQuery.support.input; +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; -jQuery.fn.extend({ - attr: function( name, value ) { - return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - removeAttr: function( name ) { - return this.each(function() { - jQuery.removeAttr( this, name ); - }); - }, +var isHidden = function( 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 ); + }; - prop: function( name, value ) { - return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - removeProp: function( name ) { - name = jQuery.propFix[ name ] || name; - return this.each(function() { - // try/catch handles cases where IE balks (such as removing a property on window) - try { - this[ name ] = undefined; - delete this[ name ]; - } catch( e ) {} - }); - }, - addClass: function( value ) { - var classes, elem, cur, clazz, j, - i = 0, - len = this.length, - proceed = typeof value === "string" && value; +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).addClass( value.call( this, j, this.className ) ); - }); + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); } - if ( proceed ) { - // The disjunction here is for better compressibility (see removeClass) - classes = ( value || "" ).match( core_rnotwhite ) || []; + // Sets one value + } else if ( value !== undefined ) { + chainable = true; - for ( ; i < len; i++ ) { - elem = this[ i ]; - cur = elem.nodeType === 1 && ( elem.className ? - ( " " + elem.className + " " ).replace( rclass, " " ) : - " " - ); - - if ( cur ) { - j = 0; - while ( (clazz = classes[j++]) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - elem.className = jQuery.trim( cur ); - - } - } + if ( !jQuery.isFunction( value ) ) { + raw = true; } - return this; - }, + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; - removeClass: function( value ) { - 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 ) ); - }); - } - if ( proceed ) { - classes = ( value || "" ).match( core_rnotwhite ) || []; - - for ( ; i < len; i++ ) { - elem = this[ i ]; - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( elem.className ? - ( " " + elem.className + " " ).replace( rclass, " " ) : - "" - ); - - 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( cur ) : ""; - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - 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 ) { - jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); - }); - } - - return this.each(function() { - if ( type === "string" ) { - // toggle individual class names - var className, - i = 0, - self = jQuery( this ), - classNames = value.match( core_rnotwhite ) || []; - - while ( (className = classNames[ i++ ]) ) { - // check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( type === core_strundefined || type === "boolean" ) { - if ( this.className ) { - // store className if set - jQuery._data( this, "__className__", this.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__" ) || ""; - } - }); - }, - - hasClass: function( selector ) { - var className = " " + selector + " ", - i = 0, - l = this.length; - for ( ; i < l; i++ ) { - if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { - return true; - } - } - - return false; - }, - - val: function( value ) { - var ret, hooks, isFunction, - elem = this[0]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { - return ret; - } - - ret = elem.value; - - return typeof ret === "string" ? - // handle most common string cases - ret.replace(rreturn, "") : - // handle cases where value is null/undef or number - ret == null ? "" : ret; - } - - return; - } - - isFunction = jQuery.isFunction( value ); - - return this.each(function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call( this, i, jQuery( this ).val() ); + // ...except when executing function values } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - } else if ( typeof val === "number" ) { - val += ""; - } else if ( jQuery.isArray( val ) ) { - val = jQuery.map(val, function ( value ) { - return value == null ? "" : value + ""; - }); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - }); - } -}); - -jQuery.extend({ - valHooks: { - option: { - get: function( elem ) { - // 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, option, - options = elem.options, - 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 - for ( ; i < max; i++ ) { - option = options[ i ]; - - // 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(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) { - optionSet = true; - } - } - - // force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; } } - }, - attr: function( elem, name, value ) { - var hooks, ret, - nType = elem.nodeType; - - // don't get/set attributes on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === core_strundefined ) { - return jQuery.prop( elem, name, value ); - } - - // All attributes are lowercase - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - name = name.toLowerCase(); - hooks = jQuery.attrHooks[ name ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook ); - } - - if ( value !== undefined ) { - - if ( value === null ) { - jQuery.removeAttr( elem, name ); - - } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { - return ret; - - } else { - elem.setAttribute( name, value + "" ); - return value; - } - - } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? - undefined : - ret; - } - }, - - removeAttr: function( elem, value ) { - var name, propName, - i = 0, - attrNames = value && value.match( core_rnotwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( (name = attrNames[i++]) ) { - propName = jQuery.propFix[ name ] || name; - - // 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 ); - } - } - }, - - attrHooks: { - type: { - set: function( elem, value ) { - 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 default in case type is set after value during creation - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - }, - - prop: function( elem, name, value ) { - var ret, hooks, notxml, - nType = elem.nodeType; - - // don't get/set properties on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - - if ( notxml ) { - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ? - ret : - ( elem[ name ] = value ); - - } else { - return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ? - ret : - elem[ name ]; - } - }, - - propHooks: { - tabIndex: { - 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/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - return tabindex ? - parseInt( tabindex, 10 ) : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - -1; + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); } } } -}); -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { - // IE<8 needs the *property* name - elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); + return chainable ? + elems : - // Use defaultChecked and defaultSelected for oldIE - } else { - elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; - } - - return name; - } + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; }; -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 ) { - - // Use this for any attribute in IE6/7 - // This fixes almost every IE6/7 issue - nodeHook = { - set: function( elem, value, name ) { - // Set the existing or create a new attribute node - var ret = elem.getAttributeNode( name ); - if ( !ret ) { - elem.setAttributeNode( - (ret = elem.ownerDocument.createAttribute( name )) - ); - } - - 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 ] = { - set: function( elem, value ) { - if ( value === "" ) { - elem.setAttribute( name, "auto" ); - return value; - } - } - }; - }); -} +var rcheckableType = (/^(?:checkbox|radio)$/i); -// Some attributes require a special call on IE -// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !jQuery.support.hrefNormalized ) { - // href/src property should get the full normalized URL (#10299/#12915) - jQuery.each([ "href", "src" ], function( i, name ) { - jQuery.propHooks[ name ] = { - get: function( elem ) { - return elem.getAttribute( name, 4 ); - } - }; - }); -} -if ( !jQuery.support.style ) { - jQuery.attrHooks.style = { - get: function( elem ) { - // Return undefined in the case of empty string - // 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 + "" ); - } - }; -} +(function() { + var fragment = document.createDocumentFragment(), + div = document.createElement("div"), + input = document.createElement("input"); -// 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 = { - get: function( elem ) { - var parent = elem.parentNode; + // Setup + div.setAttribute( "className", "t" ); + div.innerHTML = "
a"; - if ( parent ) { - parent.selectedIndex; + // IE strips leading whitespace when .innerHTML is used + support.leadingWhitespace = div.firstChild.nodeType === 3; - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - return null; - } - }; -} + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + support.tbody = !div.getElementsByTagName( "tbody" ).length; -jQuery.each([ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -}); + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + support.htmlSerialize = !!div.getElementsByTagName( "link" ).length; -// IE6/7 call enctype encoding -if ( !jQuery.support.enctype ) { - jQuery.propFix.enctype = "encoding"; -} + // 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>"; -// Radios and checkboxes getter/setter -jQuery.each([ "radio", "checkbox" ], function() { - 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; - }; + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + input.type = "checkbox"; + input.checked = true; + fragment.appendChild( input ); + support.appendChecked = input.checked; + + // Make sure textarea (and checkbox) defaultValue is properly cloned + // Support: IE6-IE11+ + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // #11217 - WebKit loses check when the name is after the checked attribute + fragment.appendChild( div ); + div.innerHTML = ""; + + // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 + // old WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // 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() + support.noCloneEvent = true; + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); } -}); + + // Execute the test only if not already executed in another module. + if (support.deleteExpando == null) { + // Support: IE<9 + support.deleteExpando = true; + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + } + + // Null elements to avoid leaks in IE. + fragment = div = input = null; +})(); + + +(function() { + var i, eventName, + div = document.createElement( "div" ); + + // Support: IE<9 (lack submit/change bubble), Firefox 23+ (lack focusin event) + for ( i in { submit: true, change: true, focusin: true }) { + eventName = "on" + i; + + if ( !(support[ i + "Bubbles" ] = eventName in window) ) { + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + div.setAttribute( eventName, "t" ); + support[ i + "Bubbles" ] = div.attributes[ eventName ].expando === false; + } + } + + // Null elements to avoid leaks in IE. + div = null; +})(); + + var rformElems = /^(?:input|select|textarea)$/i, rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|contextmenu)|click/, @@ -4762,7 +4288,7 @@ jQuery.event = { 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 !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? + return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; @@ -4771,7 +4297,7 @@ jQuery.event = { } // Handle multiple events separated by a space - types = ( types || "" ).match( core_rnotwhite ) || [""]; + types = ( types || "" ).match( rnotwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[t] ) || []; @@ -4857,7 +4383,7 @@ jQuery.event = { } // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( core_rnotwhite ) || [""]; + types = ( types || "" ).match( rnotwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[t] ) || []; @@ -4922,8 +4448,8 @@ jQuery.event = { 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(".") : []; + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; cur = tmp = elem = elem || document; @@ -5009,8 +4535,11 @@ jQuery.event = { // Native handler handle = ontype && cur[ ontype ]; - if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { - event.preventDefault(); + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } } } event.type = type; @@ -5060,7 +4589,7 @@ jQuery.event = { var i, ret, handleObj, matched, j, handlerQueue = [], - args = core_slice.call( arguments ), + args = slice.call( arguments ), handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; @@ -5350,7 +4879,7 @@ jQuery.removeEvent = document.removeEventListener ? // #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 ] === core_strundefined ) { + if ( typeof elem[ name ] === strundefined ) { elem[ name ] = null; } @@ -5371,8 +4900,14 @@ jQuery.Event = function( src, props ) { // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || - src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && ( + // Support: IE < 9 + src.returnValue === false || + // Support: Android < 4.0 + src.getPreventDefault && src.getPreventDefault() ) ? + returnTrue : + returnFalse; // Event type } else { @@ -5466,7 +5001,7 @@ jQuery.each({ }); // IE submit delegation -if ( !jQuery.support.submitBubbles ) { +if ( !support.submitBubbles ) { jQuery.event.special.submit = { setup: function() { @@ -5513,7 +5048,7 @@ if ( !jQuery.support.submitBubbles ) { } // IE change delegation and checkbox/radio fix -if ( !jQuery.support.changeBubbles ) { +if ( !support.changeBubbles ) { jQuery.event.special.change = { @@ -5572,24 +5107,33 @@ if ( !jQuery.support.changeBubbles ) { } // Create "bubbling" focus and blur events -if ( !jQuery.support.focusinBubbles ) { +if ( !support.focusinBubbles ) { jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - // Attach a single capturing handler while someone wants focusin/focusout - var attaches = 0, - handler = function( event ) { + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); }; jQuery.event.special[ fix ] = { setup: function() { - if ( attaches++ === 0 ) { - document.addEventListener( orig, handler, true ); + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); } + jQuery._data( doc, fix, ( attaches || 0 ) + 1 ); }, teardown: function() { - if ( --attaches === 0 ) { - document.removeEventListener( orig, handler, true ); + var doc = this.ownerDocument || this, + attaches = jQuery._data( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + jQuery._removeData( doc, fix ); + } else { + jQuery._data( doc, fix, attaches ); } } }; @@ -5698,289 +5242,8 @@ jQuery.fn.extend({ } } }); -var isSimple = /^.[^:#\[\.,]*$/, - rparentsprev = /^(?:parents|prev(?:Until|All))/, - rneedsContext = jQuery.expr.match.needsContext, - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; -jQuery.fn.extend({ - find: function( selector ) { - var i, - ret = [], - self = this, - len = self.length; - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter(function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - }) ); - } - - 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; - }, - - has: function( target ) { - var i, - targets = jQuery( target, this ), - len = targets.length; - - return this.filter(function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - not: function( selector ) { - return this.pushStack( winnow(this, selector || [], true) ); - }, - - filter: function( selector ) { - return this.pushStack( winnow(this, selector || [], false) ); - }, - - is: function( selector ) { - 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 ) { - var cur, - i = 0, - l = this.length, - ret = [], - pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? - jQuery( selectors, context || this.context ) : - 0; - - for ( ; i < l; i++ ) { - for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { - // Always skip document fragments - if ( cur.nodeType < 11 && (pos ? - pos.index(cur) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector(cur, selectors)) ) { - - cur = ret.push( cur ); - break; - } - } - } - - return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; - } - - // index in selector - if ( typeof elem === "string" ) { - return jQuery.inArray( this[0], jQuery( elem ) ); - } - - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[0] : elem, this ); - }, - - add: function( selector, context ) { - var set = typeof selector === "string" ? - jQuery( selector, context ) : - jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), - all = jQuery.merge( this.get(), set ); - - return this.pushStack( jQuery.unique(all) ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter(selector) - ); - } -}); - -function sibling( cur, dir ) { - do { - cur = cur[ dir ]; - } while ( cur && cur.nodeType !== 1 ); - - return cur; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return jQuery.nodeName( elem, "iframe" ) ? - elem.contentDocument || elem.contentWindow.document : - jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - ret = jQuery.filter( selector, ret ); - } - - if ( this.length > 1 ) { - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - ret = jQuery.unique( ret ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - ret = ret.reverse(); - } - } - - return this.pushStack( ret ); - }; -}); - -jQuery.extend({ - filter: function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - 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 ) { - var matched = [], - cur = elem[ dir ]; - - while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { - if ( cur.nodeType === 1 ) { - matched.push( cur ); - } - cur = cur[dir]; - } - return matched; - }, - - sibling: function( n, elem ) { - var r = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - r.push( n ); - } - } - - return r; - } -}); - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - /* jshint -W018 */ - return !!qualifier.call( elem, i, elem ) !== not; - }); - - } - - 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(); @@ -6005,7 +5268,6 @@ var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figca rtbody = /", "" ] + _default: support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] }, safeFragment = createSafeFragment( document ), fragmentDiv = safeFragment.appendChild( document.createElement("div") ); @@ -6034,270 +5296,40 @@ wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; -jQuery.fn.extend({ - text: function( value ) { - return jQuery.access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); - }, null, value, arguments.length ); - }, +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; - 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 ); + 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 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; } -}); + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} // 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" ) ? + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? elem.getElementsByTagName("tbody")[0] || elem.appendChild( elem.ownerDocument.createElement("tbody") ) : @@ -6367,7 +5399,7 @@ function fixCloneNodeIssues( src, dest ) { nodeName = dest.nodeName.toLowerCase(); // IE6-8 copies events bound via attachEvent when using cloneNode. - if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { + if ( !support.noCloneEvent && dest[ jQuery.expando ] ) { data = jQuery._data( dest ); for ( e in data.events ) { @@ -6394,11 +5426,11 @@ function fixCloneNodeIssues( src, dest ) { // 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) ) ) { + if ( support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { dest.innerHTML = src.innerHTML; } - } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + } 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 @@ -6423,67 +5455,12 @@ function fixCloneNodeIssues( src, dest ) { } } -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 + ">" ) ) { + if ( support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { clone = elem.cloneNode( true ); // IE<=8 does not properly clone detached, unknown element nodes @@ -6492,7 +5469,7 @@ jQuery.extend({ fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); } - if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + if ( (!support.noCloneEvent || !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 @@ -6563,7 +5540,7 @@ jQuery.extend({ tmp = tmp || safe.appendChild( context.createElement("div") ); // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + tag = (rtagName.exec( elem ) || [ "", "" ])[ 1 ].toLowerCase(); wrap = wrapMap[ tag ] || wrapMap._default; tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; @@ -6575,12 +5552,12 @@ jQuery.extend({ } // Manually add leading whitespace removed by IE - if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + if ( !support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); } // Remove IE's autoinserted from table fragments - if ( !jQuery.support.tbody ) { + if ( !support.tbody ) { // String was a , *may* have spurious elem = tag === "table" && !rtbody.test( elem ) ? @@ -6622,7 +5599,7 @@ jQuery.extend({ // Reset defaultChecked for any radios and checkboxes // about to be appended to the DOM in IE 6/7 (#8060) - if ( !jQuery.support.appendChecked ) { + if ( !support.appendChecked ) { jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); } @@ -6666,11 +5643,10 @@ jQuery.extend({ i = 0, internalKey = jQuery.expando, cache = jQuery.cache, - deleteExpando = jQuery.support.deleteExpando, + deleteExpando = support.deleteExpando, special = jQuery.event.special; for ( ; (elem = elems[i]) != null; i++ ) { - if ( acceptData || jQuery.acceptData( elem ) ) { id = elem[ internalKey ]; @@ -6700,399 +5676,452 @@ jQuery.extend({ if ( deleteExpando ) { delete elem[ internalKey ]; - } else if ( typeof elem.removeAttribute !== core_strundefined ) { + } else if ( typeof elem.removeAttribute !== strundefined ) { elem.removeAttribute( internalKey ); } else { elem[ internalKey ] = null; } - core_deletedIds.push( id ); + 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) { - jQuery(this).wrapAll( html.call(this, i) ); - }); - } + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, - if ( this[0] ) { - // The elements to wrap the target around - var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + 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 ); + } + }); + }, - if ( this[0].parentNode ) { - wrap.insertBefore( this[0] ); + 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 ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + 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 ) ); } - wrap.map(function() { - var elem = this; - - while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { - elem = elem.firstChild; + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); } - - return elem; - }).append( this ); + elem.parentNode.removeChild( elem ); + } } return this; }, - wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each(function(i) { - jQuery(this).wrapInner( html.call(this, i) ); - }); + 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.each(function() { - var self = jQuery( this ), - contents = self.contents(); + return this; + }, - if ( contents.length ) { - contents.wrapAll( html ); + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - } else { - self.append( html ); - } + return this.map(function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); }); }, - wrap: function( html ) { - var isFunction = jQuery.isFunction( html ); + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; - return this.each(function(i) { - jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); - }); - }, - - unwrap: function() { - return this.parent().each(function() { - if ( !jQuery.nodeName( this, "body" ) ) { - jQuery( this ).replaceWith( this.childNodes ); - } - }).end(); - } -}); -var iframe, getStyles, curCSS, - ralpha = /alpha\([^)]*\)/i, - 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 - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rmargin = /^margin/, - rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), - rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), - rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), - elemdisplay = { BODY: "block" }, - - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: 0, - fontWeight: 400 - }, - - cssExpand = [ "Top", "Right", "Bottom", "Left" ], - cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; - -// return a css property mapped to a potentially vendor prefixed property -function vendorPropName( style, name ) { - - // shortcut for names that are not vendor prefixed - if ( name in style ) { - return name; - } - - // check for vendor prefixed names - var capName = name.charAt(0).toUpperCase() + name.slice(1), - origName = name, - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in style ) { - return name; - } - } - - return origName; -} - -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 display, elem, hidden, - values = [], - index = 0, - length = elements.length; - - for ( ; index < length; index++ ) { - elem = elements[ index ]; - 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 ] && display === "none" ) { - elem.style.display = ""; + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; } - // Set elements which have been overridden with display: none - // in a stylesheet to whatever the default browser style is - // for such an element - if ( elem.style.display === "" && isHidden( elem ) ) { - values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); - } - } else { + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ (rtagName.exec( value ) || [ "", "" ])[ 1 ].toLowerCase() ] ) { - if ( !values[ index ] ) { - hidden = isHidden( elem ); + value = value.replace( rxhtmlTag, "<$1>" ); - if ( display && display !== "none" || !hidden ) { - jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); - } - } - } - } - - // Set the display of most of the elements in a second loop - // to avoid the constant reflow - for ( index = 0; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - if ( !show || elem.style.display === "none" || elem.style.display === "" ) { - elem.style.display = show ? values[ index ] || "" : "none"; - } - } - - return elements; -} - -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 ); - }, name, value, arguments.length > 1 ); - }, - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each(function() { - if ( isHidden( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - }); - } -}); - -jQuery.extend({ - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // 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, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: { - // normalize float css property - "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" - }, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = jQuery.camelCase( name ), - style = elem.style; - - name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); - - // gets hook for the prefixed version - // followed by the unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // convert relative number strings (+= or -=) to relative numbers. #7345 - if ( type === "string" && (ret = rrelNum.exec( value )) ) { - value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); - // Fixes bug #9237 - type = "number"; - } - - // Make sure that NaN and null values aren't set. See: #7116 - if ( value == null || type === "number" && isNaN( value ) ) { - return; - } - - // If a number was passed in, add 'px' to the (except for certain CSS properties) - if ( type === "number" && !jQuery.cssNumber[ origName ] ) { - 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 { - style[ name ] = value; + 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) {} } - } else { - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { - return ret; + if ( elem ) { + this.empty().append( value ); } - - // Otherwise just get the value from the style object - return style[ name ]; - } + }, null, value, arguments.length ); }, - css: function( elem, name, extra, styles ) { - var num, val, hooks, - origName = jQuery.camelCase( name ); + replaceWith: function() { + var arg = arguments[ 0 ]; - // Make sure that we're working with the right name - name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; - // gets hook for the prefixed version - // followed by the unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + jQuery.cleanData( getAll( this ) ); - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = 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" && + !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 ); + }); } - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, 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 ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } } - //convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Return, converting to number if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; - } - return val; + return this; } }); -// NOTE: we've included the "window" in window.getComputedStyle -// because jsdom on node.js will break without it. -if ( window.getComputedStyle ) { - getStyles = function( elem ) { - return window.getComputedStyle( elem, null ); +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() + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle ? + + // Use of this method is a temporary fix (more like optmization) until something better comes along, + // since it was removed from specification and supported only in FF + window.getDefaultComputedStyle( elem[ 0 ] ).display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( "