diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..26b4129 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,29 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +root = true + + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# Tabs in JS unless otherwise specified +[**.js] +indent_style = tab + +[Makefile] +indent_style = tab + + +[test/**.xml] +indent_style = tab + +[test/**.html] +indent_style = tab + +[test/**.css] +indent_style = space +indent_size = 8 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..531643c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* eol=lf +*.jar binary \ No newline at end of file diff --git a/.gitignore b/.gitignore index b7db392..2acd79f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ .DS_Store .bower.json .sizecache.json + /bower_components /node_modules diff --git a/.jscs.json b/.jscs.json new file mode 100644 index 0000000..f6e26ce --- /dev/null +++ b/.jscs.json @@ -0,0 +1,21 @@ +{ + "requireCurlyBraces": [ "if", "else", "for", "while", "do" ], + "requireSpaceAfterKeywords": [ "if", "else", "for", "while", "do", "switch", "return" ], + "requireSpacesInFunctionExpression": { + "beforeOpeningCurlyBrace": true + }, + "disallowSpacesInFunctionExpression": { + "beforeOpeningRoundBrace": true + }, + "requireMultipleVarDecl": true, + "requireSpacesInsideObjectBrackets": "all", + "requireSpacesInsideArrayBrackets": "all", + "disallowLeftStickedOperators": [ "?", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<=" ], + "disallowRightStickedOperators": [ "?", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<=" ], + "requireRightStickedOperators": [ "!" ], + "requireLeftStickedOperators": [ "," ], + "disallowKeywords": [ "with" ], + "disallowMultipleLineBreaks": true, + "disallowKeywordsOnNewLine": [ "else" ], + "requireLineFeedAtFileEnd": true +} diff --git a/.jshintrc b/.jshintrc index ddc9e08..1e12a54 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,17 +1,17 @@ { - "boss": true, - "curly": true, - "eqeqeq": true, - "eqnull": true, - "expr": true, - "immed": true, - "noarg": true, - "onevar": true, - "quotmark": "double", - "smarttabs": true, - "trailing": true, - "undef": true, - "unused": true, + "boss": true, + "curly": true, + "eqeqeq": true, + "eqnull": true, + "expr": true, + "immed": true, + "noarg": true, + "onevar": true, + "quotmark": "double", + "smarttabs": true, + "trailing": true, + "undef": true, + "unused": true, - "node": true + "node": true } diff --git a/.npmignore b/.npmignore index 22a69b9..d184f6e 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,11 @@ .jshintignore .jshintrc +/.editorconfig +/.gitattributes +/.jscs.json +/.travis.yml + /build /test /Gruntfile.js diff --git a/Gruntfile.js b/Gruntfile.js index 2c08ef4..56dddf9 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,113 +1,119 @@ -module.exports = function (grunt) { - "use strict"; +module.exports = function(grunt) { + "use strict"; - function readOptionalJSON(filepath) { - var data = {}; - try { - data = grunt.file.readJSON(filepath); - } catch (e) {} - return data; - } + var gzip = require("gzip-js"); - var gzip = require( "gzip-js" ), - srcHintOptions = readOptionalJSON("src/.jshintrc"); + grunt.initConfig({ + pkg: grunt.file.readJSON("package.json"), + bowercopy: { + options: { + clean: true + }, + tests: { + files: { + "test/libs/qunit.js": "qunit/qunit/qunit.js", + "test/libs/qunit.css": "qunit/qunit/qunit.css", + "test/libs/require.js": "requirejs/require.js", + "test/libs/modernizr.js": "modernizr/modernizr.js" + } + } + }, + compare_size: { + files: [ "dist/<%= pkg.name %>.js", "dist/<%= pkg.name %>.min.js" ], + options: { + compress: { + gz: function(contents) { + return gzip.zip(contents, {}).length; + } + }, + cache: "dist/.sizecache.json" + } + }, + compile: { + all: { + dest: "dist/<%= pkg.name %>.js", + src: "src/<%= pkg.name %>.js" + } + }, + coveralls: { + options: { + force: true + }, + all: { + // LCOV coverage file relevant to every target + src: "_tests/reports/lcov/lcov.info" + } + }, + jscs: { + src: "src/**/*.js", + gruntfile: "Gruntfile.js", + tasks: "build/tasks/*.js", + options: { + config: ".jscs.json" + } + }, + jshint: { + source: { + src: "src/<%= pkg.name %>.js", + options: { + jshintrc: "src/.jshintrc" + } + }, + grunt: { + src: [ "Gruntfile.js", "tasks/*" ], + options: { + jshintrc: ".jshintrc" + } + } + }, + jsonlint: { + pkg: { + src: [ "package.json" ] + }, + jscs: { + src: [ ".jscs.json" ] + }, + bower: { + src: [ "bower.json" ] + } + }, + qunit: { + files: [ "test/index.html" ] + }, + uglify: { + all: { + files: { + "dist/<%= pkg.name %>.min.js": [ "dist/<%= pkg.name %>.js" ] + }, + options: { + banner: "/*! <%= pkg.title %> v<%= pkg.version %> | (c) 2012 <%= pkg.author.name %> | Licensed <%= _.pluck(pkg.licenses, 'type').join(', ') %> */", + beautify: { + ascii_only: true + }, + compress: { + hoist_funs: false, + loops: false + }, + sourceMap: "dist/<%= pkg.name %>.min.map" + } + } + }, + version: { + files: [ "package.json", "bower.json" ] + } + }); - // The concatenated file won"t pass onevar - // But our modules can - delete srcHintOptions.onevar; + // Load grunt tasks from NPM packages + require("load-grunt-tasks")(grunt); - grunt.initConfig({ - pkg: grunt.file.readJSON("package.json"), - dst: readOptionalJSON("dist/.destination.json"), - compare_size: { - files: [ "dist/<%= pkg.name %>.js", "dist/<%= pkg.name %>.min.js" ], - options: { - compress: { - gz: function( contents ) { - return gzip.zip( contents, {} ).length; - } - }, - cache: "build/.sizecache.json" - } - }, - jsonlint: { - pkg: { - src: [ "package.json" ] - }, - bower: { - src: [ "bower.json" ] - } - }, - jshint: { - all: { - src: [ - "src/**/*.js", "Gruntfile.js" - ], - options: { - jshintrc: true - } - }, - dist: { - src: "dist/<%= pkg.name %>.js", - options: srcHintOptions - } - }, - concat: { - options: { - separator: ";" - }, - dist: { - src: ["src/**/*.js"], - dest: "dist/<%= pkg.name %>.js" - } - }, - watch: { - files: [ "<%= jshint.all.src %>" ], - tasks: "dev" - }, - uglify: { - all: { - files: { - "dist/<%= pkg.name %>.min.js": ["dist/<%= pkg.name %>.js"] - }, - options: { - preserveComments: false, - sourceMap: "dist/<%= pkg.name %>.min.map", - sourceMappingURL: "<%= pkg.name %>.min.map", - report: "min", - beautify: { - ascii_only: true - }, - banner: "/*! <%= pkg.title %> - v<%= pkg.version %> - " + - "<%= grunt.template.today('isoDate') %>\n" + - "<%= pkg.homepage ? '* ' + pkg.homepage + '\\n' : '' %>" + - "* Copyright <%= grunt.template.today('yyyy') %> <%= pkg.author.name %>" + - " Licensed <%= _.pluck(pkg.licenses, 'type').join(', ') %> */\n", - compress: { - hoist_funs: false, - loops: false, - unused: false - } - } - } - }, - qunit: { - files: ["test/**/*.html"] - } - }); + // Integrate Detectizr specific tasks + grunt.loadTasks("tasks"); - // Load grunt tasks from NPM packages - require("load-grunt-tasks")(grunt); + grunt.registerTask("lint", [ "jsonlint", "jshint", "jscs" ]); + grunt.registerTask("build", [ "lint", "compile", "uglify", "dist", "compare_size" ]); + grunt.registerTask("test", [ "lint", "qunit" ]); + grunt.registerTask("default", [ "test", "compare_size" ]); - // Integrate jQuery specific tasks - grunt.loadTasks("build/tasks"); - - // Short list as a high frequency watch task - grunt.registerTask("dev", ["jshint"]); - - // this would be run by typing "grunt test" on the command line - grunt.registerTask("test", ["jshint", "qunit"]); - - // Default grunt - grunt.registerTask("default", [ "jshint", "jsonlint", "concat", "uglify", "compare_size"]); + // Task aliases + grunt.registerTask("bower", "bowercopy"); }; diff --git a/README.md b/README.md index c9fa82e..3a33516 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Using it is a simple matter, illustrated in the following excerpt: diff --git a/bower.json b/bower.json index a46eac1..879f5c0 100644 --- a/bower.json +++ b/bower.json @@ -1,20 +1,21 @@ { "name": "detectizr", - "version": "1.5.0", + "version": "2.0.0", + "main": "dist/detectizr.js", + "license": "MIT", "ignore": [ "**/.*", "test", "*.md", "Gruntfile.js", - "package.json", - "bower.json" + "package.json" ], "dependencies": { - "modernizr": "2.6.2" + "modernizr": "2.7.1" }, "devDependencies": { - "requirejs": "~2.1.8", - "qunit": "~1.12.0" + "requirejs": "2.1.11", + "qunit": "1.14.0" }, "keywords": [ "detect", diff --git a/build/tasks/dist.js b/build/tasks/dist.js deleted file mode 100644 index 0b2f392..0000000 --- a/build/tasks/dist.js +++ /dev/null @@ -1,72 +0,0 @@ -module.exports = function( grunt ) { - - "use strict"; - - var fs = require( "fs" ), - distpaths = [ - "dist/detectizr.js", - "dist/detectizr.min.map", - "dist/detectizr.min.js" - ]; - - // Process files for distribution - grunt.registerTask( "dist", function() { - var stored, flags, paths, nonascii; - - // Check for stored destination paths - // ( set in dist/.destination.json ) - stored = Object.keys( grunt.config( "dst" ) ); - - // Allow command line input as well - flags = Object.keys( this.flags ); - - // Combine all output target paths - paths = [].concat( stored, flags ).filter(function( path ) { - return path !== "*"; - }); - - // Ensure the dist files are pure ASCII - nonascii = false; - - distpaths.forEach(function( filename ) { - var i, c, - text = fs.readFileSync( filename, "utf8" ); - - // Ensure only ASCII chars so script tags don't need a charset attribute - if ( text.length !== Buffer.byteLength( text, "utf8" ) ) { - grunt.log.writeln( filename + ": Non-ASCII characters detected:" ); - for ( i = 0; i < text.length; i++ ) { - c = text.charCodeAt( i ); - if ( c > 127 ) { - grunt.log.writeln( "- position " + i + ": " + c ); - grunt.log.writeln( "-- " + text.substring( i - 20, i + 20 ) ); - break; - } - } - nonascii = true; - } - - // Modify map/min so that it points to files in the same folder; - // see https://github.com/mishoo/UglifyJS2/issues/47 - if ( /\.map$/.test( filename ) ) { - text = text.replace( /"dist\//g, "\"" ); - fs.writeFileSync( filename, text, "utf-8" ); - } - - // Optionally copy dist files to other locations - paths.forEach(function( path ) { - var created; - - if ( !/\/$/.test( path ) ) { - path += "/"; - } - - created = path + filename.replace( "dist/", "" ); - grunt.file.write( created, text ); - grunt.log.writeln( "File '" + created + "' created." ); - }); - }); - - return !nonascii; - }); -}; \ No newline at end of file diff --git a/dist/detectizr.js b/dist/detectizr.js index 715b7ce..f9161fb 100644 --- a/dist/detectizr.js +++ b/dist/detectizr.js @@ -1,494 +1,502 @@ -/*! - * Detectizr v1.5.0 - * http://barisaydinoglu.github.com/Detectizr/ - * https://github.com/barisaydinoglu/Detectizr - * Written by Baris Aydinoglu (http://baris.aydinoglu.info) - Copyright 2012 - * Contributor: Adrian Maurer (https://github.com/adrianmaurer) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Inspirations: - * - Browser selectors in CSS - http://37signals.com/svn/archives2/browser_selectors_in_css.php - * - Categorizr - http://www.brettjankord.com/2012/01/16/categorizr-a-modern-device-detection-script/ - */ -/* - * Detectizr, which requires Modernizr, adds some tests to Modernizr. - * It detects device, device model, screen size, operating system, - * and browser details. - * Detection of these sets are optional and can be disabled. - * - * Detectable device types are: tv (includes smart tv and game console), - * mobile, tablet, and desktop. Device models of tv, mobile and tablet - * are being detected. - * - * Author Baris Aydinoglu - */ -/* - * jslint browser: true, regexp: true, sloppy: true, white: true - * jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, curly:true, browser:true, indent:4, maxerr:50, regexp:false, white:false - */ -(function (window, navigator) { - var Modernizr = window.Modernizr, - options = { - // option for enabling HTML classes of all features (not only the true features) to be added - addAllFeaturesAsClass: false, - // option for enabling detection of device - detectDevice: true, - // option for enabling detection of device model - detectDeviceModel: true, - // option for enabling detection of screen size - detectScreen: true, - // option for enabling detection of operating system type and version - detectOS: true, - // option for enabling detection of browser type and version - detectBrowser: true, - // option for enabling detection of common browser plugins - detectPlugins: true - }; - - function Detectizr(opt) { - // Create Global "extend" method, so Detectizr does not need jQuery.extend - var extend = function (obj, extObj) { - var a, b, i; - if (arguments.length > 2) { - for (a = 1, b = arguments.length; a < b; a += 1) { - extend(obj, arguments[a]); - } - } else { - for (i in extObj) { - if (extObj.hasOwnProperty(i)) { - obj[i] = extObj[i]; - } - } - } - return obj; - }, - that = this, - device = Modernizr.Detectizr.device, - docElement = document.documentElement, - deviceTypes = ["tv", "tablet", "mobile", "desktop"], - rclass = /[\t\r\n]/g, - plugins2detect = { - acrobat: { - substrs: ["Adobe", "Acrobat"], - progIds: ["AcroPDF.PDF", "PDF.PDFCtrl.5"] - }, - flash: { - substrs: ["Shockwave", "Flash"], - progIds: ["ShockwaveFlash.ShockwaveFlash.1"] - }, - mediaplayer: { - substrs: ["Windows Media"], - progIds: ["wmplayer.ocx"] - }, - silverlight: { - substrs: ["Silverlight"], - progIds: ["AgControl.AgControl"] - } - }, - i, j, k, l, alias, plugin, resizeTimeoutId, oldOrientation; - options = extend({}, options, opt || {}); - - // simplified and localized indexOf method as one parameter fixed as useragent - that.is = function (key) { - return device.userAgent.indexOf(key) > -1; - }; - - // simplified and localized regex test method as one parameter fixed as useragent - that.test = function (regex) { - return regex.test(device.userAgent); - }; - - // simplified and localized regex exec method as one parameter fixed as useragent - that.exec = function (regex) { - return regex.exec(device.userAgent); - }; - - // localized string trim method - that.trim = function (value) { - return value.replace(/^\s+|\s+$/g, ""); - }; - - // convert string to camelcase - that.toCamel = function (string) { - if (string === null || string === undefined) { - return ""; - } - return String(string).replace(/((\s|\-|\.)+[a-z0-9])/g, function ($1) { - return $1.toUpperCase().replace(/(\s|\-|\.)/g, ""); - }); - }; - - // removeClass function inspired from jQuery.removeClass - that.removeClass = function (element, value) { - var class2remove = value || "", - cur = element.nodeType === 1 && ( element.className ? ( " " + element.className + " " ).replace( rclass, " " ) : ""); - if ( cur ) { - while ( cur.indexOf( " " + class2remove + " " ) >= 0 ) { - cur = cur.replace( " " + class2remove + " ", " " ); - } - element.className = value ? that.trim(cur) : ""; - } - }; - - // add version test to Modernizr - that.addVersionTest = function (mainTest, version, maxLength) { - if (version !== null && version !== undefined && version !== "") { - version = that.toCamel(version); - if (version !== "") { - if (maxLength !== undefined && maxLength > 0) { - version = version.substr(0, maxLength); - } - that.addConditionalTest(mainTest + version, true); - } - } - }; - - that.checkOrientation = function () { - //timeout wrapper points with doResizeCode as callback - window.clearTimeout(resizeTimeoutId); - resizeTimeoutId = window.setTimeout(function () { - oldOrientation = device.orientation; - //wrapper for height/width check - if (window.innerHeight > window.innerWidth) { - device.orientation = "portrait"; - } else { - device.orientation = "landscape"; - } - that.addConditionalTest(device.orientation, true); - if (oldOrientation !== device.orientation) { - that.addConditionalTest(oldOrientation, false); - } - }, 10); - }; - - // add test to Modernizr based on a conditi - that.addConditionalTest = function (feature, test) { - if (feature === null || feature === undefined || feature === "") { - return; - } - if (options.addAllFeaturesAsClass) { - Modernizr.addTest(feature, test); - } else { - test = typeof test === "function" ? test() : test; - if (test) { - Modernizr.addTest(feature, true); - } else { - delete Modernizr[feature]; - that.removeClass(docElement, feature); - } - } - }; - - /** Device detection **/ - if (options.detectDevice) { - if (that.test(/GoogleTV|SmartTV|Internet.TV|NetCast|NETTV|AppleTV|boxee|Kylo|Roku|DLNADOC|CE\-HTML/i)) { - // Check if user agent is a smart tv - device.type = deviceTypes[0]; - device.model = "smartTv"; - } else if (that.test(/Xbox|PLAYSTATION.3|Wii/i)) { - // Check if user agent is a game console - device.type = deviceTypes[0]; - device.model = "gameConsole"; - } else if (that.test(/iP(a|ro)d/i)) { - // Check if user agent is a iPad - device.type = deviceTypes[1]; - device.model = "ipad"; - } else if ((that.test(/tablet/i) && !that.test(/RX-34/i)) || that.test(/FOLIO/i)) { - // Check if user agent is a Tablet - device.type = deviceTypes[1]; - device.model = String(that.exec(/playbook/) || ""); - } else if (that.test(/Linux/i) && that.test(/Android/i) && !that.test(/Fennec|mobi|HTC.Magic|HTCX06HT|Nexus.One|SC-02B|fone.945/i)) { - // Check if user agent is an Android Tablet - device.type = deviceTypes[1]; - device.model = "android"; - } else if (that.test(/Kindle/i) || (that.test(/Mac.OS/i) && that.test(/Silk/i))) { - // Check if user agent is a Kindle or Kindle Fire - device.type = deviceTypes[1]; - device.model = "kindle"; - } else if (that.test(/GT-P10|SC-01C|SHW-M180S|SGH-T849|SCH-I800|SHW-M180L|SPH-P100|SGH-I987|zt180|HTC(.Flyer|\_Flyer)|Sprint.ATP51|ViewPad7|pandigital(sprnova|nova)|Ideos.S7|Dell.Streak.7|Advent.Vega|A101IT|A70BHT|MID7015|Next2|nook/i) || (that.test(/MB511/i) && that.test(/RUTEM/i))) { - // Check if user agent is a pre Android 3.0 Tablet - device.type = deviceTypes[1]; - device.model = "android"; - } else if (that.test(/BB10/i)) { - // Check if user agent is a BB10 device - device.type = deviceTypes[1]; - device.model = "blackberry"; - } else { - // Check if user agent is one of common mobile types - device.model = that.exec(/iphone|ipod|android|blackberry|opera mini|opera mobi|skyfire|maemo|windows phone|palm|iemobile|symbian|symbianos|fennec|j2me/i); - if (device.model !== null) { - device.type = deviceTypes[2]; - device.model = String(device.model); - } else { - device.model = ""; - if (that.test(/BOLT|Fennec|Iris|Maemo|Minimo|Mobi|mowser|NetFront|Novarra|Prism|RX-34|Skyfire|Tear|XV6875|XV6975|Google.Wireless.Transcoder/i)) { - // Check if user agent is unique Mobile User Agent - device.type = deviceTypes[2]; - } else if (that.test(/Opera/i) && that.test(/Windows.NT.5/i) && that.test(/HTC|Xda|Mini|Vario|SAMSUNG\-GT\-i8000|SAMSUNG\-SGH\-i9/i)) { - // Check if user agent is an odd Opera User Agent - http://goo.gl/nK90K - device.type = deviceTypes[2]; - } else if ((that.test(/Windows.(NT|XP|ME|9)/i) && !that.test(/Phone/i)) || that.test(/Win(9|.9|NT)/i) || that.test(/\(Windows 8\)/i)) { - // Check if user agent is Windows Desktop, "(Windows 8)" Chrome extra exception - device.type = deviceTypes[3]; - } else if (that.test(/Macintosh|PowerPC/i) && !that.test(/Silk/i)) { - // Check if agent is Mac Desktop - device.type = deviceTypes[3]; - } else if (that.test(/Linux/i) && that.test(/X11/i)) { - // Check if user agent is a Linux Desktop - device.type = deviceTypes[3]; - } else if (that.test(/Solaris|SunOS|BSD/i)) { - // Check if user agent is a Solaris, SunOS, BSD Desktop - device.type = deviceTypes[3]; - } else if (that.test(/Bot|Crawler|Spider|Yahoo|ia_archiver|Covario-IDS|findlinks|DataparkSearch|larbin|Mediapartners-Google|NG-Search|Snappy|Teoma|Jeeves|TinEye/i) && !that.test(/Mobile/i)) { - // Check if user agent is a Desktop BOT/Crawler/Spider - device.type = deviceTypes[3]; - device.model = "crawler"; - } else { - // Otherwise assume it is a Mobile Device - device.type = deviceTypes[2]; - } - } - } - for (i = 0, j = deviceTypes.length; i < j; i += 1) { - that.addConditionalTest(deviceTypes[i], (device.type === deviceTypes[i])); - } - if (options.detectDeviceModel) { - that.addConditionalTest(that.toCamel(device.model), true); - } - if (device.type === deviceTypes[1] || device.type === deviceTypes[2]) { - window.onresize = function (event) { - that.checkOrientation(event); - }; - that.checkOrientation(); - } - } - - /** Screen detection **/ - if (options.detectScreen && !!Modernizr.mq) { - that.addConditionalTest("smallScreen", Modernizr.mq("only screen and (max-width: 480px)")); - that.addConditionalTest("verySmallScreen", Modernizr.mq("only screen and (max-width: 320px)")); - that.addConditionalTest("veryVerySmallScreen", Modernizr.mq("only screen and (max-width: 240px)")); - } - - /** OS detection **/ - if (options.detectOS) { - if (device.model !== "") { - if (device.model === "ipad" || device.model === "iphone" || device.model === "ipod") { - device.osVersion = (that.test(/os\s(\d+)_/) ? RegExp.$1 : ""); - device.os = "ios"; - // Full version check - device.osVersionFull = (that.test(/os ([^\s]+)/) ? RegExp.$1.replace(/_/g,".") : ""); - } else if (device.model === "android") { - device.osVersion = (that.test(/os\s(\d+)_/) ? RegExp.$1 : "").substr(0, 2); - if (!device.osVersion) { - device.osVersion = (that.test(/android\s(\d+)\./) ? RegExp.$1 : ""); - device.osVersionFull = (that.test(/android ([^\s]+)/) ? RegExp.$1.replace(/_/g,".") : ""); - } - device.os = "android"; - } else if (device.model === "blackberry") { - device.osVersion = (that.test(/version\/([^\s]+)/) ? RegExp.$1 : ""); - device.os = "blackberry"; - } else if (device.model === "playbook") { - device.osVersion = (that.test(/os ([^\s]+)/) ? RegExp.$1.replace(";", "") : ""); - device.os = "blackberry"; - } - } - if (device.os === "") { - if (that.is("win") || that.is("16bit")) { - device.os = "windows"; - if (that.is("windows nt 6.3")) { - device.osVersion = "8"; - device.osVersionFull = "8.1"; - } else if (that.is("windows nt 6.2") || that.test(/\(windows 8\)/)) { //windows 8 chrome mac fix - device.osVersion = "8"; - } else if (that.is("windows nt 6.1")) { - device.osVersion = "7"; - } else if (that.is("windows nt 6.0")) { - device.osVersion = "vista"; - } else if (that.is("windows nt 5.2") || that.is("windows nt 5.1") || that.is("windows xp")) { - device.osVersion = "xp"; - } else if (that.is("windows nt 5.0") || that.is("windows 2000")) { - device.osVersion = "2k"; - } else if (that.is("winnt") || that.is("windows nt")) { - device.osVersion = "nt"; - } else if (that.is("win98") || that.is("windows 98")) { - device.osVersion = "98"; - } else if (that.is("win95") || that.is("windows 95")) { - device.osVersion = "95"; - } - } else if (that.is("mac") || that.is("darwin")) { - device.os = "mac"; - if (that.is("68k") || that.is("68000")) { - device.osVersion = "68k"; - } else if (that.is("ppc") || that.is("powerpc")) { - device.osVersion = "ppc"; - } else if (that.is("os x")) { - device.osVersion = "os x"; - } - } else if (that.is("webtv")) { - device.os = "webtv"; - } else if (that.is("x11") || that.is("inux")) { - device.os = "linux"; - } else if (that.is("sunos")) { - device.os = "sun"; - } else if (that.is("irix")) { - device.os = "irix"; - } else if (that.is("freebsd")) { - device.os = "freebsd"; - } else if (that.is("bsd")) { - device.os = "bsd"; - } - } - if (device.os !== "") { - // assign the full version property if not ios (special case. see above ios check) - if (!device.osVersionFull && !!device.osVersion) { - device.osVersionFull = device.osVersion; - } - that.addConditionalTest(device.os, true); - that.addVersionTest(device.os, device.osVersionFull.replace(/\./g, "_")); - that.addVersionTest(device.os, device.osVersion); - } - - } - - /** Browser detection **/ - if (options.detectBrowser) { - if (!that.test(/opera|webtv/i) && (that.test(/msie\s([0-9]{1,})/) || that.is("trident"))) { - device.browserEngine = "trident"; - device.browser = "ie"; - if (!window.addEventListener && document.documentMode && document.documentMode === 7) { - device.browserVersion = "8compat"; - } else if (that.test(/trident.*rv[ :](\d+)\./)) { - device.browserVersion = RegExp.$1; - } else { - device.browserVersion = (that.test(/trident\/4\.0/) ? "8" : RegExp.$1); - } - } else if (that.is("firefox")) { - device.browserEngine = "gecko"; - device.browser = "firefox"; - device.browserVersion = (that.test(/firefox\/(\d+(\.?\d+)*)/) ? RegExp.$1 : "").substr(0, 2); - } else if (that.is("gecko/")) { - device.browserEngine = "gecko"; - } else if (that.is("opera")) { - device.browser = "opera"; - device.browserEngine = "presto"; - device.browserVersion = (that.test(/version\/(\d+)/) ? RegExp.$1 : (that.test(/opera(\s|\/)(\d+)/) ? RegExp.$2 : "")); - } else if (that.is("konqueror")) { - device.browser = "konqueror"; - } else if (that.is("chrome")) { - device.browserEngine = "webkit"; - device.browser = "chrome"; - device.browserVersion = (that.test(/chrome\/(\d+)/) ? RegExp.$1 : ""); - } else if (that.is("iron")) { - device.browserEngine = "webkit"; - device.browser = "iron"; - } else if (that.is("crios")) { - device.browser = "chrome"; - device.browserEngine = "webkit"; - device.browserVersion = (that.test(/crios\/(\d+)/) ? RegExp.$1 : ""); - } else if (that.is("applewebkit/")) { - device.browser = "safari"; - device.browserEngine = "webkit"; - device.browserVersion = (that.test(/version\/(\d+)/) ? RegExp.$1 : ""); - } else if (that.is("mozilla/")) { - device.browserEngine = "gecko"; - } - if (device.browser !== "") { - that.addConditionalTest(device.browser, true); - if (device.browserVersion !== "") { - that.addVersionTest(device.browser, device.browserVersion); - } - } - that.addConditionalTest(device.browserEngine, true); - } - - /** Plugin detection **/ - if (options.detectPlugins) { - that.detectPlugin = function (substrs) { - for (i = 0, j = navigator.plugins.length; i < j; i++) { - var plugin = navigator.plugins[i], - haystack = plugin.name + plugin.description, - found = 0; - for (k = 0, l = substrs.length; k < l; k += 1) { - if (haystack.indexOf(substrs[k]) !== -1) { - found += 1; - } - } - if (found === substrs.length) { - return true; - } - } - return false; - }; - that.detectObject = function (progIds, fns) { - for (i = 0, j = progIds.length; i < j; i++) { - try { - var obj = new ActiveXObject(progIds[i]); - if (obj) { - return fns && fns[i] ? fns[i].call(obj) : true; - } - } catch (e) { - // Ignore - } - } - return false; - }; - if (window.ActiveXObject) { - for (alias in plugins2detect) { - if (plugins2detect.hasOwnProperty(alias)) { - plugin = plugins2detect[alias]; - if (that.detectObject(plugin.progIds, plugin.fns)) { - device.browserPlugins.push(alias); - that.addConditionalTest(alias, true); - } - } - } - } else if (navigator.plugins) { - for (alias in plugins2detect) { - if (plugins2detect.hasOwnProperty(alias)) { - plugin = plugins2detect[alias]; - if (that.detectPlugin(plugin.substrs)) { - device.browserPlugins.push(alias); - that.addConditionalTest(alias, true); - } - } - } - } - if (navigator.javaEnabled()) { - device.browserPlugins.push("java"); - that.addConditionalTest("java", true); - } - } - } - - function init() { - if (Modernizr !== undefined) { - Modernizr.Detectizr = Modernizr.Detectizr || {}; - Modernizr.Detectizr.device = { - type: "", - model: "", - orientation: "", - browser: "", - browserEngine: "", - browserPlugins: [], - browserVersion: "", - os: "", - osVersion: "", - osVersionFull: "", - userAgent: (navigator.userAgent || navigator.vendor || window.opera).toLowerCase() - }; - Modernizr.Detectizr.detect = function (settings) { - return new Detectizr(settings); - }; - } - } - init(); -}(this, navigator)); - -/** Sample usages **/ -// Modernizr.Detectizr.detect(); -// Modernizr.Detectizr.detect({detectScreen:false}); +/*! + * Detectizr v2.0.0 + * http://barisaydinoglu.github.com/Detectizr/ + * + * Written by Baris Aydinoglu (http://baris.aydinoglu.info) - Copyright 2012 + * Released under the MIT license + * + * Date: 2014-03-21 + */ +window.Detectizr = (function(window, navigator, document, undefined) { + var Detectizr = {}, + Modernizr = window.Modernizr, + deviceTypes = [ "tv", "tablet", "mobile", "desktop" ], + options = { + // option for enabling HTML classes of all features (not only the true features) to be added + addAllFeaturesAsClass: false, + // option for enabling detection of device + detectDevice: true, + // option for enabling detection of device model + detectDeviceModel: true, + // option for enabling detection of screen size + detectScreen: true, + // option for enabling detection of operating system type and version + detectOS: true, + // option for enabling detection of browser type and version + detectBrowser: true, + // option for enabling detection of common browser plugins + detectPlugins: true + }, + plugins2detect = [ { + name: "adobereader", + substrs: [ "Adobe", "Acrobat" ], + // AcroPDF.PDF is used by version 7 and later + // PDF.PdfCtrl is used by version 6 and earlier + progIds: [ "AcroPDF.PDF", "PDF.PDFCtrl.5" ] + }, { + name: "flash", + substrs: [ "Shockwave Flash" ], + progIds: [ "ShockwaveFlash.ShockwaveFlash.1" ] + }, { + name: "wmplayer", + substrs: [ "Windows Media" ], + progIds: [ "wmplayer.ocx" ] + }, { + name: "silverlight", + substrs: [ "Silverlight" ], + progIds: [ "AgControl.AgControl" ] + }, { + name: "quicktime", + substrs: [ "QuickTime" ], + progIds: [ "QuickTime.QuickTime" ] + } ], + rclass = /[\t\r\n]/g, + docElement = document.documentElement, + resizeTimeoutId, + oldOrientation; + + function extend(obj, extObj) { + var a, b, i; + if (arguments.length > 2) { + for (a = 1, b = arguments.length; a < b; a += 1) { + extend(obj, arguments[a]); + } + } else { + for (i in extObj) { + if (extObj.hasOwnProperty(i)) { + obj[i] = extObj[i]; + } + } + } + return obj; + } + + // simplified and localized indexOf method as one parameter fixed as useragent + function is(key) { + return Detectizr.browser.userAgent.indexOf(key) > -1; + } + + // simplified and localized regex test method as one parameter fixed as useragent + function test(regex) { + return regex.test(Detectizr.browser.userAgent); + } + + // simplified and localized regex exec method as one parameter fixed as useragent + function exec(regex) { + return regex.exec(Detectizr.browser.userAgent); + } + + // localized string trim method + function trim(value) { + return value.replace(/^\s+|\s+$/g, ""); + } + + // convert string to camelcase + function toCamel(string) { + if (string === null || string === undefined) { + return ""; + } + return String(string).replace(/((\s|\-|\.)+[a-z0-9])/g, function($1) { + return $1.toUpperCase().replace(/(\s|\-|\.)/g, ""); + }); + } + + // removeClass function inspired from jQuery.removeClass + function removeClass(element, value) { + var class2remove = value || "", + cur = element.nodeType === 1 && (element.className ? (" " + element.className + " ").replace(rclass, " ") : ""); + if (cur) { + while (cur.indexOf(" " + class2remove + " ") >= 0) { + cur = cur.replace(" " + class2remove + " ", " "); + } + element.className = value ? trim(cur) : ""; + } + } + + // add version test to Modernizr + function addVersionTest(version, major, minor) { + if ( !!version) { + version = toCamel(version); + if ( !!major) { + major = toCamel(major); + addConditionalTest(version + major, true); + if ( !!minor) { + addConditionalTest(version + major + "_" + minor, true); + } + } + } + } + + function checkOrientation() { + //timeout wrapper points with doResizeCode as callback + window.clearTimeout(resizeTimeoutId); + resizeTimeoutId = window.setTimeout(function() { + oldOrientation = Detectizr.device.orientation; + //wrapper for height/width check + if (window.innerHeight > window.innerWidth) { + Detectizr.device.orientation = "portrait"; + } else { + Detectizr.device.orientation = "landscape"; + } + addConditionalTest(Detectizr.device.orientation, true); + if (oldOrientation !== Detectizr.device.orientation) { + addConditionalTest(oldOrientation, false); + } + }, 10); + } + + // add test to Modernizr based on a condition + function addConditionalTest(feature, test) { + if ( !!feature && !!Modernizr) { + if (options.addAllFeaturesAsClass) { + Modernizr.addTest(feature, test); + } else { + test = typeof test === "function" ? test() : test; + if (test) { + Modernizr.addTest(feature, true); + } else { + delete Modernizr[feature]; + removeClass(docElement, feature); + } + } + } + } + + // set version based on versionFull + function setVersion(versionType, versionFull) { + versionType.version = versionFull; + var versionArray = versionFull.split("."); + if (versionArray.length > 0) { + versionArray = versionArray.reverse(); + versionType.major = versionArray.pop(); + if (versionArray.length > 0) { + versionType.minor = versionArray.pop(); + if (versionArray.length > 0) { + versionArray = versionArray.reverse(); + versionType.patch = versionArray.join("."); + } else { + versionType.patch = "0"; + } + } else { + versionType.minor = "0"; + } + } else { + versionType.major = "0"; + } + } + + function detect(opt) { + // Create Global "extend" method, so Detectizr does not need jQuery.extend + var that = this, + i, j, k, device, os, browser, plugin2detect, pluginFound; + options = extend({}, options, opt || {}); + + /** Device detection **/ + if (options.detectDevice) { + Detectizr.device = { + type: "", + model: "", + orientation: "" + }; + device = Detectizr.device; + if (test(/googletv|smarttv|internet.tv|netcast|nettv|appletv|boxee|kylo|roku|dlnadoc|ce\-html/)) { + // Check if user agent is a smart tv + device.type = deviceTypes[0]; + device.model = "smartTv"; + } else if (test(/xbox|playstation.3|wii/)) { + // Check if user agent is a game console + device.type = deviceTypes[0]; + device.model = "gameConsole"; + } else if (test(/ip(a|ro)d/)) { + // Check if user agent is a iPad + device.type = deviceTypes[1]; + device.model = "ipad"; + } else if ((test(/tablet/) && !test(/rx-34/)) || test(/folio/)) { + // Check if user agent is a Tablet + device.type = deviceTypes[1]; + device.model = String(exec(/playbook/) || ""); + } else if (test(/linux/) && test(/android/) && !test(/fennec|mobi|htc.magic|htcX06ht|nexus.one|sc-02b|fone.945/)) { + // Check if user agent is an Android Tablet + device.type = deviceTypes[1]; + device.model = "android"; + } else if (test(/kindle/) || (test(/mac.os/) && test(/silk/))) { + // Check if user agent is a Kindle or Kindle Fire + device.type = deviceTypes[1]; + device.model = "kindle"; + } else if (test(/gt-p10|sc-01c|shw-m180s|sgh-t849|sch-i800|shw-m180l|sph-p100|sgh-i987|zt180|htc(.flyer|\_flyer)|sprint.atp51|viewpad7|pandigital(sprnova|nova)|ideos.s7|dell.streak.7|advent.vega|a101it|a70bht|mid7015|next2|nook/) || (test(/mb511/) && test(/rutem/))) { + // Check if user agent is a pre Android 3.0 Tablet + device.type = deviceTypes[1]; + device.model = "android"; + } else if (test(/bb10/)) { + // Check if user agent is a BB10 device + device.type = deviceTypes[1]; + device.model = "blackberry"; + } else { + // Check if user agent is one of common mobile types + device.model = exec(/iphone|ipod|android|blackberry|opera mini|opera mobi|skyfire|maemo|windows phone|palm|iemobile|symbian|symbianos|fennec|j2me/); + if (device.model !== null) { + device.type = deviceTypes[2]; + device.model = String(device.model); + } else { + device.model = ""; + if (test(/bolt|fennec|iris|maemo|minimo|mobi|mowser|netfront|novarra|prism|rx-34|skyfire|tear|xv6875|xv6975|google.wireless.transcoder/)) { + // Check if user agent is unique Mobile User Agent + device.type = deviceTypes[2]; + } else if (test(/opera/) && test(/windows.nt.5/) && test(/htc|xda|mini|vario|samsung\-gt\-i8000|samsung\-sgh\-i9/)) { + // Check if user agent is an odd Opera User Agent - http://goo.gl/nK90K + device.type = deviceTypes[2]; + } else if ((test(/windows.(nt|xp|me|9)/) && !test(/phone/)) || test(/win(9|.9|nt)/) || test(/\(windows 8\)/)) { + // Check if user agent is Windows Desktop, "(Windows 8)" Chrome extra exception + device.type = deviceTypes[3]; + } else if (test(/macintosh|powerpc/) && !test(/silk/)) { + // Check if agent is Mac Desktop + device.type = deviceTypes[3]; + device.model = "mac"; + } else if (test(/linux/) && test(/x11/)) { + // Check if user agent is a Linux Desktop + device.type = deviceTypes[3]; + } else if (test(/solaris|sunos|bsd/)) { + // Check if user agent is a Solaris, SunOS, BSD Desktop + device.type = deviceTypes[3]; + } else if (test(/bot|crawler|spider|yahoo|ia_archiver|covario-ids|findlinks|dataparksearch|larbin|mediapartners-google|ng-search|snappy|teoma|jeeves|tineye/) && !test(/mobile/)) { + // Check if user agent is a Desktop BOT/Crawler/Spider + device.type = deviceTypes[3]; + device.model = "crawler"; + } else { + // Otherwise assume it is a Mobile Device + device.type = deviceTypes[2]; + } + } + } + for (i = 0, j = deviceTypes.length; i < j; i += 1) { + addConditionalTest(deviceTypes[i], (device.type === deviceTypes[i])); + } + if (options.detectDeviceModel) { + addConditionalTest(toCamel(device.model), true); + } + } + + /** Screen detection **/ + if (options.detectScreen) { + if ( !!Modernizr && !!Modernizr.mq) { + addConditionalTest("smallScreen", Modernizr.mq("only screen and (max-width: 480px)")); + addConditionalTest("verySmallScreen", Modernizr.mq("only screen and (max-width: 320px)")); + addConditionalTest("veryVerySmallScreen", Modernizr.mq("only screen and (max-width: 240px)")); + } + if (device.type === deviceTypes[1] || device.type === deviceTypes[2]) { + window.onresize = function(event) { + checkOrientation(event); + }; + checkOrientation(); + } else { + device.orientation = "landscape"; + addConditionalTest(device.orientation, true); + } + } + + /** OS detection **/ + if (options.detectOS) { + Detectizr.os = {}; + os = Detectizr.os; + if (device.model !== "") { + if (device.model === "ipad" || device.model === "iphone" || device.model === "ipod") { + os.name = "ios"; + setVersion(os, (test(/os\s([\d_]+)/) ? RegExp.$1 : "").replace(/_/g, ".")); + } else if (device.model === "android") { + os.name = "android"; + setVersion(os, (test(/android\s([\d\.]+)/) ? RegExp.$1 : "")); + } else if (device.model === "blackberry") { + os.name = "blackberry"; + setVersion(os, (test(/version\/([^\s]+)/) ? RegExp.$1 : "")); + } else if (device.model === "playbook") { + os.name = "blackberry"; + setVersion(os, (test(/os ([^\s]+)/) ? RegExp.$1.replace(";", "") : "")); + } + } + if (!os.name) { + if (is("win") || is("16bit")) { + os.name = "windows"; + if (is("windows nt 6.3")) { + setVersion(os, "8.1"); + } else if (is("windows nt 6.2") || test(/\(windows 8\)/)) { //windows 8 chrome mac fix + setVersion(os, "8"); + } else if (is("windows nt 6.1")) { + setVersion(os, "7"); + } else if (is("windows nt 6.0")) { + setVersion(os, "vista"); + } else if (is("windows nt 5.2") || is("windows nt 5.1") || is("windows xp")) { + setVersion(os, "xp"); + } else if (is("windows nt 5.0") || is("windows 2000")) { + setVersion(os, "2k"); + } else if (is("winnt") || is("windows nt")) { + setVersion(os, "nt"); + } else if (is("win98") || is("windows 98")) { + setVersion(os, "98"); + } else if (is("win95") || is("windows 95")) { + setVersion(os, "95"); + } + } else if (is("mac") || is("darwin")) { + os.name = "mac os"; + if (is("68k") || is("68000")) { + setVersion(os, "68k"); + } else if (is("ppc") || is("powerpc")) { + setVersion(os, "ppc"); + } else if (is("os x")) { + setVersion(os, (test(/os\sx\s([\d_]+)/) ? RegExp.$1 : "os x").replace(/_/g, ".")); + } + } else if (is("webtv")) { + os.name = "webtv"; + } else if (is("x11") || is("inux")) { + os.name = "linux"; + } else if (is("sunos")) { + os.name = "sun"; + } else if (is("irix")) { + os.name = "irix"; + } else if (is("freebsd")) { + os.name = "freebsd"; + } else if (is("bsd")) { + os.name = "bsd"; + } + } + if ( !!os.name) { + addConditionalTest(os.name, true); + if ( !!os.major) { + addVersionTest(os.name, os.major); + if ( !!os.minor) { + addVersionTest(os.name, os.major, os.minor); + } + } + } + if (test(/\sx64|\sx86|\swin64|\swow64|\samd64/)) { + os.addressRegisterSize = "64bit"; + } else { + os.addressRegisterSize = "32bit"; + } + addConditionalTest(os.addressRegisterSize, true); + } + + /** Browser detection **/ + if (options.detectBrowser) { + browser = Detectizr.browser; + if (!test(/opera|webtv/) && (test(/msie\s([\d\w\.]+)/) || is("trident"))) { + browser.engine = "trident"; + browser.name = "ie"; + if (!window.addEventListener && document.documentMode && document.documentMode === 7) { + setVersion(browser, "8.compat"); + } else if (test(/trident.*rv[ :](\d+)\./)) { + setVersion(browser, RegExp.$1); + } else { + setVersion(browser, (test(/trident\/4\.0/) ? "8" : RegExp.$1)); + } + } else if (is("firefox")) { + browser.engine = "gecko"; + browser.name = "firefox"; + setVersion(browser, (test(/firefox\/([\d\w\.]+)/) ? RegExp.$1 : "")); + } else if (is("gecko/")) { + browser.engine = "gecko"; + } else if (is("opera")) { + browser.name = "opera"; + browser.engine = "presto"; + setVersion(browser, (test(/version\/([\d\.]+)/) ? RegExp.$1 : (test(/opera(\s|\/)([\d\.]+)/) ? RegExp.$2 : ""))); + } else if (is("konqueror")) { + browser.name = "konqueror"; + } else if (is("chrome")) { + browser.engine = "webkit"; + browser.name = "chrome"; + setVersion(browser, (test(/chrome\/([\d\.]+)/) ? RegExp.$1 : "")); + } else if (is("iron")) { + browser.engine = "webkit"; + browser.name = "iron"; + } else if (is("crios")) { + browser.name = "chrome"; + browser.engine = "webkit"; + setVersion(browser, (test(/crios\/([\d\.]+)/) ? RegExp.$1 : "")); + } else if (is("applewebkit/")) { + browser.name = "safari"; + browser.engine = "webkit"; + setVersion(browser, (test(/version\/([\d\.]+)/) ? RegExp.$1 : "")); + } else if (is("mozilla/")) { + browser.engine = "gecko"; + } + if ( !!browser.name) { + addConditionalTest(browser.name, true); + if ( !!browser.major) { + addVersionTest(browser.name, browser.major); + if ( !!browser.minor) { + addVersionTest(browser.name, browser.major, browser.minor); + } + } + } + addConditionalTest(browser.engine, true); + + // Browser Language + browser.language = navigator.userLanguage || navigator.language; + addConditionalTest(browser.language, true); + } + + /** Plugin detection **/ + if (options.detectPlugins) { + browser.plugins = []; + that.detectPlugin = function(substrs) { + var plugins = navigator.plugins, + plugin, haystack, pluginFoundText; + for (j = plugins.length - 1; j >= 0; j--) { + plugin = plugins[j]; + haystack = plugin.name + plugin.description; + pluginFoundText = 0; + for (k = substrs.length; k >= 0; k--) { + if (haystack.indexOf(substrs[k]) !== -1) { + pluginFoundText += 1; + } + } + if (pluginFoundText === substrs.length) { + return true; + } + } + return false; + }; + that.detectObject = function(progIds) { + for (j = progIds.length - 1; j >= 0; j--) { + try { + new ActiveXObject(progIds[j]); + } catch (e) { + // Ignore + } + } + return false; + }; + for (i = plugins2detect.length - 1; i >= 0; i--) { + plugin2detect = plugins2detect[i]; + pluginFound = false; + if (window.ActiveXObject) { + pluginFound = that.detectObject(plugin2detect.progIds); + } else if (navigator.plugins) { + pluginFound = that.detectPlugin(plugin2detect.substrs); + } + if (pluginFound) { + browser.plugins.push(plugin2detect.name); + addConditionalTest(plugin2detect.name, true); + } + } + if (navigator.javaEnabled()) { + browser.plugins.push("java"); + addConditionalTest("java", true); + } + } + } + + Detectizr.detect = function(settings) { + return detect(settings); + }; + Detectizr.init = function() { + if (Detectizr !== undefined) { + Detectizr.browser = { + userAgent: (navigator.userAgent || navigator.vendor || window.opera).toLowerCase() + }; + Detectizr.detect(); + } + }; + Detectizr.init(); + + return Detectizr; +}(this, this.navigator, this.document)); diff --git a/dist/detectizr.min.js b/dist/detectizr.min.js index 9f63d7f..6d20660 100644 --- a/dist/detectizr.min.js +++ b/dist/detectizr.min.js @@ -1,6 +1,3 @@ -/*! Detectizr - v1.5.0 - 2014-02-04 -* https://github.com/barisaydinoglu/Detectizr -* Copyright 2014 Baris Aydinoglu Licensed MIT */ - -!function(a,b){var c=a.Modernizr,d={addAllFeaturesAsClass:!1,detectDevice:!0,detectDeviceModel:!0,detectScreen:!0,detectOS:!0,detectBrowser:!0,detectPlugins:!0};function e(e){var f=function(a,b){var c,d,e;if(arguments.length>2)for(c=1,d=arguments.length;d>c;c+=1)f(a,arguments[c]);else for(e in b)b.hasOwnProperty(e)&&(a[e]=b[e]);return a},g=this,h=c.Detectizr.device,i=document.documentElement,j=["tv","tablet","mobile","desktop"],k=/[\t\r\n]/g,l={acrobat:{substrs:["Adobe","Acrobat"],progIds:["AcroPDF.PDF","PDF.PDFCtrl.5"]},flash:{substrs:["Shockwave","Flash"],progIds:["ShockwaveFlash.ShockwaveFlash.1"]},mediaplayer:{substrs:["Windows Media"],progIds:["wmplayer.ocx"]},silverlight:{substrs:["Silverlight"],progIds:["AgControl.AgControl"]}},m,n,o,p,q,r,s,t;if(d=f({},d,e||{}),g.is=function(a){return h.userAgent.indexOf(a)>-1},g.test=function(a){return a.test(h.userAgent)},g.exec=function(a){return a.exec(h.userAgent)},g.trim=function(a){return a.replace(/^\s+|\s+$/g,"")},g.toCamel=function(a){return null===a||void 0===a?"":String(a).replace(/((\s|\-|\.)+[a-z0-9])/g,function(a){return a.toUpperCase().replace(/(\s|\-|\.)/g,"")})},g.removeClass=function(a,b){var c=b||"",d=1===a.nodeType&&(a.className?(" "+a.className+" ").replace(k," "):"");if(d){while(d.indexOf(" "+c+" ")>=0)d=d.replace(" "+c+" "," ");a.className=b?g.trim(d):""}},g.addVersionTest=function(a,b,c){null!==b&&void 0!==b&&""!==b&&(b=g.toCamel(b),""!==b&&(void 0!==c&&c>0&&(b=b.substr(0,c)),g.addConditionalTest(a+b,!0)))},g.checkOrientation=function(){a.clearTimeout(s),s=a.setTimeout(function(){t=h.orientation,h.orientation=a.innerHeight>a.innerWidth?"portrait":"landscape",g.addConditionalTest(h.orientation,!0),t!==h.orientation&&g.addConditionalTest(t,!1)},10)},g.addConditionalTest=function(a,b){null!==a&&void 0!==a&&""!==a&&(d.addAllFeaturesAsClass?c.addTest(a,b):(b="function"==typeof b?b():b,b?c.addTest(a,!0):(delete c[a],g.removeClass(i,a))))},d.detectDevice){for(g.test(/GoogleTV|SmartTV|Internet.TV|NetCast|NETTV|AppleTV|boxee|Kylo|Roku|DLNADOC|CE\-HTML/i)?(h.type=j[0],h.model="smartTv"):g.test(/Xbox|PLAYSTATION.3|Wii/i)?(h.type=j[0],h.model="gameConsole"):g.test(/iP(a|ro)d/i)?(h.type=j[1],h.model="ipad"):g.test(/tablet/i)&&!g.test(/RX-34/i)||g.test(/FOLIO/i)?(h.type=j[1],h.model=String(g.exec(/playbook/)||"")):g.test(/Linux/i)&&g.test(/Android/i)&&!g.test(/Fennec|mobi|HTC.Magic|HTCX06HT|Nexus.One|SC-02B|fone.945/i)?(h.type=j[1],h.model="android"):g.test(/Kindle/i)||g.test(/Mac.OS/i)&&g.test(/Silk/i)?(h.type=j[1],h.model="kindle"):g.test(/GT-P10|SC-01C|SHW-M180S|SGH-T849|SCH-I800|SHW-M180L|SPH-P100|SGH-I987|zt180|HTC(.Flyer|\_Flyer)|Sprint.ATP51|ViewPad7|pandigital(sprnova|nova)|Ideos.S7|Dell.Streak.7|Advent.Vega|A101IT|A70BHT|MID7015|Next2|nook/i)||g.test(/MB511/i)&&g.test(/RUTEM/i)?(h.type=j[1],h.model="android"):g.test(/BB10/i)?(h.type=j[1],h.model="blackberry"):(h.model=g.exec(/iphone|ipod|android|blackberry|opera mini|opera mobi|skyfire|maemo|windows phone|palm|iemobile|symbian|symbianos|fennec|j2me/i),null!==h.model?(h.type=j[2],h.model=String(h.model)):(h.model="",g.test(/BOLT|Fennec|Iris|Maemo|Minimo|Mobi|mowser|NetFront|Novarra|Prism|RX-34|Skyfire|Tear|XV6875|XV6975|Google.Wireless.Transcoder/i)?h.type=j[2]:g.test(/Opera/i)&&g.test(/Windows.NT.5/i)&&g.test(/HTC|Xda|Mini|Vario|SAMSUNG\-GT\-i8000|SAMSUNG\-SGH\-i9/i)?h.type=j[2]:g.test(/Windows.(NT|XP|ME|9)/i)&&!g.test(/Phone/i)||g.test(/Win(9|.9|NT)/i)||g.test(/\(Windows 8\)/i)?h.type=j[3]:g.test(/Macintosh|PowerPC/i)&&!g.test(/Silk/i)?h.type=j[3]:g.test(/Linux/i)&&g.test(/X11/i)?h.type=j[3]:g.test(/Solaris|SunOS|BSD/i)?h.type=j[3]:g.test(/Bot|Crawler|Spider|Yahoo|ia_archiver|Covario-IDS|findlinks|DataparkSearch|larbin|Mediapartners-Google|NG-Search|Snappy|Teoma|Jeeves|TinEye/i)&&!g.test(/Mobile/i)?(h.type=j[3],h.model="crawler"):h.type=j[2])),m=0,n=j.length;n>m;m+=1)g.addConditionalTest(j[m],h.type===j[m]);d.detectDeviceModel&&g.addConditionalTest(g.toCamel(h.model),!0),(h.type===j[1]||h.type===j[2])&&(a.onresize=function(a){g.checkOrientation(a)},g.checkOrientation())}if(d.detectScreen&&c.mq&&(g.addConditionalTest("smallScreen",c.mq("only screen and (max-width: 480px)")),g.addConditionalTest("verySmallScreen",c.mq("only screen and (max-width: 320px)")),g.addConditionalTest("veryVerySmallScreen",c.mq("only screen and (max-width: 240px)"))),d.detectOS&&(""!==h.model&&("ipad"===h.model||"iphone"===h.model||"ipod"===h.model?(h.osVersion=g.test(/os\s(\d+)_/)?RegExp.$1:"",h.os="ios",h.osVersionFull=g.test(/os ([^\s]+)/)?RegExp.$1.replace(/_/g,"."):""):"android"===h.model?(h.osVersion=(g.test(/os\s(\d+)_/)?RegExp.$1:"").substr(0,2),h.osVersion||(h.osVersion=g.test(/android\s(\d+)\./)?RegExp.$1:"",h.osVersionFull=g.test(/android ([^\s]+)/)?RegExp.$1.replace(/_/g,"."):""),h.os="android"):"blackberry"===h.model?(h.osVersion=g.test(/version\/([^\s]+)/)?RegExp.$1:"",h.os="blackberry"):"playbook"===h.model&&(h.osVersion=g.test(/os ([^\s]+)/)?RegExp.$1.replace(";",""):"",h.os="blackberry")),""===h.os&&(g.is("win")||g.is("16bit")?(h.os="windows",g.is("windows nt 6.3")?(h.osVersion="8",h.osVersionFull="8.1"):g.is("windows nt 6.2")||g.test(/\(windows 8\)/)?h.osVersion="8":g.is("windows nt 6.1")?h.osVersion="7":g.is("windows nt 6.0")?h.osVersion="vista":g.is("windows nt 5.2")||g.is("windows nt 5.1")||g.is("windows xp")?h.osVersion="xp":g.is("windows nt 5.0")||g.is("windows 2000")?h.osVersion="2k":g.is("winnt")||g.is("windows nt")?h.osVersion="nt":g.is("win98")||g.is("windows 98")?h.osVersion="98":(g.is("win95")||g.is("windows 95"))&&(h.osVersion="95")):g.is("mac")||g.is("darwin")?(h.os="mac",g.is("68k")||g.is("68000")?h.osVersion="68k":g.is("ppc")||g.is("powerpc")?h.osVersion="ppc":g.is("os x")&&(h.osVersion="os x")):g.is("webtv")?h.os="webtv":g.is("x11")||g.is("inux")?h.os="linux":g.is("sunos")?h.os="sun":g.is("irix")?h.os="irix":g.is("freebsd")?h.os="freebsd":g.is("bsd")&&(h.os="bsd")),""!==h.os&&(!h.osVersionFull&&h.osVersion&&(h.osVersionFull=h.osVersion),g.addConditionalTest(h.os,!0),g.addVersionTest(h.os,h.osVersionFull.replace(/\./g,"_")),g.addVersionTest(h.os,h.osVersion))),d.detectBrowser&&(g.test(/opera|webtv/i)||!g.test(/msie\s([0-9]{1,})/)&&!g.is("trident")?g.is("firefox")?(h.browserEngine="gecko",h.browser="firefox",h.browserVersion=(g.test(/firefox\/(\d+(\.?\d+)*)/)?RegExp.$1:"").substr(0,2)):g.is("gecko/")?h.browserEngine="gecko":g.is("opera")?(h.browser="opera",h.browserEngine="presto",h.browserVersion=g.test(/version\/(\d+)/)?RegExp.$1:g.test(/opera(\s|\/)(\d+)/)?RegExp.$2:""):g.is("konqueror")?h.browser="konqueror":g.is("chrome")?(h.browserEngine="webkit",h.browser="chrome",h.browserVersion=g.test(/chrome\/(\d+)/)?RegExp.$1:""):g.is("iron")?(h.browserEngine="webkit",h.browser="iron"):g.is("crios")?(h.browser="chrome",h.browserEngine="webkit",h.browserVersion=g.test(/crios\/(\d+)/)?RegExp.$1:""):g.is("applewebkit/")?(h.browser="safari",h.browserEngine="webkit",h.browserVersion=g.test(/version\/(\d+)/)?RegExp.$1:""):g.is("mozilla/")&&(h.browserEngine="gecko"):(h.browserEngine="trident",h.browser="ie",h.browserVersion=!a.addEventListener&&document.documentMode&&7===document.documentMode?"8compat":g.test(/trident.*rv[ :](\d+)\./)?RegExp.$1:g.test(/trident\/4\.0/)?"8":RegExp.$1),""!==h.browser&&(g.addConditionalTest(h.browser,!0),""!==h.browserVersion&&g.addVersionTest(h.browser,h.browserVersion)),g.addConditionalTest(h.browserEngine,!0)),d.detectPlugins){if(g.detectPlugin=function(a){for(m=0,n=b.plugins.length;n>m;m++){var c=b.plugins[m],d=c.name+c.description,e=0;for(o=0,p=a.length;p>o;o+=1)-1!==d.indexOf(a[o])&&(e+=1);if(e===a.length)return!0}return!1},g.detectObject=function(a,b){for(m=0,n=a.length;n>m;m++)try{var c=new ActiveXObject(a[m]);if(c)return b&&b[m]?b[m].call(c):!0}catch(d){}return!1},a.ActiveXObject)for(q in l)l.hasOwnProperty(q)&&(r=l[q],g.detectObject(r.progIds,r.fns)&&(h.browserPlugins.push(q),g.addConditionalTest(q,!0)));else if(b.plugins)for(q in l)l.hasOwnProperty(q)&&(r=l[q],g.detectPlugin(r.substrs)&&(h.browserPlugins.push(q),g.addConditionalTest(q,!0)));b.javaEnabled()&&(h.browserPlugins.push("java"),g.addConditionalTest("java",!0))}}function f(){void 0!==c&&(c.Detectizr=c.Detectizr||{},c.Detectizr.device={type:"",model:"",orientation:"",browser:"",browserEngine:"",browserPlugins:[],browserVersion:"",os:"",osVersion:"",osVersionFull:"",userAgent:(b.userAgent||b.vendor||a.opera).toLowerCase()},c.Detectizr.detect=function(a){return new e(a)})}f()}(this,navigator); +/*! Detectizr v2.0.0 | (c) 2012 Baris Aydinoglu | Licensed MIT */ +window.Detectizr=function(a,b,c,d){var e,f,g={},h=a.Modernizr,i=["tv","tablet","mobile","desktop"],j={addAllFeaturesAsClass:!1,detectDevice:!0,detectDeviceModel:!0,detectScreen:!0,detectOS:!0,detectBrowser:!0,detectPlugins:!0},k=[{name:"adobereader",substrs:["Adobe","Acrobat"],progIds:["AcroPDF.PDF","PDF.PDFCtrl.5"]},{name:"flash",substrs:["Shockwave Flash"],progIds:["ShockwaveFlash.ShockwaveFlash.1"]},{name:"wmplayer",substrs:["Windows Media"],progIds:["wmplayer.ocx"]},{name:"silverlight",substrs:["Silverlight"],progIds:["AgControl.AgControl"]},{name:"quicktime",substrs:["QuickTime"],progIds:["QuickTime.QuickTime"]}],l=/[\t\r\n]/g,m=c.documentElement;function n(a,b){var c,d,e;if(arguments.length>2)for(c=1,d=arguments.length;d>c;c+=1)n(a,arguments[c]);else for(e in b)b.hasOwnProperty(e)&&(a[e]=b[e]);return a}function o(a){return g.browser.userAgent.indexOf(a)>-1}function p(a){return a.test(g.browser.userAgent)}function q(a){return a.exec(g.browser.userAgent)}function r(a){return a.replace(/^\s+|\s+$/g,"")}function s(a){return null===a||a===d?"":String(a).replace(/((\s|\-|\.)+[a-z0-9])/g,function(a){return a.toUpperCase().replace(/(\s|\-|\.)/g,"")})}function t(a,b){var c=b||"",d=1===a.nodeType&&(a.className?(" "+a.className+" ").replace(l," "):"");if(d){while(d.indexOf(" "+c+" ")>=0)d=d.replace(" "+c+" "," ");a.className=b?r(d):""}}function u(a,b,c){a&&(a=s(a),b&&(b=s(b),w(a+b,!0),c&&w(a+b+"_"+c,!0)))}function v(){a.clearTimeout(e),e=a.setTimeout(function(){f=g.device.orientation,g.device.orientation=a.innerHeight>a.innerWidth?"portrait":"landscape",w(g.device.orientation,!0),f!==g.device.orientation&&w(f,!1)},10)}function w(a,b){a&&h&&(j.addAllFeaturesAsClass?h.addTest(a,b):(b="function"==typeof b?b():b,b?h.addTest(a,!0):(delete h[a],t(m,a))))}function x(a,b){a.version=b;var c=b.split(".");c.length>0?(c=c.reverse(),a.major=c.pop(),c.length>0?(a.minor=c.pop(),c.length>0?(c=c.reverse(),a.patch=c.join(".")):a.patch="0"):a.minor="0"):a.major="0"}function y(d){var e,f,l,m,r,t,y,z,A=this;if(j=n({},j,d||{}),j.detectDevice){for(g.device={type:"",model:"",orientation:""},m=g.device,p(/googletv|smarttv|internet.tv|netcast|nettv|appletv|boxee|kylo|roku|dlnadoc|ce\-html/)?(m.type=i[0],m.model="smartTv"):p(/xbox|playstation.3|wii/)?(m.type=i[0],m.model="gameConsole"):p(/ip(a|ro)d/)?(m.type=i[1],m.model="ipad"):p(/tablet/)&&!p(/rx-34/)||p(/folio/)?(m.type=i[1],m.model=String(q(/playbook/)||"")):p(/linux/)&&p(/android/)&&!p(/fennec|mobi|htc.magic|htcX06ht|nexus.one|sc-02b|fone.945/)?(m.type=i[1],m.model="android"):p(/kindle/)||p(/mac.os/)&&p(/silk/)?(m.type=i[1],m.model="kindle"):p(/gt-p10|sc-01c|shw-m180s|sgh-t849|sch-i800|shw-m180l|sph-p100|sgh-i987|zt180|htc(.flyer|\_flyer)|sprint.atp51|viewpad7|pandigital(sprnova|nova)|ideos.s7|dell.streak.7|advent.vega|a101it|a70bht|mid7015|next2|nook/)||p(/mb511/)&&p(/rutem/)?(m.type=i[1],m.model="android"):p(/bb10/)?(m.type=i[1],m.model="blackberry"):(m.model=q(/iphone|ipod|android|blackberry|opera mini|opera mobi|skyfire|maemo|windows phone|palm|iemobile|symbian|symbianos|fennec|j2me/),null!==m.model?(m.type=i[2],m.model=String(m.model)):(m.model="",p(/bolt|fennec|iris|maemo|minimo|mobi|mowser|netfront|novarra|prism|rx-34|skyfire|tear|xv6875|xv6975|google.wireless.transcoder/)?m.type=i[2]:p(/opera/)&&p(/windows.nt.5/)&&p(/htc|xda|mini|vario|samsung\-gt\-i8000|samsung\-sgh\-i9/)?m.type=i[2]:p(/windows.(nt|xp|me|9)/)&&!p(/phone/)||p(/win(9|.9|nt)/)||p(/\(windows 8\)/)?m.type=i[3]:p(/macintosh|powerpc/)&&!p(/silk/)?(m.type=i[3],m.model="mac"):p(/linux/)&&p(/x11/)?m.type=i[3]:p(/solaris|sunos|bsd/)?m.type=i[3]:p(/bot|crawler|spider|yahoo|ia_archiver|covario-ids|findlinks|dataparksearch|larbin|mediapartners-google|ng-search|snappy|teoma|jeeves|tineye/)&&!p(/mobile/)?(m.type=i[3],m.model="crawler"):m.type=i[2])),e=0,f=i.length;f>e;e+=1)w(i[e],m.type===i[e]);j.detectDeviceModel&&w(s(m.model),!0)}if(j.detectScreen&&(h&&h.mq&&(w("smallScreen",h.mq("only screen and (max-width: 480px)")),w("verySmallScreen",h.mq("only screen and (max-width: 320px)")),w("veryVerySmallScreen",h.mq("only screen and (max-width: 240px)"))),m.type===i[1]||m.type===i[2]?(a.onresize=function(a){v(a)},v()):(m.orientation="landscape",w(m.orientation,!0))),j.detectOS&&(g.os={},r=g.os,""!==m.model&&("ipad"===m.model||"iphone"===m.model||"ipod"===m.model?(r.name="ios",x(r,(p(/os\s([\d_]+)/)?RegExp.$1:"").replace(/_/g,"."))):"android"===m.model?(r.name="android",x(r,p(/android\s([\d\.]+)/)?RegExp.$1:"")):"blackberry"===m.model?(r.name="blackberry",x(r,p(/version\/([^\s]+)/)?RegExp.$1:"")):"playbook"===m.model&&(r.name="blackberry",x(r,p(/os ([^\s]+)/)?RegExp.$1.replace(";",""):""))),r.name||(o("win")||o("16bit")?(r.name="windows",o("windows nt 6.3")?x(r,"8.1"):o("windows nt 6.2")||p(/\(windows 8\)/)?x(r,"8"):o("windows nt 6.1")?x(r,"7"):o("windows nt 6.0")?x(r,"vista"):o("windows nt 5.2")||o("windows nt 5.1")||o("windows xp")?x(r,"xp"):o("windows nt 5.0")||o("windows 2000")?x(r,"2k"):o("winnt")||o("windows nt")?x(r,"nt"):o("win98")||o("windows 98")?x(r,"98"):(o("win95")||o("windows 95"))&&x(r,"95")):o("mac")||o("darwin")?(r.name="mac os",o("68k")||o("68000")?x(r,"68k"):o("ppc")||o("powerpc")?x(r,"ppc"):o("os x")&&x(r,(p(/os\sx\s([\d_]+)/)?RegExp.$1:"os x").replace(/_/g,"."))):o("webtv")?r.name="webtv":o("x11")||o("inux")?r.name="linux":o("sunos")?r.name="sun":o("irix")?r.name="irix":o("freebsd")?r.name="freebsd":o("bsd")&&(r.name="bsd")),r.name&&(w(r.name,!0),r.major&&(u(r.name,r.major),r.minor&&u(r.name,r.major,r.minor))),r.addressRegisterSize=p(/\sx64|\sx86|\swin64|\swow64|\samd64/)?"64bit":"32bit",w(r.addressRegisterSize,!0)),j.detectBrowser&&(t=g.browser,p(/opera|webtv/)||!p(/msie\s([\d\w\.]+)/)&&!o("trident")?o("firefox")?(t.engine="gecko",t.name="firefox",x(t,p(/firefox\/([\d\w\.]+)/)?RegExp.$1:"")):o("gecko/")?t.engine="gecko":o("opera")?(t.name="opera",t.engine="presto",x(t,p(/version\/([\d\.]+)/)?RegExp.$1:p(/opera(\s|\/)([\d\.]+)/)?RegExp.$2:"")):o("konqueror")?t.name="konqueror":o("chrome")?(t.engine="webkit",t.name="chrome",x(t,p(/chrome\/([\d\.]+)/)?RegExp.$1:"")):o("iron")?(t.engine="webkit",t.name="iron"):o("crios")?(t.name="chrome",t.engine="webkit",x(t,p(/crios\/([\d\.]+)/)?RegExp.$1:"")):o("applewebkit/")?(t.name="safari",t.engine="webkit",x(t,p(/version\/([\d\.]+)/)?RegExp.$1:"")):o("mozilla/")&&(t.engine="gecko"):(t.engine="trident",t.name="ie",!a.addEventListener&&c.documentMode&&7===c.documentMode?x(t,"8.compat"):p(/trident.*rv[ :](\d+)\./)?x(t,RegExp.$1):x(t,p(/trident\/4\.0/)?"8":RegExp.$1)),t.name&&(w(t.name,!0),t.major&&(u(t.name,t.major),t.minor&&u(t.name,t.major,t.minor))),w(t.engine,!0),t.language=b.userLanguage||b.language,w(t.language,!0)),j.detectPlugins){for(t.plugins=[],A.detectPlugin=function(a){var c,d,e,g=b.plugins;for(f=g.length-1;f>=0;f--){for(c=g[f],d=c.name+c.description,e=0,l=a.length;l>=0;l--)-1!==d.indexOf(a[l])&&(e+=1);if(e===a.length)return!0}return!1},A.detectObject=function(a){for(f=a.length-1;f>=0;f--)try{new ActiveXObject(a[f])}catch(b){}return!1},e=k.length-1;e>=0;e--)y=k[e],z=!1,a.ActiveXObject?z=A.detectObject(y.progIds):b.plugins&&(z=A.detectPlugin(y.substrs)),z&&(t.plugins.push(y.name),w(y.name,!0));b.javaEnabled()&&(t.plugins.push("java"),w("java",!0))}}return g.detect=function(a){return y(a)},g.init=function(){g!==d&&(g.browser={userAgent:(b.userAgent||b.vendor||a.opera).toLowerCase()},g.detect())},g.init(),g}(this,this.navigator,this.document); //# sourceMappingURL=detectizr.min.map \ No newline at end of file diff --git a/dist/detectizr.min.map b/dist/detectizr.min.map index 26e08ae..25e617b 100644 --- a/dist/detectizr.min.map +++ b/dist/detectizr.min.map @@ -1 +1 @@ -{"version":3,"file":"dist/detectizr.min.js","sources":["dist/detectizr.js"],"names":["window","navigator","Modernizr","options","addAllFeaturesAsClass","detectDevice","detectDeviceModel","detectScreen","detectOS","detectBrowser","detectPlugins","Detectizr","opt","extend","obj","extObj","a","b","i","arguments","length","hasOwnProperty","that","this","device","docElement","document","documentElement","deviceTypes","rclass","plugins2detect","acrobat","substrs","progIds","flash","mediaplayer","silverlight","j","k","l","alias","plugin","resizeTimeoutId","oldOrientation","is","key","userAgent","indexOf","test","regex","exec","trim","value","replace","toCamel","string","undefined","String","$1","toUpperCase","removeClass","element","class2remove","cur","nodeType","className","addVersionTest","mainTest","version","maxLength","substr","addConditionalTest","checkOrientation","clearTimeout","setTimeout","orientation","innerHeight","innerWidth","feature","addTest","type","model","onresize","event","mq","osVersion","RegExp","os","osVersionFull","browserEngine","browser","browserVersion","$2","addEventListener","documentMode","detectPlugin","plugins","haystack","name","description","found","detectObject","fns","ActiveXObject","call","e","browserPlugins","push","javaEnabled","init","vendor","opera","toLowerCase","detect","settings"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"detectizr.min.js","sources":["detectizr.js"],"names":["window","Detectizr","navigator","document","undefined","resizeTimeoutId","oldOrientation","Modernizr","deviceTypes","options","addAllFeaturesAsClass","detectDevice","detectDeviceModel","detectScreen","detectOS","detectBrowser","detectPlugins","plugins2detect","name","substrs","progIds","rclass","docElement","documentElement","extend","obj","extObj","a","b","i","arguments","length","hasOwnProperty","is","key","browser","userAgent","indexOf","test","regex","exec","trim","value","replace","toCamel","string","String","$1","toUpperCase","removeClass","element","class2remove","cur","nodeType","className","addVersionTest","version","major","minor","addConditionalTest","checkOrientation","clearTimeout","setTimeout","device","orientation","innerHeight","innerWidth","feature","addTest","setVersion","versionType","versionFull","versionArray","split","reverse","pop","patch","join","detect","opt","j","k","os","plugin2detect","pluginFound","that","this","type","model","mq","onresize","event","RegExp","addressRegisterSize","engine","$2","addEventListener","documentMode","language","userLanguage","plugins","detectPlugin","plugin","haystack","pluginFoundText","description","detectObject","ActiveXObject","e","push","javaEnabled","settings","init","vendor","opera","toLowerCase"],"mappings":"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"} \ No newline at end of file diff --git a/package.json b/package.json index fe739a4..b4ed5ad 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "detectizr", "title": "Detectizr", "description": "Detectizr is a Modernizr extension to detect device, browser, operating system and screen.", - "version": "1.5.0", + "version": "2.0.0", "main": "dist/detectizr.js", "homepage": "https://github.com/barisaydinoglu/Detectizr", "author": { @@ -38,24 +38,28 @@ "directories": { "test": "test" }, + "dependencies": {}, "devDependencies": { - "gzip-js": "0.3.2", - "load-grunt-tasks": "0.2.0", - "requirejs": "2.1.9", "grunt": "0.4.2", - "grunt-cli": "0.1.11", - "grunt-contrib-jshint": "0.7.2", - "grunt-contrib-uglify": "0.2.7", - "grunt-contrib-watch": "0.5.3", - "grunt-contrib-concat": "~0.3.0", - "grunt-contrib-qunit": "~0.4.0", + "grunt-bowercopy": "0.5.0", + "grunt-cli": "0.1.13", "grunt-compare-size": "0.4.0", + "grunt-contrib-clean": "0.5.0", + "grunt-contrib-concat": "0.3.0", + "grunt-contrib-jshint": "0.8.0", + "grunt-contrib-qunit": "0.4.0", + "grunt-contrib-requirejs": "0.4.3", + "grunt-contrib-uglify": "0.4.0", + "grunt-coveralls": "0.3.0", "grunt-git-authors": "1.2.0", - "grunt-jsonlint": "1.0.4" + "grunt-jscs-checker": "~0.4.0", + "grunt-jsonlint": "1.0.4", + "gzip-js": "0.3.2", + "load-grunt-tasks": "0.4.0" }, "scripts": { "build": "npm install && grunt", - "start": "grunt watch", - "test": "grunt" + "ci": "grunt test coveralls", + "test": "grunt test" } } diff --git a/src/detectizr.js b/src/detectizr.js index 715b7ce..dd75349 100644 --- a/src/detectizr.js +++ b/src/detectizr.js @@ -1,494 +1,502 @@ -/*! - * Detectizr v1.5.0 - * http://barisaydinoglu.github.com/Detectizr/ - * https://github.com/barisaydinoglu/Detectizr - * Written by Baris Aydinoglu (http://baris.aydinoglu.info) - Copyright 2012 - * Contributor: Adrian Maurer (https://github.com/adrianmaurer) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Inspirations: - * - Browser selectors in CSS - http://37signals.com/svn/archives2/browser_selectors_in_css.php - * - Categorizr - http://www.brettjankord.com/2012/01/16/categorizr-a-modern-device-detection-script/ - */ -/* - * Detectizr, which requires Modernizr, adds some tests to Modernizr. - * It detects device, device model, screen size, operating system, - * and browser details. - * Detection of these sets are optional and can be disabled. - * - * Detectable device types are: tv (includes smart tv and game console), - * mobile, tablet, and desktop. Device models of tv, mobile and tablet - * are being detected. - * - * Author Baris Aydinoglu - */ -/* - * jslint browser: true, regexp: true, sloppy: true, white: true - * jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, curly:true, browser:true, indent:4, maxerr:50, regexp:false, white:false - */ -(function (window, navigator) { - var Modernizr = window.Modernizr, - options = { - // option for enabling HTML classes of all features (not only the true features) to be added - addAllFeaturesAsClass: false, - // option for enabling detection of device - detectDevice: true, - // option for enabling detection of device model - detectDeviceModel: true, - // option for enabling detection of screen size - detectScreen: true, - // option for enabling detection of operating system type and version - detectOS: true, - // option for enabling detection of browser type and version - detectBrowser: true, - // option for enabling detection of common browser plugins - detectPlugins: true - }; - - function Detectizr(opt) { - // Create Global "extend" method, so Detectizr does not need jQuery.extend - var extend = function (obj, extObj) { - var a, b, i; - if (arguments.length > 2) { - for (a = 1, b = arguments.length; a < b; a += 1) { - extend(obj, arguments[a]); - } - } else { - for (i in extObj) { - if (extObj.hasOwnProperty(i)) { - obj[i] = extObj[i]; - } - } - } - return obj; - }, - that = this, - device = Modernizr.Detectizr.device, - docElement = document.documentElement, - deviceTypes = ["tv", "tablet", "mobile", "desktop"], - rclass = /[\t\r\n]/g, - plugins2detect = { - acrobat: { - substrs: ["Adobe", "Acrobat"], - progIds: ["AcroPDF.PDF", "PDF.PDFCtrl.5"] - }, - flash: { - substrs: ["Shockwave", "Flash"], - progIds: ["ShockwaveFlash.ShockwaveFlash.1"] - }, - mediaplayer: { - substrs: ["Windows Media"], - progIds: ["wmplayer.ocx"] - }, - silverlight: { - substrs: ["Silverlight"], - progIds: ["AgControl.AgControl"] - } - }, - i, j, k, l, alias, plugin, resizeTimeoutId, oldOrientation; - options = extend({}, options, opt || {}); - - // simplified and localized indexOf method as one parameter fixed as useragent - that.is = function (key) { - return device.userAgent.indexOf(key) > -1; - }; - - // simplified and localized regex test method as one parameter fixed as useragent - that.test = function (regex) { - return regex.test(device.userAgent); - }; - - // simplified and localized regex exec method as one parameter fixed as useragent - that.exec = function (regex) { - return regex.exec(device.userAgent); - }; - - // localized string trim method - that.trim = function (value) { - return value.replace(/^\s+|\s+$/g, ""); - }; - - // convert string to camelcase - that.toCamel = function (string) { - if (string === null || string === undefined) { - return ""; - } - return String(string).replace(/((\s|\-|\.)+[a-z0-9])/g, function ($1) { - return $1.toUpperCase().replace(/(\s|\-|\.)/g, ""); - }); - }; - - // removeClass function inspired from jQuery.removeClass - that.removeClass = function (element, value) { - var class2remove = value || "", - cur = element.nodeType === 1 && ( element.className ? ( " " + element.className + " " ).replace( rclass, " " ) : ""); - if ( cur ) { - while ( cur.indexOf( " " + class2remove + " " ) >= 0 ) { - cur = cur.replace( " " + class2remove + " ", " " ); - } - element.className = value ? that.trim(cur) : ""; - } - }; - - // add version test to Modernizr - that.addVersionTest = function (mainTest, version, maxLength) { - if (version !== null && version !== undefined && version !== "") { - version = that.toCamel(version); - if (version !== "") { - if (maxLength !== undefined && maxLength > 0) { - version = version.substr(0, maxLength); - } - that.addConditionalTest(mainTest + version, true); - } - } - }; - - that.checkOrientation = function () { - //timeout wrapper points with doResizeCode as callback - window.clearTimeout(resizeTimeoutId); - resizeTimeoutId = window.setTimeout(function () { - oldOrientation = device.orientation; - //wrapper for height/width check - if (window.innerHeight > window.innerWidth) { - device.orientation = "portrait"; - } else { - device.orientation = "landscape"; - } - that.addConditionalTest(device.orientation, true); - if (oldOrientation !== device.orientation) { - that.addConditionalTest(oldOrientation, false); - } - }, 10); - }; - - // add test to Modernizr based on a conditi - that.addConditionalTest = function (feature, test) { - if (feature === null || feature === undefined || feature === "") { - return; - } - if (options.addAllFeaturesAsClass) { - Modernizr.addTest(feature, test); - } else { - test = typeof test === "function" ? test() : test; - if (test) { - Modernizr.addTest(feature, true); - } else { - delete Modernizr[feature]; - that.removeClass(docElement, feature); - } - } - }; - - /** Device detection **/ - if (options.detectDevice) { - if (that.test(/GoogleTV|SmartTV|Internet.TV|NetCast|NETTV|AppleTV|boxee|Kylo|Roku|DLNADOC|CE\-HTML/i)) { - // Check if user agent is a smart tv - device.type = deviceTypes[0]; - device.model = "smartTv"; - } else if (that.test(/Xbox|PLAYSTATION.3|Wii/i)) { - // Check if user agent is a game console - device.type = deviceTypes[0]; - device.model = "gameConsole"; - } else if (that.test(/iP(a|ro)d/i)) { - // Check if user agent is a iPad - device.type = deviceTypes[1]; - device.model = "ipad"; - } else if ((that.test(/tablet/i) && !that.test(/RX-34/i)) || that.test(/FOLIO/i)) { - // Check if user agent is a Tablet - device.type = deviceTypes[1]; - device.model = String(that.exec(/playbook/) || ""); - } else if (that.test(/Linux/i) && that.test(/Android/i) && !that.test(/Fennec|mobi|HTC.Magic|HTCX06HT|Nexus.One|SC-02B|fone.945/i)) { - // Check if user agent is an Android Tablet - device.type = deviceTypes[1]; - device.model = "android"; - } else if (that.test(/Kindle/i) || (that.test(/Mac.OS/i) && that.test(/Silk/i))) { - // Check if user agent is a Kindle or Kindle Fire - device.type = deviceTypes[1]; - device.model = "kindle"; - } else if (that.test(/GT-P10|SC-01C|SHW-M180S|SGH-T849|SCH-I800|SHW-M180L|SPH-P100|SGH-I987|zt180|HTC(.Flyer|\_Flyer)|Sprint.ATP51|ViewPad7|pandigital(sprnova|nova)|Ideos.S7|Dell.Streak.7|Advent.Vega|A101IT|A70BHT|MID7015|Next2|nook/i) || (that.test(/MB511/i) && that.test(/RUTEM/i))) { - // Check if user agent is a pre Android 3.0 Tablet - device.type = deviceTypes[1]; - device.model = "android"; - } else if (that.test(/BB10/i)) { - // Check if user agent is a BB10 device - device.type = deviceTypes[1]; - device.model = "blackberry"; - } else { - // Check if user agent is one of common mobile types - device.model = that.exec(/iphone|ipod|android|blackberry|opera mini|opera mobi|skyfire|maemo|windows phone|palm|iemobile|symbian|symbianos|fennec|j2me/i); - if (device.model !== null) { - device.type = deviceTypes[2]; - device.model = String(device.model); - } else { - device.model = ""; - if (that.test(/BOLT|Fennec|Iris|Maemo|Minimo|Mobi|mowser|NetFront|Novarra|Prism|RX-34|Skyfire|Tear|XV6875|XV6975|Google.Wireless.Transcoder/i)) { - // Check if user agent is unique Mobile User Agent - device.type = deviceTypes[2]; - } else if (that.test(/Opera/i) && that.test(/Windows.NT.5/i) && that.test(/HTC|Xda|Mini|Vario|SAMSUNG\-GT\-i8000|SAMSUNG\-SGH\-i9/i)) { - // Check if user agent is an odd Opera User Agent - http://goo.gl/nK90K - device.type = deviceTypes[2]; - } else if ((that.test(/Windows.(NT|XP|ME|9)/i) && !that.test(/Phone/i)) || that.test(/Win(9|.9|NT)/i) || that.test(/\(Windows 8\)/i)) { - // Check if user agent is Windows Desktop, "(Windows 8)" Chrome extra exception - device.type = deviceTypes[3]; - } else if (that.test(/Macintosh|PowerPC/i) && !that.test(/Silk/i)) { - // Check if agent is Mac Desktop - device.type = deviceTypes[3]; - } else if (that.test(/Linux/i) && that.test(/X11/i)) { - // Check if user agent is a Linux Desktop - device.type = deviceTypes[3]; - } else if (that.test(/Solaris|SunOS|BSD/i)) { - // Check if user agent is a Solaris, SunOS, BSD Desktop - device.type = deviceTypes[3]; - } else if (that.test(/Bot|Crawler|Spider|Yahoo|ia_archiver|Covario-IDS|findlinks|DataparkSearch|larbin|Mediapartners-Google|NG-Search|Snappy|Teoma|Jeeves|TinEye/i) && !that.test(/Mobile/i)) { - // Check if user agent is a Desktop BOT/Crawler/Spider - device.type = deviceTypes[3]; - device.model = "crawler"; - } else { - // Otherwise assume it is a Mobile Device - device.type = deviceTypes[2]; - } - } - } - for (i = 0, j = deviceTypes.length; i < j; i += 1) { - that.addConditionalTest(deviceTypes[i], (device.type === deviceTypes[i])); - } - if (options.detectDeviceModel) { - that.addConditionalTest(that.toCamel(device.model), true); - } - if (device.type === deviceTypes[1] || device.type === deviceTypes[2]) { - window.onresize = function (event) { - that.checkOrientation(event); - }; - that.checkOrientation(); - } - } - - /** Screen detection **/ - if (options.detectScreen && !!Modernizr.mq) { - that.addConditionalTest("smallScreen", Modernizr.mq("only screen and (max-width: 480px)")); - that.addConditionalTest("verySmallScreen", Modernizr.mq("only screen and (max-width: 320px)")); - that.addConditionalTest("veryVerySmallScreen", Modernizr.mq("only screen and (max-width: 240px)")); - } - - /** OS detection **/ - if (options.detectOS) { - if (device.model !== "") { - if (device.model === "ipad" || device.model === "iphone" || device.model === "ipod") { - device.osVersion = (that.test(/os\s(\d+)_/) ? RegExp.$1 : ""); - device.os = "ios"; - // Full version check - device.osVersionFull = (that.test(/os ([^\s]+)/) ? RegExp.$1.replace(/_/g,".") : ""); - } else if (device.model === "android") { - device.osVersion = (that.test(/os\s(\d+)_/) ? RegExp.$1 : "").substr(0, 2); - if (!device.osVersion) { - device.osVersion = (that.test(/android\s(\d+)\./) ? RegExp.$1 : ""); - device.osVersionFull = (that.test(/android ([^\s]+)/) ? RegExp.$1.replace(/_/g,".") : ""); - } - device.os = "android"; - } else if (device.model === "blackberry") { - device.osVersion = (that.test(/version\/([^\s]+)/) ? RegExp.$1 : ""); - device.os = "blackberry"; - } else if (device.model === "playbook") { - device.osVersion = (that.test(/os ([^\s]+)/) ? RegExp.$1.replace(";", "") : ""); - device.os = "blackberry"; - } - } - if (device.os === "") { - if (that.is("win") || that.is("16bit")) { - device.os = "windows"; - if (that.is("windows nt 6.3")) { - device.osVersion = "8"; - device.osVersionFull = "8.1"; - } else if (that.is("windows nt 6.2") || that.test(/\(windows 8\)/)) { //windows 8 chrome mac fix - device.osVersion = "8"; - } else if (that.is("windows nt 6.1")) { - device.osVersion = "7"; - } else if (that.is("windows nt 6.0")) { - device.osVersion = "vista"; - } else if (that.is("windows nt 5.2") || that.is("windows nt 5.1") || that.is("windows xp")) { - device.osVersion = "xp"; - } else if (that.is("windows nt 5.0") || that.is("windows 2000")) { - device.osVersion = "2k"; - } else if (that.is("winnt") || that.is("windows nt")) { - device.osVersion = "nt"; - } else if (that.is("win98") || that.is("windows 98")) { - device.osVersion = "98"; - } else if (that.is("win95") || that.is("windows 95")) { - device.osVersion = "95"; - } - } else if (that.is("mac") || that.is("darwin")) { - device.os = "mac"; - if (that.is("68k") || that.is("68000")) { - device.osVersion = "68k"; - } else if (that.is("ppc") || that.is("powerpc")) { - device.osVersion = "ppc"; - } else if (that.is("os x")) { - device.osVersion = "os x"; - } - } else if (that.is("webtv")) { - device.os = "webtv"; - } else if (that.is("x11") || that.is("inux")) { - device.os = "linux"; - } else if (that.is("sunos")) { - device.os = "sun"; - } else if (that.is("irix")) { - device.os = "irix"; - } else if (that.is("freebsd")) { - device.os = "freebsd"; - } else if (that.is("bsd")) { - device.os = "bsd"; - } - } - if (device.os !== "") { - // assign the full version property if not ios (special case. see above ios check) - if (!device.osVersionFull && !!device.osVersion) { - device.osVersionFull = device.osVersion; - } - that.addConditionalTest(device.os, true); - that.addVersionTest(device.os, device.osVersionFull.replace(/\./g, "_")); - that.addVersionTest(device.os, device.osVersion); - } - - } - - /** Browser detection **/ - if (options.detectBrowser) { - if (!that.test(/opera|webtv/i) && (that.test(/msie\s([0-9]{1,})/) || that.is("trident"))) { - device.browserEngine = "trident"; - device.browser = "ie"; - if (!window.addEventListener && document.documentMode && document.documentMode === 7) { - device.browserVersion = "8compat"; - } else if (that.test(/trident.*rv[ :](\d+)\./)) { - device.browserVersion = RegExp.$1; - } else { - device.browserVersion = (that.test(/trident\/4\.0/) ? "8" : RegExp.$1); - } - } else if (that.is("firefox")) { - device.browserEngine = "gecko"; - device.browser = "firefox"; - device.browserVersion = (that.test(/firefox\/(\d+(\.?\d+)*)/) ? RegExp.$1 : "").substr(0, 2); - } else if (that.is("gecko/")) { - device.browserEngine = "gecko"; - } else if (that.is("opera")) { - device.browser = "opera"; - device.browserEngine = "presto"; - device.browserVersion = (that.test(/version\/(\d+)/) ? RegExp.$1 : (that.test(/opera(\s|\/)(\d+)/) ? RegExp.$2 : "")); - } else if (that.is("konqueror")) { - device.browser = "konqueror"; - } else if (that.is("chrome")) { - device.browserEngine = "webkit"; - device.browser = "chrome"; - device.browserVersion = (that.test(/chrome\/(\d+)/) ? RegExp.$1 : ""); - } else if (that.is("iron")) { - device.browserEngine = "webkit"; - device.browser = "iron"; - } else if (that.is("crios")) { - device.browser = "chrome"; - device.browserEngine = "webkit"; - device.browserVersion = (that.test(/crios\/(\d+)/) ? RegExp.$1 : ""); - } else if (that.is("applewebkit/")) { - device.browser = "safari"; - device.browserEngine = "webkit"; - device.browserVersion = (that.test(/version\/(\d+)/) ? RegExp.$1 : ""); - } else if (that.is("mozilla/")) { - device.browserEngine = "gecko"; - } - if (device.browser !== "") { - that.addConditionalTest(device.browser, true); - if (device.browserVersion !== "") { - that.addVersionTest(device.browser, device.browserVersion); - } - } - that.addConditionalTest(device.browserEngine, true); - } - - /** Plugin detection **/ - if (options.detectPlugins) { - that.detectPlugin = function (substrs) { - for (i = 0, j = navigator.plugins.length; i < j; i++) { - var plugin = navigator.plugins[i], - haystack = plugin.name + plugin.description, - found = 0; - for (k = 0, l = substrs.length; k < l; k += 1) { - if (haystack.indexOf(substrs[k]) !== -1) { - found += 1; - } - } - if (found === substrs.length) { - return true; - } - } - return false; - }; - that.detectObject = function (progIds, fns) { - for (i = 0, j = progIds.length; i < j; i++) { - try { - var obj = new ActiveXObject(progIds[i]); - if (obj) { - return fns && fns[i] ? fns[i].call(obj) : true; - } - } catch (e) { - // Ignore - } - } - return false; - }; - if (window.ActiveXObject) { - for (alias in plugins2detect) { - if (plugins2detect.hasOwnProperty(alias)) { - plugin = plugins2detect[alias]; - if (that.detectObject(plugin.progIds, plugin.fns)) { - device.browserPlugins.push(alias); - that.addConditionalTest(alias, true); - } - } - } - } else if (navigator.plugins) { - for (alias in plugins2detect) { - if (plugins2detect.hasOwnProperty(alias)) { - plugin = plugins2detect[alias]; - if (that.detectPlugin(plugin.substrs)) { - device.browserPlugins.push(alias); - that.addConditionalTest(alias, true); - } - } - } - } - if (navigator.javaEnabled()) { - device.browserPlugins.push("java"); - that.addConditionalTest("java", true); - } - } - } - - function init() { - if (Modernizr !== undefined) { - Modernizr.Detectizr = Modernizr.Detectizr || {}; - Modernizr.Detectizr.device = { - type: "", - model: "", - orientation: "", - browser: "", - browserEngine: "", - browserPlugins: [], - browserVersion: "", - os: "", - osVersion: "", - osVersionFull: "", - userAgent: (navigator.userAgent || navigator.vendor || window.opera).toLowerCase() - }; - Modernizr.Detectizr.detect = function (settings) { - return new Detectizr(settings); - }; - } - } - init(); -}(this, navigator)); - -/** Sample usages **/ -// Modernizr.Detectizr.detect(); -// Modernizr.Detectizr.detect({detectScreen:false}); +/*! + * Detectizr v@VERSION + * http://barisaydinoglu.github.com/Detectizr/ + * + * Written by Baris Aydinoglu (http://baris.aydinoglu.info) - Copyright 2012 + * Released under the MIT license + * + * Date: @DATE + */ +window.Detectizr = (function(window, navigator, document, undefined) { + var Detectizr = {}, + Modernizr = window.Modernizr, + deviceTypes = [ "tv", "tablet", "mobile", "desktop" ], + options = { + // option for enabling HTML classes of all features (not only the true features) to be added + addAllFeaturesAsClass: false, + // option for enabling detection of device + detectDevice: true, + // option for enabling detection of device model + detectDeviceModel: true, + // option for enabling detection of screen size + detectScreen: true, + // option for enabling detection of operating system type and version + detectOS: true, + // option for enabling detection of browser type and version + detectBrowser: true, + // option for enabling detection of common browser plugins + detectPlugins: true + }, + plugins2detect = [ { + name: "adobereader", + substrs: [ "Adobe", "Acrobat" ], + // AcroPDF.PDF is used by version 7 and later + // PDF.PdfCtrl is used by version 6 and earlier + progIds: [ "AcroPDF.PDF", "PDF.PDFCtrl.5" ] + }, { + name: "flash", + substrs: [ "Shockwave Flash" ], + progIds: [ "ShockwaveFlash.ShockwaveFlash.1" ] + }, { + name: "wmplayer", + substrs: [ "Windows Media" ], + progIds: [ "wmplayer.ocx" ] + }, { + name: "silverlight", + substrs: [ "Silverlight" ], + progIds: [ "AgControl.AgControl" ] + }, { + name: "quicktime", + substrs: [ "QuickTime" ], + progIds: [ "QuickTime.QuickTime" ] + } ], + rclass = /[\t\r\n]/g, + docElement = document.documentElement, + resizeTimeoutId, + oldOrientation; + + function extend(obj, extObj) { + var a, b, i; + if (arguments.length > 2) { + for (a = 1, b = arguments.length; a < b; a += 1) { + extend(obj, arguments[a]); + } + } else { + for (i in extObj) { + if (extObj.hasOwnProperty(i)) { + obj[i] = extObj[i]; + } + } + } + return obj; + } + + // simplified and localized indexOf method as one parameter fixed as useragent + function is(key) { + return Detectizr.browser.userAgent.indexOf(key) > -1; + } + + // simplified and localized regex test method as one parameter fixed as useragent + function test(regex) { + return regex.test(Detectizr.browser.userAgent); + } + + // simplified and localized regex exec method as one parameter fixed as useragent + function exec(regex) { + return regex.exec(Detectizr.browser.userAgent); + } + + // localized string trim method + function trim(value) { + return value.replace(/^\s+|\s+$/g, ""); + } + + // convert string to camelcase + function toCamel(string) { + if (string === null || string === undefined) { + return ""; + } + return String(string).replace(/((\s|\-|\.)+[a-z0-9])/g, function($1) { + return $1.toUpperCase().replace(/(\s|\-|\.)/g, ""); + }); + } + + // removeClass function inspired from jQuery.removeClass + function removeClass(element, value) { + var class2remove = value || "", + cur = element.nodeType === 1 && (element.className ? (" " + element.className + " ").replace(rclass, " ") : ""); + if (cur) { + while (cur.indexOf(" " + class2remove + " ") >= 0) { + cur = cur.replace(" " + class2remove + " ", " "); + } + element.className = value ? trim(cur) : ""; + } + } + + // add version test to Modernizr + function addVersionTest(version, major, minor) { + if ( !!version) { + version = toCamel(version); + if ( !!major) { + major = toCamel(major); + addConditionalTest(version + major, true); + if ( !!minor) { + addConditionalTest(version + major + "_" + minor, true); + } + } + } + } + + function checkOrientation() { + //timeout wrapper points with doResizeCode as callback + window.clearTimeout(resizeTimeoutId); + resizeTimeoutId = window.setTimeout(function() { + oldOrientation = Detectizr.device.orientation; + //wrapper for height/width check + if (window.innerHeight > window.innerWidth) { + Detectizr.device.orientation = "portrait"; + } else { + Detectizr.device.orientation = "landscape"; + } + addConditionalTest(Detectizr.device.orientation, true); + if (oldOrientation !== Detectizr.device.orientation) { + addConditionalTest(oldOrientation, false); + } + }, 10); + } + + // add test to Modernizr based on a condition + function addConditionalTest(feature, test) { + if ( !!feature && !!Modernizr) { + if (options.addAllFeaturesAsClass) { + Modernizr.addTest(feature, test); + } else { + test = typeof test === "function" ? test() : test; + if (test) { + Modernizr.addTest(feature, true); + } else { + delete Modernizr[feature]; + removeClass(docElement, feature); + } + } + } + } + + // set version based on versionFull + function setVersion(versionType, versionFull) { + versionType.version = versionFull; + var versionArray = versionFull.split("."); + if (versionArray.length > 0) { + versionArray = versionArray.reverse(); + versionType.major = versionArray.pop(); + if (versionArray.length > 0) { + versionType.minor = versionArray.pop(); + if (versionArray.length > 0) { + versionArray = versionArray.reverse(); + versionType.patch = versionArray.join("."); + } else { + versionType.patch = "0"; + } + } else { + versionType.minor = "0"; + } + } else { + versionType.major = "0"; + } + } + + function detect(opt) { + // Create Global "extend" method, so Detectizr does not need jQuery.extend + var that = this, + i, j, k, device, os, browser, plugin2detect, pluginFound; + options = extend({}, options, opt || {}); + + /** Device detection **/ + if (options.detectDevice) { + Detectizr.device = { + type: "", + model: "", + orientation: "" + }; + device = Detectizr.device; + if (test(/googletv|smarttv|internet.tv|netcast|nettv|appletv|boxee|kylo|roku|dlnadoc|ce\-html/)) { + // Check if user agent is a smart tv + device.type = deviceTypes[0]; + device.model = "smartTv"; + } else if (test(/xbox|playstation.3|wii/)) { + // Check if user agent is a game console + device.type = deviceTypes[0]; + device.model = "gameConsole"; + } else if (test(/ip(a|ro)d/)) { + // Check if user agent is a iPad + device.type = deviceTypes[1]; + device.model = "ipad"; + } else if ((test(/tablet/) && !test(/rx-34/)) || test(/folio/)) { + // Check if user agent is a Tablet + device.type = deviceTypes[1]; + device.model = String(exec(/playbook/) || ""); + } else if (test(/linux/) && test(/android/) && !test(/fennec|mobi|htc.magic|htcX06ht|nexus.one|sc-02b|fone.945/)) { + // Check if user agent is an Android Tablet + device.type = deviceTypes[1]; + device.model = "android"; + } else if (test(/kindle/) || (test(/mac.os/) && test(/silk/))) { + // Check if user agent is a Kindle or Kindle Fire + device.type = deviceTypes[1]; + device.model = "kindle"; + } else if (test(/gt-p10|sc-01c|shw-m180s|sgh-t849|sch-i800|shw-m180l|sph-p100|sgh-i987|zt180|htc(.flyer|\_flyer)|sprint.atp51|viewpad7|pandigital(sprnova|nova)|ideos.s7|dell.streak.7|advent.vega|a101it|a70bht|mid7015|next2|nook/) || (test(/mb511/) && test(/rutem/))) { + // Check if user agent is a pre Android 3.0 Tablet + device.type = deviceTypes[1]; + device.model = "android"; + } else if (test(/bb10/)) { + // Check if user agent is a BB10 device + device.type = deviceTypes[1]; + device.model = "blackberry"; + } else { + // Check if user agent is one of common mobile types + device.model = exec(/iphone|ipod|android|blackberry|opera mini|opera mobi|skyfire|maemo|windows phone|palm|iemobile|symbian|symbianos|fennec|j2me/); + if (device.model !== null) { + device.type = deviceTypes[2]; + device.model = String(device.model); + } else { + device.model = ""; + if (test(/bolt|fennec|iris|maemo|minimo|mobi|mowser|netfront|novarra|prism|rx-34|skyfire|tear|xv6875|xv6975|google.wireless.transcoder/)) { + // Check if user agent is unique Mobile User Agent + device.type = deviceTypes[2]; + } else if (test(/opera/) && test(/windows.nt.5/) && test(/htc|xda|mini|vario|samsung\-gt\-i8000|samsung\-sgh\-i9/)) { + // Check if user agent is an odd Opera User Agent - http://goo.gl/nK90K + device.type = deviceTypes[2]; + } else if ((test(/windows.(nt|xp|me|9)/) && !test(/phone/)) || test(/win(9|.9|nt)/) || test(/\(windows 8\)/)) { + // Check if user agent is Windows Desktop, "(Windows 8)" Chrome extra exception + device.type = deviceTypes[3]; + } else if (test(/macintosh|powerpc/) && !test(/silk/)) { + // Check if agent is Mac Desktop + device.type = deviceTypes[3]; + device.model = "mac"; + } else if (test(/linux/) && test(/x11/)) { + // Check if user agent is a Linux Desktop + device.type = deviceTypes[3]; + } else if (test(/solaris|sunos|bsd/)) { + // Check if user agent is a Solaris, SunOS, BSD Desktop + device.type = deviceTypes[3]; + } else if (test(/bot|crawler|spider|yahoo|ia_archiver|covario-ids|findlinks|dataparksearch|larbin|mediapartners-google|ng-search|snappy|teoma|jeeves|tineye/) && !test(/mobile/)) { + // Check if user agent is a Desktop BOT/Crawler/Spider + device.type = deviceTypes[3]; + device.model = "crawler"; + } else { + // Otherwise assume it is a Mobile Device + device.type = deviceTypes[2]; + } + } + } + for (i = 0, j = deviceTypes.length; i < j; i += 1) { + addConditionalTest(deviceTypes[i], (device.type === deviceTypes[i])); + } + if (options.detectDeviceModel) { + addConditionalTest(toCamel(device.model), true); + } + } + + /** Screen detection **/ + if (options.detectScreen) { + if ( !!Modernizr && !!Modernizr.mq) { + addConditionalTest("smallScreen", Modernizr.mq("only screen and (max-width: 480px)")); + addConditionalTest("verySmallScreen", Modernizr.mq("only screen and (max-width: 320px)")); + addConditionalTest("veryVerySmallScreen", Modernizr.mq("only screen and (max-width: 240px)")); + } + if (device.type === deviceTypes[1] || device.type === deviceTypes[2]) { + window.onresize = function(event) { + checkOrientation(event); + }; + checkOrientation(); + } else { + device.orientation = "landscape"; + addConditionalTest(device.orientation, true); + } + } + + /** OS detection **/ + if (options.detectOS) { + Detectizr.os = {}; + os = Detectizr.os; + if (device.model !== "") { + if (device.model === "ipad" || device.model === "iphone" || device.model === "ipod") { + os.name = "ios"; + setVersion(os, (test(/os\s([\d_]+)/) ? RegExp.$1 : "").replace(/_/g, ".")); + } else if (device.model === "android") { + os.name = "android"; + setVersion(os, (test(/android\s([\d\.]+)/) ? RegExp.$1 : "")); + } else if (device.model === "blackberry") { + os.name = "blackberry"; + setVersion(os, (test(/version\/([^\s]+)/) ? RegExp.$1 : "")); + } else if (device.model === "playbook") { + os.name = "blackberry"; + setVersion(os, (test(/os ([^\s]+)/) ? RegExp.$1.replace(";", "") : "")); + } + } + if (!os.name) { + if (is("win") || is("16bit")) { + os.name = "windows"; + if (is("windows nt 6.3")) { + setVersion(os, "8.1"); + } else if (is("windows nt 6.2") || test(/\(windows 8\)/)) { //windows 8 chrome mac fix + setVersion(os, "8"); + } else if (is("windows nt 6.1")) { + setVersion(os, "7"); + } else if (is("windows nt 6.0")) { + setVersion(os, "vista"); + } else if (is("windows nt 5.2") || is("windows nt 5.1") || is("windows xp")) { + setVersion(os, "xp"); + } else if (is("windows nt 5.0") || is("windows 2000")) { + setVersion(os, "2k"); + } else if (is("winnt") || is("windows nt")) { + setVersion(os, "nt"); + } else if (is("win98") || is("windows 98")) { + setVersion(os, "98"); + } else if (is("win95") || is("windows 95")) { + setVersion(os, "95"); + } + } else if (is("mac") || is("darwin")) { + os.name = "mac os"; + if (is("68k") || is("68000")) { + setVersion(os, "68k"); + } else if (is("ppc") || is("powerpc")) { + setVersion(os, "ppc"); + } else if (is("os x")) { + setVersion(os, (test(/os\sx\s([\d_]+)/) ? RegExp.$1 : "os x").replace(/_/g, ".")); + } + } else if (is("webtv")) { + os.name = "webtv"; + } else if (is("x11") || is("inux")) { + os.name = "linux"; + } else if (is("sunos")) { + os.name = "sun"; + } else if (is("irix")) { + os.name = "irix"; + } else if (is("freebsd")) { + os.name = "freebsd"; + } else if (is("bsd")) { + os.name = "bsd"; + } + } + if ( !!os.name) { + addConditionalTest(os.name, true); + if ( !!os.major) { + addVersionTest(os.name, os.major); + if ( !!os.minor) { + addVersionTest(os.name, os.major, os.minor); + } + } + } + if (test(/\sx64|\sx86|\swin64|\swow64|\samd64/)) { + os.addressRegisterSize = "64bit"; + } else { + os.addressRegisterSize = "32bit"; + } + addConditionalTest(os.addressRegisterSize, true); + } + + /** Browser detection **/ + if (options.detectBrowser) { + browser = Detectizr.browser; + if (!test(/opera|webtv/) && (test(/msie\s([\d\w\.]+)/) || is("trident"))) { + browser.engine = "trident"; + browser.name = "ie"; + if (!window.addEventListener && document.documentMode && document.documentMode === 7) { + setVersion(browser, "8.compat"); + } else if (test(/trident.*rv[ :](\d+)\./)) { + setVersion(browser, RegExp.$1); + } else { + setVersion(browser, (test(/trident\/4\.0/) ? "8" : RegExp.$1)); + } + } else if (is("firefox")) { + browser.engine = "gecko"; + browser.name = "firefox"; + setVersion(browser, (test(/firefox\/([\d\w\.]+)/) ? RegExp.$1 : "")); + } else if (is("gecko/")) { + browser.engine = "gecko"; + } else if (is("opera")) { + browser.name = "opera"; + browser.engine = "presto"; + setVersion(browser, (test(/version\/([\d\.]+)/) ? RegExp.$1 : (test(/opera(\s|\/)([\d\.]+)/) ? RegExp.$2 : ""))); + } else if (is("konqueror")) { + browser.name = "konqueror"; + } else if (is("chrome")) { + browser.engine = "webkit"; + browser.name = "chrome"; + setVersion(browser, (test(/chrome\/([\d\.]+)/) ? RegExp.$1 : "")); + } else if (is("iron")) { + browser.engine = "webkit"; + browser.name = "iron"; + } else if (is("crios")) { + browser.name = "chrome"; + browser.engine = "webkit"; + setVersion(browser, (test(/crios\/([\d\.]+)/) ? RegExp.$1 : "")); + } else if (is("applewebkit/")) { + browser.name = "safari"; + browser.engine = "webkit"; + setVersion(browser, (test(/version\/([\d\.]+)/) ? RegExp.$1 : "")); + } else if (is("mozilla/")) { + browser.engine = "gecko"; + } + if ( !!browser.name) { + addConditionalTest(browser.name, true); + if ( !!browser.major) { + addVersionTest(browser.name, browser.major); + if ( !!browser.minor) { + addVersionTest(browser.name, browser.major, browser.minor); + } + } + } + addConditionalTest(browser.engine, true); + + // Browser Language + browser.language = navigator.userLanguage || navigator.language; + addConditionalTest(browser.language, true); + } + + /** Plugin detection **/ + if (options.detectPlugins) { + browser.plugins = []; + that.detectPlugin = function(substrs) { + var plugins = navigator.plugins, + plugin, haystack, pluginFoundText; + for (j = plugins.length - 1; j >= 0; j--) { + plugin = plugins[j]; + haystack = plugin.name + plugin.description; + pluginFoundText = 0; + for (k = substrs.length; k >= 0; k--) { + if (haystack.indexOf(substrs[k]) !== -1) { + pluginFoundText += 1; + } + } + if (pluginFoundText === substrs.length) { + return true; + } + } + return false; + }; + that.detectObject = function(progIds) { + for (j = progIds.length - 1; j >= 0; j--) { + try { + new ActiveXObject(progIds[j]); + } catch (e) { + // Ignore + } + } + return false; + }; + for (i = plugins2detect.length - 1; i >= 0; i--) { + plugin2detect = plugins2detect[i]; + pluginFound = false; + if (window.ActiveXObject) { + pluginFound = that.detectObject(plugin2detect.progIds); + } else if (navigator.plugins) { + pluginFound = that.detectPlugin(plugin2detect.substrs); + } + if (pluginFound) { + browser.plugins.push(plugin2detect.name); + addConditionalTest(plugin2detect.name, true); + } + } + if (navigator.javaEnabled()) { + browser.plugins.push("java"); + addConditionalTest("java", true); + } + } + } + + Detectizr.detect = function(settings) { + return detect(settings); + }; + Detectizr.init = function() { + if (Detectizr !== undefined) { + Detectizr.browser = { + userAgent: (navigator.userAgent || navigator.vendor || window.opera).toLowerCase() + }; + Detectizr.detect(); + } + }; + Detectizr.init(); + + return Detectizr; +}(this, this.navigator, this.document)); diff --git a/tasks/commit.js b/tasks/commit.js new file mode 100644 index 0000000..18d57b4 --- /dev/null +++ b/tasks/commit.js @@ -0,0 +1,10 @@ +"use strict"; + +var exec = require( "child_process" ).exec; + +module.exports = function( grunt ) { + grunt.registerTask( "commit", "Add and commit changes", function( message ) { + // Always add dist directory + exec( "git add dist && git commit -m " + message, this.async() ); + }); +}; diff --git a/tasks/compile.js b/tasks/compile.js new file mode 100644 index 0000000..fff3a98 --- /dev/null +++ b/tasks/compile.js @@ -0,0 +1,34 @@ +"use strict"; + +module.exports = function( grunt ) { + grunt.registerMultiTask( + "compile", + "Compile detectizr.js to the dist directory. Embed date/version.", + function() { + var data = this.data, + dest = data.dest, + src = data.src, + version = grunt.config( "pkg.version" ), + compiled = grunt.file.read( src ); + + // Embed version and date + compiled = compiled + .replace( /@VERSION/g, version ) + .replace( "@DATE", function() { + var date = new Date(); + + // YYYY-MM-DD + return [ + date.getFullYear(), + ( "0" + ( date.getMonth() + 1 ) ).slice( -2 ), + ( "0" + date.getDate() ).slice( -2 ) + ].join( "-" ); + }); + + // Write source to file + grunt.file.write( dest, compiled ); + + grunt.log.ok( "File written to " + dest ); + } + ); +}; diff --git a/tasks/dist.js b/tasks/dist.js new file mode 100644 index 0000000..f0fdcb9 --- /dev/null +++ b/tasks/dist.js @@ -0,0 +1,35 @@ +"use strict"; + +var fs = require( "fs" ); + +module.exports = function( grunt ) { + grunt.registerTask( "dist", "Process files for distribution", function() { + var files = grunt.file.expand( { filter: "isFile" }, "dist/*" ); + + files.forEach(function( filename ) { + var map, + text = fs.readFileSync( filename, "utf8" ); + + // Modify map/min so that it points to files in the same folder; + // see https://github.com/mishoo/UglifyJS2/issues/47 + if ( /\.map$/.test( filename ) ) { + text = text.replace( /"dist\//g, "\"" ); + fs.writeFileSync( filename, text, "utf-8" ); + } else if ( /\.min\.js$/.test( filename ) ) { + // Wrap sourceMap directive in multiline comments (#13274) + text = text.replace( /\n?(\/\/@\s*sourceMappingURL=)(.*)/, + function( _, directive, path ) { + map = "\n" + directive + path.replace( /^dist\//, "" ); + return ""; + }); + if ( map ) { + text = text.replace( /(^\/\*[\w\W]*?)\s*\*\/|$/, + function( _, comment ) { + return ( comment || "\n/*" ) + map + "\n*/"; + }); + } + fs.writeFileSync( filename, text, "utf-8" ); + } + }); + }); +}; diff --git a/tasks/release.js b/tasks/release.js new file mode 100644 index 0000000..2b0b05d --- /dev/null +++ b/tasks/release.js @@ -0,0 +1,32 @@ +"use strict"; + +var exec = require( "child_process" ).exec; + +module.exports = function( grunt ) { + var rpreversion = /\d\.\d+\.\d+/; + + grunt.registerTask( "release", + "Release a version of detectizr", function( version ) { + + if ( !rpreversion.test( version ) ) { + grunt.fatal( "Version must follow semver release format: " + version ); + return; + } + + var done = this.async(); + exec( "git diff HEAD --name-only", function( err ) { + if ( err ) { + grunt.fatal( "The working directory should be clean when releasing. Commit or stash changes." + err ); + return; + } + // Build to dist directories along with a map and tag the release + grunt.task.run([ + // Commit new version + "version:" + version, + // Tag new version + "tag:" + version + ]); + done(); + }); + }); +}; diff --git a/tasks/tag.js b/tasks/tag.js new file mode 100644 index 0000000..23d6df0 --- /dev/null +++ b/tasks/tag.js @@ -0,0 +1,9 @@ +"use strict"; + +var exec = require( "child_process" ).exec; + +module.exports = function( grunt ) { + grunt.registerTask( "tag", "Tag the specified version", function( version ) { + exec( "git tag " + version, this.async() ); + }); +}; diff --git a/tasks/version.js b/tasks/version.js new file mode 100644 index 0000000..2d07d06 --- /dev/null +++ b/tasks/version.js @@ -0,0 +1,34 @@ +"use strict"; + +var exec = require( "child_process" ).exec; + +module.exports = function( grunt ) { + grunt.registerTask( "version", "Commit a new version", function( version ) { + if ( !/\d\.\d+\.\d+/.test( version ) ) { + grunt.fatal( "Version must follow semver release format: " + version ); + return; + } + var done = this.async(), + files = grunt.config( "version.files" ), + rversion = /("version":\s*")[^"]+/; + + // Update version in specified files + files.forEach(function( filename ) { + var text = grunt.file.read( filename ); + text = text.replace( rversion, "$1" + version ); + grunt.file.write( filename, text ); + }); + + // Add files to git index + exec( "git add -A", function( err ) { + if ( err ) { + grunt.fatal( err ); + return; + } + // Commit next pre version + grunt.config( "pkg.version", version ); + grunt.task.run([ "build", "bower", "test", "commit:'Update version to " + version + "'" ]); + done(); + }); + }); +}; diff --git a/test/index.html b/test/index.html index 057ec57..7f065c0 100644 --- a/test/index.html +++ b/test/index.html @@ -1,16 +1,16 @@ - - - - - Detectizr Test Suite - - - - - - - -
- - - + + + + + Detectizr Test Suite + + + + + + + +
+ + + diff --git a/test/libs/modernizr.js b/test/libs/modernizr.js index e1f7053..07ea93f 100644 --- a/test/libs/modernizr.js +++ b/test/libs/modernizr.js @@ -1,815 +1,1406 @@ -/* Modernizr 2.6.2 (Custom Build) | MIT & BSD - * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load - */ -; - - - -window.Modernizr = (function( window, document, undefined ) { - - var version = '2.6.2', - - Modernizr = {}, - - enableClasses = true, - - docElement = document.documentElement, - - mod = 'modernizr', - modElem = document.createElement(mod), - mStyle = modElem.style, - - inputElem = document.createElement('input') , - - smile = ':)', - - toString = {}.toString, - - prefixes = ' -webkit- -moz- -o- -ms- '.split(' '), - - - - omPrefixes = 'Webkit Moz O ms', - - cssomPrefixes = omPrefixes.split(' '), - - domPrefixes = omPrefixes.toLowerCase().split(' '), - - ns = {'svg': 'http://www.w3.org/2000/svg'}, - - tests = {}, - inputs = {}, - attrs = {}, - - classes = [], - - slice = classes.slice, - - featureName, - - - injectElementWithStyles = function( rule, callback, nodes, testnames ) { - - var style, ret, node, docOverflow, - div = document.createElement('div'), - body = document.body, - fakeBody = body || document.createElement('body'); - - if ( parseInt(nodes, 10) ) { - while ( nodes-- ) { - node = document.createElement('div'); - node.id = testnames ? testnames[nodes] : mod + (nodes + 1); - div.appendChild(node); - } - } - - style = ['­',''].join(''); - div.id = mod; - (body ? div : fakeBody).innerHTML += style; - fakeBody.appendChild(div); - if ( !body ) { - fakeBody.style.background = ''; - fakeBody.style.overflow = 'hidden'; - docOverflow = docElement.style.overflow; - docElement.style.overflow = 'hidden'; - docElement.appendChild(fakeBody); - } - - ret = callback(div, rule); - if ( !body ) { - fakeBody.parentNode.removeChild(fakeBody); - docElement.style.overflow = docOverflow; - } else { - div.parentNode.removeChild(div); - } - - return !!ret; - - }, - - - - isEventSupported = (function() { - - var TAGNAMES = { - 'select': 'input', 'change': 'input', - 'submit': 'form', 'reset': 'form', - 'error': 'img', 'load': 'img', 'abort': 'img' - }; - - function isEventSupported( eventName, element ) { - - element = element || document.createElement(TAGNAMES[eventName] || 'div'); - eventName = 'on' + eventName; - - var isSupported = eventName in element; - - if ( !isSupported ) { - if ( !element.setAttribute ) { - element = document.createElement('div'); - } - if ( element.setAttribute && element.removeAttribute ) { - element.setAttribute(eventName, ''); - isSupported = is(element[eventName], 'function'); - - if ( !is(element[eventName], 'undefined') ) { - element[eventName] = undefined; - } - element.removeAttribute(eventName); - } - } - - element = null; - return isSupported; - } - return isEventSupported; - })(), - - - _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; - - if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { - hasOwnProp = function (object, property) { - return _hasOwnProperty.call(object, property); - }; - } - else { - hasOwnProp = function (object, property) { - return ((property in object) && is(object.constructor.prototype[property], 'undefined')); - }; - } - - - if (!Function.prototype.bind) { - Function.prototype.bind = function bind(that) { - - var target = this; - - if (typeof target != "function") { - throw new TypeError(); - } - - var args = slice.call(arguments, 1), - bound = function () { - - if (this instanceof bound) { - - var F = function(){}; - F.prototype = target.prototype; - var self = new F(); - - var result = target.apply( - self, - args.concat(slice.call(arguments)) - ); - if (Object(result) === result) { - return result; - } - return self; - - } else { - - return target.apply( - that, - args.concat(slice.call(arguments)) - ); - - } - - }; - - return bound; - }; - } - - function setCss( str ) { - mStyle.cssText = str; - } - - function setCssAll( str1, str2 ) { - return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); - } - - function is( obj, type ) { - return typeof obj === type; - } - - function contains( str, substr ) { - return !!~('' + str).indexOf(substr); - } - - function testProps( props, prefixed ) { - for ( var i in props ) { - var prop = props[i]; - if ( !contains(prop, "-") && mStyle[prop] !== undefined ) { - return prefixed == 'pfx' ? prop : true; - } - } - return false; - } - - function testDOMProps( props, obj, elem ) { - for ( var i in props ) { - var item = obj[props[i]]; - if ( item !== undefined) { - - if (elem === false) return props[i]; - - if (is(item, 'function')){ - return item.bind(elem || obj); - } - - return item; - } - } - return false; - } - - function testPropsAll( prop, prefixed, elem ) { - - var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), - props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); - - if(is(prefixed, "string") || is(prefixed, "undefined")) { - return testProps(props, prefixed); - - } else { - props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); - return testDOMProps(props, prefixed, elem); - } - } tests['flexbox'] = function() { - return testPropsAll('flexWrap'); - }; tests['canvas'] = function() { - var elem = document.createElement('canvas'); - return !!(elem.getContext && elem.getContext('2d')); - }; - - tests['canvastext'] = function() { - return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); - }; - - - - tests['webgl'] = function() { - return !!window.WebGLRenderingContext; - }; - - - tests['touch'] = function() { - var bool; - - if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { - bool = true; - } else { - injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) { - bool = node.offsetTop === 9; - }); - } - - return bool; - }; - - - - tests['geolocation'] = function() { - return 'geolocation' in navigator; - }; - - - tests['postmessage'] = function() { - return !!window.postMessage; - }; - - - tests['websqldatabase'] = function() { - return !!window.openDatabase; - }; - - tests['indexedDB'] = function() { - return !!testPropsAll("indexedDB", window); - }; - - tests['hashchange'] = function() { - return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7); - }; - - tests['history'] = function() { - return !!(window.history && history.pushState); - }; - - tests['draganddrop'] = function() { - var div = document.createElement('div'); - return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); - }; - - tests['websockets'] = function() { - return 'WebSocket' in window || 'MozWebSocket' in window; - }; - - - tests['rgba'] = function() { - setCss('background-color:rgba(150,255,150,.5)'); - - return contains(mStyle.backgroundColor, 'rgba'); - }; - - tests['hsla'] = function() { - setCss('background-color:hsla(120,40%,100%,.5)'); - - return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla'); - }; - - tests['multiplebgs'] = function() { - setCss('background:url(https://),url(https://),red url(https://)'); - - return (/(url\s*\(.*?){3}/).test(mStyle.background); - }; tests['backgroundsize'] = function() { - return testPropsAll('backgroundSize'); - }; - - tests['borderimage'] = function() { - return testPropsAll('borderImage'); - }; - - - - tests['borderradius'] = function() { - return testPropsAll('borderRadius'); - }; - - tests['boxshadow'] = function() { - return testPropsAll('boxShadow'); - }; - - tests['textshadow'] = function() { - return document.createElement('div').style.textShadow === ''; - }; - - - tests['opacity'] = function() { - setCssAll('opacity:.55'); - - return (/^0.55$/).test(mStyle.opacity); - }; - - - tests['cssanimations'] = function() { - return testPropsAll('animationName'); - }; - - - tests['csscolumns'] = function() { - return testPropsAll('columnCount'); - }; - - - tests['cssgradients'] = function() { - var str1 = 'background-image:', - str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', - str3 = 'linear-gradient(left top,#9f9, white);'; - - setCss( - (str1 + '-webkit- '.split(' ').join(str2 + str1) + - prefixes.join(str3 + str1)).slice(0, -str1.length) - ); - - return contains(mStyle.backgroundImage, 'gradient'); - }; - - - tests['cssreflections'] = function() { - return testPropsAll('boxReflect'); - }; - - - tests['csstransforms'] = function() { - return !!testPropsAll('transform'); - }; - - - tests['csstransforms3d'] = function() { - - var ret = !!testPropsAll('perspective'); - - if ( ret && 'webkitPerspective' in docElement.style ) { - - injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) { - ret = node.offsetLeft === 9 && node.offsetHeight === 3; - }); - } - return ret; - }; - - - tests['csstransitions'] = function() { - return testPropsAll('transition'); - }; - - - - tests['fontface'] = function() { - var bool; - - injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) { - var style = document.getElementById('smodernizr'), - sheet = style.sheet || style.styleSheet, - cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : ''; - - bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0; - }); - - return bool; - }; - - tests['generatedcontent'] = function() { - var bool; - - injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) { - bool = node.offsetHeight >= 3; - }); - - return bool; - }; - tests['video'] = function() { - var elem = document.createElement('video'), - bool = false; - - try { - if ( bool = !!elem.canPlayType ) { - bool = new Boolean(bool); - bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,''); - - bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,''); - - bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,''); - } - - } catch(e) { } - - return bool; - }; - - tests['audio'] = function() { - var elem = document.createElement('audio'), - bool = false; - - try { - if ( bool = !!elem.canPlayType ) { - bool = new Boolean(bool); - bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''); - bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,''); - - bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,''); - bool.m4a = ( elem.canPlayType('audio/x-m4a;') || - elem.canPlayType('audio/aac;')) .replace(/^no$/,''); - } - } catch(e) { } - - return bool; - }; - - - tests['localstorage'] = function() { - try { - localStorage.setItem(mod, mod); - localStorage.removeItem(mod); - return true; - } catch(e) { - return false; - } - }; - - tests['sessionstorage'] = function() { - try { - sessionStorage.setItem(mod, mod); - sessionStorage.removeItem(mod); - return true; - } catch(e) { - return false; - } - }; - - - tests['webworkers'] = function() { - return !!window.Worker; - }; - - - tests['applicationcache'] = function() { - return !!window.applicationCache; - }; - - - tests['svg'] = function() { - return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; - }; - - tests['inlinesvg'] = function() { - var div = document.createElement('div'); - div.innerHTML = ''; - return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; - }; - - tests['smil'] = function() { - return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate'))); - }; - - - tests['svgclippaths'] = function() { - return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); - }; - - function webforms() { - Modernizr['input'] = (function( props ) { - for ( var i = 0, len = props.length; i < len; i++ ) { - attrs[ props[i] ] = !!(props[i] in inputElem); - } - if (attrs.list){ - attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement); - } - return attrs; - })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); - Modernizr['inputtypes'] = (function(props) { - - for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) { - - inputElem.setAttribute('type', inputElemType = props[i]); - bool = inputElem.type !== 'text'; - - if ( bool ) { - - inputElem.value = smile; - inputElem.style.cssText = 'position:absolute;visibility:hidden;'; - - if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) { - - docElement.appendChild(inputElem); - defaultView = document.defaultView; - - bool = defaultView.getComputedStyle && - defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' && - (inputElem.offsetHeight !== 0); - - docElement.removeChild(inputElem); - - } else if ( /^(search|tel)$/.test(inputElemType) ){ - } else if ( /^(url|email)$/.test(inputElemType) ) { - bool = inputElem.checkValidity && inputElem.checkValidity() === false; - - } else { - bool = inputElem.value != smile; - } - } - - inputs[ props[i] ] = !!bool; - } - return inputs; - })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); - } - for ( var feature in tests ) { - if ( hasOwnProp(tests, feature) ) { - featureName = feature.toLowerCase(); - Modernizr[featureName] = tests[feature](); - - classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); - } - } - - Modernizr.input || webforms(); - - - Modernizr.addTest = function ( feature, test ) { - if ( typeof feature == 'object' ) { - for ( var key in feature ) { - if ( hasOwnProp( feature, key ) ) { - Modernizr.addTest( key, feature[ key ] ); - } - } - } else { - - feature = feature.toLowerCase(); - - if ( Modernizr[feature] !== undefined ) { - return Modernizr; - } - - test = typeof test == 'function' ? test() : test; - - if (typeof enableClasses !== "undefined" && enableClasses) { - docElement.className += ' ' + (test ? '' : 'no-') + feature; - } - Modernizr[feature] = test; - - } - - return Modernizr; - }; - - - setCss(''); - modElem = inputElem = null; - - ;(function(window, document) { - var options = window.html5 || {}; - - var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; - - var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; - - var supportsHtml5Styles; - - var expando = '_html5shiv'; - - var expanID = 0; - - var expandoData = {}; - - var supportsUnknownElements; - - (function() { - try { - var a = document.createElement('a'); - a.innerHTML = ''; - supportsHtml5Styles = ('hidden' in a); - - supportsUnknownElements = a.childNodes.length == 1 || (function() { - (document.createElement)('a'); - var frag = document.createDocumentFragment(); - return ( - typeof frag.cloneNode == 'undefined' || - typeof frag.createDocumentFragment == 'undefined' || - typeof frag.createElement == 'undefined' - ); - }()); - } catch(e) { - supportsHtml5Styles = true; - supportsUnknownElements = true; - } - - }()); function addStyleSheet(ownerDocument, cssText) { - var p = ownerDocument.createElement('p'), - parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; - - p.innerHTML = 'x'; - return parent.insertBefore(p.lastChild, parent.firstChild); - } - - function getElements() { - var elements = html5.elements; - return typeof elements == 'string' ? elements.split(' ') : elements; - } - - function getExpandoData(ownerDocument) { - var data = expandoData[ownerDocument[expando]]; - if (!data) { - data = {}; - expanID++; - ownerDocument[expando] = expanID; - expandoData[expanID] = data; - } - return data; - } - - function createElement(nodeName, ownerDocument, data){ - if (!ownerDocument) { - ownerDocument = document; - } - if(supportsUnknownElements){ - return ownerDocument.createElement(nodeName); - } - if (!data) { - data = getExpandoData(ownerDocument); - } - var node; - - if (data.cache[nodeName]) { - node = data.cache[nodeName].cloneNode(); - } else if (saveClones.test(nodeName)) { - node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); - } else { - node = data.createElem(nodeName); - } - - return node.canHaveChildren && !reSkip.test(nodeName) ? data.frag.appendChild(node) : node; - } - - function createDocumentFragment(ownerDocument, data){ - if (!ownerDocument) { - ownerDocument = document; - } - if(supportsUnknownElements){ - return ownerDocument.createDocumentFragment(); - } - data = data || getExpandoData(ownerDocument); - var clone = data.frag.cloneNode(), - i = 0, - elems = getElements(), - l = elems.length; - for(;i element. This information allows you to progressively enhance + * your pages with a granular level of control over the experience. + * + * Modernizr has an optional (not included) conditional resource loader + * called Modernizr.load(), based on Yepnope.js (yepnopejs.com). + * To get a build that includes Modernizr.load(), as well as choosing + * which tests to include, go to www.modernizr.com/download/ + * + * Authors Faruk Ates, Paul Irish, Alex Sexton + * Contributors Ryan Seddon, Ben Alman + */ + +window.Modernizr = (function( window, document, undefined ) { + + var version = '2.7.1', + + Modernizr = {}, + + /*>>cssclasses*/ + // option for enabling the HTML classes to be added + enableClasses = true, + /*>>cssclasses*/ + + docElement = document.documentElement, + + /** + * Create our "modernizr" element that we do most feature tests on. + */ + mod = 'modernizr', + modElem = document.createElement(mod), + mStyle = modElem.style, + + /** + * Create the input element for various Web Forms feature tests. + */ + inputElem /*>>inputelem*/ = document.createElement('input') /*>>inputelem*/ , + + /*>>smile*/ + smile = ':)', + /*>>smile*/ + + toString = {}.toString, + + // TODO :: make the prefixes more granular + /*>>prefixes*/ + // List of property values to set for css tests. See ticket #21 + prefixes = ' -webkit- -moz- -o- -ms- '.split(' '), + /*>>prefixes*/ + + /*>>domprefixes*/ + // Following spec is to expose vendor-specific style properties as: + // elem.style.WebkitBorderRadius + // and the following would be incorrect: + // elem.style.webkitBorderRadius + + // Webkit ghosts their properties in lowercase but Opera & Moz do not. + // Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+ + // erik.eae.net/archives/2008/03/10/21.48.10/ + + // More here: github.com/Modernizr/Modernizr/issues/issue/21 + omPrefixes = 'Webkit Moz O ms', + + cssomPrefixes = omPrefixes.split(' '), + + domPrefixes = omPrefixes.toLowerCase().split(' '), + /*>>domprefixes*/ + + /*>>ns*/ + ns = {'svg': 'http://www.w3.org/2000/svg'}, + /*>>ns*/ + + tests = {}, + inputs = {}, + attrs = {}, + + classes = [], + + slice = classes.slice, + + featureName, // used in testing loop + + + /*>>teststyles*/ + // Inject element with style element and some CSS rules + injectElementWithStyles = function( rule, callback, nodes, testnames ) { + + var style, ret, node, docOverflow, + div = document.createElement('div'), + // After page load injecting a fake body doesn't work so check if body exists + body = document.body, + // IE6 and 7 won't return offsetWidth or offsetHeight unless it's in the body element, so we fake it. + fakeBody = body || document.createElement('body'); + + if ( parseInt(nodes, 10) ) { + // In order not to give false positives we create a node for each test + // This also allows the method to scale for unspecified uses + while ( nodes-- ) { + node = document.createElement('div'); + node.id = testnames ? testnames[nodes] : mod + (nodes + 1); + div.appendChild(node); + } + } + + // '].join(''); + div.id = mod; + // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody. + // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270 + (body ? div : fakeBody).innerHTML += style; + fakeBody.appendChild(div); + if ( !body ) { + //avoid crashing IE8, if background image is used + fakeBody.style.background = ''; + //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible + fakeBody.style.overflow = 'hidden'; + docOverflow = docElement.style.overflow; + docElement.style.overflow = 'hidden'; + docElement.appendChild(fakeBody); + } + + ret = callback(div, rule); + // If this is done after page load we don't want to remove the body so check if body exists + if ( !body ) { + fakeBody.parentNode.removeChild(fakeBody); + docElement.style.overflow = docOverflow; + } else { + div.parentNode.removeChild(div); + } + + return !!ret; + + }, + /*>>teststyles*/ + + /*>>mq*/ + // adapted from matchMedia polyfill + // by Scott Jehl and Paul Irish + // gist.github.com/786768 + testMediaQuery = function( mq ) { + + var matchMedia = window.matchMedia || window.msMatchMedia; + if ( matchMedia ) { + return matchMedia(mq).matches; + } + + var bool; + + injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) { + bool = (window.getComputedStyle ? + getComputedStyle(node, null) : + node.currentStyle)['position'] == 'absolute'; + }); + + return bool; + + }, + /*>>mq*/ + + + /*>>hasevent*/ + // + // isEventSupported determines if a given element supports the given event + // kangax.github.com/iseventsupported/ + // + // The following results are known incorrects: + // Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative + // Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333 + // ... + isEventSupported = (function() { + + var TAGNAMES = { + 'select': 'input', 'change': 'input', + 'submit': 'form', 'reset': 'form', + 'error': 'img', 'load': 'img', 'abort': 'img' + }; + + function isEventSupported( eventName, element ) { + + element = element || document.createElement(TAGNAMES[eventName] || 'div'); + eventName = 'on' + eventName; + + // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those + var isSupported = eventName in element; + + if ( !isSupported ) { + // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element + if ( !element.setAttribute ) { + element = document.createElement('div'); + } + if ( element.setAttribute && element.removeAttribute ) { + element.setAttribute(eventName, ''); + isSupported = is(element[eventName], 'function'); + + // If property was created, "remove it" (by setting value to `undefined`) + if ( !is(element[eventName], 'undefined') ) { + element[eventName] = undefined; + } + element.removeAttribute(eventName); + } + } + + element = null; + return isSupported; + } + return isEventSupported; + })(), + /*>>hasevent*/ + + // TODO :: Add flag for hasownprop ? didn't last time + + // hasOwnProperty shim by kangax needed for Safari 2.0 support + _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; + + if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { + hasOwnProp = function (object, property) { + return _hasOwnProperty.call(object, property); + }; + } + else { + hasOwnProp = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ + return ((property in object) && is(object.constructor.prototype[property], 'undefined')); + }; + } + + // Adapted from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js + // es5.github.com/#x15.3.4.5 + + if (!Function.prototype.bind) { + Function.prototype.bind = function bind(that) { + + var target = this; + + if (typeof target != "function") { + throw new TypeError(); + } + + var args = slice.call(arguments, 1), + bound = function () { + + if (this instanceof bound) { + + var F = function(){}; + F.prototype = target.prototype; + var self = new F(); + + var result = target.apply( + self, + args.concat(slice.call(arguments)) + ); + if (Object(result) === result) { + return result; + } + return self; + + } else { + + return target.apply( + that, + args.concat(slice.call(arguments)) + ); + + } + + }; + + return bound; + }; + } + + /** + * setCss applies given styles to the Modernizr DOM node. + */ + function setCss( str ) { + mStyle.cssText = str; + } + + /** + * setCssAll extrapolates all vendor-specific css strings. + */ + function setCssAll( str1, str2 ) { + return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); + } + + /** + * is returns a boolean for if typeof obj is exactly type. + */ + function is( obj, type ) { + return typeof obj === type; + } + + /** + * contains returns a boolean for if substr is found within str. + */ + function contains( str, substr ) { + return !!~('' + str).indexOf(substr); + } + + /*>>testprop*/ + + // testProps is a generic CSS / DOM property test. + + // In testing support for a given CSS property, it's legit to test: + // `elem.style[styleName] !== undefined` + // If the property is supported it will return an empty string, + // if unsupported it will return undefined. + + // We'll take advantage of this quick test and skip setting a style + // on our modernizr element, but instead just testing undefined vs + // empty string. + + // Because the testing of the CSS property names (with "-", as + // opposed to the camelCase DOM properties) is non-portable and + // non-standard but works in WebKit and IE (but not Gecko or Opera), + // we explicitly reject properties with dashes so that authors + // developing in WebKit or IE first don't end up with + // browser-specific content by accident. + + function testProps( props, prefixed ) { + for ( var i in props ) { + var prop = props[i]; + if ( !contains(prop, "-") && mStyle[prop] !== undefined ) { + return prefixed == 'pfx' ? prop : true; + } + } + return false; + } + /*>>testprop*/ + + // TODO :: add testDOMProps + /** + * testDOMProps is a generic DOM property test; if a browser supports + * a certain property, it won't return undefined for it. + */ + function testDOMProps( props, obj, elem ) { + for ( var i in props ) { + var item = obj[props[i]]; + if ( item !== undefined) { + + // return the property name as a string + if (elem === false) return props[i]; + + // let's bind a function + if (is(item, 'function')){ + // default to autobind unless override + return item.bind(elem || obj); + } + + // return the unbound function or obj or value + return item; + } + } + return false; + } + + /*>>testallprops*/ + /** + * testPropsAll tests a list of DOM properties we want to check against. + * We specify literally ALL possible (known and/or likely) properties on + * the element including the non-vendor prefixed one, for forward- + * compatibility. + */ + function testPropsAll( prop, prefixed, elem ) { + + var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), + props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); + + // did they call .prefixed('boxSizing') or are we just testing a prop? + if(is(prefixed, "string") || is(prefixed, "undefined")) { + return testProps(props, prefixed); + + // otherwise, they called .prefixed('requestAnimationFrame', window[, elem]) + } else { + props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); + return testDOMProps(props, prefixed, elem); + } + } + /*>>testallprops*/ + + + /** + * Tests + * ----- + */ + + // The *new* flexbox + // dev.w3.org/csswg/css3-flexbox + + tests['flexbox'] = function() { + return testPropsAll('flexWrap'); + }; + + // The *old* flexbox + // www.w3.org/TR/2009/WD-css3-flexbox-20090723/ + + tests['flexboxlegacy'] = function() { + return testPropsAll('boxDirection'); + }; + + // On the S60 and BB Storm, getContext exists, but always returns undefined + // so we actually have to call getContext() to verify + // github.com/Modernizr/Modernizr/issues/issue/97/ + + tests['canvas'] = function() { + var elem = document.createElement('canvas'); + return !!(elem.getContext && elem.getContext('2d')); + }; + + tests['canvastext'] = function() { + return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); + }; + + // webk.it/70117 is tracking a legit WebGL feature detect proposal + + // We do a soft detect which may false positive in order to avoid + // an expensive context creation: bugzil.la/732441 + + tests['webgl'] = function() { + return !!window.WebGLRenderingContext; + }; + + /* + * The Modernizr.touch test only indicates if the browser supports + * touch events, which does not necessarily reflect a touchscreen + * device, as evidenced by tablets running Windows 7 or, alas, + * the Palm Pre / WebOS (touch) phones. + * + * Additionally, Chrome (desktop) used to lie about its support on this, + * but that has since been rectified: crbug.com/36415 + * + * We also test for Firefox 4 Multitouch Support. + * + * For more info, see: modernizr.github.com/Modernizr/touch.html + */ + + tests['touch'] = function() { + var bool; + + if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { + bool = true; + } else { + injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) { + bool = node.offsetTop === 9; + }); + } + + return bool; + }; + + + // geolocation is often considered a trivial feature detect... + // Turns out, it's quite tricky to get right: + // + // Using !!navigator.geolocation does two things we don't want. It: + // 1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513 + // 2. Disables page caching in WebKit: webk.it/43956 + // + // Meanwhile, in Firefox < 8, an about:config setting could expose + // a false positive that would throw an exception: bugzil.la/688158 + + tests['geolocation'] = function() { + return 'geolocation' in navigator; + }; + + + tests['postmessage'] = function() { + return !!window.postMessage; + }; + + + // Chrome incognito mode used to throw an exception when using openDatabase + // It doesn't anymore. + tests['websqldatabase'] = function() { + return !!window.openDatabase; + }; + + // Vendors had inconsistent prefixing with the experimental Indexed DB: + // - Webkit's implementation is accessible through webkitIndexedDB + // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB + // For speed, we don't test the legacy (and beta-only) indexedDB + tests['indexedDB'] = function() { + return !!testPropsAll("indexedDB", window); + }; + + // documentMode logic from YUI to filter out IE8 Compat Mode + // which false positives. + tests['hashchange'] = function() { + return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7); + }; + + // Per 1.6: + // This used to be Modernizr.historymanagement but the longer + // name has been deprecated in favor of a shorter and property-matching one. + // The old API is still available in 1.6, but as of 2.0 will throw a warning, + // and in the first release thereafter disappear entirely. + tests['history'] = function() { + return !!(window.history && history.pushState); + }; + + tests['draganddrop'] = function() { + var div = document.createElement('div'); + return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); + }; + + // FF3.6 was EOL'ed on 4/24/12, but the ESR version of FF10 + // will be supported until FF19 (2/12/13), at which time, ESR becomes FF17. + // FF10 still uses prefixes, so check for it until then. + // for more ESR info, see: mozilla.org/en-US/firefox/organizations/faq/ + tests['websockets'] = function() { + return 'WebSocket' in window || 'MozWebSocket' in window; + }; + + + // css-tricks.com/rgba-browser-support/ + tests['rgba'] = function() { + // Set an rgba() color and check the returned value + + setCss('background-color:rgba(150,255,150,.5)'); + + return contains(mStyle.backgroundColor, 'rgba'); + }; + + tests['hsla'] = function() { + // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally, + // except IE9 who retains it as hsla + + setCss('background-color:hsla(120,40%,100%,.5)'); + + return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla'); + }; + + tests['multiplebgs'] = function() { + // Setting multiple images AND a color on the background shorthand property + // and then querying the style.background property value for the number of + // occurrences of "url(" is a reliable method for detecting ACTUAL support for this! + + setCss('background:url(https://),url(https://),red url(https://)'); + + // If the UA supports multiple backgrounds, there should be three occurrences + // of the string "url(" in the return value for elemStyle.background + + return (/(url\s*\(.*?){3}/).test(mStyle.background); + }; + + + + // this will false positive in Opera Mini + // github.com/Modernizr/Modernizr/issues/396 + + tests['backgroundsize'] = function() { + return testPropsAll('backgroundSize'); + }; + + tests['borderimage'] = function() { + return testPropsAll('borderImage'); + }; + + + // Super comprehensive table about all the unique implementations of + // border-radius: muddledramblings.com/table-of-css3-border-radius-compliance + + tests['borderradius'] = function() { + return testPropsAll('borderRadius'); + }; + + // WebOS unfortunately false positives on this test. + tests['boxshadow'] = function() { + return testPropsAll('boxShadow'); + }; + + // FF3.0 will false positive on this test + tests['textshadow'] = function() { + return document.createElement('div').style.textShadow === ''; + }; + + + tests['opacity'] = function() { + // Browsers that actually have CSS Opacity implemented have done so + // according to spec, which means their return values are within the + // range of [0.0,1.0] - including the leading zero. + + setCssAll('opacity:.55'); + + // The non-literal . in this regex is intentional: + // German Chrome returns this value as 0,55 + // github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632 + return (/^0.55$/).test(mStyle.opacity); + }; + + + // Note, Android < 4 will pass this test, but can only animate + // a single property at a time + // daneden.me/2011/12/putting-up-with-androids-bullshit/ + tests['cssanimations'] = function() { + return testPropsAll('animationName'); + }; + + + tests['csscolumns'] = function() { + return testPropsAll('columnCount'); + }; + + + tests['cssgradients'] = function() { + /** + * For CSS Gradients syntax, please see: + * webkit.org/blog/175/introducing-css-gradients/ + * developer.mozilla.org/en/CSS/-moz-linear-gradient + * developer.mozilla.org/en/CSS/-moz-radial-gradient + * dev.w3.org/csswg/css3-images/#gradients- + */ + + var str1 = 'background-image:', + str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', + str3 = 'linear-gradient(left top,#9f9, white);'; + + setCss( + // legacy webkit syntax (FIXME: remove when syntax not in use anymore) + (str1 + '-webkit- '.split(' ').join(str2 + str1) + + // standard syntax // trailing 'background-image:' + prefixes.join(str3 + str1)).slice(0, -str1.length) + ); + + return contains(mStyle.backgroundImage, 'gradient'); + }; + + + tests['cssreflections'] = function() { + return testPropsAll('boxReflect'); + }; + + + tests['csstransforms'] = function() { + return !!testPropsAll('transform'); + }; + + + tests['csstransforms3d'] = function() { + + var ret = !!testPropsAll('perspective'); + + // Webkit's 3D transforms are passed off to the browser's own graphics renderer. + // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in + // some conditions. As a result, Webkit typically recognizes the syntax but + // will sometimes throw a false positive, thus we must do a more thorough check: + if ( ret && 'webkitPerspective' in docElement.style ) { + + // Webkit allows this media query to succeed only if the feature is enabled. + // `@media (transform-3d),(-webkit-transform-3d){ ... }` + injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) { + ret = node.offsetLeft === 9 && node.offsetHeight === 3; + }); + } + return ret; + }; + + + tests['csstransitions'] = function() { + return testPropsAll('transition'); + }; + + + /*>>fontface*/ + // @font-face detection routine by Diego Perini + // javascript.nwbox.com/CSSSupport/ + + // false positives: + // WebOS github.com/Modernizr/Modernizr/issues/342 + // WP7 github.com/Modernizr/Modernizr/issues/538 + tests['fontface'] = function() { + var bool; + + injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) { + var style = document.getElementById('smodernizr'), + sheet = style.sheet || style.styleSheet, + cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : ''; + + bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0; + }); + + return bool; + }; + /*>>fontface*/ + + // CSS generated content detection + tests['generatedcontent'] = function() { + var bool; + + injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) { + bool = node.offsetHeight >= 3; + }); + + return bool; + }; + + + + // These tests evaluate support of the video/audio elements, as well as + // testing what types of content they support. + // + // We're using the Boolean constructor here, so that we can extend the value + // e.g. Modernizr.video // true + // Modernizr.video.ogg // 'probably' + // + // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 + // thx to NielsLeenheer and zcorpan + + // Note: in some older browsers, "no" was a return value instead of empty string. + // It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2 + // It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5 + + tests['video'] = function() { + var elem = document.createElement('video'), + bool = false; + + // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224 + try { + if ( bool = !!elem.canPlayType ) { + bool = new Boolean(bool); + bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,''); + + // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546 + bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,''); + + bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,''); + } + + } catch(e) { } + + return bool; + }; + + tests['audio'] = function() { + var elem = document.createElement('audio'), + bool = false; + + try { + if ( bool = !!elem.canPlayType ) { + bool = new Boolean(bool); + bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''); + bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,''); + + // Mimetypes accepted: + // developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements + // bit.ly/iphoneoscodecs + bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,''); + bool.m4a = ( elem.canPlayType('audio/x-m4a;') || + elem.canPlayType('audio/aac;')) .replace(/^no$/,''); + } + } catch(e) { } + + return bool; + }; + + + // In FF4, if disabled, window.localStorage should === null. + + // Normally, we could not test that directly and need to do a + // `('localStorage' in window) && ` test first because otherwise Firefox will + // throw bugzil.la/365772 if cookies are disabled + + // Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem + // will throw the exception: + // QUOTA_EXCEEDED_ERRROR DOM Exception 22. + // Peculiarly, getItem and removeItem calls do not throw. + + // Because we are forced to try/catch this, we'll go aggressive. + + // Just FWIW: IE8 Compat mode supports these features completely: + // www.quirksmode.org/dom/html5.html + // But IE8 doesn't support either with local files + + tests['localstorage'] = function() { + try { + localStorage.setItem(mod, mod); + localStorage.removeItem(mod); + return true; + } catch(e) { + return false; + } + }; + + tests['sessionstorage'] = function() { + try { + sessionStorage.setItem(mod, mod); + sessionStorage.removeItem(mod); + return true; + } catch(e) { + return false; + } + }; + + + tests['webworkers'] = function() { + return !!window.Worker; + }; + + + tests['applicationcache'] = function() { + return !!window.applicationCache; + }; + + + // Thanks to Erik Dahlstrom + tests['svg'] = function() { + return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; + }; + + // specifically for SVG inline in HTML, not within XHTML + // test page: paulirish.com/demo/inline-svg + tests['inlinesvg'] = function() { + var div = document.createElement('div'); + div.innerHTML = ''; + return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; + }; + + // SVG SMIL animation + tests['smil'] = function() { + return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate'))); + }; + + // This test is only for clip paths in SVG proper, not clip paths on HTML content + // demo: srufaculty.sru.edu/david.dailey/svg/newstuff/clipPath4.svg + + // However read the comments to dig into applying SVG clippaths to HTML content here: + // github.com/Modernizr/Modernizr/issues/213#issuecomment-1149491 + tests['svgclippaths'] = function() { + return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); + }; + + /*>>webforms*/ + // input features and input types go directly onto the ret object, bypassing the tests loop. + // Hold this guy to execute in a moment. + function webforms() { + /*>>input*/ + // Run through HTML5's new input attributes to see if the UA understands any. + // We're using f which is the element created early on + // Mike Taylr has created a comprehensive resource for testing these attributes + // when applied to all input types: + // miketaylr.com/code/input-type-attr.html + // spec: www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary + + // Only input placeholder is tested while textarea's placeholder is not. + // Currently Safari 4 and Opera 11 have support only for the input placeholder + // Both tests are available in feature-detects/forms-placeholder.js + Modernizr['input'] = (function( props ) { + for ( var i = 0, len = props.length; i < len; i++ ) { + attrs[ props[i] ] = !!(props[i] in inputElem); + } + if (attrs.list){ + // safari false positive's on datalist: webk.it/74252 + // see also github.com/Modernizr/Modernizr/issues/146 + attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement); + } + return attrs; + })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); + /*>>input*/ + + /*>>inputtypes*/ + // Run through HTML5's new input types to see if the UA understands any. + // This is put behind the tests runloop because it doesn't return a + // true/false like all the other tests; instead, it returns an object + // containing each input type with its corresponding true/false value + + // Big thanks to @miketaylr for the html5 forms expertise. miketaylr.com/ + Modernizr['inputtypes'] = (function(props) { + + for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) { + + inputElem.setAttribute('type', inputElemType = props[i]); + bool = inputElem.type !== 'text'; + + // We first check to see if the type we give it sticks.. + // If the type does, we feed it a textual value, which shouldn't be valid. + // If the value doesn't stick, we know there's input sanitization which infers a custom UI + if ( bool ) { + + inputElem.value = smile; + inputElem.style.cssText = 'position:absolute;visibility:hidden;'; + + if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) { + + docElement.appendChild(inputElem); + defaultView = document.defaultView; + + // Safari 2-4 allows the smiley as a value, despite making a slider + bool = defaultView.getComputedStyle && + defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' && + // Mobile android web browser has false positive, so must + // check the height to see if the widget is actually there. + (inputElem.offsetHeight !== 0); + + docElement.removeChild(inputElem); + + } else if ( /^(search|tel)$/.test(inputElemType) ){ + // Spec doesn't define any special parsing or detectable UI + // behaviors so we pass these through as true + + // Interestingly, opera fails the earlier test, so it doesn't + // even make it here. + + } else if ( /^(url|email)$/.test(inputElemType) ) { + // Real url and email support comes with prebaked validation. + bool = inputElem.checkValidity && inputElem.checkValidity() === false; + + } else { + // If the upgraded input compontent rejects the :) text, we got a winner + bool = inputElem.value != smile; + } + } + + inputs[ props[i] ] = !!bool; + } + return inputs; + })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); + /*>>inputtypes*/ + } + /*>>webforms*/ + + + // End of test definitions + // ----------------------- + + + + // Run through all tests and detect their support in the current UA. + // todo: hypothetically we could be doing an array of tests and use a basic loop here. + for ( var feature in tests ) { + if ( hasOwnProp(tests, feature) ) { + // run the test, throw the return value into the Modernizr, + // then based on that boolean, define an appropriate className + // and push it into an array of classes we'll join later. + featureName = feature.toLowerCase(); + Modernizr[featureName] = tests[feature](); + + classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); + } + } + + /*>>webforms*/ + // input tests need to run. + Modernizr.input || webforms(); + /*>>webforms*/ + + + /** + * addTest allows the user to define their own feature tests + * the result will be added onto the Modernizr object, + * as well as an appropriate className set on the html element + * + * @param feature - String naming the feature + * @param test - Function returning true if feature is supported, false if not + */ + Modernizr.addTest = function ( feature, test ) { + if ( typeof feature == 'object' ) { + for ( var key in feature ) { + if ( hasOwnProp( feature, key ) ) { + Modernizr.addTest( key, feature[ key ] ); + } + } + } else { + + feature = feature.toLowerCase(); + + if ( Modernizr[feature] !== undefined ) { + // we're going to quit if you're trying to overwrite an existing test + // if we were to allow it, we'd do this: + // var re = new RegExp("\\b(no-)?" + feature + "\\b"); + // docElement.className = docElement.className.replace( re, '' ); + // but, no rly, stuff 'em. + return Modernizr; + } + + test = typeof test == 'function' ? test() : test; + + if (typeof enableClasses !== "undefined" && enableClasses) { + docElement.className += ' ' + (test ? '' : 'no-') + feature; + } + Modernizr[feature] = test; + + } + + return Modernizr; // allow chaining. + }; + + + // Reset modElem.cssText to nothing to reduce memory footprint. + setCss(''); + modElem = inputElem = null; + + /*>>shiv*/ + /** + * @preserve HTML5 Shiv prev3.7.1 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed + */ + ;(function(window, document) { + /*jshint evil:true */ + /** version */ + var version = '3.7.0'; + + /** Preset options */ + var options = window.html5 || {}; + + /** Used to skip problem elements */ + var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; + + /** Not all elements can be cloned in IE **/ + var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; + + /** Detect whether the browser supports default html5 styles */ + var supportsHtml5Styles; + + /** Name of the expando, to work with multiple documents or to re-shiv one document */ + var expando = '_html5shiv'; + + /** The id for the the documents expando */ + var expanID = 0; + + /** Cached data for each document */ + var expandoData = {}; + + /** Detect whether the browser supports unknown elements */ + var supportsUnknownElements; + + (function() { + try { + var a = document.createElement('a'); + a.innerHTML = ''; + //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles + supportsHtml5Styles = ('hidden' in a); + + supportsUnknownElements = a.childNodes.length == 1 || (function() { + // assign a false positive if unable to shiv + (document.createElement)('a'); + var frag = document.createDocumentFragment(); + return ( + typeof frag.cloneNode == 'undefined' || + typeof frag.createDocumentFragment == 'undefined' || + typeof frag.createElement == 'undefined' + ); + }()); + } catch(e) { + // assign a false positive if detection fails => unable to shiv + supportsHtml5Styles = true; + supportsUnknownElements = true; + } + + }()); + + /*--------------------------------------------------------------------------*/ + + /** + * Creates a style sheet with the given CSS text and adds it to the document. + * @private + * @param {Document} ownerDocument The document. + * @param {String} cssText The CSS text. + * @returns {StyleSheet} The style element. + */ + function addStyleSheet(ownerDocument, cssText) { + var p = ownerDocument.createElement('p'), + parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; + + p.innerHTML = 'x'; + return parent.insertBefore(p.lastChild, parent.firstChild); + } + + /** + * Returns the value of `html5.elements` as an array. + * @private + * @returns {Array} An array of shived element node names. + */ + function getElements() { + var elements = html5.elements; + return typeof elements == 'string' ? elements.split(' ') : elements; + } + + /** + * Returns the data associated to the given document + * @private + * @param {Document} ownerDocument The document. + * @returns {Object} An object of data. + */ + function getExpandoData(ownerDocument) { + var data = expandoData[ownerDocument[expando]]; + if (!data) { + data = {}; + expanID++; + ownerDocument[expando] = expanID; + expandoData[expanID] = data; + } + return data; + } + + /** + * returns a shived element for the given nodeName and document + * @memberOf html5 + * @param {String} nodeName name of the element + * @param {Document} ownerDocument The context document. + * @returns {Object} The shived element. + */ + function createElement(nodeName, ownerDocument, data){ + if (!ownerDocument) { + ownerDocument = document; + } + if(supportsUnknownElements){ + return ownerDocument.createElement(nodeName); + } + if (!data) { + data = getExpandoData(ownerDocument); + } + var node; + + if (data.cache[nodeName]) { + node = data.cache[nodeName].cloneNode(); + } else if (saveClones.test(nodeName)) { + node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); + } else { + node = data.createElem(nodeName); + } + + // Avoid adding some elements to fragments in IE < 9 because + // * Attributes like `name` or `type` cannot be set/changed once an element + // is inserted into a document/fragment + // * Link elements with `src` attributes that are inaccessible, as with + // a 403 response, will cause the tab/window to crash + // * Script elements appended to fragments will execute when their `src` + // or `text` property is set + return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; + } + + /** + * returns a shived DocumentFragment for the given document + * @memberOf html5 + * @param {Document} ownerDocument The context document. + * @returns {Object} The shived DocumentFragment. + */ + function createDocumentFragment(ownerDocument, data){ + if (!ownerDocument) { + ownerDocument = document; + } + if(supportsUnknownElements){ + return ownerDocument.createDocumentFragment(); + } + data = data || getExpandoData(ownerDocument); + var clone = data.frag.cloneNode(), + i = 0, + elems = getElements(), + l = elems.length; + for(;i>shiv*/ + + // Assign private properties to the return object with prefix + Modernizr._version = version; + + // expose these for the plugin API. Look in the source for how to join() them against your input + /*>>prefixes*/ + Modernizr._prefixes = prefixes; + /*>>prefixes*/ + /*>>domprefixes*/ + Modernizr._domPrefixes = domPrefixes; + Modernizr._cssomPrefixes = cssomPrefixes; + /*>>domprefixes*/ + + /*>>mq*/ + // Modernizr.mq tests a given media query, live against the current state of the window + // A few important notes: + // * If a browser does not support media queries at all (eg. oldIE) the mq() will always return false + // * A max-width or orientation query will be evaluated against the current state, which may change later. + // * You must specify values. Eg. If you are testing support for the min-width media query use: + // Modernizr.mq('(min-width:0)') + // usage: + // Modernizr.mq('only screen and (max-width:768)') + Modernizr.mq = testMediaQuery; + /*>>mq*/ + + /*>>hasevent*/ + // Modernizr.hasEvent() detects support for a given event, with an optional element to test on + // Modernizr.hasEvent('gesturestart', elem) + Modernizr.hasEvent = isEventSupported; + /*>>hasevent*/ + + /*>>testprop*/ + // Modernizr.testProp() investigates whether a given style property is recognized + // Note that the property names must be provided in the camelCase variant. + // Modernizr.testProp('pointerEvents') + Modernizr.testProp = function(prop){ + return testProps([prop]); + }; + /*>>testprop*/ + + /*>>testallprops*/ + // Modernizr.testAllProps() investigates whether a given style property, + // or any of its vendor-prefixed variants, is recognized + // Note that the property names must be provided in the camelCase variant. + // Modernizr.testAllProps('boxSizing') + Modernizr.testAllProps = testPropsAll; + /*>>testallprops*/ + + + /*>>teststyles*/ + // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards + // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... }) + Modernizr.testStyles = injectElementWithStyles; + /*>>teststyles*/ + + + /*>>prefixed*/ + // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input + // Modernizr.prefixed('boxSizing') // 'MozBoxSizing' + + // Properties must be passed as dom-style camelcase, rather than `box-sizing` hypentated style. + // Return values will also be the camelCase variant, if you need to translate that to hypenated style use: + // + // str.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-'); + + // If you're trying to ascertain which transition end event to bind to, you might do something like... + // + // var transEndEventNames = { + // 'WebkitTransition' : 'webkitTransitionEnd', + // 'MozTransition' : 'transitionend', + // 'OTransition' : 'oTransitionEnd', + // 'msTransition' : 'MSTransitionEnd', + // 'transition' : 'transitionend' + // }, + // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ]; + + Modernizr.prefixed = function(prop, obj, elem){ + if(!obj) { + return testPropsAll(prop, 'pfx'); + } else { + // Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame' + return testPropsAll(prop, obj, elem); + } + }; + /*>>prefixed*/ + + + /*>>cssclasses*/ + // Remove "no-js" class from element, if it exists: + docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') + + + // Add the new classes to the element. + (enableClasses ? ' js ' + classes.join(' ') : ''); + /*>>cssclasses*/ + + return Modernizr; + +})(this, this.document); diff --git a/test/libs/qunit.css b/test/libs/qunit.css index 9fcc536..93026e3 100644 --- a/test/libs/qunit.css +++ b/test/libs/qunit.css @@ -1,17 +1,18 @@ -/** - * QUnit v1.12.0 - A JavaScript Unit Testing Framework +/*! + * QUnit 1.14.0 + * http://qunitjs.com/ * - * http://qunitjs.com - * - * Copyright 2012 jQuery Foundation and other contributors - * Released under the MIT license. + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license * http://jquery.org/license + * + * Date: 2014-01-31T16:40Z */ /** Font Family and Sizes */ #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { - font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; + font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; } #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } @@ -21,200 +22,192 @@ /** Resets */ #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { - margin: 0; - padding: 0; + margin: 0; + padding: 0; } /** Header */ #qunit-header { - padding: 0.5em 0 0.5em 1em; + padding: 0.5em 0 0.5em 1em; - color: #8699a4; - background-color: #0d3349; + color: #8699A4; + background-color: #0D3349; - font-size: 1.5em; - line-height: 1em; - font-weight: normal; + font-size: 1.5em; + line-height: 1em; + font-weight: 400; - border-radius: 5px 5px 0 0; - -moz-border-radius: 5px 5px 0 0; - -webkit-border-top-right-radius: 5px; - -webkit-border-top-left-radius: 5px; + border-radius: 5px 5px 0 0; } #qunit-header a { - text-decoration: none; - color: #c2ccd1; + text-decoration: none; + color: #C2CCD1; } #qunit-header a:hover, #qunit-header a:focus { - color: #fff; + color: #FFF; } #qunit-testrunner-toolbar label { - display: inline-block; - padding: 0 .5em 0 .1em; + display: inline-block; + padding: 0 0.5em 0 0.1em; } #qunit-banner { - height: 5px; + height: 5px; } #qunit-testrunner-toolbar { - padding: 0.5em 0 0.5em 2em; - color: #5E740B; - background-color: #eee; - overflow: hidden; + padding: 0.5em 0 0.5em 2em; + color: #5E740B; + background-color: #EEE; + overflow: hidden; } #qunit-userAgent { - padding: 0.5em 0 0.5em 2.5em; - background-color: #2b81af; - color: #fff; - text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; + padding: 0.5em 0 0.5em 2.5em; + background-color: #2B81AF; + color: #FFF; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; } #qunit-modulefilter-container { - float: right; + float: right; } /** Tests: Pass/Fail */ #qunit-tests { - list-style-position: inside; + list-style-position: inside; } #qunit-tests li { - padding: 0.4em 0.5em 0.4em 2.5em; - border-bottom: 1px solid #fff; - list-style-position: inside; + padding: 0.4em 0.5em 0.4em 2.5em; + border-bottom: 1px solid #FFF; + list-style-position: inside; } #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { - display: none; + display: none; } #qunit-tests li strong { - cursor: pointer; + cursor: pointer; } #qunit-tests li a { - padding: 0.5em; - color: #c2ccd1; - text-decoration: none; + padding: 0.5em; + color: #C2CCD1; + text-decoration: none; } #qunit-tests li a:hover, #qunit-tests li a:focus { - color: #000; + color: #000; } #qunit-tests li .runtime { - float: right; - font-size: smaller; + float: right; + font-size: smaller; } .qunit-assert-list { - margin-top: 0.5em; - padding: 0.5em; + margin-top: 0.5em; + padding: 0.5em; - background-color: #fff; + background-color: #FFF; - border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; + border-radius: 5px; } .qunit-collapsed { - display: none; + display: none; } #qunit-tests table { - border-collapse: collapse; - margin-top: .2em; + border-collapse: collapse; + margin-top: 0.2em; } #qunit-tests th { - text-align: right; - vertical-align: top; - padding: 0 .5em 0 0; + text-align: right; + vertical-align: top; + padding: 0 0.5em 0 0; } #qunit-tests td { - vertical-align: top; + vertical-align: top; } #qunit-tests pre { - margin: 0; - white-space: pre-wrap; - word-wrap: break-word; + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; } #qunit-tests del { - background-color: #e0f2be; - color: #374e0c; - text-decoration: none; + background-color: #E0F2BE; + color: #374E0C; + text-decoration: none; } #qunit-tests ins { - background-color: #ffcaca; - color: #500; - text-decoration: none; + background-color: #FFCACA; + color: #500; + text-decoration: none; } /*** Test Counts */ -#qunit-tests b.counts { color: black; } +#qunit-tests b.counts { color: #000; } #qunit-tests b.passed { color: #5E740B; } #qunit-tests b.failed { color: #710909; } #qunit-tests li li { - padding: 5px; - background-color: #fff; - border-bottom: none; - list-style-position: inside; + padding: 5px; + background-color: #FFF; + border-bottom: none; + list-style-position: inside; } /*** Passing Styles */ #qunit-tests li li.pass { - color: #3c510c; - background-color: #fff; - border-left: 10px solid #C6E746; + color: #3C510C; + background-color: #FFF; + border-left: 10px solid #C6E746; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } #qunit-tests .pass .test-name { color: #366097; } #qunit-tests .pass .test-actual, -#qunit-tests .pass .test-expected { color: #999999; } +#qunit-tests .pass .test-expected { color: #999; } #qunit-banner.qunit-pass { background-color: #C6E746; } /*** Failing Styles */ #qunit-tests li li.fail { - color: #710909; - background-color: #fff; - border-left: 10px solid #EE5757; - white-space: pre; + color: #710909; + background-color: #FFF; + border-left: 10px solid #EE5757; + white-space: pre; } #qunit-tests > li:last-child { - border-radius: 0 0 5px 5px; - -moz-border-radius: 0 0 5px 5px; - -webkit-border-bottom-right-radius: 5px; - -webkit-border-bottom-left-radius: 5px; + border-radius: 0 0 5px 5px; } -#qunit-tests .fail { color: #000000; background-color: #EE5757; } +#qunit-tests .fail { color: #000; background-color: #EE5757; } #qunit-tests .fail .test-name, -#qunit-tests .fail .module-name { color: #000000; } +#qunit-tests .fail .module-name { color: #000; } #qunit-tests .fail .test-actual { color: #EE5757; } -#qunit-tests .fail .test-expected { color: green; } +#qunit-tests .fail .test-expected { color: #008000; } #qunit-banner.qunit-fail { background-color: #EE5757; } @@ -222,23 +215,23 @@ /** Result */ #qunit-testresult { - padding: 0.5em 0.5em 0.5em 2.5em; + padding: 0.5em 0.5em 0.5em 2.5em; - color: #2b81af; - background-color: #D2E0E6; + color: #2B81AF; + background-color: #D2E0E6; - border-bottom: 1px solid white; + border-bottom: 1px solid #FFF; } #qunit-testresult .module-name { - font-weight: bold; + font-weight: 700; } /** Fixture */ #qunit-fixture { - position: absolute; - top: -10000px; - left: -10000px; - width: 1000px; - height: 1000px; + position: absolute; + top: -10000px; + left: -10000px; + width: 1000px; + height: 1000px; } diff --git a/test/libs/qunit.js b/test/libs/qunit.js index 06b6a3a..0e279fd 100644 --- a/test/libs/qunit.js +++ b/test/libs/qunit.js @@ -1,696 +1,219 @@ -/** - * QUnit v1.12.0 - A JavaScript Unit Testing Framework - * - * http://qunitjs.com +/*! + * QUnit 1.14.0 + * http://qunitjs.com/ * * Copyright 2013 jQuery Foundation and other contributors - * Released under the MIT license. - * https://jquery.org/license/ + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-01-31T16:40Z */ (function( window ) { var QUnit, - assert, - config, - onErrorFnPrev, - testId = 0, - fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - // Keep a local reference to Date (GH-283) - Date = window.Date, - setTimeout = window.setTimeout, - defined = { - setTimeout: typeof window.setTimeout !== "undefined", - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch( e ) { - return false; - } - }()) - }, - /** - * Provides a normalized error string, correcting an issue - * with IE 7 (and prior) where Error.prototype.toString is - * not properly implemented - * - * Based on http://es5.github.com/#x15.11.4.4 - * - * @param {String|Error} error - * @return {String} error message - */ - errorString = function( error ) { - var name, message, - errorString = error.toString(); - if ( errorString.substring( 0, 7 ) === "[object" ) { - name = error.name ? error.name.toString() : "Error"; - message = error.message ? error.message.toString() : ""; - if ( name && message ) { - return name + ": " + message; - } else if ( name ) { - return name; - } else if ( message ) { - return message; - } else { - return "Error"; - } - } else { - return errorString; - } - }, - /** - * Makes a clone of an object using only Array or Object as base, - * and copies over the own enumerable properties. - * - * @param {Object} obj - * @return {Object} New object with only the own properties (recursively). - */ - objectValues = function( obj ) { - // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. - /*jshint newcap: false */ - var key, val, - vals = QUnit.is( "array", obj ) ? [] : {}; - for ( key in obj ) { - if ( hasOwn.call( obj, key ) ) { - val = obj[key]; - vals[key] = val === Object(val) ? objectValues(val) : val; - } - } - return vals; - }; - -function Test( settings ) { - extend( this, settings ); - this.assertions = []; - this.testNumber = ++Test.count; -} - -Test.count = 0; + assert, + config, + onErrorFnPrev, + testId = 0, + fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + // Keep a local reference to Date (GH-283) + Date = window.Date, + setTimeout = window.setTimeout, + clearTimeout = window.clearTimeout, + defined = { + document: typeof window.document !== "undefined", + setTimeout: typeof window.setTimeout !== "undefined", + sessionStorage: (function() { + var x = "qunit-test-string"; + try { + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); + return true; + } catch( e ) { + return false; + } + }()) + }, + /** + * Provides a normalized error string, correcting an issue + * with IE 7 (and prior) where Error.prototype.toString is + * not properly implemented + * + * Based on http://es5.github.com/#x15.11.4.4 + * + * @param {String|Error} error + * @return {String} error message + */ + errorString = function( error ) { + var name, message, + errorString = error.toString(); + if ( errorString.substring( 0, 7 ) === "[object" ) { + name = error.name ? error.name.toString() : "Error"; + message = error.message ? error.message.toString() : ""; + if ( name && message ) { + return name + ": " + message; + } else if ( name ) { + return name; + } else if ( message ) { + return message; + } else { + return "Error"; + } + } else { + return errorString; + } + }, + /** + * Makes a clone of an object using only Array or Object as base, + * and copies over the own enumerable properties. + * + * @param {Object} obj + * @return {Object} New object with only the own properties (recursively). + */ + objectValues = function( obj ) { + // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392. + /*jshint newcap: false */ + var key, val, + vals = QUnit.is( "array", obj ) ? [] : {}; + for ( key in obj ) { + if ( hasOwn.call( obj, key ) ) { + val = obj[key]; + vals[key] = val === Object(val) ? objectValues(val) : val; + } + } + return vals; + }; -Test.prototype = { - init: function() { - var a, b, li, - tests = id( "qunit-tests" ); - - if ( tests ) { - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml; - - // `a` initialized at top of scope - a = document.createElement( "a" ); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ testNumber: this.testNumber }); - - li = document.createElement( "li" ); - li.appendChild( b ); - li.appendChild( a ); - li.className = "running"; - li.id = this.id = "qunit-test-output" + testId++; - - tests.appendChild( li ); - } - }, - setup: function() { - if ( - // Emit moduleStart when we're switching from one module to another - this.module !== config.previousModule || - // They could be equal (both undefined) but if the previousModule property doesn't - // yet exist it means this is the first test in a suite that isn't wrapped in a - // module, in which case we'll just emit a moduleStart event for 'undefined'. - // Without this, reporters can get testStart before moduleStart which is a problem. - !hasOwn.call( config, "previousModule" ) - ) { - if ( hasOwn.call( config, "previousModule" ) ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.previousModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0 }; - runLoggingCallbacks( "moduleStart", QUnit, { - name: this.module - }); - } - - config.current = this; - - this.testEnvironment = extend({ - setup: function() {}, - teardown: function() {} - }, this.moduleTestEnvironment ); - - this.started = +new Date(); - runLoggingCallbacks( "testStart", QUnit, { - name: this.testName, - module: this.module - }); - - /*jshint camelcase:false */ - - - /** - * Expose the current test environment. - * - * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. - */ - QUnit.current_testEnvironment = this.testEnvironment; - - /*jshint camelcase:true */ - - if ( !config.pollution ) { - saveGlobal(); - } - if ( config.notrycatch ) { - this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); - return; - } - try { - this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); - } - }, - run: function() { - config.current = this; - - var running = id( "qunit-testresult" ); - - if ( running ) { - running.innerHTML = "Running:
" + this.nameHtml; - } - - if ( this.async ) { - QUnit.stop(); - } - - this.callbackStarted = +new Date(); - - if ( config.notrycatch ) { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; - return; - } - - try { - this.callback.call( this.testEnvironment, QUnit.assert ); - this.callbackRuntime = +new Date() - this.callbackStarted; - } catch( e ) { - this.callbackRuntime = +new Date() - this.callbackStarted; - - QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - // else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - QUnit.start(); - } - } - }, - teardown: function() { - config.current = this; - if ( config.notrycatch ) { - if ( typeof this.callbackRuntime === "undefined" ) { - this.callbackRuntime = +new Date() - this.callbackStarted; - } - this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); - return; - } else { - try { - this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); - } catch( e ) { - QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); - } - } - checkPollution(); - }, - finish: function() { - config.current = this; - if ( config.requireExpects && this.expected === null ) { - QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); - } else if ( this.expected !== null && this.expected !== this.assertions.length ) { - QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); - } else if ( this.expected === null && !this.assertions.length ) { - QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); - } - - var i, assertion, a, b, time, li, ol, - test = this, - good = 0, - bad = 0, - tests = id( "qunit-tests" ); - - this.runtime = +new Date() - this.started; - config.stats.all += this.assertions.length; - config.moduleStats.all += this.assertions.length; - - if ( tests ) { - ol = document.createElement( "ol" ); - ol.className = "qunit-assert-list"; - - for ( i = 0; i < this.assertions.length; i++ ) { - assertion = this.assertions[i]; - - li = document.createElement( "li" ); - li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); - ol.appendChild( li ); - - if ( assertion.result ) { - good++; - } else { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - // store result when possible - if ( QUnit.config.reorder && defined.sessionStorage ) { - if ( bad ) { - sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); - } else { - sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); - } - } - - if ( bad === 0 ) { - addClass( ol, "qunit-collapsed" ); - } - - // `b` initialized at top of scope - b = document.createElement( "strong" ); - b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; - - addEvent(b, "click", function() { - var next = b.parentNode.lastChild, - collapsed = hasClass( next, "qunit-collapsed" ); - ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); - }); - - addEvent(b, "dblclick", function( e ) { - var target = e && e.target ? e.target : window.event.srcElement; - if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { - target = target.parentNode; - } - if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ testNumber: test.testNumber }); - } - }); - - // `time` initialized at top of scope - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = this.runtime + " ms"; - - // `li` initialized at top of scope - li = id( this.id ); - li.className = bad ? "fail" : "pass"; - li.removeChild( li.firstChild ); - a = li.firstChild; - li.appendChild( b ); - li.appendChild( a ); - li.appendChild( time ); - li.appendChild( ol ); - - } else { - for ( i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[i].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - } - - runLoggingCallbacks( "testDone", QUnit, { - name: this.testName, - module: this.module, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length, - duration: this.runtime - }); - - QUnit.reset(); - - config.current = undefined; - }, - - queue: function() { - var bad, - test = this; - - synchronize(function() { - test.init(); - }); - function run() { - // each of these can by async - synchronize(function() { - test.setup(); - }); - synchronize(function() { - test.run(); - }); - synchronize(function() { - test.teardown(); - }); - synchronize(function() { - test.finish(); - }); - } - - // `bad` initialized at top of scope - // defer when previous test run passed, if storage is available - bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); - - if ( bad ) { - run(); - } else { - synchronize( run, true ); - } - } -}; // Root QUnit object. // `QUnit` initialized at top of scope QUnit = { - // call on start of module test to prepend name to all tests - module: function( name, testEnvironment ) { - config.currentModule = name; - config.currentModuleTestEnvironment = testEnvironment; - config.modules[name] = true; - }, - - asyncTest: function( testName, expected, callback ) { - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - QUnit.test( testName, expected, callback, true ); - }, - - test: function( testName, expected, callback, async ) { - var test, - nameHtml = "" + escapeText( testName ) + ""; - - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - if ( config.currentModule ) { - nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; - } - - test = new Test({ - nameHtml: nameHtml, - testName: testName, - expected: expected, - async: async, - callback: callback, - module: config.currentModule, - moduleTestEnvironment: config.currentModuleTestEnvironment, - stack: sourceFromStacktrace( 2 ) - }); - - if ( !validTest( test ) ) { - return; - } - - test.queue(); - }, - - // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. - expect: function( asserts ) { - if (arguments.length === 1) { - config.current.expected = asserts; - } else { - return config.current.expected; - } - }, - - start: function( count ) { - // QUnit hasn't been initialized yet. - // Note: RequireJS (et al) may delay onLoad - if ( config.semaphore === undefined ) { - QUnit.begin(function() { - // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first - setTimeout(function() { - QUnit.start( count ); - }); - }); - return; - } - - config.semaphore -= count || 1; - // don't start until equal number of stop-calls - if ( config.semaphore > 0 ) { - return; - } - // ignore if start is called more often then stop - if ( config.semaphore < 0 ) { - config.semaphore = 0; - QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); - return; - } - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - setTimeout(function() { - if ( config.semaphore > 0 ) { - return; - } - if ( config.timeout ) { - clearTimeout( config.timeout ); - } - - config.blocking = false; - process( true ); - }, 13); - } else { - config.blocking = false; - process( true ); - } - }, - - stop: function( count ) { - config.semaphore += count || 1; - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - config.semaphore = 1; - QUnit.start(); - }, config.testTimeout ); - } - } + // call on start of module test to prepend name to all tests + module: function( name, testEnvironment ) { + config.currentModule = name; + config.currentModuleTestEnvironment = testEnvironment; + config.modules[name] = true; + }, + + asyncTest: function( testName, expected, callback ) { + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + QUnit.test( testName, expected, callback, true ); + }, + + test: function( testName, expected, callback, async ) { + var test, + nameHtml = "" + escapeText( testName ) + ""; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + + if ( config.currentModule ) { + nameHtml = "" + escapeText( config.currentModule ) + ": " + nameHtml; + } + + test = new Test({ + nameHtml: nameHtml, + testName: testName, + expected: expected, + async: async, + callback: callback, + module: config.currentModule, + moduleTestEnvironment: config.currentModuleTestEnvironment, + stack: sourceFromStacktrace( 2 ) + }); + + if ( !validTest( test ) ) { + return; + } + + test.queue(); + }, + + // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through. + expect: function( asserts ) { + if (arguments.length === 1) { + config.current.expected = asserts; + } else { + return config.current.expected; + } + }, + + start: function( count ) { + // QUnit hasn't been initialized yet. + // Note: RequireJS (et al) may delay onLoad + if ( config.semaphore === undefined ) { + QUnit.begin(function() { + // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first + setTimeout(function() { + QUnit.start( count ); + }); + }); + return; + } + + config.semaphore -= count || 1; + // don't start until equal number of stop-calls + if ( config.semaphore > 0 ) { + return; + } + // ignore if start is called more often then stop + if ( config.semaphore < 0 ) { + config.semaphore = 0; + QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) ); + return; + } + // A slight delay, to avoid any current callbacks + if ( defined.setTimeout ) { + setTimeout(function() { + if ( config.semaphore > 0 ) { + return; + } + if ( config.timeout ) { + clearTimeout( config.timeout ); + } + + config.blocking = false; + process( true ); + }, 13); + } else { + config.blocking = false; + process( true ); + } + }, + + stop: function( count ) { + config.semaphore += count || 1; + config.blocking = true; + + if ( config.testTimeout && defined.setTimeout ) { + clearTimeout( config.timeout ); + config.timeout = setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + config.semaphore = 1; + QUnit.start(); + }, config.testTimeout ); + } + } }; -// `assert` initialized at top of scope -// Assert helpers -// All of these must either call QUnit.push() or manually do: -// - runLoggingCallbacks( "log", .. ); -// - config.current.assertions.push({ .. }); -// We attach it to the QUnit object *after* we expose the public API, -// otherwise `assert` will become a global variable in browsers (#341). -assert = { - /** - * Asserts rough true-ish result. - * @name ok - * @function - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function( result, msg ) { - if ( !config.current ) { - throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); - } - result = !!result; - msg = msg || (result ? "okay" : "failed" ); - - var source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: msg - }; - - msg = "" + escapeText( msg ) + ""; - - if ( !result ) { - source = sourceFromStacktrace( 2 ); - if ( source ) { - details.source = source; - msg += "
Source:
" + escapeText( source ) + "
"; - } - } - runLoggingCallbacks( "log", QUnit, details ); - config.current.assertions.push({ - result: result, - message: msg - }); - }, - - /** - * Assert that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * @name equal - * @function - * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); - */ - equal: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - QUnit.push( expected == actual, actual, expected, message ); - }, - - /** - * @name notEqual - * @function - */ - notEqual: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - QUnit.push( expected != actual, actual, expected, message ); - }, - - /** - * @name propEqual - * @function - */ - propEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name notPropEqual - * @function - */ - notPropEqual: function( actual, expected, message ) { - actual = objectValues(actual); - expected = objectValues(expected); - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name deepEqual - * @function - */ - deepEqual: function( actual, expected, message ) { - QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name notDeepEqual - * @function - */ - notDeepEqual: function( actual, expected, message ) { - QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); - }, - - /** - * @name strictEqual - * @function - */ - strictEqual: function( actual, expected, message ) { - QUnit.push( expected === actual, actual, expected, message ); - }, - - /** - * @name notStrictEqual - * @function - */ - notStrictEqual: function( actual, expected, message ) { - QUnit.push( expected !== actual, actual, expected, message ); - }, - - "throws": function( block, expected, message ) { - var actual, - expectedOutput = expected, - ok = false; - - // 'expected' is optional - if ( typeof expected === "string" ) { - message = expected; - expected = null; - } - - config.current.ignoreGlobalErrors = true; - try { - block.call( config.current.testEnvironment ); - } catch (e) { - actual = e; - } - config.current.ignoreGlobalErrors = false; - - if ( actual ) { - // we don't want to validate thrown error - if ( !expected ) { - ok = true; - expectedOutput = null; - // expected is a regexp - } else if ( QUnit.objectType( expected ) === "regexp" ) { - ok = expected.test( errorString( actual ) ); - // expected is a constructor - } else if ( actual instanceof expected ) { - ok = true; - // expected is a validation function which returns true is validation passed - } else if ( expected.call( {}, actual ) === true ) { - expectedOutput = null; - ok = true; - } - - QUnit.push( ok, actual, expectedOutput, message ); - } else { - QUnit.pushFailure( message, null, "No exception was thrown." ); - } - } -}; - -/** - * @deprecated since 1.8.0 - * Kept assertion helpers in root for backwards compatibility. - */ -extend( QUnit, assert ); - -/** - * @deprecated since 1.9.0 - * Kept root "raises()" for backwards compatibility. - * (Note that we don't introduce assert.raises). - */ -QUnit.raises = assert[ "throws" ]; - -/** - * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 - * Kept to avoid TypeErrors for undefined methods. - */ -QUnit.equals = function() { - QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); -}; -QUnit.same = function() { - QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); -}; - -// We want access to the constructor's prototype +// We use the prototype to distinguish between properties that should +// be exposed as globals (and in exports) and those that shouldn't (function() { - function F() {} - F.prototype = QUnit; - QUnit = new F(); - // Make F QUnit's constructor so that we can add to the prototype later - QUnit.constructor = F; + function F() {} + F.prototype = QUnit; + QUnit = new F(); + // Make F QUnit's constructor so that we can add to the prototype later + QUnit.constructor = F; }()); /** @@ -699,326 +222,317 @@ QUnit.same = function() { * `config` initialized at top of scope */ config = { - // The queue of tests to run - queue: [], - - // block until document ready - blocking: true, - - // when enabled, show only failing tests - // gets persisted through sessionStorage and can be changed in UI via checkbox - hidepassed: false, - - // by default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - // by default, modify document.title when suite is done - altertitle: true, - - // when enabled, all tests must call expect() - requireExpects: false, - - // add checkboxes that are persisted in the query-string - // when enabled, the id is set to `true` as a `QUnit.config` property - urlConfig: [ - { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." - }, - { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." - } - ], - - // Set of all modules. - modules: {}, - - // logging callback queues - begin: [], - done: [], - log: [], - testStart: [], - testDone: [], - moduleStart: [], - moduleDone: [] + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true, + + // when enabled, show only failing tests + // gets persisted through sessionStorage and can be changed in UI via checkbox + hidepassed: false, + + // by default, run previously failed tests first + // very useful in combination with "Hide passed tests" checked + reorder: true, + + // by default, modify document.title when suite is done + altertitle: true, + + // by default, scroll to top of the page when suite is done + scrolltop: true, + + // when enabled, all tests must call expect() + requireExpects: false, + + // add checkboxes that are persisted in the query-string + // when enabled, the id is set to `true` as a `QUnit.config` property + urlConfig: [ + { + id: "noglobals", + label: "Check for Globals", + tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." + }, + { + id: "notrycatch", + label: "No try-catch", + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." + } + ], + + // Set of all modules. + modules: {}, + + // logging callback queues + begin: [], + done: [], + log: [], + testStart: [], + testDone: [], + moduleStart: [], + moduleDone: [] }; -// Export global variables, unless an 'exports' object exists, -// in that case we assume we're in CommonJS (dealt with on the bottom of the script) -if ( typeof exports === "undefined" ) { - extend( window, QUnit.constructor.prototype ); - - // Expose QUnit object - window.QUnit = QUnit; -} - // Initialize more QUnit.config and QUnit.urlParams (function() { - var i, - location = window.location || { search: "", protocol: "file:" }, - params = location.search.slice( 1 ).split( "&" ), - length = params.length, - urlParams = {}, - current; - - if ( params[ 0 ] ) { - for ( i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - urlParams[ current[ 0 ] ] = current[ 1 ]; - } - } - - QUnit.urlParams = urlParams; - - // String search anywhere in moduleName+testName - config.filter = urlParams.filter; - - // Exact match of the module name - config.module = urlParams.module; - - config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = location.protocol === "file:"; + var i, current, + location = window.location || { search: "", protocol: "file:" }, + params = location.search.slice( 1 ).split( "&" ), + length = params.length, + urlParams = {}; + + if ( params[ 0 ] ) { + for ( i = 0; i < length; i++ ) { + current = params[ i ].split( "=" ); + current[ 0 ] = decodeURIComponent( current[ 0 ] ); + + // allow just a key to turn on a flag, e.g., test.html?noglobals + current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; + if ( urlParams[ current[ 0 ] ] ) { + urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); + } else { + urlParams[ current[ 0 ] ] = current[ 1 ]; + } + } + } + + QUnit.urlParams = urlParams; + + // String search anywhere in moduleName+testName + config.filter = urlParams.filter; + + // Exact match of the module name + config.module = urlParams.module; + + config.testNumber = []; + if ( urlParams.testNumber ) { + + // Ensure that urlParams.testNumber is an array + urlParams.testNumber = [].concat( urlParams.testNumber ); + for ( i = 0; i < urlParams.testNumber.length; i++ ) { + current = urlParams.testNumber[ i ]; + config.testNumber.push( parseInt( current, 10 ) ); + } + } + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = location.protocol === "file:"; }()); -// Extend QUnit object, -// these after set here because they should not be exposed as global functions extend( QUnit, { - assert: assert, - - config: config, - - // Initialize the configuration options - init: function() { - extend( config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: +new Date(), - updateRate: 1000, - blocking: false, - autostart: true, - autorun: false, - filter: "", - queue: [], - semaphore: 1 - }); - - var tests, banner, result, - qunit = id( "qunit" ); - - if ( qunit ) { - qunit.innerHTML = - "

" + escapeText( document.title ) + "

" + - "

" + - "
" + - "

" + - "
    "; - } - - tests = id( "qunit-tests" ); - banner = id( "qunit-banner" ); - result = id( "qunit-testresult" ); - - if ( tests ) { - tests.innerHTML = ""; - } - - if ( banner ) { - banner.className = ""; - } - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
     "; - } - }, - - // Resets the test setup. Useful for tests that modify the DOM. - /* - DEPRECATED: Use multiple tests instead of resetting inside a test. - Use testStart or testDone for custom cleanup. - This method will throw an error in 2.0, and will be removed in 2.1 - */ - reset: function() { - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - fixture.innerHTML = config.fixture; - } - }, - - // Trigger an event on an element. - // @example triggerEvent( document.body, "click" ); - triggerEvent: function( elem, type, event ) { - if ( document.createEvent ) { - event = document.createEvent( "MouseEvents" ); - event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, - 0, 0, 0, 0, 0, false, false, false, false, 0, null); - - elem.dispatchEvent( event ); - } else if ( elem.fireEvent ) { - elem.fireEvent( "on" + type ); - } - }, - - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) === type; - }, - - objectType: function( obj ) { - if ( typeof obj === "undefined" ) { - return "undefined"; - // consider: typeof null === object - } - if ( obj === null ) { - return "null"; - } - - var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), - type = match && match[1] || ""; - - switch ( type ) { - case "Number": - if ( isNaN(obj) ) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Date": - case "RegExp": - case "Function": - return type.toLowerCase(); - } - if ( typeof obj === "object" ) { - return "object"; - } - return undefined; - }, - - push: function( result, actual, expected, message ) { - if ( !config.current ) { - throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); - } - - var output, source, - details = { - module: config.current.module, - name: config.current.testName, - result: result, - message: message, - actual: actual, - expected: expected - }; - - message = escapeText( message ) || ( result ? "okay" : "failed" ); - message = "" + message + ""; - output = message; - - if ( !result ) { - expected = escapeText( QUnit.jsDump.parse(expected) ); - actual = escapeText( QUnit.jsDump.parse(actual) ); - output += ""; - - if ( actual !== expected ) { - output += ""; - output += ""; - } - - source = sourceFromStacktrace(); - - if ( source ) { - details.source = source; - output += ""; - } - - output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeText( source ) + "
    "; - } - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: !!result, - message: output - }); - }, - - pushFailure: function( message, source, actual ) { - if ( !config.current ) { - throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); - } - - var output, - details = { - module: config.current.module, - name: config.current.testName, - result: false, - message: message - }; - - message = escapeText( message ) || "error"; - message = "" + message + ""; - output = message; - - output += ""; - - if ( actual ) { - output += ""; - } - - if ( source ) { - details.source = source; - output += ""; - } - - output += "
    Result:
    " + escapeText( actual ) + "
    Source:
    " + escapeText( source ) + "
    "; - - runLoggingCallbacks( "log", QUnit, details ); - - config.current.assertions.push({ - result: false, - message: output - }); - }, - - url: function( params ) { - params = extend( extend( {}, QUnit.urlParams ), params ); - var key, - querystring = "?"; - - for ( key in params ) { - if ( hasOwn.call( params, key ) ) { - querystring += encodeURIComponent( key ) + "=" + - encodeURIComponent( params[ key ] ) + "&"; - } - } - return window.location.protocol + "//" + window.location.host + - window.location.pathname + querystring.slice( 0, -1 ); - }, - - extend: extend, - id: id, - addEvent: addEvent, - addClass: addClass, - hasClass: hasClass, - removeClass: removeClass - // load, equiv, jsDump, diff: Attached later + + config: config, + + // Initialize the configuration options + init: function() { + extend( config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date(), + updateRate: 1000, + blocking: false, + autostart: true, + autorun: false, + filter: "", + queue: [], + semaphore: 1 + }); + + var tests, banner, result, + qunit = id( "qunit" ); + + if ( qunit ) { + qunit.innerHTML = + "

    " + escapeText( document.title ) + "

    " + + "

    " + + "
    " + + "

    " + + "
      "; + } + + tests = id( "qunit-tests" ); + banner = id( "qunit-banner" ); + result = id( "qunit-testresult" ); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = "Running...
       "; + } + }, + + // Resets the test setup. Useful for tests that modify the DOM. + /* + DEPRECATED: Use multiple tests instead of resetting inside a test. + Use testStart or testDone for custom cleanup. + This method will throw an error in 2.0, and will be removed in 2.1 + */ + reset: function() { + var fixture = id( "qunit-fixture" ); + if ( fixture ) { + fixture.innerHTML = config.fixture; + } + }, + + // Safe object type checking + is: function( type, obj ) { + return QUnit.objectType( obj ) === type; + }, + + objectType: function( obj ) { + if ( typeof obj === "undefined" ) { + return "undefined"; + } + + // Consider: typeof null === object + if ( obj === null ) { + return "null"; + } + + var match = toString.call( obj ).match(/^\[object\s(.*)\]$/), + type = match && match[1] || ""; + + switch ( type ) { + case "Number": + if ( isNaN(obj) ) { + return "nan"; + } + return "number"; + case "String": + case "Boolean": + case "Array": + case "Date": + case "RegExp": + case "Function": + return type.toLowerCase(); + } + if ( typeof obj === "object" ) { + return "object"; + } + return undefined; + }, + + push: function( result, actual, expected, message ) { + if ( !config.current ) { + throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); + } + + var output, source, + details = { + module: config.current.module, + name: config.current.testName, + result: result, + message: message, + actual: actual, + expected: expected + }; + + message = escapeText( message ) || ( result ? "okay" : "failed" ); + message = "" + message + ""; + output = message; + + if ( !result ) { + expected = escapeText( QUnit.jsDump.parse(expected) ); + actual = escapeText( QUnit.jsDump.parse(actual) ); + output += ""; + + if ( actual !== expected ) { + output += ""; + output += ""; + } + + source = sourceFromStacktrace(); + + if ( source ) { + details.source = source; + output += ""; + } + + output += "
      Expected:
      " + expected + "
      Result:
      " + actual + "
      Diff:
      " + QUnit.diff( expected, actual ) + "
      Source:
      " + escapeText( source ) + "
      "; + } + + runLoggingCallbacks( "log", QUnit, details ); + + config.current.assertions.push({ + result: !!result, + message: output + }); + }, + + pushFailure: function( message, source, actual ) { + if ( !config.current ) { + throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); + } + + var output, + details = { + module: config.current.module, + name: config.current.testName, + result: false, + message: message + }; + + message = escapeText( message ) || "error"; + message = "" + message + ""; + output = message; + + output += ""; + + if ( actual ) { + output += ""; + } + + if ( source ) { + details.source = source; + output += ""; + } + + output += "
      Result:
      " + escapeText( actual ) + "
      Source:
      " + escapeText( source ) + "
      "; + + runLoggingCallbacks( "log", QUnit, details ); + + config.current.assertions.push({ + result: false, + message: output + }); + }, + + url: function( params ) { + params = extend( extend( {}, QUnit.urlParams ), params ); + var key, + querystring = "?"; + + for ( key in params ) { + if ( hasOwn.call( params, key ) ) { + querystring += encodeURIComponent( key ) + "=" + + encodeURIComponent( params[ key ] ) + "&"; + } + } + return window.location.protocol + "//" + window.location.host + + window.location.pathname + querystring.slice( 0, -1 ); + }, + + extend: extend, + id: id, + addEvent: addEvent, + addClass: addClass, + hasClass: hasClass, + removeClass: removeClass + // load, equiv, jsDump, diff: Attached later }); /** @@ -1030,190 +544,237 @@ extend( QUnit, { */ extend( QUnit.constructor.prototype, { - // Logging callbacks; all receive a single argument with the listed properties - // run test/logs.html for any related changes - begin: registerLoggingCallback( "begin" ), + // Logging callbacks; all receive a single argument with the listed properties + // run test/logs.html for any related changes + begin: registerLoggingCallback( "begin" ), - // done: { failed, passed, total, runtime } - done: registerLoggingCallback( "done" ), + // done: { failed, passed, total, runtime } + done: registerLoggingCallback( "done" ), - // log: { result, actual, expected, message } - log: registerLoggingCallback( "log" ), + // log: { result, actual, expected, message } + log: registerLoggingCallback( "log" ), - // testStart: { name } - testStart: registerLoggingCallback( "testStart" ), + // testStart: { name } + testStart: registerLoggingCallback( "testStart" ), - // testDone: { name, failed, passed, total, duration } - testDone: registerLoggingCallback( "testDone" ), + // testDone: { name, failed, passed, total, runtime } + testDone: registerLoggingCallback( "testDone" ), - // moduleStart: { name } - moduleStart: registerLoggingCallback( "moduleStart" ), + // moduleStart: { name } + moduleStart: registerLoggingCallback( "moduleStart" ), - // moduleDone: { name, failed, passed, total } - moduleDone: registerLoggingCallback( "moduleDone" ) + // moduleDone: { name, failed, passed, total } + moduleDone: registerLoggingCallback( "moduleDone" ) }); -if ( typeof document === "undefined" || document.readyState === "complete" ) { - config.autorun = true; +if ( !defined.document || document.readyState === "complete" ) { + config.autorun = true; } QUnit.load = function() { - runLoggingCallbacks( "begin", QUnit, {} ); - - // Initialize the config, saving the execution queue - var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, - urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter, - numModules = 0, - moduleNames = [], - moduleFilterHtml = "", - urlConfigHtml = "", - oldconfig = extend( {}, config ); - - QUnit.init(); - extend(config, oldconfig); - - config.blocking = false; - - len = config.urlConfig.length; - - for ( i = 0; i < len; i++ ) { - val = config.urlConfig[i]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val, - tooltip: "[no tooltip available]" - }; - } - config[ val.id ] = QUnit.urlParams[ val.id ]; - urlConfigHtml += ""; - } - for ( i in config.modules ) { - if ( config.modules.hasOwnProperty( i ) ) { - moduleNames.push(i); - } - } - numModules = moduleNames.length; - moduleNames.sort( function( a, b ) { - return a.localeCompare( b ); - }); - moduleFilterHtml += ""; - - // `userAgent` initialized at top of scope - userAgent = id( "qunit-userAgent" ); - if ( userAgent ) { - userAgent.innerHTML = navigator.userAgent; - } - - // `banner` initialized at top of scope - banner = id( "qunit-header" ); - if ( banner ) { - banner.innerHTML = "" + banner.innerHTML + " "; - } - - // `toolbar` initialized at top of scope - toolbar = id( "qunit-testrunner-toolbar" ); - if ( toolbar ) { - // `filter` initialized at top of scope - filter = document.createElement( "input" ); - filter.type = "checkbox"; - filter.id = "qunit-filter-pass"; - - addEvent( filter, "click", function() { - var tmp, - ol = document.getElementById( "qunit-tests" ); - - if ( filter.checked ) { - ol.className = ol.className + " hidepass"; - } else { - tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace( / hidepass /, " " ); - } - if ( defined.sessionStorage ) { - if (filter.checked) { - sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); - } else { - sessionStorage.removeItem( "qunit-filter-passed-tests" ); - } - } - }); - - if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { - filter.checked = true; - // `ol` initialized at top of scope - ol = document.getElementById( "qunit-tests" ); - ol.className = ol.className + " hidepass"; - } - toolbar.appendChild( filter ); - - // `label` initialized at top of scope - label = document.createElement( "label" ); - label.setAttribute( "for", "qunit-filter-pass" ); - label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); - label.innerHTML = "Hide passed tests"; - toolbar.appendChild( label ); - - urlConfigCheckboxesContainer = document.createElement("span"); - urlConfigCheckboxesContainer.innerHTML = urlConfigHtml; - urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input"); - // For oldIE support: - // * Add handlers to the individual elements instead of the container - // * Use "click" instead of "change" - // * Fallback from event.target to event.srcElement - addEvents( urlConfigCheckboxes, "click", function( event ) { - var params = {}, - target = event.target || event.srcElement; - params[ target.name ] = target.checked ? true : undefined; - window.location = QUnit.url( params ); - }); - toolbar.appendChild( urlConfigCheckboxesContainer ); - - if (numModules > 1) { - moduleFilter = document.createElement( "span" ); - moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); - moduleFilter.innerHTML = moduleFilterHtml; - addEvent( moduleFilter.lastChild, "change", function() { - var selectBox = moduleFilter.getElementsByTagName("select")[0], - selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); - - window.location = QUnit.url({ - module: ( selectedModule === "" ) ? undefined : selectedModule, - // Remove any existing filters - filter: undefined, - testNumber: undefined - }); - }); - toolbar.appendChild(moduleFilter); - } - } - - // `main` initialized at top of scope - main = id( "qunit-fixture" ); - if ( main ) { - config.fixture = main.innerHTML; - } - - if ( config.autostart ) { - QUnit.start(); - } + runLoggingCallbacks( "begin", QUnit, {} ); + + // Initialize the config, saving the execution queue + var banner, filter, i, j, label, len, main, ol, toolbar, val, selection, + urlConfigContainer, moduleFilter, userAgent, + numModules = 0, + moduleNames = [], + moduleFilterHtml = "", + urlConfigHtml = "", + oldconfig = extend( {}, config ); + + QUnit.init(); + extend(config, oldconfig); + + config.blocking = false; + + len = config.urlConfig.length; + + for ( i = 0; i < len; i++ ) { + val = config.urlConfig[i]; + if ( typeof val === "string" ) { + val = { + id: val, + label: val + }; + } + config[ val.id ] = QUnit.urlParams[ val.id ]; + if ( !val.value || typeof val.value === "string" ) { + urlConfigHtml += ""; + } else { + urlConfigHtml += ""; + } + } + for ( i in config.modules ) { + if ( config.modules.hasOwnProperty( i ) ) { + moduleNames.push(i); + } + } + numModules = moduleNames.length; + moduleNames.sort( function( a, b ) { + return a.localeCompare( b ); + }); + moduleFilterHtml += ""; + + // `userAgent` initialized at top of scope + userAgent = id( "qunit-userAgent" ); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + + // `banner` initialized at top of scope + banner = id( "qunit-header" ); + if ( banner ) { + banner.innerHTML = "" + banner.innerHTML + " "; + } + + // `toolbar` initialized at top of scope + toolbar = id( "qunit-testrunner-toolbar" ); + if ( toolbar ) { + // `filter` initialized at top of scope + filter = document.createElement( "input" ); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + + addEvent( filter, "click", function() { + var tmp, + ol = id( "qunit-tests" ); + + if ( filter.checked ) { + ol.className = ol.className + " hidepass"; + } else { + tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; + ol.className = tmp.replace( / hidepass /, " " ); + } + if ( defined.sessionStorage ) { + if (filter.checked) { + sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); + } else { + sessionStorage.removeItem( "qunit-filter-passed-tests" ); + } + } + }); + + if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { + filter.checked = true; + // `ol` initialized at top of scope + ol = id( "qunit-tests" ); + ol.className = ol.className + " hidepass"; + } + toolbar.appendChild( filter ); + + // `label` initialized at top of scope + label = document.createElement( "label" ); + label.setAttribute( "for", "qunit-filter-pass" ); + label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." ); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + + urlConfigContainer = document.createElement("span"); + urlConfigContainer.innerHTML = urlConfigHtml; + // For oldIE support: + // * Add handlers to the individual elements instead of the container + // * Use "click" instead of "change" for checkboxes + // * Fallback from event.target to event.srcElement + addEvents( urlConfigContainer.getElementsByTagName("input"), "click", function( event ) { + var params = {}, + target = event.target || event.srcElement; + params[ target.name ] = target.checked ? + target.defaultValue || true : + undefined; + window.location = QUnit.url( params ); + }); + addEvents( urlConfigContainer.getElementsByTagName("select"), "change", function( event ) { + var params = {}, + target = event.target || event.srcElement; + params[ target.name ] = target.options[ target.selectedIndex ].value || undefined; + window.location = QUnit.url( params ); + }); + toolbar.appendChild( urlConfigContainer ); + + if (numModules > 1) { + moduleFilter = document.createElement( "span" ); + moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); + moduleFilter.innerHTML = moduleFilterHtml; + addEvent( moduleFilter.lastChild, "change", function() { + var selectBox = moduleFilter.getElementsByTagName("select")[0], + selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); + + window.location = QUnit.url({ + module: ( selectedModule === "" ) ? undefined : selectedModule, + // Remove any existing filters + filter: undefined, + testNumber: undefined + }); + }); + toolbar.appendChild(moduleFilter); + } + } + + // `main` initialized at top of scope + main = id( "qunit-fixture" ); + if ( main ) { + config.fixture = main.innerHTML; + } + + if ( config.autostart ) { + QUnit.start(); + } }; -addEvent( window, "load", QUnit.load ); +if ( defined.document ) { + addEvent( window, "load", QUnit.load ); +} // `onErrorFnPrev` initialized at top of scope // Preserve other handlers @@ -1223,311 +784,313 @@ onErrorFnPrev = window.onerror; // Returning true will suppress the default browser handler, // returning false will let it run. window.onerror = function ( error, filePath, linerNr ) { - var ret = false; - if ( onErrorFnPrev ) { - ret = onErrorFnPrev( error, filePath, linerNr ); - } - - // Treat return value as window.onerror itself does, - // Only do our handling if not suppressed. - if ( ret !== true ) { - if ( QUnit.config.current ) { - if ( QUnit.config.current.ignoreGlobalErrors ) { - return true; - } - QUnit.pushFailure( error, filePath + ":" + linerNr ); - } else { - QUnit.test( "global failure", extend( function() { - QUnit.pushFailure( error, filePath + ":" + linerNr ); - }, { validTest: validTest } ) ); - } - return false; - } - - return ret; + var ret = false; + if ( onErrorFnPrev ) { + ret = onErrorFnPrev( error, filePath, linerNr ); + } + + // Treat return value as window.onerror itself does, + // Only do our handling if not suppressed. + if ( ret !== true ) { + if ( QUnit.config.current ) { + if ( QUnit.config.current.ignoreGlobalErrors ) { + return true; + } + QUnit.pushFailure( error, filePath + ":" + linerNr ); + } else { + QUnit.test( "global failure", extend( function() { + QUnit.pushFailure( error, filePath + ":" + linerNr ); + }, { validTest: validTest } ) ); + } + return false; + } + + return ret; }; function done() { - config.autorun = true; - - // Log the last module results - if ( config.currentModule ) { - runLoggingCallbacks( "moduleDone", QUnit, { - name: config.currentModule, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all - }); - } - delete config.previousModule; - - var i, key, - banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - runtime = +new Date() - config.started, - passed = config.stats.all - config.stats.bad, - html = [ - "Tests completed in ", - runtime, - " milliseconds.
      ", - "", - passed, - " assertions of ", - config.stats.all, - " passed, ", - config.stats.bad, - " failed." - ].join( "" ); - - if ( banner ) { - banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); - } - - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } - - if ( config.altertitle && typeof document !== "undefined" && document.title ) { - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = [ - ( config.stats.bad ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } - - // clear own sessionStorage items if all tests passed - if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { - // `key` & `i` initialized at top of scope - for ( i = 0; i < sessionStorage.length; i++ ) { - key = sessionStorage.key( i++ ); - if ( key.indexOf( "qunit-test-" ) === 0 ) { - sessionStorage.removeItem( key ); - } - } - } - - // scroll back to top to show results - if ( window.scrollTo ) { - window.scrollTo(0, 0); - } - - runLoggingCallbacks( "done", QUnit, { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - }); + config.autorun = true; + + // Log the last module results + if ( config.previousModule ) { + runLoggingCallbacks( "moduleDone", QUnit, { + name: config.previousModule, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all + }); + } + delete config.previousModule; + + var i, key, + banner = id( "qunit-banner" ), + tests = id( "qunit-tests" ), + runtime = +new Date() - config.started, + passed = config.stats.all - config.stats.bad, + html = [ + "Tests completed in ", + runtime, + " milliseconds.
      ", + "", + passed, + " assertions of ", + config.stats.all, + " passed, ", + config.stats.bad, + " failed." + ].join( "" ); + + if ( banner ) { + banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); + } + + if ( tests ) { + id( "qunit-testresult" ).innerHTML = html; + } + + if ( config.altertitle && defined.document && document.title ) { + // show ✖ for good, ✔ for bad suite result in title + // use escape sequences in case file gets loaded with non-utf-8-charset + document.title = [ + ( config.stats.bad ? "\u2716" : "\u2714" ), + document.title.replace( /^[\u2714\u2716] /i, "" ) + ].join( " " ); + } + + // clear own sessionStorage items if all tests passed + if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { + // `key` & `i` initialized at top of scope + for ( i = 0; i < sessionStorage.length; i++ ) { + key = sessionStorage.key( i++ ); + if ( key.indexOf( "qunit-test-" ) === 0 ) { + sessionStorage.removeItem( key ); + } + } + } + + // scroll back to top to show results + if ( config.scrolltop && window.scrollTo ) { + window.scrollTo(0, 0); + } + + runLoggingCallbacks( "done", QUnit, { + failed: config.stats.bad, + passed: passed, + total: config.stats.all, + runtime: runtime + }); } /** @return Boolean: true if this test should be ran */ function validTest( test ) { - var include, - filter = config.filter && config.filter.toLowerCase(), - module = config.module && config.module.toLowerCase(), - fullName = (test.module + ": " + test.testName).toLowerCase(); - - // Internally-generated tests are always valid - if ( test.callback && test.callback.validTest === validTest ) { - delete test.callback.validTest; - return true; - } - - if ( config.testNumber ) { - return test.testNumber === config.testNumber; - } - - if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { - return false; - } - - if ( !filter ) { - return true; - } - - include = filter.charAt( 0 ) !== "!"; - if ( !include ) { - filter = filter.slice( 1 ); - } - - // If the filter matches, we need to honour include - if ( fullName.indexOf( filter ) !== -1 ) { - return include; - } - - // Otherwise, do the opposite - return !include; + var include, + filter = config.filter && config.filter.toLowerCase(), + module = config.module && config.module.toLowerCase(), + fullName = ( test.module + ": " + test.testName ).toLowerCase(); + + // Internally-generated tests are always valid + if ( test.callback && test.callback.validTest === validTest ) { + delete test.callback.validTest; + return true; + } + + if ( config.testNumber.length > 0 ) { + if ( inArray( test.testNumber, config.testNumber ) < 0 ) { + return false; + } + } + + if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { + return false; + } + + if ( !filter ) { + return true; + } + + include = filter.charAt( 0 ) !== "!"; + if ( !include ) { + filter = filter.slice( 1 ); + } + + // If the filter matches, we need to honour include + if ( fullName.indexOf( filter ) !== -1 ) { + return include; + } + + // Otherwise, do the opposite + return !include; } // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) // Later Safari and IE10 are supposed to support error.stack as well // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack function extractStacktrace( e, offset ) { - offset = offset === undefined ? 3 : offset; - - var stack, include, i; - - if ( e.stacktrace ) { - // Opera - return e.stacktrace.split( "\n" )[ offset + 3 ]; - } else if ( e.stack ) { - // Firefox, Chrome - stack = e.stack.split( "\n" ); - if (/^error$/i.test( stack[0] ) ) { - stack.shift(); - } - if ( fileName ) { - include = []; - for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) !== -1 ) { - break; - } - include.push( stack[ i ] ); - } - if ( include.length ) { - return include.join( "\n" ); - } - } - return stack[ offset ]; - } else if ( e.sourceURL ) { - // Safari, PhantomJS - // hopefully one day Safari provides actual stacktraces - // exclude useless self-reference for generated Error objects - if ( /qunit.js$/.test( e.sourceURL ) ) { - return; - } - // for actual exceptions, this is useful - return e.sourceURL + ":" + e.line; - } + offset = offset === undefined ? 3 : offset; + + var stack, include, i; + + if ( e.stacktrace ) { + // Opera + return e.stacktrace.split( "\n" )[ offset + 3 ]; + } else if ( e.stack ) { + // Firefox, Chrome + stack = e.stack.split( "\n" ); + if (/^error$/i.test( stack[0] ) ) { + stack.shift(); + } + if ( fileName ) { + include = []; + for ( i = offset; i < stack.length; i++ ) { + if ( stack[ i ].indexOf( fileName ) !== -1 ) { + break; + } + include.push( stack[ i ] ); + } + if ( include.length ) { + return include.join( "\n" ); + } + } + return stack[ offset ]; + } else if ( e.sourceURL ) { + // Safari, PhantomJS + // hopefully one day Safari provides actual stacktraces + // exclude useless self-reference for generated Error objects + if ( /qunit.js$/.test( e.sourceURL ) ) { + return; + } + // for actual exceptions, this is useful + return e.sourceURL + ":" + e.line; + } } function sourceFromStacktrace( offset ) { - try { - throw new Error(); - } catch ( e ) { - return extractStacktrace( e, offset ); - } + try { + throw new Error(); + } catch ( e ) { + return extractStacktrace( e, offset ); + } } /** * Escape text for attribute or text content. */ function escapeText( s ) { - if ( !s ) { - return ""; - } - s = s + ""; - // Both single quotes and double quotes (for attributes) - return s.replace( /['"<>&]/g, function( s ) { - switch( s ) { - case "'": - return "'"; - case "\"": - return """; - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - } - }); + if ( !s ) { + return ""; + } + s = s + ""; + // Both single quotes and double quotes (for attributes) + return s.replace( /['"<>&]/g, function( s ) { + switch( s ) { + case "'": + return "'"; + case "\"": + return """; + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + } + }); } function synchronize( callback, last ) { - config.queue.push( callback ); + config.queue.push( callback ); - if ( config.autorun && !config.blocking ) { - process( last ); - } + if ( config.autorun && !config.blocking ) { + process( last ); + } } function process( last ) { - function next() { - process( last ); - } - var start = new Date().getTime(); - config.depth = config.depth ? config.depth + 1 : 1; - - while ( config.queue.length && !config.blocking ) { - if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { - config.queue.shift()(); - } else { - setTimeout( next, 13 ); - break; - } - } - config.depth--; - if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { - done(); - } + function next() { + process( last ); + } + var start = new Date().getTime(); + config.depth = config.depth ? config.depth + 1 : 1; + + while ( config.queue.length && !config.blocking ) { + if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { + config.queue.shift()(); + } else { + setTimeout( next, 13 ); + break; + } + } + config.depth--; + if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { + done(); + } } function saveGlobal() { - config.pollution = []; - - if ( config.noglobals ) { - for ( var key in window ) { - if ( hasOwn.call( window, key ) ) { - // in Opera sometimes DOM element ids show up here, ignore them - if ( /^qunit-test-output/.test( key ) ) { - continue; - } - config.pollution.push( key ); - } - } - } + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + if ( hasOwn.call( window, key ) ) { + // in Opera sometimes DOM element ids show up here, ignore them + if ( /^qunit-test-output/.test( key ) ) { + continue; + } + config.pollution.push( key ); + } + } + } } function checkPollution() { - var newGlobals, - deletedGlobals, - old = config.pollution; + var newGlobals, + deletedGlobals, + old = config.pollution; - saveGlobal(); + saveGlobal(); - newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); - } + newGlobals = diff( config.pollution, old ); + if ( newGlobals.length > 0 ) { + QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); + } - deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); - } + deletedGlobals = diff( old, config.pollution ); + if ( deletedGlobals.length > 0 ) { + QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); + } } // returns a new Array with the elements that are in a but not in b function diff( a, b ) { - var i, j, - result = a.slice(); - - for ( i = 0; i < result.length; i++ ) { - for ( j = 0; j < b.length; j++ ) { - if ( result[i] === b[j] ) { - result.splice( i, 1 ); - i--; - break; - } - } - } - return result; + var i, j, + result = a.slice(); + + for ( i = 0; i < result.length; i++ ) { + for ( j = 0; j < b.length; j++ ) { + if ( result[i] === b[j] ) { + result.splice( i, 1 ); + i--; + break; + } + } + } + return result; } function extend( a, b ) { - for ( var prop in b ) { - if ( hasOwn.call( b, prop ) ) { - // Avoid "Member not found" error in IE8 caused by messing with window.constructor - if ( !( prop === "constructor" && a === window ) ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - } else { - a[ prop ] = b[ prop ]; - } - } - } - } - - return a; + for ( var prop in b ) { + if ( hasOwn.call( b, prop ) ) { + // Avoid "Member not found" error in IE8 caused by messing with window.constructor + if ( !( prop === "constructor" && a === window ) ) { + if ( b[ prop ] === undefined ) { + delete a[ prop ]; + } else { + a[ prop ] = b[ prop ]; + } + } + } + } + + return a; } /** @@ -1536,13 +1099,19 @@ function extend( a, b ) { * @param {Function} fn */ function addEvent( elem, type, fn ) { - // Standards-based browsers - if ( elem.addEventListener ) { - elem.addEventListener( type, fn, false ); - // IE - } else { - elem.attachEvent( "on" + type, fn ); - } + if ( elem.addEventListener ) { + + // Standards-based browsers + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + + // support: IE <9 + elem.attachEvent( "on" + type, fn ); + } else { + + // Caller must ensure support for event listeners is present + throw new Error( "addEvent() was called in a context without event listener support" ); + } } /** @@ -1551,262 +1120,775 @@ function addEvent( elem, type, fn ) { * @param {Function} fn */ function addEvents( elems, type, fn ) { - var i = elems.length; - while ( i-- ) { - addEvent( elems[i], type, fn ); - } + var i = elems.length; + while ( i-- ) { + addEvent( elems[i], type, fn ); + } } function hasClass( elem, name ) { - return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; + return (" " + elem.className + " ").indexOf(" " + name + " ") > -1; } function addClass( elem, name ) { - if ( !hasClass( elem, name ) ) { - elem.className += (elem.className ? " " : "") + name; - } + if ( !hasClass( elem, name ) ) { + elem.className += (elem.className ? " " : "") + name; + } } function removeClass( elem, name ) { - var set = " " + elem.className + " "; - // Class name may appear multiple times - while ( set.indexOf(" " + name + " ") > -1 ) { - set = set.replace(" " + name + " " , " "); - } - // If possible, trim it for prettiness, but not necessarily - elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); + var set = " " + elem.className + " "; + // Class name may appear multiple times + while ( set.indexOf(" " + name + " ") > -1 ) { + set = set.replace(" " + name + " " , " "); + } + // If possible, trim it for prettiness, but not necessarily + elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, ""); } function id( name ) { - return !!( typeof document !== "undefined" && document && document.getElementById ) && - document.getElementById( name ); + return defined.document && document.getElementById && document.getElementById( name ); } function registerLoggingCallback( key ) { - return function( callback ) { - config[key].push( callback ); - }; + return function( callback ) { + config[key].push( callback ); + }; } // Supports deprecated method of completely overwriting logging callbacks function runLoggingCallbacks( key, scope, args ) { - var i, callbacks; - if ( QUnit.hasOwnProperty( key ) ) { - QUnit[ key ].call(scope, args ); - } else { - callbacks = config[ key ]; - for ( i = 0; i < callbacks.length; i++ ) { - callbacks[ i ].call( scope, args ); - } - } + var i, callbacks; + if ( QUnit.hasOwnProperty( key ) ) { + QUnit[ key ].call(scope, args ); + } else { + callbacks = config[ key ]; + for ( i = 0; i < callbacks.length; i++ ) { + callbacks[ i ].call( scope, args ); + } + } +} + +// from jquery.js +function inArray( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; +} + +function Test( settings ) { + extend( this, settings ); + this.assertions = []; + this.testNumber = ++Test.count; } +Test.count = 0; + +Test.prototype = { + init: function() { + var a, b, li, + tests = id( "qunit-tests" ); + + if ( tests ) { + b = document.createElement( "strong" ); + b.innerHTML = this.nameHtml; + + // `a` initialized at top of scope + a = document.createElement( "a" ); + a.innerHTML = "Rerun"; + a.href = QUnit.url({ testNumber: this.testNumber }); + + li = document.createElement( "li" ); + li.appendChild( b ); + li.appendChild( a ); + li.className = "running"; + li.id = this.id = "qunit-test-output" + testId++; + + tests.appendChild( li ); + } + }, + setup: function() { + if ( + // Emit moduleStart when we're switching from one module to another + this.module !== config.previousModule || + // They could be equal (both undefined) but if the previousModule property doesn't + // yet exist it means this is the first test in a suite that isn't wrapped in a + // module, in which case we'll just emit a moduleStart event for 'undefined'. + // Without this, reporters can get testStart before moduleStart which is a problem. + !hasOwn.call( config, "previousModule" ) + ) { + if ( hasOwn.call( config, "previousModule" ) ) { + runLoggingCallbacks( "moduleDone", QUnit, { + name: config.previousModule, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all + }); + } + config.previousModule = this.module; + config.moduleStats = { all: 0, bad: 0 }; + runLoggingCallbacks( "moduleStart", QUnit, { + name: this.module + }); + } + + config.current = this; + + this.testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, this.moduleTestEnvironment ); + + this.started = +new Date(); + runLoggingCallbacks( "testStart", QUnit, { + name: this.testName, + module: this.module + }); + + /*jshint camelcase:false */ + + + /** + * Expose the current test environment. + * + * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead. + */ + QUnit.current_testEnvironment = this.testEnvironment; + + /*jshint camelcase:true */ + + if ( !config.pollution ) { + saveGlobal(); + } + if ( config.notrycatch ) { + this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); + return; + } + try { + this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert ); + } catch( e ) { + QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); + } + }, + run: function() { + config.current = this; + + var running = id( "qunit-testresult" ); + + if ( running ) { + running.innerHTML = "Running:
      " + this.nameHtml; + } + + if ( this.async ) { + QUnit.stop(); + } + + this.callbackStarted = +new Date(); + + if ( config.notrycatch ) { + this.callback.call( this.testEnvironment, QUnit.assert ); + this.callbackRuntime = +new Date() - this.callbackStarted; + return; + } + + try { + this.callback.call( this.testEnvironment, QUnit.assert ); + this.callbackRuntime = +new Date() - this.callbackStarted; + } catch( e ) { + this.callbackRuntime = +new Date() - this.callbackStarted; + + QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + QUnit.start(); + } + } + }, + teardown: function() { + config.current = this; + if ( config.notrycatch ) { + if ( typeof this.callbackRuntime === "undefined" ) { + this.callbackRuntime = +new Date() - this.callbackStarted; + } + this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); + return; + } else { + try { + this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert ); + } catch( e ) { + QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) ); + } + } + checkPollution(); + }, + finish: function() { + config.current = this; + if ( config.requireExpects && this.expected === null ) { + QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); + } else if ( this.expected !== null && this.expected !== this.assertions.length ) { + QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); + } else if ( this.expected === null && !this.assertions.length ) { + QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); + } + + var i, assertion, a, b, time, li, ol, + test = this, + good = 0, + bad = 0, + tests = id( "qunit-tests" ); + + this.runtime = +new Date() - this.started; + config.stats.all += this.assertions.length; + config.moduleStats.all += this.assertions.length; + + if ( tests ) { + ol = document.createElement( "ol" ); + ol.className = "qunit-assert-list"; + + for ( i = 0; i < this.assertions.length; i++ ) { + assertion = this.assertions[i]; + + li = document.createElement( "li" ); + li.className = assertion.result ? "pass" : "fail"; + li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + // store result when possible + if ( QUnit.config.reorder && defined.sessionStorage ) { + if ( bad ) { + sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); + } else { + sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); + } + } + + if ( bad === 0 ) { + addClass( ol, "qunit-collapsed" ); + } + + // `b` initialized at top of scope + b = document.createElement( "strong" ); + b.innerHTML = this.nameHtml + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; + + addEvent(b, "click", function() { + var next = b.parentNode.lastChild, + collapsed = hasClass( next, "qunit-collapsed" ); + ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" ); + }); + + addEvent(b, "dblclick", function( e ) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { + target = target.parentNode; + } + if ( window.location && target.nodeName.toLowerCase() === "strong" ) { + window.location = QUnit.url({ testNumber: test.testNumber }); + } + }); + + // `time` initialized at top of scope + time = document.createElement( "span" ); + time.className = "runtime"; + time.innerHTML = this.runtime + " ms"; + + // `li` initialized at top of scope + li = id( this.id ); + li.className = bad ? "fail" : "pass"; + li.removeChild( li.firstChild ); + a = li.firstChild; + li.appendChild( b ); + li.appendChild( a ); + li.appendChild( time ); + li.appendChild( ol ); + + } else { + for ( i = 0; i < this.assertions.length; i++ ) { + if ( !this.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + runLoggingCallbacks( "testDone", QUnit, { + name: this.testName, + module: this.module, + failed: bad, + passed: this.assertions.length - bad, + total: this.assertions.length, + runtime: this.runtime, + // DEPRECATED: this property will be removed in 2.0.0, use runtime instead + duration: this.runtime + }); + + QUnit.reset(); + + config.current = undefined; + }, + + queue: function() { + var bad, + test = this; + + synchronize(function() { + test.init(); + }); + function run() { + // each of these can by async + synchronize(function() { + test.setup(); + }); + synchronize(function() { + test.run(); + }); + synchronize(function() { + test.teardown(); + }); + synchronize(function() { + test.finish(); + }); + } + + // `bad` initialized at top of scope + // defer when previous test run passed, if storage is available + bad = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); + + if ( bad ) { + run(); + } else { + synchronize( run, true ); + } + } +}; + +// `assert` initialized at top of scope +// Assert helpers +// All of these must either call QUnit.push() or manually do: +// - runLoggingCallbacks( "log", .. ); +// - config.current.assertions.push({ .. }); +assert = QUnit.assert = { + /** + * Asserts rough true-ish result. + * @name ok + * @function + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function( result, msg ) { + if ( !config.current ) { + throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); + } + result = !!result; + msg = msg || ( result ? "okay" : "failed" ); + + var source, + details = { + module: config.current.module, + name: config.current.testName, + result: result, + message: msg + }; + + msg = "" + escapeText( msg ) + ""; + + if ( !result ) { + source = sourceFromStacktrace( 2 ); + if ( source ) { + details.source = source; + msg += "
      Source:
      " +
      +					escapeText( source ) +
      +					"
      "; + } + } + runLoggingCallbacks( "log", QUnit, details ); + config.current.assertions.push({ + result: result, + message: msg + }); + }, + + /** + * Assert that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * @name equal + * @function + * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); + */ + equal: function( actual, expected, message ) { + /*jshint eqeqeq:false */ + QUnit.push( expected == actual, actual, expected, message ); + }, + + /** + * @name notEqual + * @function + */ + notEqual: function( actual, expected, message ) { + /*jshint eqeqeq:false */ + QUnit.push( expected != actual, actual, expected, message ); + }, + + /** + * @name propEqual + * @function + */ + propEqual: function( actual, expected, message ) { + actual = objectValues(actual); + expected = objectValues(expected); + QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name notPropEqual + * @function + */ + notPropEqual: function( actual, expected, message ) { + actual = objectValues(actual); + expected = objectValues(expected); + QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name deepEqual + * @function + */ + deepEqual: function( actual, expected, message ) { + QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name notDeepEqual + * @function + */ + notDeepEqual: function( actual, expected, message ) { + QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); + }, + + /** + * @name strictEqual + * @function + */ + strictEqual: function( actual, expected, message ) { + QUnit.push( expected === actual, actual, expected, message ); + }, + + /** + * @name notStrictEqual + * @function + */ + notStrictEqual: function( actual, expected, message ) { + QUnit.push( expected !== actual, actual, expected, message ); + }, + + "throws": function( block, expected, message ) { + var actual, + expectedOutput = expected, + ok = false; + + // 'expected' is optional + if ( !message && typeof expected === "string" ) { + message = expected; + expected = null; + } + + config.current.ignoreGlobalErrors = true; + try { + block.call( config.current.testEnvironment ); + } catch (e) { + actual = e; + } + config.current.ignoreGlobalErrors = false; + + if ( actual ) { + + // we don't want to validate thrown error + if ( !expected ) { + ok = true; + expectedOutput = null; + + // expected is an Error object + } else if ( expected instanceof Error ) { + ok = actual instanceof Error && + actual.name === expected.name && + actual.message === expected.message; + + // expected is a regexp + } else if ( QUnit.objectType( expected ) === "regexp" ) { + ok = expected.test( errorString( actual ) ); + + // expected is a string + } else if ( QUnit.objectType( expected ) === "string" ) { + ok = expected === errorString( actual ); + + // expected is a constructor + } else if ( actual instanceof expected ) { + ok = true; + + // expected is a validation function which returns true is validation passed + } else if ( expected.call( {}, actual ) === true ) { + expectedOutput = null; + ok = true; + } + + QUnit.push( ok, actual, expectedOutput, message ); + } else { + QUnit.pushFailure( message, null, "No exception was thrown." ); + } + } +}; + +/** + * @deprecated since 1.8.0 + * Kept assertion helpers in root for backwards compatibility. + */ +extend( QUnit.constructor.prototype, assert ); + +/** + * @deprecated since 1.9.0 + * Kept to avoid TypeErrors for undefined methods. + */ +QUnit.constructor.prototype.raises = function() { + QUnit.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" ); +}; + +/** + * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 + * Kept to avoid TypeErrors for undefined methods. + */ +QUnit.constructor.prototype.equals = function() { + QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); +}; +QUnit.constructor.prototype.same = function() { + QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); +}; + // Test for equality any JavaScript type. // Author: Philippe Rathé QUnit.equiv = (function() { - // Call the o related callback with the given arguments. - function bindCallbacks( o, callbacks, args ) { - var prop = QUnit.objectType( o ); - if ( prop ) { - if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { - return callbacks[ prop ].apply( callbacks, args ); - } else { - return callbacks[ prop ]; // or undefined - } - } - } - - // the real equiv function - var innerEquiv, - // stack to decide between skip/abort functions - callers = [], - // stack to avoiding loops from circular referencing - parents = [], - parentsB = [], - - getProto = Object.getPrototypeOf || function ( obj ) { - /*jshint camelcase:false */ - return obj.__proto__; - }, - callbacks = (function () { - - // for string, boolean, number and null - function useStrictEquality( b, a ) { - /*jshint eqeqeq:false */ - if ( b instanceof a.constructor || a instanceof b.constructor ) { - // to catch short annotation VS 'new' annotation of a - // declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } - - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - - "nan": function( b ) { - return isNaN( b ); - }, - - "date": function( b, a ) { - return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); - }, - - "regexp": function( b, a ) { - return QUnit.objectType( b ) === "regexp" && - // the regex itself - a.source === b.source && - // and its modifiers - a.global === b.global && - // (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline && - a.sticky === b.sticky; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function() { - var caller = callers[callers.length - 1]; - return caller !== Object && typeof caller !== "undefined"; - }, - - "array": function( b, a ) { - var i, j, len, loop, aCircular, bCircular; - - // b could be an object literal here - if ( QUnit.objectType( b ) !== "array" ) { - return false; - } - - len = a.length; - if ( len !== b.length ) { - // safe and faster - return false; - } - - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - for ( i = 0; i < len; i++ ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[j] === a[i]; - bCircular = parentsB[j] === b[i]; - if ( aCircular || bCircular ) { - if ( a[i] === b[i] || aCircular && bCircular ) { - loop = true; - } else { - parents.pop(); - parentsB.pop(); - return false; - } - } - } - if ( !loop && !innerEquiv(a[i], b[i]) ) { - parents.pop(); - parentsB.pop(); - return false; - } - } - parents.pop(); - parentsB.pop(); - return true; - }, - - "object": function( b, a ) { - /*jshint forin:false */ - var i, j, loop, aCircular, bCircular, - // Default to true - eq = true, - aProperties = [], - bProperties = []; - - // comparing constructors is more strict than using - // instanceof - if ( a.constructor !== b.constructor ) { - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || - ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { - return false; - } - } - - // stack constructor before traversing properties - callers.push( a.constructor ); - - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - - // be strict: don't ensure hasOwnProperty and go deep - for ( i in a ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[j] === a[i]; - bCircular = parentsB[j] === b[i]; - if ( aCircular || bCircular ) { - if ( a[i] === b[i] || aCircular && bCircular ) { - loop = true; - } else { - eq = false; - break; - } - } - } - aProperties.push(i); - if ( !loop && !innerEquiv(a[i], b[i]) ) { - eq = false; - break; - } - } - - parents.pop(); - parentsB.pop(); - callers.pop(); // unstack, we are done - - for ( i in b ) { - bProperties.push( i ); // collect b's properties - } - - // Ensures identical properties name - return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); - } - }; - }()); - - innerEquiv = function() { // can take multiple arguments - var args = [].slice.apply( arguments ); - if ( args.length < 2 ) { - return true; // end transition - } - - return (function( a, b ) { - if ( a === b ) { - return true; // catch the most you can - } else if ( a === null || b === null || typeof a === "undefined" || - typeof b === "undefined" || - QUnit.objectType(a) !== QUnit.objectType(b) ) { - return false; // don't lose time with error prone cases - } else { - return bindCallbacks(a, callbacks, [ b, a ]); - } - - // apply transition with (1..n) arguments - }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); - }; - - return innerEquiv; + // Call the o related callback with the given arguments. + function bindCallbacks( o, callbacks, args ) { + var prop = QUnit.objectType( o ); + if ( prop ) { + if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { + return callbacks[ prop ].apply( callbacks, args ); + } else { + return callbacks[ prop ]; // or undefined + } + } + } + + // the real equiv function + var innerEquiv, + // stack to decide between skip/abort functions + callers = [], + // stack to avoiding loops from circular referencing + parents = [], + parentsB = [], + + getProto = Object.getPrototypeOf || function ( obj ) { + /*jshint camelcase:false */ + return obj.__proto__; + }, + callbacks = (function () { + + // for string, boolean, number and null + function useStrictEquality( b, a ) { + /*jshint eqeqeq:false */ + if ( b instanceof a.constructor || a instanceof b.constructor ) { + // to catch short annotation VS 'new' annotation of a + // declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function( b ) { + return isNaN( b ); + }, + + "date": function( b, a ) { + return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function( b, a ) { + return QUnit.objectType( b ) === "regexp" && + // the regex itself + a.source === b.source && + // and its modifiers + a.global === b.global && + // (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline && + a.sticky === b.sticky; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function() { + var caller = callers[callers.length - 1]; + return caller !== Object && typeof caller !== "undefined"; + }, + + "array": function( b, a ) { + var i, j, len, loop, aCircular, bCircular; + + // b could be an object literal here + if ( QUnit.objectType( b ) !== "array" ) { + return false; + } + + len = a.length; + if ( len !== b.length ) { + // safe and faster + return false; + } + + // track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + for ( i = 0; i < len; i++ ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[j] === a[i]; + bCircular = parentsB[j] === b[i]; + if ( aCircular || bCircular ) { + if ( a[i] === b[i] || aCircular && bCircular ) { + loop = true; + } else { + parents.pop(); + parentsB.pop(); + return false; + } + } + } + if ( !loop && !innerEquiv(a[i], b[i]) ) { + parents.pop(); + parentsB.pop(); + return false; + } + } + parents.pop(); + parentsB.pop(); + return true; + }, + + "object": function( b, a ) { + /*jshint forin:false */ + var i, j, loop, aCircular, bCircular, + // Default to true + eq = true, + aProperties = [], + bProperties = []; + + // comparing constructors is more strict than using + // instanceof + if ( a.constructor !== b.constructor ) { + // Allow objects with no prototype to be equivalent to + // objects with Object as their constructor. + if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || + ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { + return false; + } + } + + // stack constructor before traversing properties + callers.push( a.constructor ); + + // track reference to avoid circular references + parents.push( a ); + parentsB.push( b ); + + // be strict: don't ensure hasOwnProperty and go deep + for ( i in a ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + aCircular = parents[j] === a[i]; + bCircular = parentsB[j] === b[i]; + if ( aCircular || bCircular ) { + if ( a[i] === b[i] || aCircular && bCircular ) { + loop = true; + } else { + eq = false; + break; + } + } + } + aProperties.push(i); + if ( !loop && !innerEquiv(a[i], b[i]) ) { + eq = false; + break; + } + } + + parents.pop(); + parentsB.pop(); + callers.pop(); // unstack, we are done + + for ( i in b ) { + bProperties.push( i ); // collect b's properties + } + + // Ensures identical properties name + return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); + } + }; + }()); + + innerEquiv = function() { // can take multiple arguments + var args = [].slice.apply( arguments ); + if ( args.length < 2 ) { + return true; // end transition + } + + return (function( a, b ) { + if ( a === b ) { + return true; // catch the most you can + } else if ( a === null || b === null || typeof a === "undefined" || + typeof b === "undefined" || + QUnit.objectType(a) !== QUnit.objectType(b) ) { + return false; // don't lose time with error prone cases + } else { + return bindCallbacks(a, callbacks, [ b, a ]); + } + + // apply transition with (1..n) arguments + }( args[0], args[1] ) && innerEquiv.apply( this, args.splice(1, args.length - 1 )) ); + }; + + return innerEquiv; }()); /** @@ -1820,241 +1902,226 @@ QUnit.equiv = (function() { * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} */ QUnit.jsDump = (function() { - function quote( str ) { - return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; - } - function literal( o ) { - return o + ""; - } - function join( pre, arr, post ) { - var s = jsDump.separator(), - base = jsDump.indent(), - inner = jsDump.indent(1); - if ( arr.join ) { - arr = arr.join( "," + s + inner ); - } - if ( !arr ) { - return pre + post; - } - return [ pre, inner + arr, base + post ].join(s); - } - function array( arr, stack ) { - var i = arr.length, ret = new Array(i); - this.up(); - while ( i-- ) { - ret[i] = this.parse( arr[i] , undefined , stack); - } - this.down(); - return join( "[", ret, "]" ); - } - - var reName = /^function (\w+)/, - jsDump = { - // type is used mostly internally, you can fix a (custom)type in advance - parse: function( obj, type, stack ) { - stack = stack || [ ]; - var inStack, res, - parser = this.parsers[ type || this.typeOf(obj) ]; - - type = typeof parser; - inStack = inArray( obj, stack ); - - if ( inStack !== -1 ) { - return "recursion(" + (inStack - stack.length) + ")"; - } - if ( type === "function" ) { - stack.push( obj ); - res = parser.call( this, obj, stack ); - stack.pop(); - return res; - } - return ( type === "string" ) ? parser : this.parsers.error; - }, - typeOf: function( obj ) { - var type; - if ( obj === null ) { - type = "null"; - } else if ( typeof obj === "undefined" ) { - type = "undefined"; - } else if ( QUnit.is( "regexp", obj) ) { - type = "regexp"; - } else if ( QUnit.is( "date", obj) ) { - type = "date"; - } else if ( QUnit.is( "function", obj) ) { - type = "function"; - } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { - type = "window"; - } else if ( obj.nodeType === 9 ) { - type = "document"; - } else if ( obj.nodeType ) { - type = "node"; - } else if ( - // native arrays - toString.call( obj ) === "[object Array]" || - // NodeList objects - ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) - ) { - type = "array"; - } else if ( obj.constructor === Error.prototype.constructor ) { - type = "error"; - } else { - type = typeof obj; - } - return type; - }, - separator: function() { - return this.multiline ? this.HTML ? "
      " : "\n" : this.HTML ? " " : " "; - }, - // extra can be a number, shortcut for increasing-calling-decreasing - indent: function( extra ) { - if ( !this.multiline ) { - return ""; - } - var chr = this.indentChar; - if ( this.HTML ) { - chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); - } - return new Array( this.depth + ( extra || 0 ) ).join(chr); - }, - up: function( a ) { - this.depth += a || 1; - }, - down: function( a ) { - this.depth -= a || 1; - }, - setParser: function( name, parser ) { - this.parsers[name] = parser; - }, - // The next 3 are exposed so you can use them - quote: quote, - literal: literal, - join: join, - // - depth: 1, - // This is the list of parsers, to modify them, use jsDump.setParser - parsers: { - window: "[Window]", - document: "[Document]", - error: function(error) { - return "Error(\"" + error.message + "\")"; - }, - unknown: "[Unknown]", - "null": "null", - "undefined": "undefined", - "function": function( fn ) { - var ret = "function", - // functions never have name in IE - name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; - - if ( name ) { - ret += " " + name; - } - ret += "( "; - - ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); - return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); - }, - array: array, - nodelist: array, - "arguments": array, - object: function( map, stack ) { - /*jshint forin:false */ - var ret = [ ], keys, key, val, i; - QUnit.jsDump.up(); - keys = []; - for ( key in map ) { - keys.push( key ); - } - keys.sort(); - for ( i = 0; i < keys.length; i++ ) { - key = keys[ i ]; - val = map[ key ]; - ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); - } - QUnit.jsDump.down(); - return join( "{", ret, "}" ); - }, - node: function( node ) { - var len, i, val, - open = QUnit.jsDump.HTML ? "<" : "<", - close = QUnit.jsDump.HTML ? ">" : ">", - tag = node.nodeName.toLowerCase(), - ret = open + tag, - attrs = node.attributes; - - if ( attrs ) { - for ( i = 0, len = attrs.length; i < len; i++ ) { - val = attrs[i].nodeValue; - // IE6 includes all attributes in .attributes, even ones not explicitly set. - // Those have values like undefined, null, 0, false, "" or "inherit". - if ( val && val !== "inherit" ) { - ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); - } - } - } - ret += close; - - // Show content of TextNode or CDATASection - if ( node.nodeType === 3 || node.nodeType === 4 ) { - ret += node.nodeValue; - } - - return ret + open + "/" + tag + close; - }, - // function calls it internally, it's the arguments part of the function - functionArgs: function( fn ) { - var args, - l = fn.length; - - if ( !l ) { - return ""; - } - - args = new Array(l); - while ( l-- ) { - // 97 is 'a' - args[l] = String.fromCharCode(97+l); - } - return " " + args.join( ", " ) + " "; - }, - // object calls it internally, the key part of an item in a map - key: quote, - // function calls it internally, it's the content of the function - functionCode: "[code]", - // node calls it internally, it's an html attribute value - attribute: quote, - string: quote, - date: quote, - regexp: literal, - number: literal, - "boolean": literal - }, - // if true, entities are escaped ( <, >, \t, space and \n ) - HTML: false, - // indentation unit - indentChar: " ", - // if true, items in a collection, are separated by a \n, else just a space. - multiline: true - }; - - return jsDump; + function quote( str ) { + return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; + } + function literal( o ) { + return o + ""; + } + function join( pre, arr, post ) { + var s = jsDump.separator(), + base = jsDump.indent(), + inner = jsDump.indent(1); + if ( arr.join ) { + arr = arr.join( "," + s + inner ); + } + if ( !arr ) { + return pre + post; + } + return [ pre, inner + arr, base + post ].join(s); + } + function array( arr, stack ) { + var i = arr.length, ret = new Array(i); + this.up(); + while ( i-- ) { + ret[i] = this.parse( arr[i] , undefined , stack); + } + this.down(); + return join( "[", ret, "]" ); + } + + var reName = /^function (\w+)/, + jsDump = { + // type is used mostly internally, you can fix a (custom)type in advance + parse: function( obj, type, stack ) { + stack = stack || [ ]; + var inStack, res, + parser = this.parsers[ type || this.typeOf(obj) ]; + + type = typeof parser; + inStack = inArray( obj, stack ); + + if ( inStack !== -1 ) { + return "recursion(" + (inStack - stack.length) + ")"; + } + if ( type === "function" ) { + stack.push( obj ); + res = parser.call( this, obj, stack ); + stack.pop(); + return res; + } + return ( type === "string" ) ? parser : this.parsers.error; + }, + typeOf: function( obj ) { + var type; + if ( obj === null ) { + type = "null"; + } else if ( typeof obj === "undefined" ) { + type = "undefined"; + } else if ( QUnit.is( "regexp", obj) ) { + type = "regexp"; + } else if ( QUnit.is( "date", obj) ) { + type = "date"; + } else if ( QUnit.is( "function", obj) ) { + type = "function"; + } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { + type = "window"; + } else if ( obj.nodeType === 9 ) { + type = "document"; + } else if ( obj.nodeType ) { + type = "node"; + } else if ( + // native arrays + toString.call( obj ) === "[object Array]" || + // NodeList objects + ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) + ) { + type = "array"; + } else if ( obj.constructor === Error.prototype.constructor ) { + type = "error"; + } else { + type = typeof obj; + } + return type; + }, + separator: function() { + return this.multiline ? this.HTML ? "
      " : "\n" : this.HTML ? " " : " "; + }, + // extra can be a number, shortcut for increasing-calling-decreasing + indent: function( extra ) { + if ( !this.multiline ) { + return ""; + } + var chr = this.indentChar; + if ( this.HTML ) { + chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); + } + return new Array( this.depth + ( extra || 0 ) ).join(chr); + }, + up: function( a ) { + this.depth += a || 1; + }, + down: function( a ) { + this.depth -= a || 1; + }, + setParser: function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote: quote, + literal: literal, + join: join, + // + depth: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers: { + window: "[Window]", + document: "[Document]", + error: function(error) { + return "Error(\"" + error.message + "\")"; + }, + unknown: "[Unknown]", + "null": "null", + "undefined": "undefined", + "function": function( fn ) { + var ret = "function", + // functions never have name in IE + name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1]; + + if ( name ) { + ret += " " + name; + } + ret += "( "; + + ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); + return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); + }, + array: array, + nodelist: array, + "arguments": array, + object: function( map, stack ) { + /*jshint forin:false */ + var ret = [ ], keys, key, val, i; + QUnit.jsDump.up(); + keys = []; + for ( key in map ) { + keys.push( key ); + } + keys.sort(); + for ( i = 0; i < keys.length; i++ ) { + key = keys[ i ]; + val = map[ key ]; + ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); + } + QUnit.jsDump.down(); + return join( "{", ret, "}" ); + }, + node: function( node ) { + var len, i, val, + open = QUnit.jsDump.HTML ? "<" : "<", + close = QUnit.jsDump.HTML ? ">" : ">", + tag = node.nodeName.toLowerCase(), + ret = open + tag, + attrs = node.attributes; + + if ( attrs ) { + for ( i = 0, len = attrs.length; i < len; i++ ) { + val = attrs[i].nodeValue; + // IE6 includes all attributes in .attributes, even ones not explicitly set. + // Those have values like undefined, null, 0, false, "" or "inherit". + if ( val && val !== "inherit" ) { + ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" ); + } + } + } + ret += close; + + // Show content of TextNode or CDATASection + if ( node.nodeType === 3 || node.nodeType === 4 ) { + ret += node.nodeValue; + } + + return ret + open + "/" + tag + close; + }, + // function calls it internally, it's the arguments part of the function + functionArgs: function( fn ) { + var args, + l = fn.length; + + if ( !l ) { + return ""; + } + + args = new Array(l); + while ( l-- ) { + // 97 is 'a' + args[l] = String.fromCharCode(97+l); + } + return " " + args.join( ", " ) + " "; + }, + // object calls it internally, the key part of an item in a map + key: quote, + // function calls it internally, it's the content of the function + functionCode: "[code]", + // node calls it internally, it's an html attribute value + attribute: quote, + string: quote, + date: quote, + regexp: literal, + number: literal, + "boolean": literal + }, + // if true, entities are escaped ( <, >, \t, space and \n ) + HTML: false, + // indentation unit + indentChar: " ", + // if true, items in a collection, are separated by a \n, else just a space. + multiline: true + }; + + return jsDump; }()); -// from jquery.js -function inArray( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; -} - /* * Javascript Diff Algorithm * By John Resig (http://ejohn.org/) @@ -2070,143 +2137,152 @@ function inArray( elem, array ) { * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" */ QUnit.diff = (function() { - /*jshint eqeqeq:false, eqnull:true */ - function diff( o, n ) { - var i, - ns = {}, - os = {}; - - for ( i = 0; i < n.length; i++ ) { - if ( !hasOwn.call( ns, n[i] ) ) { - ns[ n[i] ] = { - rows: [], - o: null - }; - } - ns[ n[i] ].rows.push( i ); - } - - for ( i = 0; i < o.length; i++ ) { - if ( !hasOwn.call( os, o[i] ) ) { - os[ o[i] ] = { - rows: [], - n: null - }; - } - os[ o[i] ].rows.push( i ); - } - - for ( i in ns ) { - if ( hasOwn.call( ns, i ) ) { - if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { - n[ ns[i].rows[0] ] = { - text: n[ ns[i].rows[0] ], - row: os[i].rows[0] - }; - o[ os[i].rows[0] ] = { - text: o[ os[i].rows[0] ], - row: ns[i].rows[0] - }; - } - } - } - - for ( i = 0; i < n.length - 1; i++ ) { - if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && - n[ i + 1 ] == o[ n[i].row + 1 ] ) { - - n[ i + 1 ] = { - text: n[ i + 1 ], - row: n[i].row + 1 - }; - o[ n[i].row + 1 ] = { - text: o[ n[i].row + 1 ], - row: i + 1 - }; - } - } - - for ( i = n.length - 1; i > 0; i-- ) { - if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && - n[ i - 1 ] == o[ n[i].row - 1 ]) { - - n[ i - 1 ] = { - text: n[ i - 1 ], - row: n[i].row - 1 - }; - o[ n[i].row - 1 ] = { - text: o[ n[i].row - 1 ], - row: i - 1 - }; - } - } - - return { - o: o, - n: n - }; - } - - return function( o, n ) { - o = o.replace( /\s+$/, "" ); - n = n.replace( /\s+$/, "" ); - - var i, pre, - str = "", - out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), - oSpace = o.match(/\s+/g), - nSpace = n.match(/\s+/g); - - if ( oSpace == null ) { - oSpace = [ " " ]; - } - else { - oSpace.push( " " ); - } - - if ( nSpace == null ) { - nSpace = [ " " ]; - } - else { - nSpace.push( " " ); - } - - if ( out.n.length === 0 ) { - for ( i = 0; i < out.o.length; i++ ) { - str += "" + out.o[i] + oSpace[i] + ""; - } - } - else { - if ( out.n[0].text == null ) { - for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { - str += "" + out.o[n] + oSpace[n] + ""; - } - } - - for ( i = 0; i < out.n.length; i++ ) { - if (out.n[i].text == null) { - str += "" + out.n[i] + nSpace[i] + ""; - } - else { - // `pre` initialized at top of scope - pre = ""; - - for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { - pre += "" + out.o[n] + oSpace[n] + ""; - } - str += " " + out.n[i].text + nSpace[i] + pre; - } - } - } - - return str; - }; + /*jshint eqeqeq:false, eqnull:true */ + function diff( o, n ) { + var i, + ns = {}, + os = {}; + + for ( i = 0; i < n.length; i++ ) { + if ( !hasOwn.call( ns, n[i] ) ) { + ns[ n[i] ] = { + rows: [], + o: null + }; + } + ns[ n[i] ].rows.push( i ); + } + + for ( i = 0; i < o.length; i++ ) { + if ( !hasOwn.call( os, o[i] ) ) { + os[ o[i] ] = { + rows: [], + n: null + }; + } + os[ o[i] ].rows.push( i ); + } + + for ( i in ns ) { + if ( hasOwn.call( ns, i ) ) { + if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) { + n[ ns[i].rows[0] ] = { + text: n[ ns[i].rows[0] ], + row: os[i].rows[0] + }; + o[ os[i].rows[0] ] = { + text: o[ os[i].rows[0] ], + row: ns[i].rows[0] + }; + } + } + } + + for ( i = 0; i < n.length - 1; i++ ) { + if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && + n[ i + 1 ] == o[ n[i].row + 1 ] ) { + + n[ i + 1 ] = { + text: n[ i + 1 ], + row: n[i].row + 1 + }; + o[ n[i].row + 1 ] = { + text: o[ n[i].row + 1 ], + row: i + 1 + }; + } + } + + for ( i = n.length - 1; i > 0; i-- ) { + if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && + n[ i - 1 ] == o[ n[i].row - 1 ]) { + + n[ i - 1 ] = { + text: n[ i - 1 ], + row: n[i].row - 1 + }; + o[ n[i].row - 1 ] = { + text: o[ n[i].row - 1 ], + row: i - 1 + }; + } + } + + return { + o: o, + n: n + }; + } + + return function( o, n ) { + o = o.replace( /\s+$/, "" ); + n = n.replace( /\s+$/, "" ); + + var i, pre, + str = "", + out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), + oSpace = o.match(/\s+/g), + nSpace = n.match(/\s+/g); + + if ( oSpace == null ) { + oSpace = [ " " ]; + } + else { + oSpace.push( " " ); + } + + if ( nSpace == null ) { + nSpace = [ " " ]; + } + else { + nSpace.push( " " ); + } + + if ( out.n.length === 0 ) { + for ( i = 0; i < out.o.length; i++ ) { + str += "" + out.o[i] + oSpace[i] + ""; + } + } + else { + if ( out.n[0].text == null ) { + for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { + str += "" + out.o[n] + oSpace[n] + ""; + } + } + + for ( i = 0; i < out.n.length; i++ ) { + if (out.n[i].text == null) { + str += "" + out.n[i] + nSpace[i] + ""; + } + else { + // `pre` initialized at top of scope + pre = ""; + + for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { + pre += "" + out.o[n] + oSpace[n] + ""; + } + str += " " + out.n[i].text + nSpace[i] + pre; + } + } + } + + return str; + }; }()); -// for CommonJS environments, export everything -if ( typeof exports !== "undefined" ) { - extend( exports, QUnit.constructor.prototype ); +// For browser, export only select globals +if ( typeof window !== "undefined" ) { + extend( window, QUnit.constructor.prototype ); + window.QUnit = QUnit; } -// get at whatever the global object is, like window in browsers -}( (function() {return this;}.call()) )); +// For CommonJS environments, export everything +if ( typeof module !== "undefined" && module.exports ) { + module.exports = QUnit; +} + + +// Get a reference to the global object, like window in browsers +}( (function() { + return this; +})() )); diff --git a/test/libs/require.js b/test/libs/require.js new file mode 100644 index 0000000..24b061e --- /dev/null +++ b/test/libs/require.js @@ -0,0 +1,2068 @@ +/** vim: et:ts=4:sw=4:sts=4 + * @license RequireJS 2.1.11 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/jrburke/requirejs for details + */ +//Not using strict: uneven strict support in browsers, #392, and causes +//problems with requirejs.exec()/transpiler plugins that may not be strict. +/*jslint regexp: true, nomen: true, sloppy: true */ +/*global window, navigator, document, importScripts, setTimeout, opera */ + +var requirejs, require, define; +(function (global) { + var req, s, head, baseElement, dataMain, src, + interactiveScript, currentlyAddingScript, mainScript, subPath, + version = '2.1.11', + commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg, + cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g, + jsSuffixRegExp = /\.js$/, + currDirRegExp = /^\.\//, + op = Object.prototype, + ostring = op.toString, + hasOwn = op.hasOwnProperty, + ap = Array.prototype, + apsp = ap.splice, + isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document), + isWebWorker = !isBrowser && typeof importScripts !== 'undefined', + //PS3 indicates loaded and complete, but need to wait for complete + //specifically. Sequence is 'loading', 'loaded', execution, + // then 'complete'. The UA check is unfortunate, but not sure how + //to feature test w/o causing perf issues. + readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ? + /^complete$/ : /^(complete|loaded)$/, + defContextName = '_', + //Oh the tragedy, detecting opera. See the usage of isOpera for reason. + isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]', + contexts = {}, + cfg = {}, + globalDefQueue = [], + useInteractive = false; + + function isFunction(it) { + return ostring.call(it) === '[object Function]'; + } + + function isArray(it) { + return ostring.call(it) === '[object Array]'; + } + + /** + * Helper function for iterating over an array. If the func returns + * a true value, it will break out of the loop. + */ + function each(ary, func) { + if (ary) { + var i; + for (i = 0; i < ary.length; i += 1) { + if (ary[i] && func(ary[i], i, ary)) { + break; + } + } + } + } + + /** + * Helper function for iterating over an array backwards. If the func + * returns a true value, it will break out of the loop. + */ + function eachReverse(ary, func) { + if (ary) { + var i; + for (i = ary.length - 1; i > -1; i -= 1) { + if (ary[i] && func(ary[i], i, ary)) { + break; + } + } + } + } + + function hasProp(obj, prop) { + return hasOwn.call(obj, prop); + } + + function getOwn(obj, prop) { + return hasProp(obj, prop) && obj[prop]; + } + + /** + * Cycles over properties in an object and calls a function for each + * property value. If the function returns a truthy value, then the + * iteration is stopped. + */ + function eachProp(obj, func) { + var prop; + for (prop in obj) { + if (hasProp(obj, prop)) { + if (func(obj[prop], prop)) { + break; + } + } + } + } + + /** + * Simple function to mix in properties from source into target, + * but only if target does not already have a property of the same name. + */ + function mixin(target, source, force, deepStringMixin) { + if (source) { + eachProp(source, function (value, prop) { + if (force || !hasProp(target, prop)) { + if (deepStringMixin && typeof value === 'object' && value && + !isArray(value) && !isFunction(value) && + !(value instanceof RegExp)) { + + if (!target[prop]) { + target[prop] = {}; + } + mixin(target[prop], value, force, deepStringMixin); + } else { + target[prop] = value; + } + } + }); + } + return target; + } + + //Similar to Function.prototype.bind, but the 'this' object is specified + //first, since it is easier to read/figure out what 'this' will be. + function bind(obj, fn) { + return function () { + return fn.apply(obj, arguments); + }; + } + + function scripts() { + return document.getElementsByTagName('script'); + } + + function defaultOnError(err) { + throw err; + } + + //Allow getting a global that is expressed in + //dot notation, like 'a.b.c'. + function getGlobal(value) { + if (!value) { + return value; + } + var g = global; + each(value.split('.'), function (part) { + g = g[part]; + }); + return g; + } + + /** + * Constructs an error with a pointer to an URL with more information. + * @param {String} id the error ID that maps to an ID on a web page. + * @param {String} message human readable error. + * @param {Error} [err] the original error, if there is one. + * + * @returns {Error} + */ + function makeError(id, msg, err, requireModules) { + var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id); + e.requireType = id; + e.requireModules = requireModules; + if (err) { + e.originalError = err; + } + return e; + } + + if (typeof define !== 'undefined') { + //If a define is already in play via another AMD loader, + //do not overwrite. + return; + } + + if (typeof requirejs !== 'undefined') { + if (isFunction(requirejs)) { + //Do not overwrite and existing requirejs instance. + return; + } + cfg = requirejs; + requirejs = undefined; + } + + //Allow for a require config object + if (typeof require !== 'undefined' && !isFunction(require)) { + //assume it is a config object. + cfg = require; + require = undefined; + } + + function newContext(contextName) { + var inCheckLoaded, Module, context, handlers, + checkLoadedTimeoutId, + config = { + //Defaults. Do not set a default for map + //config to speed up normalize(), which + //will run faster if there is no default. + waitSeconds: 7, + baseUrl: './', + paths: {}, + bundles: {}, + pkgs: {}, + shim: {}, + config: {} + }, + registry = {}, + //registry of just enabled modules, to speed + //cycle breaking code when lots of modules + //are registered, but not activated. + enabledRegistry = {}, + undefEvents = {}, + defQueue = [], + defined = {}, + urlFetched = {}, + bundlesMap = {}, + requireCounter = 1, + unnormalizedCounter = 1; + + /** + * Trims the . and .. from an array of path segments. + * It will keep a leading path segment if a .. will become + * the first path segment, to help with module name lookups, + * which act like paths, but can be remapped. But the end result, + * all paths that use this function should look normalized. + * NOTE: this method MODIFIES the input array. + * @param {Array} ary the array of path segments. + */ + function trimDots(ary) { + var i, part, length = ary.length; + for (i = 0; i < length; i++) { + part = ary[i]; + if (part === '.') { + ary.splice(i, 1); + i -= 1; + } else if (part === '..') { + if (i === 1 && (ary[2] === '..' || ary[0] === '..')) { + //End of the line. Keep at least one non-dot + //path segment at the front so it can be mapped + //correctly to disk. Otherwise, there is likely + //no path mapping for a path starting with '..'. + //This can still fail, but catches the most reasonable + //uses of .. + break; + } else if (i > 0) { + ary.splice(i - 1, 2); + i -= 2; + } + } + } + } + + /** + * Given a relative module name, like ./something, normalize it to + * a real name that can be mapped to a path. + * @param {String} name the relative name + * @param {String} baseName a real name that the name arg is relative + * to. + * @param {Boolean} applyMap apply the map config to the value. Should + * only be done if this normalization is for a dependency ID. + * @returns {String} normalized name + */ + function normalize(name, baseName, applyMap) { + var pkgMain, mapValue, nameParts, i, j, nameSegment, lastIndex, + foundMap, foundI, foundStarMap, starI, + baseParts = baseName && baseName.split('/'), + normalizedBaseParts = baseParts, + map = config.map, + starMap = map && map['*']; + + //Adjust any relative paths. + if (name && name.charAt(0) === '.') { + //If have a base name, try to normalize against it, + //otherwise, assume it is a top-level require that will + //be relative to baseUrl in the end. + if (baseName) { + //Convert baseName to array, and lop off the last part, + //so that . matches that 'directory' and not name of the baseName's + //module. For instance, baseName of 'one/two/three', maps to + //'one/two/three.js', but we want the directory, 'one/two' for + //this normalization. + normalizedBaseParts = baseParts.slice(0, baseParts.length - 1); + name = name.split('/'); + lastIndex = name.length - 1; + + // If wanting node ID compatibility, strip .js from end + // of IDs. Have to do this here, and not in nameToUrl + // because node allows either .js or non .js to map + // to same file. + if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) { + name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, ''); + } + + name = normalizedBaseParts.concat(name); + trimDots(name); + name = name.join('/'); + } else if (name.indexOf('./') === 0) { + // No baseName, so this is ID is resolved relative + // to baseUrl, pull off the leading dot. + name = name.substring(2); + } + } + + //Apply map config if available. + if (applyMap && map && (baseParts || starMap)) { + nameParts = name.split('/'); + + outerLoop: for (i = nameParts.length; i > 0; i -= 1) { + nameSegment = nameParts.slice(0, i).join('/'); + + if (baseParts) { + //Find the longest baseName segment match in the config. + //So, do joins on the biggest to smallest lengths of baseParts. + for (j = baseParts.length; j > 0; j -= 1) { + mapValue = getOwn(map, baseParts.slice(0, j).join('/')); + + //baseName segment has config, find if it has one for + //this name. + if (mapValue) { + mapValue = getOwn(mapValue, nameSegment); + if (mapValue) { + //Match, update name to the new value. + foundMap = mapValue; + foundI = i; + break outerLoop; + } + } + } + } + + //Check for a star map match, but just hold on to it, + //if there is a shorter segment match later in a matching + //config, then favor over this star map. + if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) { + foundStarMap = getOwn(starMap, nameSegment); + starI = i; + } + } + + if (!foundMap && foundStarMap) { + foundMap = foundStarMap; + foundI = starI; + } + + if (foundMap) { + nameParts.splice(0, foundI, foundMap); + name = nameParts.join('/'); + } + } + + // If the name points to a package's name, use + // the package main instead. + pkgMain = getOwn(config.pkgs, name); + + return pkgMain ? pkgMain : name; + } + + function removeScript(name) { + if (isBrowser) { + each(scripts(), function (scriptNode) { + if (scriptNode.getAttribute('data-requiremodule') === name && + scriptNode.getAttribute('data-requirecontext') === context.contextName) { + scriptNode.parentNode.removeChild(scriptNode); + return true; + } + }); + } + } + + function hasPathFallback(id) { + var pathConfig = getOwn(config.paths, id); + if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) { + //Pop off the first array value, since it failed, and + //retry + pathConfig.shift(); + context.require.undef(id); + context.require([id]); + return true; + } + } + + //Turns a plugin!resource to [plugin, resource] + //with the plugin being undefined if the name + //did not have a plugin prefix. + function splitPrefix(name) { + var prefix, + index = name ? name.indexOf('!') : -1; + if (index > -1) { + prefix = name.substring(0, index); + name = name.substring(index + 1, name.length); + } + return [prefix, name]; + } + + /** + * Creates a module mapping that includes plugin prefix, module + * name, and path. If parentModuleMap is provided it will + * also normalize the name via require.normalize() + * + * @param {String} name the module name + * @param {String} [parentModuleMap] parent module map + * for the module name, used to resolve relative names. + * @param {Boolean} isNormalized: is the ID already normalized. + * This is true if this call is done for a define() module ID. + * @param {Boolean} applyMap: apply the map config to the ID. + * Should only be true if this map is for a dependency. + * + * @returns {Object} + */ + function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) { + var url, pluginModule, suffix, nameParts, + prefix = null, + parentName = parentModuleMap ? parentModuleMap.name : null, + originalName = name, + isDefine = true, + normalizedName = ''; + + //If no name, then it means it is a require call, generate an + //internal name. + if (!name) { + isDefine = false; + name = '_@r' + (requireCounter += 1); + } + + nameParts = splitPrefix(name); + prefix = nameParts[0]; + name = nameParts[1]; + + if (prefix) { + prefix = normalize(prefix, parentName, applyMap); + pluginModule = getOwn(defined, prefix); + } + + //Account for relative paths if there is a base name. + if (name) { + if (prefix) { + if (pluginModule && pluginModule.normalize) { + //Plugin is loaded, use its normalize method. + normalizedName = pluginModule.normalize(name, function (name) { + return normalize(name, parentName, applyMap); + }); + } else { + normalizedName = normalize(name, parentName, applyMap); + } + } else { + //A regular module. + normalizedName = normalize(name, parentName, applyMap); + + //Normalized name may be a plugin ID due to map config + //application in normalize. The map config values must + //already be normalized, so do not need to redo that part. + nameParts = splitPrefix(normalizedName); + prefix = nameParts[0]; + normalizedName = nameParts[1]; + isNormalized = true; + + url = context.nameToUrl(normalizedName); + } + } + + //If the id is a plugin id that cannot be determined if it needs + //normalization, stamp it with a unique ID so two matching relative + //ids that may conflict can be separate. + suffix = prefix && !pluginModule && !isNormalized ? + '_unnormalized' + (unnormalizedCounter += 1) : + ''; + + return { + prefix: prefix, + name: normalizedName, + parentMap: parentModuleMap, + unnormalized: !!suffix, + url: url, + originalName: originalName, + isDefine: isDefine, + id: (prefix ? + prefix + '!' + normalizedName : + normalizedName) + suffix + }; + } + + function getModule(depMap) { + var id = depMap.id, + mod = getOwn(registry, id); + + if (!mod) { + mod = registry[id] = new context.Module(depMap); + } + + return mod; + } + + function on(depMap, name, fn) { + var id = depMap.id, + mod = getOwn(registry, id); + + if (hasProp(defined, id) && + (!mod || mod.defineEmitComplete)) { + if (name === 'defined') { + fn(defined[id]); + } + } else { + mod = getModule(depMap); + if (mod.error && name === 'error') { + fn(mod.error); + } else { + mod.on(name, fn); + } + } + } + + function onError(err, errback) { + var ids = err.requireModules, + notified = false; + + if (errback) { + errback(err); + } else { + each(ids, function (id) { + var mod = getOwn(registry, id); + if (mod) { + //Set error on module, so it skips timeout checks. + mod.error = err; + if (mod.events.error) { + notified = true; + mod.emit('error', err); + } + } + }); + + if (!notified) { + req.onError(err); + } + } + } + + /** + * Internal method to transfer globalQueue items to this context's + * defQueue. + */ + function takeGlobalQueue() { + //Push all the globalDefQueue items into the context's defQueue + if (globalDefQueue.length) { + //Array splice in the values since the context code has a + //local var ref to defQueue, so cannot just reassign the one + //on context. + apsp.apply(defQueue, + [defQueue.length, 0].concat(globalDefQueue)); + globalDefQueue = []; + } + } + + handlers = { + 'require': function (mod) { + if (mod.require) { + return mod.require; + } else { + return (mod.require = context.makeRequire(mod.map)); + } + }, + 'exports': function (mod) { + mod.usingExports = true; + if (mod.map.isDefine) { + if (mod.exports) { + return (defined[mod.map.id] = mod.exports); + } else { + return (mod.exports = defined[mod.map.id] = {}); + } + } + }, + 'module': function (mod) { + if (mod.module) { + return mod.module; + } else { + return (mod.module = { + id: mod.map.id, + uri: mod.map.url, + config: function () { + return getOwn(config.config, mod.map.id) || {}; + }, + exports: mod.exports || (mod.exports = {}) + }); + } + } + }; + + function cleanRegistry(id) { + //Clean up machinery used for waiting modules. + delete registry[id]; + delete enabledRegistry[id]; + } + + function breakCycle(mod, traced, processed) { + var id = mod.map.id; + + if (mod.error) { + mod.emit('error', mod.error); + } else { + traced[id] = true; + each(mod.depMaps, function (depMap, i) { + var depId = depMap.id, + dep = getOwn(registry, depId); + + //Only force things that have not completed + //being defined, so still in the registry, + //and only if it has not been matched up + //in the module already. + if (dep && !mod.depMatched[i] && !processed[depId]) { + if (getOwn(traced, depId)) { + mod.defineDep(i, defined[depId]); + mod.check(); //pass false? + } else { + breakCycle(dep, traced, processed); + } + } + }); + processed[id] = true; + } + } + + function checkLoaded() { + var err, usingPathFallback, + waitInterval = config.waitSeconds * 1000, + //It is possible to disable the wait interval by using waitSeconds of 0. + expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(), + noLoads = [], + reqCalls = [], + stillLoading = false, + needCycleCheck = true; + + //Do not bother if this call was a result of a cycle break. + if (inCheckLoaded) { + return; + } + + inCheckLoaded = true; + + //Figure out the state of all the modules. + eachProp(enabledRegistry, function (mod) { + var map = mod.map, + modId = map.id; + + //Skip things that are not enabled or in error state. + if (!mod.enabled) { + return; + } + + if (!map.isDefine) { + reqCalls.push(mod); + } + + if (!mod.error) { + //If the module should be executed, and it has not + //been inited and time is up, remember it. + if (!mod.inited && expired) { + if (hasPathFallback(modId)) { + usingPathFallback = true; + stillLoading = true; + } else { + noLoads.push(modId); + removeScript(modId); + } + } else if (!mod.inited && mod.fetched && map.isDefine) { + stillLoading = true; + if (!map.prefix) { + //No reason to keep looking for unfinished + //loading. If the only stillLoading is a + //plugin resource though, keep going, + //because it may be that a plugin resource + //is waiting on a non-plugin cycle. + return (needCycleCheck = false); + } + } + } + }); + + if (expired && noLoads.length) { + //If wait time expired, throw error of unloaded modules. + err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads); + err.contextName = context.contextName; + return onError(err); + } + + //Not expired, check for a cycle. + if (needCycleCheck) { + each(reqCalls, function (mod) { + breakCycle(mod, {}, {}); + }); + } + + //If still waiting on loads, and the waiting load is something + //other than a plugin resource, or there are still outstanding + //scripts, then just try back later. + if ((!expired || usingPathFallback) && stillLoading) { + //Something is still waiting to load. Wait for it, but only + //if a timeout is not already in effect. + if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) { + checkLoadedTimeoutId = setTimeout(function () { + checkLoadedTimeoutId = 0; + checkLoaded(); + }, 50); + } + } + + inCheckLoaded = false; + } + + Module = function (map) { + this.events = getOwn(undefEvents, map.id) || {}; + this.map = map; + this.shim = getOwn(config.shim, map.id); + this.depExports = []; + this.depMaps = []; + this.depMatched = []; + this.pluginMaps = {}; + this.depCount = 0; + + /* this.exports this.factory + this.depMaps = [], + this.enabled, this.fetched + */ + }; + + Module.prototype = { + init: function (depMaps, factory, errback, options) { + options = options || {}; + + //Do not do more inits if already done. Can happen if there + //are multiple define calls for the same module. That is not + //a normal, common case, but it is also not unexpected. + if (this.inited) { + return; + } + + this.factory = factory; + + if (errback) { + //Register for errors on this module. + this.on('error', errback); + } else if (this.events.error) { + //If no errback already, but there are error listeners + //on this module, set up an errback to pass to the deps. + errback = bind(this, function (err) { + this.emit('error', err); + }); + } + + //Do a copy of the dependency array, so that + //source inputs are not modified. For example + //"shim" deps are passed in here directly, and + //doing a direct modification of the depMaps array + //would affect that config. + this.depMaps = depMaps && depMaps.slice(0); + + this.errback = errback; + + //Indicate this module has be initialized + this.inited = true; + + this.ignore = options.ignore; + + //Could have option to init this module in enabled mode, + //or could have been previously marked as enabled. However, + //the dependencies are not known until init is called. So + //if enabled previously, now trigger dependencies as enabled. + if (options.enabled || this.enabled) { + //Enable this module and dependencies. + //Will call this.check() + this.enable(); + } else { + this.check(); + } + }, + + defineDep: function (i, depExports) { + //Because of cycles, defined callback for a given + //export can be called more than once. + if (!this.depMatched[i]) { + this.depMatched[i] = true; + this.depCount -= 1; + this.depExports[i] = depExports; + } + }, + + fetch: function () { + if (this.fetched) { + return; + } + this.fetched = true; + + context.startTime = (new Date()).getTime(); + + var map = this.map; + + //If the manager is for a plugin managed resource, + //ask the plugin to load it now. + if (this.shim) { + context.makeRequire(this.map, { + enableBuildCallback: true + })(this.shim.deps || [], bind(this, function () { + return map.prefix ? this.callPlugin() : this.load(); + })); + } else { + //Regular dependency. + return map.prefix ? this.callPlugin() : this.load(); + } + }, + + load: function () { + var url = this.map.url; + + //Regular dependency. + if (!urlFetched[url]) { + urlFetched[url] = true; + context.load(this.map.id, url); + } + }, + + /** + * Checks if the module is ready to define itself, and if so, + * define it. + */ + check: function () { + if (!this.enabled || this.enabling) { + return; + } + + var err, cjsModule, + id = this.map.id, + depExports = this.depExports, + exports = this.exports, + factory = this.factory; + + if (!this.inited) { + this.fetch(); + } else if (this.error) { + this.emit('error', this.error); + } else if (!this.defining) { + //The factory could trigger another require call + //that would result in checking this module to + //define itself again. If already in the process + //of doing that, skip this work. + this.defining = true; + + if (this.depCount < 1 && !this.defined) { + if (isFunction(factory)) { + //If there is an error listener, favor passing + //to that instead of throwing an error. However, + //only do it for define()'d modules. require + //errbacks should not be called for failures in + //their callbacks (#699). However if a global + //onError is set, use that. + if ((this.events.error && this.map.isDefine) || + req.onError !== defaultOnError) { + try { + exports = context.execCb(id, factory, depExports, exports); + } catch (e) { + err = e; + } + } else { + exports = context.execCb(id, factory, depExports, exports); + } + + // Favor return value over exports. If node/cjs in play, + // then will not have a return value anyway. Favor + // module.exports assignment over exports object. + if (this.map.isDefine && exports === undefined) { + cjsModule = this.module; + if (cjsModule) { + exports = cjsModule.exports; + } else if (this.usingExports) { + //exports already set the defined value. + exports = this.exports; + } + } + + if (err) { + err.requireMap = this.map; + err.requireModules = this.map.isDefine ? [this.map.id] : null; + err.requireType = this.map.isDefine ? 'define' : 'require'; + return onError((this.error = err)); + } + + } else { + //Just a literal value + exports = factory; + } + + this.exports = exports; + + if (this.map.isDefine && !this.ignore) { + defined[id] = exports; + + if (req.onResourceLoad) { + req.onResourceLoad(context, this.map, this.depMaps); + } + } + + //Clean up + cleanRegistry(id); + + this.defined = true; + } + + //Finished the define stage. Allow calling check again + //to allow define notifications below in the case of a + //cycle. + this.defining = false; + + if (this.defined && !this.defineEmitted) { + this.defineEmitted = true; + this.emit('defined', this.exports); + this.defineEmitComplete = true; + } + + } + }, + + callPlugin: function () { + var map = this.map, + id = map.id, + //Map already normalized the prefix. + pluginMap = makeModuleMap(map.prefix); + + //Mark this as a dependency for this plugin, so it + //can be traced for cycles. + this.depMaps.push(pluginMap); + + on(pluginMap, 'defined', bind(this, function (plugin) { + var load, normalizedMap, normalizedMod, + bundleId = getOwn(bundlesMap, this.map.id), + name = this.map.name, + parentName = this.map.parentMap ? this.map.parentMap.name : null, + localRequire = context.makeRequire(map.parentMap, { + enableBuildCallback: true + }); + + //If current map is not normalized, wait for that + //normalized name to load instead of continuing. + if (this.map.unnormalized) { + //Normalize the ID if the plugin allows it. + if (plugin.normalize) { + name = plugin.normalize(name, function (name) { + return normalize(name, parentName, true); + }) || ''; + } + + //prefix and name should already be normalized, no need + //for applying map config again either. + normalizedMap = makeModuleMap(map.prefix + '!' + name, + this.map.parentMap); + on(normalizedMap, + 'defined', bind(this, function (value) { + this.init([], function () { return value; }, null, { + enabled: true, + ignore: true + }); + })); + + normalizedMod = getOwn(registry, normalizedMap.id); + if (normalizedMod) { + //Mark this as a dependency for this plugin, so it + //can be traced for cycles. + this.depMaps.push(normalizedMap); + + if (this.events.error) { + normalizedMod.on('error', bind(this, function (err) { + this.emit('error', err); + })); + } + normalizedMod.enable(); + } + + return; + } + + //If a paths config, then just load that file instead to + //resolve the plugin, as it is built into that paths layer. + if (bundleId) { + this.map.url = context.nameToUrl(bundleId); + this.load(); + return; + } + + load = bind(this, function (value) { + this.init([], function () { return value; }, null, { + enabled: true + }); + }); + + load.error = bind(this, function (err) { + this.inited = true; + this.error = err; + err.requireModules = [id]; + + //Remove temp unnormalized modules for this module, + //since they will never be resolved otherwise now. + eachProp(registry, function (mod) { + if (mod.map.id.indexOf(id + '_unnormalized') === 0) { + cleanRegistry(mod.map.id); + } + }); + + onError(err); + }); + + //Allow plugins to load other code without having to know the + //context or how to 'complete' the load. + load.fromText = bind(this, function (text, textAlt) { + /*jslint evil: true */ + var moduleName = map.name, + moduleMap = makeModuleMap(moduleName), + hasInteractive = useInteractive; + + //As of 2.1.0, support just passing the text, to reinforce + //fromText only being called once per resource. Still + //support old style of passing moduleName but discard + //that moduleName in favor of the internal ref. + if (textAlt) { + text = textAlt; + } + + //Turn off interactive script matching for IE for any define + //calls in the text, then turn it back on at the end. + if (hasInteractive) { + useInteractive = false; + } + + //Prime the system by creating a module instance for + //it. + getModule(moduleMap); + + //Transfer any config to this other module. + if (hasProp(config.config, id)) { + config.config[moduleName] = config.config[id]; + } + + try { + req.exec(text); + } catch (e) { + return onError(makeError('fromtexteval', + 'fromText eval for ' + id + + ' failed: ' + e, + e, + [id])); + } + + if (hasInteractive) { + useInteractive = true; + } + + //Mark this as a dependency for the plugin + //resource + this.depMaps.push(moduleMap); + + //Support anonymous modules. + context.completeLoad(moduleName); + + //Bind the value of that module to the value for this + //resource ID. + localRequire([moduleName], load); + }); + + //Use parentName here since the plugin's name is not reliable, + //could be some weird string with no path that actually wants to + //reference the parentName's path. + plugin.load(map.name, localRequire, load, config); + })); + + context.enable(pluginMap, this); + this.pluginMaps[pluginMap.id] = pluginMap; + }, + + enable: function () { + enabledRegistry[this.map.id] = this; + this.enabled = true; + + //Set flag mentioning that the module is enabling, + //so that immediate calls to the defined callbacks + //for dependencies do not trigger inadvertent load + //with the depCount still being zero. + this.enabling = true; + + //Enable each dependency + each(this.depMaps, bind(this, function (depMap, i) { + var id, mod, handler; + + if (typeof depMap === 'string') { + //Dependency needs to be converted to a depMap + //and wired up to this module. + depMap = makeModuleMap(depMap, + (this.map.isDefine ? this.map : this.map.parentMap), + false, + !this.skipMap); + this.depMaps[i] = depMap; + + handler = getOwn(handlers, depMap.id); + + if (handler) { + this.depExports[i] = handler(this); + return; + } + + this.depCount += 1; + + on(depMap, 'defined', bind(this, function (depExports) { + this.defineDep(i, depExports); + this.check(); + })); + + if (this.errback) { + on(depMap, 'error', bind(this, this.errback)); + } + } + + id = depMap.id; + mod = registry[id]; + + //Skip special modules like 'require', 'exports', 'module' + //Also, don't call enable if it is already enabled, + //important in circular dependency cases. + if (!hasProp(handlers, id) && mod && !mod.enabled) { + context.enable(depMap, this); + } + })); + + //Enable each plugin that is used in + //a dependency + eachProp(this.pluginMaps, bind(this, function (pluginMap) { + var mod = getOwn(registry, pluginMap.id); + if (mod && !mod.enabled) { + context.enable(pluginMap, this); + } + })); + + this.enabling = false; + + this.check(); + }, + + on: function (name, cb) { + var cbs = this.events[name]; + if (!cbs) { + cbs = this.events[name] = []; + } + cbs.push(cb); + }, + + emit: function (name, evt) { + each(this.events[name], function (cb) { + cb(evt); + }); + if (name === 'error') { + //Now that the error handler was triggered, remove + //the listeners, since this broken Module instance + //can stay around for a while in the registry. + delete this.events[name]; + } + } + }; + + function callGetModule(args) { + //Skip modules already defined. + if (!hasProp(defined, args[0])) { + getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]); + } + } + + function removeListener(node, func, name, ieName) { + //Favor detachEvent because of IE9 + //issue, see attachEvent/addEventListener comment elsewhere + //in this file. + if (node.detachEvent && !isOpera) { + //Probably IE. If not it will throw an error, which will be + //useful to know. + if (ieName) { + node.detachEvent(ieName, func); + } + } else { + node.removeEventListener(name, func, false); + } + } + + /** + * Given an event from a script node, get the requirejs info from it, + * and then removes the event listeners on the node. + * @param {Event} evt + * @returns {Object} + */ + function getScriptData(evt) { + //Using currentTarget instead of target for Firefox 2.0's sake. Not + //all old browsers will be supported, but this one was easy enough + //to support and still makes sense. + var node = evt.currentTarget || evt.srcElement; + + //Remove the listeners once here. + removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange'); + removeListener(node, context.onScriptError, 'error'); + + return { + node: node, + id: node && node.getAttribute('data-requiremodule') + }; + } + + function intakeDefines() { + var args; + + //Any defined modules in the global queue, intake them now. + takeGlobalQueue(); + + //Make sure any remaining defQueue items get properly processed. + while (defQueue.length) { + args = defQueue.shift(); + if (args[0] === null) { + return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1])); + } else { + //args are id, deps, factory. Should be normalized by the + //define() function. + callGetModule(args); + } + } + } + + context = { + config: config, + contextName: contextName, + registry: registry, + defined: defined, + urlFetched: urlFetched, + defQueue: defQueue, + Module: Module, + makeModuleMap: makeModuleMap, + nextTick: req.nextTick, + onError: onError, + + /** + * Set a configuration for the context. + * @param {Object} cfg config object to integrate. + */ + configure: function (cfg) { + //Make sure the baseUrl ends in a slash. + if (cfg.baseUrl) { + if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') { + cfg.baseUrl += '/'; + } + } + + //Save off the paths since they require special processing, + //they are additive. + var shim = config.shim, + objs = { + paths: true, + bundles: true, + config: true, + map: true + }; + + eachProp(cfg, function (value, prop) { + if (objs[prop]) { + if (!config[prop]) { + config[prop] = {}; + } + mixin(config[prop], value, true, true); + } else { + config[prop] = value; + } + }); + + //Reverse map the bundles + if (cfg.bundles) { + eachProp(cfg.bundles, function (value, prop) { + each(value, function (v) { + if (v !== prop) { + bundlesMap[v] = prop; + } + }); + }); + } + + //Merge shim + if (cfg.shim) { + eachProp(cfg.shim, function (value, id) { + //Normalize the structure + if (isArray(value)) { + value = { + deps: value + }; + } + if ((value.exports || value.init) && !value.exportsFn) { + value.exportsFn = context.makeShimExports(value); + } + shim[id] = value; + }); + config.shim = shim; + } + + //Adjust packages if necessary. + if (cfg.packages) { + each(cfg.packages, function (pkgObj) { + var location, name; + + pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj; + + name = pkgObj.name; + location = pkgObj.location; + if (location) { + config.paths[name] = pkgObj.location; + } + + //Save pointer to main module ID for pkg name. + //Remove leading dot in main, so main paths are normalized, + //and remove any trailing .js, since different package + //envs have different conventions: some use a module name, + //some use a file name. + config.pkgs[name] = pkgObj.name + '/' + (pkgObj.main || 'main') + .replace(currDirRegExp, '') + .replace(jsSuffixRegExp, ''); + }); + } + + //If there are any "waiting to execute" modules in the registry, + //update the maps for them, since their info, like URLs to load, + //may have changed. + eachProp(registry, function (mod, id) { + //If module already has init called, since it is too + //late to modify them, and ignore unnormalized ones + //since they are transient. + if (!mod.inited && !mod.map.unnormalized) { + mod.map = makeModuleMap(id); + } + }); + + //If a deps array or a config callback is specified, then call + //require with those args. This is useful when require is defined as a + //config object before require.js is loaded. + if (cfg.deps || cfg.callback) { + context.require(cfg.deps || [], cfg.callback); + } + }, + + makeShimExports: function (value) { + function fn() { + var ret; + if (value.init) { + ret = value.init.apply(global, arguments); + } + return ret || (value.exports && getGlobal(value.exports)); + } + return fn; + }, + + makeRequire: function (relMap, options) { + options = options || {}; + + function localRequire(deps, callback, errback) { + var id, map, requireMod; + + if (options.enableBuildCallback && callback && isFunction(callback)) { + callback.__requireJsBuild = true; + } + + if (typeof deps === 'string') { + if (isFunction(callback)) { + //Invalid call + return onError(makeError('requireargs', 'Invalid require call'), errback); + } + + //If require|exports|module are requested, get the + //value for them from the special handlers. Caveat: + //this only works while module is being defined. + if (relMap && hasProp(handlers, deps)) { + return handlers[deps](registry[relMap.id]); + } + + //Synchronous access to one module. If require.get is + //available (as in the Node adapter), prefer that. + if (req.get) { + return req.get(context, deps, relMap, localRequire); + } + + //Normalize module name, if it contains . or .. + map = makeModuleMap(deps, relMap, false, true); + id = map.id; + + if (!hasProp(defined, id)) { + return onError(makeError('notloaded', 'Module name "' + + id + + '" has not been loaded yet for context: ' + + contextName + + (relMap ? '' : '. Use require([])'))); + } + return defined[id]; + } + + //Grab defines waiting in the global queue. + intakeDefines(); + + //Mark all the dependencies as needing to be loaded. + context.nextTick(function () { + //Some defines could have been added since the + //require call, collect them. + intakeDefines(); + + requireMod = getModule(makeModuleMap(null, relMap)); + + //Store if map config should be applied to this require + //call for dependencies. + requireMod.skipMap = options.skipMap; + + requireMod.init(deps, callback, errback, { + enabled: true + }); + + checkLoaded(); + }); + + return localRequire; + } + + mixin(localRequire, { + isBrowser: isBrowser, + + /** + * Converts a module name + .extension into an URL path. + * *Requires* the use of a module name. It does not support using + * plain URLs like nameToUrl. + */ + toUrl: function (moduleNamePlusExt) { + var ext, + index = moduleNamePlusExt.lastIndexOf('.'), + segment = moduleNamePlusExt.split('/')[0], + isRelative = segment === '.' || segment === '..'; + + //Have a file extension alias, and it is not the + //dots from a relative path. + if (index !== -1 && (!isRelative || index > 1)) { + ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length); + moduleNamePlusExt = moduleNamePlusExt.substring(0, index); + } + + return context.nameToUrl(normalize(moduleNamePlusExt, + relMap && relMap.id, true), ext, true); + }, + + defined: function (id) { + return hasProp(defined, makeModuleMap(id, relMap, false, true).id); + }, + + specified: function (id) { + id = makeModuleMap(id, relMap, false, true).id; + return hasProp(defined, id) || hasProp(registry, id); + } + }); + + //Only allow undef on top level require calls + if (!relMap) { + localRequire.undef = function (id) { + //Bind any waiting define() calls to this context, + //fix for #408 + takeGlobalQueue(); + + var map = makeModuleMap(id, relMap, true), + mod = getOwn(registry, id); + + removeScript(id); + + delete defined[id]; + delete urlFetched[map.url]; + delete undefEvents[id]; + + //Clean queued defines too. Go backwards + //in array so that the splices do not + //mess up the iteration. + eachReverse(defQueue, function(args, i) { + if(args[0] === id) { + defQueue.splice(i, 1); + } + }); + + if (mod) { + //Hold on to listeners in case the + //module will be attempted to be reloaded + //using a different config. + if (mod.events.defined) { + undefEvents[id] = mod.events; + } + + cleanRegistry(id); + } + }; + } + + return localRequire; + }, + + /** + * Called to enable a module if it is still in the registry + * awaiting enablement. A second arg, parent, the parent module, + * is passed in for context, when this method is overridden by + * the optimizer. Not shown here to keep code compact. + */ + enable: function (depMap) { + var mod = getOwn(registry, depMap.id); + if (mod) { + getModule(depMap).enable(); + } + }, + + /** + * Internal method used by environment adapters to complete a load event. + * A load event could be a script load or just a load pass from a synchronous + * load call. + * @param {String} moduleName the name of the module to potentially complete. + */ + completeLoad: function (moduleName) { + var found, args, mod, + shim = getOwn(config.shim, moduleName) || {}, + shExports = shim.exports; + + takeGlobalQueue(); + + while (defQueue.length) { + args = defQueue.shift(); + if (args[0] === null) { + args[0] = moduleName; + //If already found an anonymous module and bound it + //to this name, then this is some other anon module + //waiting for its completeLoad to fire. + if (found) { + break; + } + found = true; + } else if (args[0] === moduleName) { + //Found matching define call for this script! + found = true; + } + + callGetModule(args); + } + + //Do this after the cycle of callGetModule in case the result + //of those calls/init calls changes the registry. + mod = getOwn(registry, moduleName); + + if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) { + if (config.enforceDefine && (!shExports || !getGlobal(shExports))) { + if (hasPathFallback(moduleName)) { + return; + } else { + return onError(makeError('nodefine', + 'No define call for ' + moduleName, + null, + [moduleName])); + } + } else { + //A script that does not call define(), so just simulate + //the call for it. + callGetModule([moduleName, (shim.deps || []), shim.exportsFn]); + } + } + + checkLoaded(); + }, + + /** + * Converts a module name to a file path. Supports cases where + * moduleName may actually be just an URL. + * Note that it **does not** call normalize on the moduleName, + * it is assumed to have already been normalized. This is an + * internal API, not a public one. Use toUrl for the public API. + */ + nameToUrl: function (moduleName, ext, skipExt) { + var paths, syms, i, parentModule, url, + parentPath, bundleId, + pkgMain = getOwn(config.pkgs, moduleName); + + if (pkgMain) { + moduleName = pkgMain; + } + + bundleId = getOwn(bundlesMap, moduleName); + + if (bundleId) { + return context.nameToUrl(bundleId, ext, skipExt); + } + + //If a colon is in the URL, it indicates a protocol is used and it is just + //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?) + //or ends with .js, then assume the user meant to use an url and not a module id. + //The slash is important for protocol-less URLs as well as full paths. + if (req.jsExtRegExp.test(moduleName)) { + //Just a plain path, not module name lookup, so just return it. + //Add extension if it is included. This is a bit wonky, only non-.js things pass + //an extension, this method probably needs to be reworked. + url = moduleName + (ext || ''); + } else { + //A module that needs to be converted to a path. + paths = config.paths; + + syms = moduleName.split('/'); + //For each module name segment, see if there is a path + //registered for it. Start with most specific name + //and work up from it. + for (i = syms.length; i > 0; i -= 1) { + parentModule = syms.slice(0, i).join('/'); + + parentPath = getOwn(paths, parentModule); + if (parentPath) { + //If an array, it means there are a few choices, + //Choose the one that is desired + if (isArray(parentPath)) { + parentPath = parentPath[0]; + } + syms.splice(0, i, parentPath); + break; + } + } + + //Join the path parts together, then figure out if baseUrl is needed. + url = syms.join('/'); + url += (ext || (/^data\:|\?/.test(url) || skipExt ? '' : '.js')); + url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url; + } + + return config.urlArgs ? url + + ((url.indexOf('?') === -1 ? '?' : '&') + + config.urlArgs) : url; + }, + + //Delegates to req.load. Broken out as a separate function to + //allow overriding in the optimizer. + load: function (id, url) { + req.load(context, id, url); + }, + + /** + * Executes a module callback function. Broken out as a separate function + * solely to allow the build system to sequence the files in the built + * layer in the right sequence. + * + * @private + */ + execCb: function (name, callback, args, exports) { + return callback.apply(exports, args); + }, + + /** + * callback for script loads, used to check status of loading. + * + * @param {Event} evt the event from the browser for the script + * that was loaded. + */ + onScriptLoad: function (evt) { + //Using currentTarget instead of target for Firefox 2.0's sake. Not + //all old browsers will be supported, but this one was easy enough + //to support and still makes sense. + if (evt.type === 'load' || + (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) { + //Reset interactive script so a script node is not held onto for + //to long. + interactiveScript = null; + + //Pull out the name of the module and the context. + var data = getScriptData(evt); + context.completeLoad(data.id); + } + }, + + /** + * Callback for script errors. + */ + onScriptError: function (evt) { + var data = getScriptData(evt); + if (!hasPathFallback(data.id)) { + return onError(makeError('scripterror', 'Script error for: ' + data.id, evt, [data.id])); + } + } + }; + + context.require = context.makeRequire(); + return context; + } + + /** + * Main entry point. + * + * If the only argument to require is a string, then the module that + * is represented by that string is fetched for the appropriate context. + * + * If the first argument is an array, then it will be treated as an array + * of dependency string names to fetch. An optional function callback can + * be specified to execute when all of those dependencies are available. + * + * Make a local req variable to help Caja compliance (it assumes things + * on a require that are not standardized), and to give a short + * name for minification/local scope use. + */ + req = requirejs = function (deps, callback, errback, optional) { + + //Find the right context, use default + var context, config, + contextName = defContextName; + + // Determine if have config object in the call. + if (!isArray(deps) && typeof deps !== 'string') { + // deps is a config object + config = deps; + if (isArray(callback)) { + // Adjust args if there are dependencies + deps = callback; + callback = errback; + errback = optional; + } else { + deps = []; + } + } + + if (config && config.context) { + contextName = config.context; + } + + context = getOwn(contexts, contextName); + if (!context) { + context = contexts[contextName] = req.s.newContext(contextName); + } + + if (config) { + context.configure(config); + } + + return context.require(deps, callback, errback); + }; + + /** + * Support require.config() to make it easier to cooperate with other + * AMD loaders on globally agreed names. + */ + req.config = function (config) { + return req(config); + }; + + /** + * Execute something after the current tick + * of the event loop. Override for other envs + * that have a better solution than setTimeout. + * @param {Function} fn function to execute later. + */ + req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) { + setTimeout(fn, 4); + } : function (fn) { fn(); }; + + /** + * Export require as a global, but only if it does not already exist. + */ + if (!require) { + require = req; + } + + req.version = version; + + //Used to filter out dependencies that are already paths. + req.jsExtRegExp = /^\/|:|\?|\.js$/; + req.isBrowser = isBrowser; + s = req.s = { + contexts: contexts, + newContext: newContext + }; + + //Create default context. + req({}); + + //Exports some context-sensitive methods on global require. + each([ + 'toUrl', + 'undef', + 'defined', + 'specified' + ], function (prop) { + //Reference from contexts instead of early binding to default context, + //so that during builds, the latest instance of the default context + //with its config gets used. + req[prop] = function () { + var ctx = contexts[defContextName]; + return ctx.require[prop].apply(ctx, arguments); + }; + }); + + if (isBrowser) { + head = s.head = document.getElementsByTagName('head')[0]; + //If BASE tag is in play, using appendChild is a problem for IE6. + //When that browser dies, this can be removed. Details in this jQuery bug: + //http://dev.jquery.com/ticket/2709 + baseElement = document.getElementsByTagName('base')[0]; + if (baseElement) { + head = s.head = baseElement.parentNode; + } + } + + /** + * Any errors that require explicitly generates will be passed to this + * function. Intercept/override it if you want custom error handling. + * @param {Error} err the error object. + */ + req.onError = defaultOnError; + + /** + * Creates the node for the load command. Only used in browser envs. + */ + req.createNode = function (config, moduleName, url) { + var node = config.xhtml ? + document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') : + document.createElement('script'); + node.type = config.scriptType || 'text/javascript'; + node.charset = 'utf-8'; + node.async = true; + return node; + }; + + /** + * Does the request to load a module for the browser case. + * Make this a separate function to allow other environments + * to override it. + * + * @param {Object} context the require context to find state. + * @param {String} moduleName the name of the module. + * @param {Object} url the URL to the module. + */ + req.load = function (context, moduleName, url) { + var config = (context && context.config) || {}, + node; + if (isBrowser) { + //In the browser so use a script tag + node = req.createNode(config, moduleName, url); + + node.setAttribute('data-requirecontext', context.contextName); + node.setAttribute('data-requiremodule', moduleName); + + //Set up load listener. Test attachEvent first because IE9 has + //a subtle issue in its addEventListener and script onload firings + //that do not match the behavior of all other browsers with + //addEventListener support, which fire the onload event for a + //script right after the script execution. See: + //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution + //UNFORTUNATELY Opera implements attachEvent but does not follow the script + //script execution mode. + if (node.attachEvent && + //Check if node.attachEvent is artificially added by custom script or + //natively supported by browser + //read https://github.com/jrburke/requirejs/issues/187 + //if we can NOT find [native code] then it must NOT natively supported. + //in IE8, node.attachEvent does not have toString() + //Note the test for "[native code" with no closing brace, see: + //https://github.com/jrburke/requirejs/issues/273 + !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) && + !isOpera) { + //Probably IE. IE (at least 6-8) do not fire + //script onload right after executing the script, so + //we cannot tie the anonymous define call to a name. + //However, IE reports the script as being in 'interactive' + //readyState at the time of the define call. + useInteractive = true; + + node.attachEvent('onreadystatechange', context.onScriptLoad); + //It would be great to add an error handler here to catch + //404s in IE9+. However, onreadystatechange will fire before + //the error handler, so that does not help. If addEventListener + //is used, then IE will fire error before load, but we cannot + //use that pathway given the connect.microsoft.com issue + //mentioned above about not doing the 'script execute, + //then fire the script load event listener before execute + //next script' that other browsers do. + //Best hope: IE10 fixes the issues, + //and then destroys all installs of IE 6-9. + //node.attachEvent('onerror', context.onScriptError); + } else { + node.addEventListener('load', context.onScriptLoad, false); + node.addEventListener('error', context.onScriptError, false); + } + node.src = url; + + //For some cache cases in IE 6-8, the script executes before the end + //of the appendChild execution, so to tie an anonymous define + //call to the module name (which is stored on the node), hold on + //to a reference to this node, but clear after the DOM insertion. + currentlyAddingScript = node; + if (baseElement) { + head.insertBefore(node, baseElement); + } else { + head.appendChild(node); + } + currentlyAddingScript = null; + + return node; + } else if (isWebWorker) { + try { + //In a web worker, use importScripts. This is not a very + //efficient use of importScripts, importScripts will block until + //its script is downloaded and evaluated. However, if web workers + //are in play, the expectation that a build has been done so that + //only one script needs to be loaded anyway. This may need to be + //reevaluated if other use cases become common. + importScripts(url); + + //Account for anonymous modules + context.completeLoad(moduleName); + } catch (e) { + context.onError(makeError('importscripts', + 'importScripts failed for ' + + moduleName + ' at ' + url, + e, + [moduleName])); + } + } + }; + + function getInteractiveScript() { + if (interactiveScript && interactiveScript.readyState === 'interactive') { + return interactiveScript; + } + + eachReverse(scripts(), function (script) { + if (script.readyState === 'interactive') { + return (interactiveScript = script); + } + }); + return interactiveScript; + } + + //Look for a data-main script attribute, which could also adjust the baseUrl. + if (isBrowser && !cfg.skipDataMain) { + //Figure out baseUrl. Get it from the script tag with require.js in it. + eachReverse(scripts(), function (script) { + //Set the 'head' where we can append children by + //using the script's parent. + if (!head) { + head = script.parentNode; + } + + //Look for a data-main attribute to set main script for the page + //to load. If it is there, the path to data main becomes the + //baseUrl, if it is not already set. + dataMain = script.getAttribute('data-main'); + if (dataMain) { + //Preserve dataMain in case it is a path (i.e. contains '?') + mainScript = dataMain; + + //Set final baseUrl if there is not already an explicit one. + if (!cfg.baseUrl) { + //Pull off the directory of data-main for use as the + //baseUrl. + src = mainScript.split('/'); + mainScript = src.pop(); + subPath = src.length ? src.join('/') + '/' : './'; + + cfg.baseUrl = subPath; + } + + //Strip off any trailing .js since mainScript is now + //like a module name. + mainScript = mainScript.replace(jsSuffixRegExp, ''); + + //If mainScript is still a path, fall back to dataMain + if (req.jsExtRegExp.test(mainScript)) { + mainScript = dataMain; + } + + //Put the data-main script in the files to load. + cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript]; + + return true; + } + }); + } + + /** + * The function that handles definitions of modules. Differs from + * require() in that a string for the module should be the first argument, + * and the function to execute after dependencies are loaded should + * return a value to define the module corresponding to the first argument's + * name. + */ + define = function (name, deps, callback) { + var node, context; + + //Allow for anonymous modules + if (typeof name !== 'string') { + //Adjust args appropriately + callback = deps; + deps = name; + name = null; + } + + //This module may not have dependencies + if (!isArray(deps)) { + callback = deps; + deps = null; + } + + //If no name, and callback is a function, then figure out if it a + //CommonJS thing with dependencies. + if (!deps && isFunction(callback)) { + deps = []; + //Remove comments from the callback string, + //look for require calls, and pull them into the dependencies, + //but only if there are function args. + if (callback.length) { + callback + .toString() + .replace(commentRegExp, '') + .replace(cjsRequireRegExp, function (match, dep) { + deps.push(dep); + }); + + //May be a CommonJS thing even without require calls, but still + //could use exports, and module. Avoid doing exports and module + //work though if it just needs require. + //REQUIRES the function to expect the CommonJS variables in the + //order listed below. + deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps); + } + } + + //If in IE 6-8 and hit an anonymous define() call, do the interactive + //work. + if (useInteractive) { + node = currentlyAddingScript || getInteractiveScript(); + if (node) { + if (!name) { + name = node.getAttribute('data-requiremodule'); + } + context = contexts[node.getAttribute('data-requirecontext')]; + } + } + + //Always save off evaluating the def call until the script onload handler. + //This allows multiple modules to be in a file without prematurely + //tracing dependencies, and allows for anonymous module support, + //where the module name is not known until the script onload event + //occurs. If no context, use the global queue, and get it processed + //in the onscript load callback. + (context ? context.defQueue : globalDefQueue).push([name, deps, callback]); + }; + + define.amd = { + jQuery: true + }; + + + /** + * Executes the text. Normally just uses eval, but can be modified + * to use a better, environment-specific call. Only used for transpiling + * loader plugins, not for plain JS modules. + * @param {String} text the text to execute/evaluate. + */ + req.exec = function (text) { + /*jslint evil: true */ + return eval(text); + }; + + //Set up with config info. + req(cfg); +}(this)); diff --git a/test/tests.js b/test/tests.js index ab3efb6..450dc58 100644 --- a/test/tests.js +++ b/test/tests.js @@ -2,8 +2,8 @@ var userAgentsToTest = [{ ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36', b: 'chrome', bv: '31', - os: 'mac', - osv: 'os x' + os: 'mac os', + osv: '10' }, { ua: 'Mozilla/5.0 (Linux; U; Android 4.0.3; de-de; Build/20120717) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30', b: 'safari', @@ -18,34 +18,30 @@ var userAgentsToTest = [{ osv: '7' }]; -function isUserAgentTestOk(userAgentToTest) { - if (userAgentToTest.os == Modernizr.Detectizr.device.os && Modernizr.Detectizr.device.osVersion == userAgentToTest.osv && Modernizr.Detectizr.device.browser == userAgentToTest.b && Modernizr.Detectizr.device.browserVersion == userAgentToTest.bv) { - return true; - } - return false; -} - test('is Detectizr ready', function () { - Modernizr.Detectizr.detect(); - notEqual(Modernizr.Detectizr.device, undefined); + notEqual(Detectizr.device, undefined); }); -test('useragent tests', function () { - for (var i = userAgentsToTest.length - 1; i >= 0; i--) { - Modernizr.Detectizr.device = { - type: '', - model: '', - orientation: '', - browser: '', - browserEngine: '', - browserPlugins: [], - browserVersion: '', - os: '', - osVersion: '', - osVersionFull: '', - userAgent: userAgentsToTest[i].ua.toLowerCase() +var useragent2test, index4test = userAgentsToTest.length - 1; +module("useragent", { + setup: function() { + // prepare something for all following tests + useragent2test = userAgentsToTest[index4test]; + Detectizr.browser = { + userAgent: useragent2test.ua.toLowerCase() }; - Modernizr.Detectizr.detect(); - ok(isUserAgentTestOk(userAgentsToTest[i]), userAgentsToTest[i].ua); + Detectizr.detect(); + }, + teardown: function() { + // clean up after each test + index4test--; } }); +for (var i = userAgentsToTest.length - 1; i >= 0; i--) { + test(userAgentsToTest[i].ua, function () { + equal(Detectizr.os.name, useragent2test.os, "operating system name is OK: " + useragent2test.os); + equal(Detectizr.os.major, useragent2test.osv, "operating system name is OK: " + useragent2test.osv); + equal(Detectizr.browser.name, useragent2test.b, "browser name is OK: " + useragent2test.b); + equal(Detectizr.browser.major, useragent2test.bv, "browser version is OK: " + useragent2test.bv); + }); +}