From 7dee3d0e83785bb22485e0923e366f61beef5f39 Mon Sep 17 00:00:00 2001 From: Fraham Date: Thu, 23 Nov 2017 20:10:51 +0000 Subject: [PATCH 01/57] Merge --- index.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/index.html b/index.html index 01f1366..7bc4ec9 100644 --- a/index.html +++ b/index.html @@ -649,10 +649,7 @@ function add(x, y) { <button popover-top="Popover on top">Popover on top and on a button!</button> -<<<<<<< HEAD -======= - ->>>>>>> refs/remotes/rhyneav/master +

Cards

Full card example

From e21ea5cc8872565863b0eb43c9108fd4a2d38a1c Mon Sep 17 00:00:00 2001 From: Afzal Sayed Date: Fri, 24 Nov 2017 22:16:11 +0530 Subject: [PATCH 02/57] Adding a back to top button feature --- index.html | 6 ++++-- src/back2top.less | 20 ++++++++++++++++++++ src/styles.less | 1 + 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 src/back2top.less diff --git a/index.html b/index.html index bb4a3f4..7d42001 100644 --- a/index.html +++ b/index.html @@ -27,7 +27,7 @@
-
+

PaperCSS

@@ -62,7 +62,9 @@
  • Alerts
  • - +
    + ^ +

    Flexbox

    Flexgrid

    diff --git a/src/back2top.less b/src/back2top.less new file mode 100644 index 0000000..434305a --- /dev/null +++ b/src/back2top.less @@ -0,0 +1,20 @@ +.to-top { + opacity: 1; + display: inline-block; + padding: 1em; + position: fixed; + bottom: 1em; + right: 1em; + .paper-btn { + padding: 0.6em 1em; + background: white; + border-top-left-radius: 185px 160px; + border-top-right-radius: 200px 195px; + border-bottom-right-radius: 160px 195px; + border-bottom-left-radius: 185px 190px; + } +} + +.demo-title:hover + .to-top { + opacity: 0; +} \ No newline at end of file diff --git a/src/styles.less b/src/styles.less index c44d16e..c22317d 100644 --- a/src/styles.less +++ b/src/styles.less @@ -17,3 +17,4 @@ @import (less) "./cards.less"; @import (less) "./badges.less"; @import (less) "./alerts.less"; +@import (less) "./back2top.less"; From 1dc3adaf52495224e9de4b042595973d7dec08c2 Mon Sep 17 00:00:00 2001 From: Afzal Sayed Date: Sat, 25 Nov 2017 20:47:10 +0530 Subject: [PATCH 03/57] Added tabs feature --- index.html | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ src/styles.less | 1 + src/tabs.less | 37 +++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 src/tabs.less diff --git a/index.html b/index.html index bb4a3f4..d2b8eb4 100644 --- a/index.html +++ b/index.html @@ -60,6 +60,7 @@
  • Utilities
  • Images
  • Alerts
  • +
  • Tabs
  • @@ -1056,6 +1057,112 @@ function add(x, y) { </div>
    +
    +

    Tabs

    +
    + + + + + + + + + + + + +
    +

    + Bacon ipsum dolor sit amet beef venison beef ribs kielbasa. Sausage pig leberkas, t-bone sirloin shoulder bresaola. Frankfurter + rump porchetta ham. Pork belly prosciutto brisket meatloaf short ribs. +

    +

    + Brisket meatball turkey short loin boudin leberkas meatloaf chuck andouille pork loin pastrami spare ribs pancetta rump. + Frankfurter corned beef beef tenderloin short loin meatloaf swine ground round venison. +

    +
    + +
    +

    + Bacon ipsum dolor sit amet landjaeger sausage brisket, jerky drumstick fatback boudin ball tip turducken. Pork belly meatball + t-bone bresaola tail filet mignon kevin turkey ribeye shank flank doner cow kielbasa shankle. Pig swine + chicken hamburger, tenderloin turkey rump ball tip sirloin frankfurter meatloaf boudin brisket ham hock. + Hamburger venison brisket tri-tip andouille pork belly ball tip short ribs biltong meatball chuck. Pork + chop ribeye tail short ribs, beef hamburger meatball kielbasa rump corned beef porchetta landjaeger flank. + Doner rump frankfurter meatball meatloaf, cow kevin pork pork loin venison fatback spare ribs salami + beef ribs. +

    +
    + +
    +

    + Bacon ipsum dolor sit amet beef venison beef ribs kielbasa. Sausage pig leberkas, t-bone sirloin shoulder bresaola. Frankfurter + rump porchetta ham. Pork belly prosciutto brisket meatloaf short ribs. +

    +

    + Brisket meatball turkey short loin boudin leberkas meatloaf chuck andouille pork loin pastrami spare ribs pancetta rump. + Frankfurter corned beef beef tenderloin short loin meatloaf swine ground round venison. +

    +
    + +
    +

    + Bacon ipsum dolor sit amet landjaeger sausage brisket, jerky drumstick fatback boudin ball tip turducken. Pork belly meatball + t-bone bresaola tail filet mignon kevin turkey ribeye shank flank doner cow kielbasa shankle. Pig swine + chicken hamburger, tenderloin turkey rump ball tip sirloin frankfurter meatloaf boudin brisket ham hock. + Hamburger venison brisket tri-tip andouille pork belly ball tip short ribs biltong meatball chuck. Pork + chop ribeye tail short ribs, beef hamburger meatball kielbasa rump corned beef porchetta landjaeger flank. + Doner rump frankfurter meatball meatloaf, cow kevin pork pork loin venison fatback spare ribs salami + beef ribs. +

    +
    +
    +
    +
    +    <div class="row flex-spaces tabs">
    +      <input id="tab1" type="radio" name="tabs" checked>
    +      <label for="tab1">Tab 1</label>
    +      
    +      <input id="tab2" type="radio" name="tabs">
    +      <label for="tab2">Tab 2</label>
    +      
    +      <input id="tab3" type="radio" name="tabs">
    +      <label for="tab3">Tab 3</label>
    +      
    +      <input id="tab4" type="radio" name="tabs">
    +      <label for="tab4">Tab 4</label>
    +  
    +      <div class="content" id="content1">
    +        <p>
    +          Bacon ipsum dolor sit amet beef venison beef ribs kielbasa...
    +        </p>
    +        <p>
    +          Brisket meatball turkey short loin boudin leberkas meatloaf...
    +        </p>
    +      </div>
    +      <div class="content" id="content2">
    +        <p>
    +          Bacon ipsum dolor sit amet landjaeger sausage brisket...
    +        </p>
    +      </div>
    +      <div class="content" id="content3">
    +        <p>
    +          Bacon ipsum dolor sit amet beef venison beef ribs kielbasa...
    +        </p>
    +        <p>
    +          Brisket meatball turkey short loin boudin leberkas meatloaf...
    +        </p>
    +      </div>
    +      <div class="content" id="content4">
    +        <p>
    +          Bacon ipsum dolor sit amet landjaeger sausage brisket...
    +        </p>
    +      </div>
    +    </div>
    +
    +
    +

    Download and Link

    Download

    diff --git a/src/styles.less b/src/styles.less index c44d16e..6cf9eee 100644 --- a/src/styles.less +++ b/src/styles.less @@ -17,3 +17,4 @@ @import (less) "./cards.less"; @import (less) "./badges.less"; @import (less) "./alerts.less"; +@import (less) "./tabs.less"; \ No newline at end of file diff --git a/src/tabs.less b/src/tabs.less new file mode 100644 index 0000000..73a4b45 --- /dev/null +++ b/src/tabs.less @@ -0,0 +1,37 @@ +.tabs { + .content { + display: none; + padding: 0.75rem 0 0; + } + + input { + display: none; + } + + label { + display: inline-block; + margin: 0 0 -1px; + padding: 0.75rem 0.75rem; + font-weight: 600; + text-align: center; + color: @primary-light; + } + + label:hover { + color: @muted; + cursor: pointer; + } + + input:checked+label { + color: @primary; + border-bottom: solid 3px @secondary; + } + + #tab1:checked~#content1, + #tab2:checked~#content2, + #tab3:checked~#content3, + #tab4:checked~#content4 { + display: block; + } + +} \ No newline at end of file From dd9c203c23e6d03294fb4fb381aa603cbbc8c1c3 Mon Sep 17 00:00:00 2001 From: Afzal Sayed Date: Mon, 27 Nov 2017 11:48:05 +0530 Subject: [PATCH 04/57] Added loop for dynamic selection of tabs id --- src/tabs.less | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/tabs.less b/src/tabs.less index 73a4b45..648b894 100644 --- a/src/tabs.less +++ b/src/tabs.less @@ -27,11 +27,12 @@ border-bottom: solid 3px @secondary; } - #tab1:checked~#content1, - #tab2:checked~#content2, - #tab3:checked~#content3, - #tab4:checked~#content4 { - display: block; + .loop(@num) when (@num > 0) { + .loop((@num - 1)); + input[id^= ~"tab@{num}"]:checked~div[id^= ~"content@{num}"]{ + display:block; + } } - + + .loop(5); } \ No newline at end of file From 93ce4731784e55abb34a22bebe0f9b8db9398ea5 Mon Sep 17 00:00:00 2001 From: Afzal Sayed Date: Mon, 27 Nov 2017 18:00:48 +0530 Subject: [PATCH 05/57] small bug fix in id selection --- src/tabs.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tabs.less b/src/tabs.less index 648b894..21ef056 100644 --- a/src/tabs.less +++ b/src/tabs.less @@ -29,7 +29,7 @@ .loop(@num) when (@num > 0) { .loop((@num - 1)); - input[id^= ~"tab@{num}"]:checked~div[id^= ~"content@{num}"]{ + input[id = ~"tab@{num}"]:checked~div[id = ~"content@{num}"]{ display:block; } } From 8bbcb2e3710e2ea167d99d52f211050514f1731f Mon Sep 17 00:00:00 2001 From: Yazed Jamal Date: Mon, 27 Nov 2017 21:00:28 +0000 Subject: [PATCH 06/57] added class for inline list --- index.html | 7 +++++++ src/lists.less | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/index.html b/index.html index 61b82ad..bcda14a 100644 --- a/index.html +++ b/index.html @@ -445,6 +445,13 @@
  • And now we're are the top!
  • +

    Inline List

    +
      +
    • Item 1
    • +
    • Item 2
    • +
    • Item 3
    • +
    • Item 4
    • +
     <ol>
    diff --git a/src/lists.less b/src/lists.less
    index 053a6bc..07b5078 100644
    --- a/src/lists.less
    +++ b/src/lists.less
    @@ -41,4 +41,8 @@ ul {
                 }
             }
         }
    +    &.inline li{
    +        display: inline;
    +        margin-left: 5px;
    +    }
     }
    \ No newline at end of file
    
    From c1da0dc912bfb475966b2751e360af4a14e3d673 Mon Sep 17 00:00:00 2001
    From: Fraham 
    Date: Mon, 27 Nov 2017 23:06:12 +0000
    Subject: [PATCH 07/57] Test Setup
    
    ---
     .gitignore                            |    3 +-
     tests/build/config/build_command.js   |   16 +
     tests/build/config/jshint.conf.js     |   66 +
     tests/build/config/karma.conf.js      |   75 +
     tests/build/config/paths.js           |   20 +
     tests/build/config/tested_browsers.js |   17 +
     tests/build/scripts/build.jakefile.js |  128 +
     tests/build/scripts/run_jake.bat      |    7 +
     tests/build/scripts/run_jake.sh       |    6 +
     tests/build/scripts/watch.js          |   28 +
     tests/build/util/browserify_runner.js |   21 +
     tests/build/util/karma_runner.js      |   76 +
     tests/build/util/mocha_runner.js      |   54 +
     tests/build/util/sh.js                |   48 +
     tests/build/util/version_checker.js   |   29 +
     tests/jake.bat                        |    1 +
     tests/jake.sh                         |    2 +
     tests/package.json                    |   33 +
     tests/quixote.js                      | 4195 +++++++++++++++++++
     tests/src/_media_css_test.js          |  135 +
     tests/src/icon.svg                    |   17 +
     tests/src/index.html                  |   28 +
     tests/vendor/chai-2.1.0.js            | 5332 +++++++++++++++++++++++++
     tests/vendor/quixote.js               | 4195 +++++++++++++++++++
     24 files changed, 14531 insertions(+), 1 deletion(-)
     create mode 100644 tests/build/config/build_command.js
     create mode 100644 tests/build/config/jshint.conf.js
     create mode 100644 tests/build/config/karma.conf.js
     create mode 100644 tests/build/config/paths.js
     create mode 100644 tests/build/config/tested_browsers.js
     create mode 100644 tests/build/scripts/build.jakefile.js
     create mode 100644 tests/build/scripts/run_jake.bat
     create mode 100644 tests/build/scripts/run_jake.sh
     create mode 100644 tests/build/scripts/watch.js
     create mode 100644 tests/build/util/browserify_runner.js
     create mode 100644 tests/build/util/karma_runner.js
     create mode 100644 tests/build/util/mocha_runner.js
     create mode 100644 tests/build/util/sh.js
     create mode 100644 tests/build/util/version_checker.js
     create mode 100644 tests/jake.bat
     create mode 100644 tests/jake.sh
     create mode 100644 tests/package.json
     create mode 100644 tests/quixote.js
     create mode 100644 tests/src/_media_css_test.js
     create mode 100644 tests/src/icon.svg
     create mode 100644 tests/src/index.html
     create mode 100644 tests/vendor/chai-2.1.0.js
     create mode 100644 tests/vendor/quixote.js
    
    diff --git a/.gitignore b/.gitignore
    index 8774cec..1778ff3 100644
    --- a/.gitignore
    +++ b/.gitignore
    @@ -1,3 +1,4 @@
     /node_modules
     npm-debug.log
    -/dist
    \ No newline at end of file
    +/dist
    +/tests/node_modules
    \ No newline at end of file
    diff --git a/tests/build/config/build_command.js b/tests/build/config/build_command.js
    new file mode 100644
    index 0000000..c946448
    --- /dev/null
    +++ b/tests/build/config/build_command.js
    @@ -0,0 +1,16 @@
    +// Copyright (c) 2012 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.
    +
    +// A cross-platform mechanism for determining how to run the build.
    +(function() {
    +	"use strict";
    +
    +	var UNIX_BUILD_COMMAND = "./jake.sh";
    +	var WINDOWS_BUILD_COMMAND = "jake.bat";
    +
    +	var os = require("os");
    +
    +	exports.get = function() {
    +		return os.platform() === "win32" ? WINDOWS_BUILD_COMMAND : UNIX_BUILD_COMMAND;
    +	};
    +
    +}());
    \ No newline at end of file
    diff --git a/tests/build/config/jshint.conf.js b/tests/build/config/jshint.conf.js
    new file mode 100644
    index 0000000..68f1d81
    --- /dev/null
    +++ b/tests/build/config/jshint.conf.js
    @@ -0,0 +1,66 @@
    +// Copyright (c) 2015 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +
    +// Configuration options for JSHint. Change this to match your preferences.
    +
    +(function() {
    +	"use strict";
    +
    +	var merge = require("object-merge");
    +
    +	var universalOptions = {
    +		bitwise: true,
    +		curly: false,
    +		eqeqeq: true,
    +		forin: true,
    +		immed: true,
    +		latedef: false,
    +		newcap: true,
    +		noarg: true,
    +		noempty: true,
    +		nonew: true,
    +		regexp: true,
    +		undef: true,
    +		strict: true,
    +		globalstrict: true,     // "global" stricts are okay when using CommonJS modules
    +		trailing: true
    +	};
    +
    +	exports.nodeOptions = merge(universalOptions, {
    +		node: true
    +	});
    +
    +	exports.clientOptions = merge(universalOptions, {
    +		browser: true
    +	});
    +
    +	var universalGlobals = {
    +		// Mocha
    +		before: false,
    +		after: false,
    +		beforeEach: false,
    +		afterEach: false,
    +		describe: false,
    +		it: false
    +	};
    +
    +	exports.nodeGlobals = merge(universalGlobals, {
    +		// Jake
    +		jake: false,
    +		desc: false,
    +		task: false,
    +		directory: false,
    +		complete: false,
    +		fail: false
    +	});
    +
    +	exports.clientGlobals = merge(universalGlobals, {
    +		// CommonJS
    +		exports: false,
    +		require: false,
    +		module: false,
    +
    +		// Browser
    +		console: false
    +	});
    +
    +}());
    \ No newline at end of file
    diff --git a/tests/build/config/karma.conf.js b/tests/build/config/karma.conf.js
    new file mode 100644
    index 0000000..61f8f7c
    --- /dev/null
    +++ b/tests/build/config/karma.conf.js
    @@ -0,0 +1,75 @@
    +// Karma configuration
    +// Quixote-specific configuration starts with "QUIXOTE:"
    +
    +(function() {
    +	"use strict";
    +
    +	var paths = require("./paths.js");
    +
    +	module.exports = function(config) {
    +		config.set({
    +
    +			// base path, that will be used to resolve files and exclude
    +			basePath: '../../..',
    +
    +			// frameworks to use
    +			frameworks: ['mocha', 'commonjs', 'chai'],
    +
    +			// list of files / patterns to load in the browser
    +			files: [
    +				'tests/src/**/*.js',
    +				'tests/quixote.js',
    +				'tests/vendor/**/*.js',
    +				// QUIXOTE: Serve the CSS file so we can load it in our tests
    +				// Mark it `included: false` so Karma doesn't load it automatically
    +				{ pattern: 'dist/paper.css', included: false }
    +			],
    +
    +			// list of files to exclude
    +			exclude: [],
    +
    +			// preprocess matching files before serving them to the browser
    +			// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    +			preprocessors: {
    +				'tests/src/**/*.js': ['commonjs'],
    +				'tests/quixote.js': ['commonjs'],
    +				'tests/vendor/**/*.js': ['commonjs']
    +			},
    +
    +			// test results reporter to use
    +			// possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
    +			reporters: ['dots'],
    +
    +			// web server port
    +			port: 9876,
    +
    +			// enable / disable colors in the output (reporters and logs)
    +			colors: true,
    +
    +			// level of logging
    +			// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    +			logLevel: config.LOG_INFO,
    +
    +			// enable / disable watching file and executing tests whenever any file changes
    +			autoWatch: false,
    +
    +			// Start these browsers, currently available:
    +			// - Chrome
    +			// - ChromeCanary
    +			// - Firefox
    +			// - Opera
    +			// - Safari (only Mac)
    +			// - PhantomJS
    +			// - IE (only Windows)
    +			browsers: [],
    +
    +			// If browser does not capture in given timeout [ms], kill it
    +			captureTimeout: 60000,
    +
    +			// Continuous Integration mode
    +			// if true, it capture browsers, run tests and exit
    +			singleRun: false
    +		});
    +	};
    +
    +}());
    diff --git a/tests/build/config/paths.js b/tests/build/config/paths.js
    new file mode 100644
    index 0000000..f53f40e
    --- /dev/null
    +++ b/tests/build/config/paths.js
    @@ -0,0 +1,20 @@
    +// Copyright (c) 2015 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +
    +// Lists commonly-used directories. They're all relative to the project root.
    +
    +(function() {
    +	"use strict";
    +
    +	module.exports = {
    +		generatedDir: "generated",
    +		testDir: "generated/test",
    +		distDir: "generated/dist",
    +		clientDistDir: "generated/dist/client",
    +
    +		buildDir: "build",
    +		clientDir: "src",
    +		clientEntryPoint: "src/toggle.js",
    +		clientDistBundle: "generated/dist/client/bundle.js"
    +	};
    +
    +}());
    \ No newline at end of file
    diff --git a/tests/build/config/tested_browsers.js b/tests/build/config/tested_browsers.js
    new file mode 100644
    index 0000000..61cf10c
    --- /dev/null
    +++ b/tests/build/config/tested_browsers.js
    @@ -0,0 +1,17 @@
    +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +(function() {
    +	"use strict";
    +
    +	// Uncomment and modify the following list to cause the build to fail unless these browsers are tested.
    +	// There's no Quixote-specific configuration in this file.
    +
    +	module.exports = [
    +		//"IE 10.0.0 (Windows 7 0.0.0)",
    +		//"Firefox 41.0.0 (Mac OS X 10.10.0)",
    +		//"Chrome 46.0.2490 (Mac OS X 10.10.5)",
    +		//"Safari 9.0.1 (Mac OS X 10.10.5)",
    +		//"Mobile Safari 8.0.0 (iOS 8.4.0)",
    +		//"Chrome Mobile 44.0.2403 (Android 6.0.0)"
    +	];
    +
    +}());
    \ No newline at end of file
    diff --git a/tests/build/scripts/build.jakefile.js b/tests/build/scripts/build.jakefile.js
    new file mode 100644
    index 0000000..e371876
    --- /dev/null
    +++ b/tests/build/scripts/build.jakefile.js
    @@ -0,0 +1,128 @@
    +// Copyright (c) 2012-2014 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.
    +
    +// Main build file. Contains all tasks needed for normal development.
    +// There's no Quixote-specific configuration in this file.
    +
    +(function() {
    +	"use strict";
    +
    +	var startTime = Date.now();
    +
    +	var shell = require("shelljs");
    +	var jshint = require("simplebuild-jshint");
    +	var karma = require("simplebuild-karma");
    +	var browserify = require("../util/browserify_runner.js");
    +
    +	var browsers = require("../config/tested_browsers.js");
    +	var jshintConfig = require("../config/jshint.conf.js");
    +	var paths = require("../config/paths.js");
    +
    +	var KARMA_CONFIG = "./build/config/karma.conf.js";
    +
    +	var strict = !process.env.loose;
    +
    +
    +	//*** GENERAL
    +
    +	desc("Lint and test");
    +	task("default", [ "lint", "test" ], function() {
    +		var elapsedSeconds = (Date.now() - startTime) / 1000;
    +		console.log("\n\nBUILD OK  (" + elapsedSeconds.toFixed(2) + "s)");
    +	});
    +
    +	desc("Start server (for manual testing)");
    +	task("run", [ "build" ], function() {
    +		jake.exec("node ../node_modules/http-server/bin/http-server " + paths.clientDistDir, { interactive: true }, complete);
    +	}, { async: true });
    +
    +	desc("Delete generated files");
    +	task("clean", function() {
    +		shell.rm("-rf", paths.generatedDir);
    +	});
    +
    +
    +	//*** LINT
    +
    +	desc("Lint everything");
    +	task("lint", ["lintNode", "lintClient"]);
    +
    +	task("lintNode", function() {
    +		process.stdout.write("Linting Node.js code: ");
    +		jshint.checkFiles({
    +			files: [ paths.buildDir + "/**/*.js" ],
    +			options: jshintConfig.nodeOptions,
    +			globals: jshintConfig.nodeGlobals
    +		}, complete, fail);
    +	}, { async: true });
    +
    +	task("lintClient", function() {
    +		process.stdout.write("Linting browser code: ");
    +		jshint.checkFiles({
    +			files: [ paths.clientDir + "/**/*.js" ],
    +			options: jshintConfig.clientOptions,
    +			globals: jshintConfig.clientGlobals
    +		}, complete, fail);
    +	}, { async: true });
    +
    +
    +	//*** TEST
    +
    +	desc("Start Karma server -- run this first");
    +	task("karma", function() {
    +		karma.start({
    +			configFile: KARMA_CONFIG
    +		}, complete, fail);
    +	}, { async: true });
    +
    +	desc("Run tests");
    +	task("test", function() {
    +		console.log("Testing browser code: ");
    +
    +		var browsersToCapture = process.env.capture ? process.env.capture.split(",") : [];
    +		karma.run({
    +			configFile: KARMA_CONFIG,
    +			expectedBrowsers: browsers,
    +			strict: strict,
    +			capture: browsersToCapture
    +		}, complete, fail);
    +	}, { async: true });
    +
    +
    +	//*** BUILD
    +
    +	desc("Build distribution package");
    +	task("build", [ "prepDistDir", "buildClient" ]);
    +
    +	task("prepDistDir", function() {
    +		shell.rm("-rf", paths.distDir);
    +	});
    +
    +	task("buildClient", [ paths.clientDistDir, "bundleClientJs" ], function() {
    +		console.log("Copying client code: .");
    +		shell.cp(
    +			paths.clientDir + "/*.html",
    +			paths.clientDir + "/*.css",
    +			paths.clientDir + "/*.svg",
    +			paths.clientDistDir
    +		);
    +	});
    +
    +	task("bundleClientJs", [ paths.clientDistDir ], function() {
    +		console.log("Bundling browser code with Browserify: .");
    +		browserify.bundle({
    +			entry: paths.clientEntryPoint,
    +			outfile: paths.clientDistBundle,
    +			options: {
    +				standalone: "toggle",
    +				debug: true
    +			}
    +		}, complete, fail);
    +	}, { async: true });
    +
    +
    +	//*** CREATE DIRECTORIES
    +
    +	directory(paths.testDir);
    +	directory(paths.clientDistDir);
    +
    +}());
    \ No newline at end of file
    diff --git a/tests/build/scripts/run_jake.bat b/tests/build/scripts/run_jake.bat
    new file mode 100644
    index 0000000..edbfe6b
    --- /dev/null
    +++ b/tests/build/scripts/run_jake.bat
    @@ -0,0 +1,7 @@
    +@echo off
    +REM Runs Jake from node_modules directory, preventing it from needing to be installed globally
    +REM Also ensures node modules have been installed
    +REM There's no Quixote-specific configuration in this file.
    +
    +if not exist node_modules\.bin\jake.cmd call npm install
    +node_modules\.bin\jake %*
    \ No newline at end of file
    diff --git a/tests/build/scripts/run_jake.sh b/tests/build/scripts/run_jake.sh
    new file mode 100644
    index 0000000..89804b6
    --- /dev/null
    +++ b/tests/build/scripts/run_jake.sh
    @@ -0,0 +1,6 @@
    +# Runs Jake from node_modules directory, preventing it from needing to be installed globally
    +# Also ensures node modules have been installed
    +# There's no Quixote-specific configuration in this file.
    +
    +[ ! -f node_modules/.bin/jake ] && echo "Installing npm modules:" && npm install
    +node_modules/.bin/jake $*
    \ No newline at end of file
    diff --git a/tests/build/scripts/watch.js b/tests/build/scripts/watch.js
    new file mode 100644
    index 0000000..ee4ceb3
    --- /dev/null
    +++ b/tests/build/scripts/watch.js
    @@ -0,0 +1,28 @@
    +// Copyright (c) 2015 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +
    +// Automatically runs build when files change.
    +// There's no Quixote-specific configuration in this file.
    +
    +(function() {
    +	"use strict";
    +
    +	var nodemon = require("nodemon");
    +	var buildCommand = require("../config/build_command.js");
    +	var paths = require("../config/paths.js");
    +
    +	console.log("*** Using nodemon to run " + buildCommand.get() + ". Type 'rs' to force restart.");
    +	nodemon({
    +		ext: "sh bat json js html css",
    +		ignore: paths.generatedDir,
    +		exec: buildCommand.get() + " " + process.argv.slice(2).join(" "),
    +		execMap: {
    +			sh: "/bin/sh",
    +			bat: "cmd.exe /c",
    +			cmd: "cmd.exe /c"
    +		}
    +	}).on("restart", function(files) {
    +		if (files) console.log("*** Restarting due to", files);
    +		else console.log("*** Restarting");
    +	});
    +
    +}());
    diff --git a/tests/build/util/browserify_runner.js b/tests/build/util/browserify_runner.js
    new file mode 100644
    index 0000000..353d878
    --- /dev/null
    +++ b/tests/build/util/browserify_runner.js
    @@ -0,0 +1,21 @@
    +/* Copyright (c) 2014 Titanium I.T. LLC - See LICENSE.txt for license */
    +
    +// Helper function for running Browserify
    +// There's no Quixote-specific configuration in this file.
    +
    +"use strict";
    +
    +var fs = require("fs");
    +var path = require("path");
    +var browserify = require("browserify");
    +
    +exports.bundle = function(config, success, failure) {
    +	var b = browserify(config.options);
    +
    +	b.add(path.resolve(config.entry));
    +	b.bundle(function(err, bundle) {
    +		if (err) return failure(err);
    +		fs.writeFileSync(config.outfile, bundle);
    +		return success();
    +	});
    +};
    \ No newline at end of file
    diff --git a/tests/build/util/karma_runner.js b/tests/build/util/karma_runner.js
    new file mode 100644
    index 0000000..535b22a
    --- /dev/null
    +++ b/tests/build/util/karma_runner.js
    @@ -0,0 +1,76 @@
    +// Copyright (c) 2012-2015 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.
    +
    +// Helper functions for running Karma
    +// There's no Quixote-specific configuration in this file.
    +
    +(function() {
    +	"use strict";
    +
    +	var path = require("path");
    +	var sh = require("./sh.js");
    +	var runner = require("karma/lib/runner");
    +	var server = require("karma/lib/server");
    +
    +	var KARMA = "node node_modules/karma/bin/karma";
    +
    +	exports.serve = function(configFile, success, fail) {
    +		var command = KARMA + " start " + configFile;
    +		sh.run(command, success, function () {
    +			fail("Could not start Karma server");
    +		});
    +	};
    +
    +	exports.runTests = function(options, success, fail) {
    +		options.capture = options.capture || [];
    +		var config = {
    +			configFile: path.resolve(options.configFile),
    +			browsers: options.capture,
    +			singleRun: options.capture.length > 0
    +		};
    +
    +		var runKarma = runner.run.bind(runner);
    +		if (config.singleRun) runKarma = server.start.bind(server);
    +
    +		var stdout = new CapturedStdout();
    +		runKarma(config, function(exitCode) {
    +			stdout.restore();
    +
    +			if (exitCode) return fail("Client tests failed (did you start the Karma server?)");
    +			var browserMissing = checkRequiredBrowsers(options.browsers, stdout);
    +			if (browserMissing && options.strict) return fail("Did not test all browsers");
    +			if (stdout.capturedOutput.indexOf("TOTAL: 0 SUCCESS") !== -1) return fail("No tests were run!");
    +
    +			return success();
    +		});
    +	};
    +
    +	function checkRequiredBrowsers(requiredBrowsers, stdout) {
    +		var browserMissing = false;
    +		requiredBrowsers.forEach(function(browser) {
    +			browserMissing = lookForBrowser(browser, stdout.capturedOutput) || browserMissing;
    +		});
    +		return browserMissing;
    +	}
    +
    +	function lookForBrowser(browser, output) {
    +		var missing = output.indexOf(browser + ": Executed") === -1;
    +		if (missing) console.log("Warning: " + browser + " was not tested!");
    +		return missing;
    +	}
    +
    +	function CapturedStdout() {
    +		var self = this;
    +		self.oldStdout = process.stdout.write;
    +		self.capturedOutput = "";
    +
    +		process.stdout.write = function(data) {
    +			self.capturedOutput += data;
    +			self.oldStdout.apply(this, arguments);
    +		};
    +	}
    +
    +	CapturedStdout.prototype.restore = function() {
    +		process.stdout.write = this.oldStdout;
    +	};
    +
    +}());
    \ No newline at end of file
    diff --git a/tests/build/util/mocha_runner.js b/tests/build/util/mocha_runner.js
    new file mode 100644
    index 0000000..eb8bc86
    --- /dev/null
    +++ b/tests/build/util/mocha_runner.js
    @@ -0,0 +1,54 @@
    +// Copyright (c) 2014-2015 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.
    +
    +// Helper function for running Mocha
    +// There's no Quixote-specific configuration in this file.
    +
    +(function() {
    +	"use strict";
    +
    +	var Mocha = require("mocha");
    +	var jake = require("jake");
    +
    +	exports.runTests = function runTests(options, success, failure) {
    +		var mocha = new Mocha(options.options);
    +		var files = deglob(options.files);
    +		files.forEach(mocha.addFile.bind(mocha));
    +
    +		//   This is a bit of a hack. The issue is this: during test execution, if an exception is thrown inside
    +		// of a callback (and keep in mind that assertions throw exceptions), there's no way for Mocha to catch
    +		// that exception.
    +		//   So Mocha registers an 'uncaughtException' handler on Node's process object. That way any unhandled
    +		// exception is passed to Mocha.
    +		//   The problem is that Jake ALSO listens for 'uncaughtException'. Its handler and Mocha's handler don't
    +		// get along. Somehow the Jake handler seems to terminate Mocha's test run... not sure why. We need to
    +		// disable Jake's handler while Mocha is running.
    +		//   This code disables ALL uncaughtException handlers and then restores them after Mocha is done. It's
    +		// very hacky and likely to cause problems in certain edge cases (for example, '.once' listeners aren't
    +		// restored properly), but it seems to be working for now.
    +		//   It might be possible to create a better solution by using Node's 'domain' module. Something to look
    +		// into if you're reading this. Another solution is to just spawn Mocha in a separate process, but I
    +		// didn't want the time penalty involved. Besides, this seems to be working okay.
    +		var savedListeners = disableExceptionListeners();
    +
    +		var runner = mocha.run(function(failures) {
    +			restoreExceptionListeners(savedListeners);
    +			if (failures) return failure("Tests failed");
    +			else return success();
    +		});
    +	};
    +
    +	function deglob(globs) {
    +		return new jake.FileList(globs).toArray();
    +	}
    +
    +	function disableExceptionListeners() {
    +		var listeners = process.listeners("uncaughtException");
    +		process.removeAllListeners("uncaughtException");
    +		return listeners;
    +	}
    +
    +	function restoreExceptionListeners(listeners) {
    +		listeners.forEach(process.addListener.bind(process, "uncaughtException"));
    +	}
    +
    +}());
    diff --git a/tests/build/util/sh.js b/tests/build/util/sh.js
    new file mode 100644
    index 0000000..78b3881
    --- /dev/null
    +++ b/tests/build/util/sh.js
    @@ -0,0 +1,48 @@
    +// Copyright (c) 2012-2015 Titanium I.T. LLC. All rights reserved. See LICENSE.txt for details.
    +
    +// Helper functions for running processes.
    +// There's no Quixote-specific configuration in this file.
    +
    +(function() {
    +	"use strict";
    +
    +	var jake = require("jake");
    +
    +	exports.runMany = function(commands, successCallback, failureCallback) {
    +		var stdout = [];
    +		function serializedSh(command) {
    +			if (command) {
    +				run(command, function(oneStdout) {
    +					stdout.push(oneStdout);
    +					serializedSh(commands.shift());
    +				}, failureCallback);
    +			}
    +			else {
    +				successCallback(stdout);
    +			}
    +		}
    +		serializedSh(commands.shift());
    +	};
    +
    +	var run = exports.run = function(oneCommand, successCallback, failureCallback) {
    +		var stdout = "";
    +		var child = jake.createExec(oneCommand);
    +		child.on("stdout", function(data) {
    +			process.stdout.write(data);
    +			stdout += data;
    +		});
    +		child.on("stderr", function(data) {
    +			process.stderr.write(data);
    +		});
    +		child.on("cmdEnd", function() {
    +			successCallback(stdout);
    +		});
    +		child.on("error", function() {
    +			failureCallback(stdout);
    +		});
    +
    +		console.log("> " + oneCommand);
    +		child.run();
    +	};
    +
    +}());
    \ No newline at end of file
    diff --git a/tests/build/util/version_checker.js b/tests/build/util/version_checker.js
    new file mode 100644
    index 0000000..f4bbd1e
    --- /dev/null
    +++ b/tests/build/util/version_checker.js
    @@ -0,0 +1,29 @@
    +// Copyright (c) 2013 Titanium I.T. LLC. All rights reserved. See LICENSE.TXT for details.
    +
    +// Helper function for checking version numbers.
    +// There's no Quixote-specific configuration in this file.
    +
    +
    +(function() {
    +	"use strict";
    +
    +	var semver = require("semver");
    +
    +	exports.check = function(options, success, fail) {
    +		if (options.strict) {
    +			if (semver.neq(options.actual, options.expected)) return failWithQualifier("exactly");
    +		}
    +		else {
    +			if (semver.lt(options.actual, options.expected)) return failWithQualifier("at least");
    +			if (semver.neq(options.actual, options.expected)) console.log("Warning: Newer " + options.name +
    +				" version than expected. Expected " + options.expected + ", but was " + options.actual + ".");
    +		}
    +		return success();
    +
    +		function failWithQualifier(qualifier) {
    +			return fail("Incorrect " + options.name + " version. Expected " + qualifier +
    +				" " + options.expected + ", but was " + options.actual + ".");
    +		}
    +	};
    +
    +}());
    \ No newline at end of file
    diff --git a/tests/jake.bat b/tests/jake.bat
    new file mode 100644
    index 0000000..774c0fc
    --- /dev/null
    +++ b/tests/jake.bat
    @@ -0,0 +1 @@
    +@call build\scripts\run_jake -f build\scripts\build.jakefile.js %*
    diff --git a/tests/jake.sh b/tests/jake.sh
    new file mode 100644
    index 0000000..a3bff26
    --- /dev/null
    +++ b/tests/jake.sh
    @@ -0,0 +1,2 @@
    +#!/bin/sh
    +. build/scripts/run_jake.sh -f build/scripts/build.jakefile.js $*
    diff --git a/tests/package.json b/tests/package.json
    new file mode 100644
    index 0000000..1055f55
    --- /dev/null
    +++ b/tests/package.json
    @@ -0,0 +1,33 @@
    +{
    +  "name": "papercss-tests",
    +  "version": "1.1.0",
    +  "description": "Another CSS framework",
    +  "dependencies": {
    +    "http-server": "^0.9.0"
    +  },
    +  "devDependencies": {
    +    "browserify": "^14.1.0",
    +    "chai": "^4.1.2",
    +    "jake": "^8.0.15",
    +    "jshint": "^2.9.4",
    +    "karma": "^1.7.1",
    +    "karma-chai": "^0.1.0",
    +    "karma-commonjs": "1.0.0",
    +    "karma-firefox-launcher": "^1.0.0",
    +    "karma-mocha": "^1.3.0",
    +    "karma-quixote": "^1.0.0",
    +    "less": "2.7.2",
    +    "mocha": "^4.0.1",
    +    "nodemon": "^1.11.0",
    +    "normalize.css": "7.0.0",
    +    "npm": "5.3.0",
    +    "object-merge": "^2.5.1",
    +    "procfile": "^0.1.1",
    +    "quixote": "^0.14.0",
    +    "semver": "^5.3.0",
    +    "shelljs": "^0.7.6",
    +    "simplebuild-jshint": "^1.3.0",
    +    "simplebuild-karma": "^1.0.0",
    +    "webpack-dev-server": "2.7.1"
    +  }
    +}
    diff --git a/tests/quixote.js b/tests/quixote.js
    new file mode 100644
    index 0000000..bd9f804
    --- /dev/null
    +++ b/tests/quixote.js
    @@ -0,0 +1,4195 @@
    +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.quixote = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1) {
    +        for (var i = 1; i < arguments.length; i++) {
    +            args[i - 1] = arguments[i];
    +        }
    +    }
    +    queue.push(new Item(fun, args));
    +    if (queue.length === 1 && !draining) {
    +        runTimeout(drainQueue);
    +    }
    +};
    +
    +// v8 likes predictible objects
    +function Item(fun, array) {
    +    this.fun = fun;
    +    this.array = array;
    +}
    +Item.prototype.run = function () {
    +    this.fun.apply(null, this.array);
    +};
    +process.title = 'browser';
    +process.browser = true;
    +process.env = {};
    +process.argv = [];
    +process.version = ''; // empty string to avoid regexp issues
    +process.versions = {};
    +
    +function noop() {}
    +
    +process.on = noop;
    +process.addListener = noop;
    +process.once = noop;
    +process.off = noop;
    +process.removeListener = noop;
    +process.removeAllListeners = noop;
    +process.emit = noop;
    +process.prependListener = noop;
    +process.prependOnceListener = noop;
    +
    +process.listeners = function (name) { return [] }
    +
    +process.binding = function (name) {
    +    throw new Error('process.binding is not supported');
    +};
    +
    +process.cwd = function () { return '/' };
    +process.chdir = function (dir) {
    +    throw new Error('process.chdir is not supported');
    +};
    +process.umask = function() { return 0; };
    +
    +},{}],2:[function(require,module,exports){
    +// Copyright (c) 2015 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("./util/ensure.js");
    +var oop = require("./util/oop.js");
    +var shim = require("./util/shim.js");
    +
    +var Me = module.exports = function Assertable() {
    +	ensure.unreachable("Assertable is abstract and should not be constructed directly.");
    +};
    +Me.extend = oop.extendFn(Me);
    +oop.makeAbstract(Me, []);
    +
    +Me.prototype.assert = function assert(expected, message) {
    +	ensure.signature(arguments, [ Object, [undefined, String] ]);
    +	if (message === undefined) message = "Differences found";
    +
    +	var diff = this.diff(expected);
    +	if (diff !== "") throw new Error(message + ":\n" + diff + "\n");
    +};
    +
    +Me.prototype.diff = function diff(expected) {
    +	ensure.signature(arguments, [ Object ]);
    +
    +	var result = [];
    +	var keys = shim.Object.keys(expected);
    +	var key, oneDiff, descriptor;
    +	for (var i = 0; i < keys.length; i++) {
    +		key = keys[i];
    +		descriptor = this[key];
    +		ensure.that(
    +				descriptor !== undefined,
    +				this + " doesn't have a property named '" + key + "'. Did you misspell it?"
    +		);
    +		oneDiff = descriptor.diff(expected[key]);
    +		if (oneDiff !== "") result.push(oneDiff);
    +	}
    +
    +	return result.join("\n");
    +};
    +
    +},{"./util/ensure.js":23,"./util/oop.js":24,"./util/shim.js":25}],3:[function(require,module,exports){
    +// Copyright (c) 2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var PositionDescriptor = require("./position_descriptor.js");
    +var Position = require("../values/position.js");
    +
    +var X_DIMENSION = "x";
    +var Y_DIMENSION = "y";
    +
    +var Me = module.exports = function AbsolutePosition(dimension, value) {
    +  ensure.signature(arguments, [ String, Number ]);
    +
    +  switch(dimension) {
    +		case X_DIMENSION:
    +			PositionDescriptor.x(this);
    +			this._value = Position.x(value);
    +			break;
    +		case Y_DIMENSION:
    +			PositionDescriptor.y(this);
    +			this._value = Position.y(value);
    +			break;
    +		default: ensure.unreachable("Unknown dimension: " + dimension);
    +  }
    +  this._dimension = dimension;
    +};
    +PositionDescriptor.extend(Me);
    +
    +Me.x = function(value) {
    +	ensure.signature(arguments, [ Number ]);
    +  return new Me(X_DIMENSION, value);
    +};
    +
    +Me.y = function(value) {
    +	ensure.signature(arguments, [ Number ]);
    +	return new Me(Y_DIMENSION, value);
    +};
    +
    +Me.prototype.value = function() {
    +  return this._value;
    +};
    +
    +Me.prototype.toString = function() {
    +  return this._value + " " + this._dimension + "-coordinate";
    +};
    +
    +},{"../util/ensure.js":23,"../values/position.js":27,"./position_descriptor.js":11}],4:[function(require,module,exports){
    +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var PositionDescriptor = require("./position_descriptor.js");
    +var Position = require("../values/position.js");
    +var RelativePosition = require("./relative_position.js");
    +
    +var X_DIMENSION = "x";
    +var Y_DIMENSION = "y";
    +
    +var Me = module.exports = function Center(dimension, position1, position2, description) {
    +	ensure.signature(arguments, [ String, PositionDescriptor, PositionDescriptor, String ]);
    +
    +	if (dimension === X_DIMENSION) PositionDescriptor.x(this);
    +	else if (dimension === Y_DIMENSION) PositionDescriptor.y(this);
    +	else ensure.unreachable("Unknown dimension: " + dimension);
    +
    +	this._dimension = dimension;
    +	this._position1 = position1;
    +	this._position2 = position2;
    +	this._description = description;
    +};
    +PositionDescriptor.extend(Me);
    +
    +Me.x = factoryFn(X_DIMENSION);
    +Me.y = factoryFn(Y_DIMENSION);
    +
    +Me.prototype.value = function value() {
    +	ensure.signature(arguments, []);
    +	return this._position1.value().midpoint(this._position2.value());
    +};
    +
    +Me.prototype.toString = function toString() {
    +	ensure.signature(arguments, []);
    +	return this._description;
    +};
    +
    +function factoryFn(dimension) {
    +	return function(position1, position2, description) {
    +		return new Me(dimension, position1, position2, description);
    +	};
    +}
    +
    +},{"../util/ensure.js":23,"../values/position.js":27,"./position_descriptor.js":11,"./relative_position.js":12}],5:[function(require,module,exports){
    +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var oop = require("../util/oop.js");
    +var Value = require("../values/value.js");
    +
    +var Me = module.exports = function Descriptor() {
    +	ensure.unreachable("Descriptor is abstract and should not be constructed directly.");
    +};
    +Me.extend = oop.extendFn(Me);
    +oop.makeAbstract(Me, [
    +	"value",
    +	"toString"
    +]);
    +
    +Me.prototype.diff = function diff(expected) {
    +	expected = normalizeType(this, expected);
    +	try {
    +		var actualValue = this.value();
    +		var expectedValue = expected.value();
    +
    +		if (actualValue.equals(expectedValue)) return "";
    +
    +		var difference = actualValue.diff(expectedValue);
    +		var expectedDesc = expectedValue.toString();
    +		if (expected instanceof Me) expectedDesc += " (" + expected + ")";
    +
    +		return this + " was " + difference + ".\n" +
    +			"  Expected: " + expectedDesc + "\n" +
    +			"  But was:  " + actualValue;
    +	}
    +	catch (err) {
    +		throw new Error("Can't compare " + this + " to " + expected + ": " + err.message);
    +	}
    +};
    +
    +Me.prototype.convert = function convert(arg, type) {
    +	// This method is meant to be overridden by subclasses. It should return 'undefined' when an argument
    +	// can't be converted. In this default implementation, no arguments can be converted, so we always
    +	// return 'undefined'.
    +	return undefined;
    +};
    +
    +Me.prototype.equals = function equals(that) {
    +	// Descriptors aren't value objects. They're never equal to anything. But sometimes
    +	// they're used in the same places value objects are used, and this method gets called.
    +	return false;
    +};
    +
    +function normalizeType(self, expected) {
    +	var expectedType = typeof expected;
    +	if (expected === null) expectedType = "null";
    +
    +	if (expectedType === "object" && (expected instanceof Me || expected instanceof Value)) return expected;
    +
    +	if (expected === undefined) {
    +		throw new Error("Can't compare " + self + " to " + expected + ". Did you misspell a property name?");
    +	}
    +	else if (expectedType === "object") {
    +		throw new Error("Can't compare " + self + " to " + oop.instanceName(expected) + " instances.");
    +	}
    +	else {
    +		var converted = self.convert(expected, expectedType);
    +		if (converted !== undefined) return converted;
    +
    +		var explanation = expected;
    +		if (expectedType === "string") explanation = "'" + explanation + "'";
    +		if (expectedType === "function") explanation = "a function";
    +
    +		throw new Error("Can't compare " + self + " to " + explanation + ".");
    +	}
    +
    +}
    +
    +},{"../util/ensure.js":23,"../util/oop.js":24,"../values/value.js":30}],6:[function(require,module,exports){
    +// Copyright (c) 2014-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var Position = require("../values/position.js");
    +var PositionDescriptor = require("./position_descriptor.js");
    +
    +var TOP = "top";
    +var RIGHT = "right";
    +var BOTTOM = "bottom";
    +var LEFT = "left";
    +
    +var Me = module.exports = function ElementEdge(element, position) {
    +	var QElement = require("../q_element.js");      // break circular dependency
    +	ensure.signature(arguments, [QElement, String]);
    +
    +	if (position === LEFT || position === RIGHT) PositionDescriptor.x(this);
    +	else if (position === TOP || position === BOTTOM) PositionDescriptor.y(this);
    +	else ensure.unreachable("Unknown position: " + position);
    +
    +	this._element = element;
    +	this._position = position;
    +};
    +PositionDescriptor.extend(Me);
    +
    +Me.top = factoryFn(TOP);
    +Me.right = factoryFn(RIGHT);
    +Me.bottom = factoryFn(BOTTOM);
    +Me.left = factoryFn(LEFT);
    +
    +Me.prototype.value = function value() {
    +	ensure.signature(arguments, []);
    +
    +	var rawPosition = this._element.getRawPosition();
    +
    +	var edge = rawPosition[this._position];
    +	var scroll = this._element.frame.getRawScrollPosition();
    +
    +	if (this._position === RIGHT || this._position === LEFT) {
    +		if (!elementRendered(this, rawPosition)) return Position.noX();
    +		return Position.x(edge + scroll.x);
    +	}
    +	else {
    +		if (!elementRendered(this, rawPosition)) return Position.noY();
    +		return Position.y(edge + scroll.y);
    +	}
    +};
    +
    +Me.prototype.toString = function toString() {
    +	ensure.signature(arguments, []);
    +	return this._position + " edge of " + this._element;
    +};
    +
    +function factoryFn(position) {
    +	return function factory(element) {
    +		return new Me(element, position);
    +	};
    +}
    +
    +function elementRendered(self, rawPosition) {
    +	var element = self._element;
    +
    +	var inDom = element.frame.body().toDomElement().contains(element.toDomElement());
    +	var displayNone = element.getRawStyle("display") === "none";
    +
    +	return inDom && !displayNone;
    +}
    +
    +},{"../q_element.js":17,"../util/ensure.js":23,"../values/position.js":27,"./position_descriptor.js":11}],7:[function(require,module,exports){
    +// Copyright (c) 2016-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var RenderState = require("../values/render_state.js");
    +var Position = require("../values/position.js");
    +var Descriptor = require("./descriptor.js");
    +var ElementRenderedEdge = require("./element_rendered_edge.js");
    +var GenericSize = require("./generic_size.js");
    +var Center = require("./center.js");
    +
    +var Me = module.exports = function ElementRendered(element) {
    +	var QElement = require("../q_element.js");      // break circular dependency
    +	ensure.signature(arguments, [ QElement ]);
    +
    +	this._element = element;
    +
    +	// properties
    +	this.top = ElementRenderedEdge.top(element);
    +	this.right = ElementRenderedEdge.right(element);
    +	this.bottom = ElementRenderedEdge.bottom(element);
    +	this.left = ElementRenderedEdge.left(element);
    +
    +	this.width = GenericSize.create(this.left, this.right, "rendered width of " + element);
    +	this.height = GenericSize.create(this.top, this.bottom, "rendered height of " + element);
    +
    +	this.center = Center.x(this.left, this.right, "rendered center of " + element);
    +	this.middle = Center.y(this.top, this.bottom, "rendered middle of " + element);
    +};
    +Descriptor.extend(Me);
    +
    +Me.create = function create(element) {
    +	return new Me(element);
    +};
    +
    +Me.prototype.value = function value() {
    +	if (this.top.value().equals(Position.noY())) return RenderState.notRendered();
    +	else return RenderState.rendered();
    +};
    +
    +Me.prototype.toString = function toString() {
    +	return "render status of " + this._element.toString();
    +};
    +
    +Me.prototype.convert = function convert(arg, type) {
    +	if (type === "boolean") {
    +		return arg ? RenderState.rendered() : RenderState.notRendered();
    +	}
    +};
    +
    +},{"../q_element.js":17,"../util/ensure.js":23,"../values/position.js":27,"../values/render_state.js":28,"./center.js":4,"./descriptor.js":5,"./element_rendered_edge.js":8,"./generic_size.js":9}],8:[function(require,module,exports){
    +// Copyright (c) 2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var quixote = require("../quixote.js");
    +var PositionDescriptor = require("./position_descriptor.js");
    +var Position = require("../values/position.js");
    +var Size = require("../values/size.js");
    +var RenderState = require("../values/render_state.js");
    +
    +var TOP = "top";
    +var RIGHT = "right";
    +var BOTTOM = "bottom";
    +var LEFT = "left";
    +
    +var Me = module.exports = function ElementVisibleEdge(element, position) {
    +	var QElement = require("../q_element.js");      // break circular dependency
    +	ensure.signature(arguments, [ QElement, String ]);
    +
    +	if (position === LEFT || position === RIGHT) PositionDescriptor.x(this);
    +	else if (position === TOP || position === BOTTOM) PositionDescriptor.y(this);
    +	else unknownPosition(position);
    +
    +	this._element = element;
    +	this._position = position;
    +};
    +PositionDescriptor.extend(Me);
    +
    +Me.top = factoryFn(TOP);
    +Me.right = factoryFn(RIGHT);
    +Me.bottom = factoryFn(BOTTOM);
    +Me.left = factoryFn(LEFT);
    +
    +function factoryFn(position) {
    +	return function factory(element) {
    +		return new Me(element, position);
    +	};
    +}
    +
    +Me.prototype.toString = function toString() {
    +	ensure.signature(arguments, []);
    +	return this._position + " rendered edge of " + this._element;
    +};
    +
    +Me.prototype.value = function() {
    +	var position = this._position;
    +	var element = this._element;
    +	var page = element.frame.page();
    +
    +	if (element.top.value().equals(Position.noY())) return notRendered(position);
    +	if (element.width.value().equals(Size.create(0))) return notRendered(position);
    +	if (element.height.value().equals(Size.create(0))) return notRendered(position);
    +
    +	ensure.that(
    +		!hasClipPathProperty(element),
    +		"Can't detect element clipping boundaries when 'clip-path' property is used."
    +	);
    +
    +	var bounds = {
    +		top: page.top.value(),
    +		right: null,
    +		bottom: null,
    +		left: page.left.value()
    +	};
    +
    +	bounds = intersectionWithOverflow(element, bounds);
    +	bounds = intersectionWithClip(element, bounds);
    +
    +	var edges = intersection(
    +		bounds,
    +		element.top.value(),
    +		element.right.value(),
    +		element.bottom.value(),
    +		element.left.value()
    +	);
    +
    +	if (isClippedOutOfExistence(bounds, edges)) return notRendered(position);
    +	else return edge(edges, position);
    +};
    +
    +function hasClipPathProperty(element) {
    +	var clipPath = element.getRawStyle("clip-path");
    +	return clipPath !== "none" && clipPath !== "";
    +}
    +
    +function intersectionWithOverflow(element, bounds) {
    +	for (var container = element.parent(); container !== null; container = container.parent()) {
    +		if (isClippedByAncestorOverflow(element, container)) {
    +			bounds = intersection(
    +				bounds,
    +				container.top.value(),
    +				container.right.value(),
    +				container.bottom.value(),
    +				container.left.value()
    +			);
    +		}
    +	}
    +
    +	return bounds;
    +}
    +
    +function intersectionWithClip(element, bounds) {
    +	// WORKAROUND IE 8: Doesn't have any way to detect 'clip: auto' value.
    +	ensure.that(!quixote.browser.misreportsClipAutoProperty(),
    +		"Can't determine element clipping values on this browser because it misreports the value of the" +
    +		" `clip: auto` property. You can use `quixote.browser.misreportsClipAutoProperty()` to skip this browser."
    +	);
    +
    +	for ( ; element !== null; element = element.parent()) {
    +		var clip = element.getRawStyle("clip");
    +		if (clip === "auto" || !canBeClippedByClipProperty(element)) continue;
    +
    +		var clipEdges = normalizeClipProperty(element, clip);
    +		bounds = intersection(
    +			bounds,
    +			clipEdges.top,
    +			clipEdges.right,
    +			clipEdges.bottom,
    +			clipEdges.left
    +		);
    +	}
    +
    +	return bounds;
    +}
    +
    +function normalizeClipProperty(element, clip) {
    +	var clipValues = parseClipProperty(element, clip);
    +
    +	return {
    +		top: clipValues[0] === "auto" ?
    +			element.top.value() :
    +			element.top.value().plus(Position.y(Number(clipValues[0]))),
    +		right: clipValues[1] === "auto" ?
    +			element.right.value() :
    +			element.left.value().plus(Position.x(Number(clipValues[1]))),
    +		bottom: clipValues[2] === "auto" ?
    +			element.bottom.value() :
    +			element.top.value().plus(Position.y(Number(clipValues[2]))),
    +		left: clipValues[3] === "auto" ?
    +			element.left.value() :
    +			element.left.value().plus(Position.x(Number(clipValues[3])))
    +	};
    +
    +	function parseClipProperty(element, clip) {
    +		// WORKAROUND IE 11, Chrome Mobile 44: Reports 0px instead of 'auto' when computing rect() in clip property.
    +		ensure.that(!quixote.browser.misreportsAutoValuesInClipProperty(),
    +			"Can't determine element clipping values on this browser because it misreports the value of the `clip`" +
    +			" property. You can use `quixote.browser.misreportsAutoValuesInClipProperty()` to skip this browser."
    +		);
    +
    +		var clipRegex = /rect\((.*?),? (.*?),? (.*?),? (.*?)\)/;
    +		var matches = clipRegex.exec(clip);
    +		ensure.that(matches !== null, "Unable to parse clip property: " + clip);
    +
    +		return [
    +			parseLength(matches[1], clip),
    +			parseLength(matches[2], clip),
    +			parseLength(matches[3], clip),
    +			parseLength(matches[4], clip)
    +		];
    +	}
    +
    +	function parseLength(pxString, clip) {
    +		if (pxString === "auto") return pxString;
    +
    +		var pxRegex = /^(.*?)px$/;
    +		var matches = pxRegex.exec(pxString);
    +		ensure.that(matches !== null, "Unable to parse '" + pxString + "' in clip property: " + clip);
    +
    +		return matches[1];
    +	}
    +}
    +
    +function isClippedByAncestorOverflow(element, ancestor) {
    +	return canBeClippedByOverflowProperty(element) && hasClippingOverflow(ancestor);
    +}
    +
    +function canBeClippedByOverflowProperty(element) {
    +	var position = element.getRawStyle("position");
    +	switch (position) {
    +		case "static":
    +		case "relative":
    +		case "absolute":
    +		case "sticky":
    +			return true;
    +		case "fixed":
    +			return false;
    +		default:
    +			ensure.unreachable("Unknown position property: " + position);
    +	}
    +}
    +
    +function hasClippingOverflow(element) {
    +	var overflow = element.getRawStyle("overflow");
    +	switch (overflow) {
    +		case "hidden":
    +		case "scroll":
    +		case "auto":
    +			return true;
    +		case "visible":
    +			return false;
    +		default:
    +			ensure.unreachable("Unknown overflow property: " + overflow);
    +	}
    +}
    +
    +function canBeClippedByClipProperty(element) {
    +	var position = element.getRawStyle("position");
    +	switch (position) {
    +		case "absolute":
    +		case "fixed":
    +			return true;
    +		case "static":
    +		case "relative":
    +		case "sticky":
    +			return false;
    +		default:
    +			ensure.unreachable("Unknown position property: " + position);
    +	}
    +}
    +
    +function intersection(bounds, top, right, bottom, left) {
    +	bounds.top = bounds.top.max(top);
    +	bounds.right = (bounds.right === null) ? right : bounds.right.min(right);
    +	bounds.bottom = (bounds.bottom === null) ? bottom : bounds.bottom.min(bottom);
    +	bounds.left = bounds.left.max(left);
    +
    +	return bounds;
    +}
    +
    +function isClippedOutOfExistence(bounds, edges) {
    +	return (bounds.top.compare(edges.bottom) >= 0) ||
    +		(bounds.right !== null && bounds.right.compare(edges.left) <= 0) ||
    +		(bounds.bottom !== null && bounds.bottom.compare(edges.top) <= 0) ||
    +		(bounds.left.compare(edges.right) >= 0);
    +}
    +
    +function notRendered(position) {
    +	switch(position) {
    +		case TOP:
    +		case BOTTOM:
    +			return Position.noY();
    +		case LEFT:
    +		case RIGHT:
    +			return Position.noX();
    +		default: unknownPosition(position);
    +	}
    +}
    +
    +function edge(edges, position) {
    +	switch(position) {
    +		case TOP: return edges.top;
    +		case RIGHT: return edges.right;
    +		case BOTTOM: return edges.bottom;
    +		case LEFT: return edges.left;
    +		default: unknownPosition(position);
    +	}
    +}
    +
    +function unknownPosition(position) {
    +	ensure.unreachable("Unknown position: " + position);
    +}
    +
    +},{"../q_element.js":17,"../quixote.js":22,"../util/ensure.js":23,"../values/position.js":27,"../values/render_state.js":28,"../values/size.js":29,"./position_descriptor.js":11}],9:[function(require,module,exports){
    +// Copyright (c) 2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var PositionDescriptor = require("./position_descriptor.js");
    +var SizeDescriptor = require("./size_descriptor.js");
    +
    +var Me = module.exports = function GenericSize(from, to, description) {
    +  ensure.signature(arguments, [ PositionDescriptor, PositionDescriptor, String ]);
    +
    +  this._from = from;
    +  this._to = to;
    +  this._description = description;
    +};
    +SizeDescriptor.extend(Me);
    +
    +Me.create = function(from, to, description) {
    +  return new Me(from, to, description);
    +};
    +
    +Me.prototype.value = function() {
    +  ensure.signature(arguments, []);
    +  return this._from.value().distanceTo(this._to.value());
    +};
    +
    +Me.prototype.toString = function() {
    +  return this._description;
    +};
    +
    +},{"../util/ensure.js":23,"./position_descriptor.js":11,"./size_descriptor.js":14}],10:[function(require,module,exports){
    +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var PositionDescriptor = require("./position_descriptor.js");
    +var Position = require("../values/position.js");
    +
    +var TOP = "top";
    +var RIGHT = "right";
    +var BOTTOM = "bottom";
    +var LEFT = "left";
    +
    +var Me = module.exports = function PageEdge(edge, frame) {
    +	var QFrame = require("../q_frame.js");    // break circular dependency
    +	ensure.signature(arguments, [ String, QFrame ]);
    +
    +	if (edge === LEFT || edge === RIGHT) PositionDescriptor.x(this);
    +	else if (edge === TOP || edge === BOTTOM) PositionDescriptor.y(this);
    +	else ensure.unreachable("Unknown edge: " + edge);
    +
    +	this._edge = edge;
    +	this._frame = frame;
    +};
    +PositionDescriptor.extend(Me);
    +
    +Me.top = factoryFn(TOP);
    +Me.right = factoryFn(RIGHT);
    +Me.bottom = factoryFn(BOTTOM);
    +Me.left = factoryFn(LEFT);
    +
    +Me.prototype.value = function value() {
    +	ensure.signature(arguments, []);
    +
    +	var size = pageSize(this._frame.toDomElement().contentDocument);
    +	switch(this._edge) {
    +		case TOP: return Position.y(0);
    +		case RIGHT: return Position.x(size.width);
    +		case BOTTOM: return Position.y(size.height);
    +		case LEFT: return Position.x(0);
    +
    +		default: ensure.unreachable();
    +	}
    +};
    +
    +Me.prototype.toString = function toString() {
    +	ensure.signature(arguments, []);
    +
    +	switch(this._edge) {
    +		case TOP: return "top of page";
    +		case RIGHT: return "right side of page";
    +		case BOTTOM: return "bottom of page";
    +		case LEFT: return "left side of page";
    +
    +		default: ensure.unreachable();
    +	}
    +};
    +
    +function factoryFn(edge) {
    +	return function factory(frame) {
    +		return new Me(edge, frame);
    +	};
    +}
    +
    +
    +
    +// USEFUL READING: http://www.quirksmode.org/mobile/viewports.html
    +// and http://www.quirksmode.org/mobile/viewports2.html
    +
    +// API SEMANTICS.
    +// Ref https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/Determining_the_dimensions_of_elements
    +//    getBoundingClientRect().width: sum of bounding boxes of element (the displayed width of the element,
    +//      including padding and border). Fractional. Applies transformations.
    +//    clientWidth: visible width of element including padding (but not border). EXCEPT on root element (html), where
    +//      it is the width of the viewport. Rounds to an integer. Doesn't apply transformations.
    +//    offsetWidth: visible width of element including padding, border, and scrollbars (if any). Rounds to an integer.
    +//      Doesn't apply transformations.
    +//    scrollWidth: entire width of element, including any part that's not visible due to scrollbars. Rounds to
    +//      an integer. Doesn't apply transformations. Not clear if it includes scrollbars, but I think not. Also
    +//      not clear if it includes borders or padding. (But from tests, apparently not borders. Except on root
    +//      element and body element, which have special results that vary by browser.)
    +
    +// TEST RESULTS: WIDTH
    +//   ✔ = correct answer
    +//   ✘ = incorrect answer and diverges from spec
    +//   ~ = incorrect answer, but matches spec
    +// BROWSERS TESTED: Safari 6.2.0 (Mac OS X 10.8.5); Mobile Safari 7.0.0 (iOS 7.1); Firefox 32.0.0 (Mac OS X 10.8);
    +//    Firefox 33.0.0 (Windows 7); Chrome 38.0.2125 (Mac OS X 10.8.5); Chrome 38.0.2125 (Windows 7); IE 8, 9, 10, 11
    +
    +// html width style smaller than viewport width; body width style smaller than html width style
    +//  NOTE: These tests were conducted when correct result was width of border. That has been changed
    +//  to "width of viewport."
    +//    html.getBoundingClientRect().width
    +//      ✘ IE 8, 9, 10: width of viewport
    +//      ✔ Safari, Mobile Safari, Chrome, Firefox, IE 11: width of html, including border
    +//    html.clientWidth
    +//      ~ Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11: width of viewport
    +//    html.offsetWidth
    +//      ✘ IE 8, 9, 10: width of viewport
    +//      ✔ Safari, Mobile Safari, Chrome, Firefox, IE 11: width of html, including border
    +//    html.scrollWidth
    +//      ✘ IE 8, 9, 10, 11, Firefox: width of viewport
    +//      ~ Safari, Mobile Safari, Chrome: width of html, excluding border
    +//    body.getBoundingClientRect().width
    +//      ~ Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11: width of body, including border
    +//    body.clientWidth
    +//      ~ Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11: width of body, excluding border
    +//    body.offsetWidth
    +//      ~ Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11: width of body, including border
    +//    body.scrollWidth
    +//      ✘ Safari, Mobile Safari, Chrome: width of viewport
    +//      ~ Firefox, IE 8, 9, 10, 11: width of body, excluding border
    +
    +// element width style wider than viewport; body and html width styles at default
    +// BROWSER BEHAVIOR: html and body border extend to width of viewport and not beyond (except on Mobile Safari)
    +// Correct result is element width + body border-left + html border-left (except on Mobile Safari)
    +// Mobile Safari uses a layout viewport, so it's expected to include body border-right and html border-right.
    +//    html.getBoundingClientRect().width
    +//      ✔ Mobile Safari: element width + body border + html border
    +//      ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: viewport width
    +//    html.clientWidth
    +//      ✔ Mobile Safari: element width + body border + html border
    +//      ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: viewport width
    +//    html.offsetWidth
    +//      ✔ Mobile Safari: element width + body border + html border
    +//      ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: viewport width
    +//    html.scrollWidth
    +//      ✔ Mobile Safari: element width + body border + html border
    +//      ✘ Safari, Chrome: element width + body border-left (BUT NOT html border-left)
    +//      ✔ Firefox, IE 8, 9, 10, 11: element width + body border-left + html border-left
    +//    body.getBoundingClientRect().width
    +//      ~ Mobile Safari: element width + body border
    +//      ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: viewport width - html border
    +//    body.clientWidth
    +//      ~ Mobile Safari: element width
    +//      ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: viewport width - html border - body border
    +//    body.offsetWidth
    +//      ~ Mobile Safari: element width + body border
    +//      ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: viewport width - html border
    +//    body.scrollWidth
    +//      ✔ Mobile Safari: element width + body border + html border
    +//      ✔ Safari, Chrome: element width + body border-left + html border-left (matches actual browser)
    +//      ~ Firefox, IE 8, 9, 10, 11: element width
    +
    +// TEST RESULTS: HEIGHT
    +//   ✔ = correct answer
    +//   ✘ = incorrect answer and diverges from spec
    +//   ~ = incorrect answer, but matches spec
    +
    +// html height style smaller than viewport height; body height style smaller than html height style
    +//  NOTE: These tests were conducted when correct result was height of viewport.
    +//    html.clientHeight
    +//      ✔ Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11: height of viewport
    +
    +// element height style taller than viewport; body and html width styles at default
    +// BROWSER BEHAVIOR: html and body border enclose entire element
    +// Correct result is element width + body border-top + html border-top + body border-bottom + html border-bottom
    +//    html.clientHeight
    +//      ✔ Mobile Safari: element height + all borders
    +//      ~ Safari, Chrome, Firefox, IE 8, 9, 10, 11: height of viewport
    +//    html.scrollHeight
    +//      ✔ Firefox, IE 8, 9, 10, 11: element height + all borders
    +//      ✘ Safari, Mobile Safari, Chrome: element height + html border-bottom
    +//    body.scrollHeight
    +//      ✔ Safari, Mobile Safari, Chrome: element height + all borders
    +//      ~ Firefox, IE 8, 9, 10, 11: element height (body height - body border)
    +function pageSize(document) {
    +	var html = document.documentElement;
    +	var body = document.body;
    +
    +// BEST WIDTH ANSWER SO FAR (ASSUMING VIEWPORT IS MINIMUM ANSWER):
    +	var width = Math.max(body.scrollWidth, html.scrollWidth);
    +
    +// BEST HEIGHT ANSWER SO FAR (ASSUMING VIEWPORT IS MINIMUM ANSWER):
    +	var height = Math.max(body.scrollHeight, html.scrollHeight);
    +
    +	return {
    +		width: width,
    +		height: height
    +	};
    +}
    +
    +},{"../q_frame.js":19,"../util/ensure.js":23,"../values/position.js":27,"./position_descriptor.js":11}],11:[function(require,module,exports){
    +// Copyright (c) 2014-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +/*eslint new-cap: "off" */
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var oop = require("../util/oop.js");
    +var Descriptor = require("./descriptor.js");
    +var Position = require("../values/position.js");
    +
    +// break circular dependencies
    +function RelativePosition() {
    +	return require("./relative_position.js");
    +}
    +function AbsolutePosition() {
    +	return require("./absolute_position.js");
    +}
    +function GenericSize() {
    +	return require("./generic_size.js");
    +}
    +
    +var X_DIMENSION = "x";
    +var Y_DIMENSION = "y";
    +
    +var Me = module.exports = function PositionDescriptor(dimension) {
    +	ensure.signature(arguments, [ String ]);
    +	ensure.unreachable("PositionDescriptor is abstract and should not be constructed directly.");
    +};
    +Descriptor.extend(Me);
    +Me.extend = oop.extendFn(Me);
    +
    +Me.x = factoryFn(X_DIMENSION);
    +Me.y = factoryFn(Y_DIMENSION);
    +
    +Me.prototype.plus = function plus(amount) {
    +	if (this._pdbc.dimension === X_DIMENSION) return RelativePosition().right(this, amount);
    +	else return RelativePosition().down(this, amount);
    +};
    +
    +Me.prototype.minus = function minus(amount) {
    +	if (this._pdbc.dimension === X_DIMENSION) return RelativePosition().left(this, amount);
    +	else return RelativePosition().up(this, amount);
    +};
    +
    +Me.prototype.to = function to(position) {
    +	ensure.signature(arguments, [[ Me, Number ]]);
    +	if (typeof position === "number") {
    +		if (this._pdbc.dimension === X_DIMENSION) position = AbsolutePosition().x(position);
    +		else position = AbsolutePosition().y(position);
    +	}
    +	if (this._pdbc.dimension !== position._pdbc.dimension) {
    +		throw new Error("Can only calculate distance between two X coordinates or two Y coordinates");
    +	}
    +
    +	return GenericSize().create(this, position, "distance from " + this + " to " + position);
    +};
    +
    +Me.prototype.convert = function convert(arg, type) {
    +	switch (type) {
    +		case "number": return this._pdbc.dimension === X_DIMENSION ? Position.x(arg) : Position.y(arg);
    +		case "string":
    +			if (arg === "none") return this._pdbc.dimension === X_DIMENSION ? Position.noX() : Position.noY();
    +			else return undefined;
    +			break;
    +		default: return undefined;
    +	}
    +};
    +
    +function factoryFn(dimension) {
    +	return function factory(self) {
    +		// _pdbc: "PositionDescriptor base class." An attempt to prevent name conflicts.
    +		self._pdbc = { dimension: dimension };
    +	};
    +}
    +
    +},{"../util/ensure.js":23,"../util/oop.js":24,"../values/position.js":27,"./absolute_position.js":3,"./descriptor.js":5,"./generic_size.js":9,"./relative_position.js":12}],12:[function(require,module,exports){
    +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var Descriptor = require("./descriptor.js");
    +var PositionDescriptor = require("./position_descriptor.js");
    +var Value = require("../values/value.js");
    +var Size = require("../values/size.js");
    +
    +var X_DIMENSION = "x";
    +var Y_DIMENSION = "y";
    +var PLUS = 1;
    +var MINUS = -1;
    +
    +var Me = module.exports = function RelativePosition(dimension, direction, relativeTo, relativeAmount) {
    +	ensure.signature(arguments, [ String, Number, Descriptor, [Number, Descriptor, Value] ]);
    +
    +	if (dimension === X_DIMENSION) PositionDescriptor.x(this);
    +	else if (dimension === Y_DIMENSION) PositionDescriptor.y(this);
    +	else ensure.unreachable("Unknown dimension: " + dimension);
    +
    +	this._dimension = dimension;
    +	this._direction = direction;
    +	this._relativeTo = relativeTo;
    +
    +	if (typeof relativeAmount === "number") {
    +		if (relativeAmount < 0) this._direction *= -1;
    +		this._amount = Size.create(Math.abs(relativeAmount));
    +	}
    +	else {
    +		this._amount = relativeAmount;
    +	}
    +};
    +PositionDescriptor.extend(Me);
    +
    +Me.right = createFn(X_DIMENSION, PLUS);
    +Me.down = createFn(Y_DIMENSION, PLUS);
    +Me.left = createFn(X_DIMENSION, MINUS);
    +Me.up = createFn(Y_DIMENSION, MINUS);
    +
    +function createFn(dimension, direction) {
    +	return function create(relativeTo, relativeAmount) {
    +		return new Me(dimension, direction, relativeTo, relativeAmount);
    +	};
    +}
    +
    +Me.prototype.value = function value() {
    +	ensure.signature(arguments, []);
    +
    +	var baseValue = this._relativeTo.value();
    +	var relativeValue = this._amount.value();
    +
    +	if (this._direction === PLUS) return baseValue.plus(relativeValue);
    +	else return baseValue.minus(relativeValue);
    +};
    +
    +Me.prototype.toString = function toString() {
    +	ensure.signature(arguments, []);
    +
    +	var base = this._relativeTo.toString();
    +	if (this._amount.equals(Size.create(0))) return base;
    +
    +	var relation = this._amount.toString();
    +	if (this._dimension === X_DIMENSION) relation += (this._direction === PLUS) ? " to right of " : " to left of ";
    +	else relation += (this._direction === PLUS) ? " below " : " above ";
    +
    +	return relation + base;
    +};
    +
    +},{"../util/ensure.js":23,"../values/size.js":29,"../values/value.js":30,"./descriptor.js":5,"./position_descriptor.js":11}],13:[function(require,module,exports){
    +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var Size = require("../values/size.js");
    +var Descriptor = require("./descriptor.js");
    +var SizeDescriptor = require("./size_descriptor.js");
    +var Value = require("../values/value.js");
    +var SizeMultiple = require("./size_multiple.js");
    +
    +var PLUS = 1;
    +var MINUS = -1;
    +
    +var Me = module.exports = function RelativeSize(direction, relativeTo, amount) {
    +	ensure.signature(arguments, [ Number, Descriptor, [Number, Descriptor, Value] ]);
    +
    +	this._direction = direction;
    +	this._relativeTo = relativeTo;
    +
    +	if (typeof amount === "number") {
    +		this._amount = Size.create(Math.abs(amount));
    +		if (amount < 0) this._direction *= -1;
    +	}
    +	else {
    +		this._amount = amount;
    +	}
    +};
    +SizeDescriptor.extend(Me);
    +
    +Me.larger = factoryFn(PLUS);
    +Me.smaller = factoryFn(MINUS);
    +
    +Me.prototype.value = function value() {
    +	ensure.signature(arguments, []);
    +
    +	var baseValue = this._relativeTo.value();
    +	var relativeValue = this._amount.value();
    +
    +	if (this._direction === PLUS) return baseValue.plus(relativeValue);
    +	else return baseValue.minus(relativeValue);
    +};
    +
    +Me.prototype.toString = function toString() {
    +	ensure.signature(arguments, []);
    +
    +	var base = this._relativeTo.toString();
    +	if (this._amount.equals(Size.create(0))) return base;
    +
    +	var relation = this._amount.toString();
    +	if (this._direction === PLUS) relation += " larger than ";
    +	else relation += " smaller than ";
    +
    +	return relation + base;
    +};
    +
    +function factoryFn(direction) {
    +	return function factory(relativeTo, amount) {
    +		return new Me(direction, relativeTo, amount);
    +	};
    +}
    +},{"../util/ensure.js":23,"../values/size.js":29,"../values/value.js":30,"./descriptor.js":5,"./size_descriptor.js":14,"./size_multiple.js":15}],14:[function(require,module,exports){
    +// Copyright (c) 2014-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +/*eslint new-cap: "off" */
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var oop = require("../util/oop.js");
    +var Descriptor = require("./descriptor.js");
    +var Size = require("../values/size.js");
    +
    +function RelativeSize() {
    +	return require("./relative_size.js");   	// break circular dependency
    +}
    +
    +function SizeMultiple() {
    +	return require("./size_multiple.js");   	// break circular dependency
    +}
    +
    +var Me = module.exports = function SizeDescriptor() {
    +	ensure.unreachable("SizeDescriptor is abstract and should not be constructed directly.");
    +};
    +Descriptor.extend(Me);
    +Me.extend = oop.extendFn(Me);
    +
    +Me.prototype.plus = function plus(amount) {
    +	return RelativeSize().larger(this, amount);
    +};
    +
    +Me.prototype.minus = function minus(amount) {
    +	return RelativeSize().smaller(this, amount);
    +};
    +
    +Me.prototype.times = function times(amount) {
    +	return SizeMultiple().create(this, amount);
    +};
    +
    +Me.prototype.convert = function convert(arg, type) {
    +	switch(type) {
    +		case "number": return Size.create(arg);
    +		case "string": return arg === "none" ? Size.createNone() : undefined;
    +		default: return undefined;
    +	}
    +};
    +
    +},{"../util/ensure.js":23,"../util/oop.js":24,"../values/size.js":29,"./descriptor.js":5,"./relative_size.js":13,"./size_multiple.js":15}],15:[function(require,module,exports){
    +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var Descriptor = require("./descriptor.js");
    +var SizeDescriptor = require("./size_descriptor.js");
    +var Size = require("../values/size.js");
    +
    +var Me = module.exports = function SizeMultiple(relativeTo, multiple) {
    +	ensure.signature(arguments, [ Descriptor, Number ]);
    +
    +	this._relativeTo = relativeTo;
    +	this._multiple = multiple;
    +};
    +SizeDescriptor.extend(Me);
    +
    +Me.create = function create(relativeTo, multiple) {
    +	return new Me(relativeTo, multiple);
    +};
    +
    +Me.prototype.value = function value() {
    +	ensure.signature(arguments, []);
    +
    +	return this._relativeTo.value().times(this._multiple);
    +};
    +
    +Me.prototype.toString = function toString() {
    +	ensure.signature(arguments, []);
    +
    +	var multiple = this._multiple;
    +	var base = this._relativeTo.toString();
    +	if (multiple === 1) return base;
    +
    +	var desc;
    +	switch(multiple) {
    +		case 1/2: desc = "half of "; break;
    +		case 1/3: desc = "one-third of "; break;
    +		case 2/3: desc = "two-thirds of "; break;
    +		case 1/4: desc = "one-quarter of "; break;
    +		case 3/4: desc = "three-quarters of "; break;
    +		case 1/5: desc = "one-fifth of "; break;
    +		case 2/5: desc = "two-fifths of "; break;
    +		case 3/5: desc = "three-fifths of "; break;
    +		case 4/5: desc = "four-fifths of "; break;
    +		case 1/6: desc = "one-sixth of "; break;
    +		case 5/6: desc = "five-sixths of "; break;
    +		case 1/8: desc = "one-eighth of "; break;
    +		case 3/8: desc = "three-eighths of "; break;
    +		case 5/8: desc = "five-eighths of "; break;
    +		case 7/8: desc = "seven-eighths of "; break;
    +		default:
    +			if (multiple > 1) desc = multiple + " times ";
    +			else desc = (multiple * 100) + "% of ";
    +	}
    +
    +	return desc + base;
    +};
    +},{"../util/ensure.js":23,"../values/size.js":29,"./descriptor.js":5,"./size_descriptor.js":14}],16:[function(require,module,exports){
    +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("../util/ensure.js");
    +var PositionDescriptor = require("./position_descriptor.js");
    +var Position = require("../values/position.js");
    +
    +var TOP = "top";
    +var RIGHT = "right";
    +var BOTTOM = "bottom";
    +var LEFT = "left";
    +
    +var Me = module.exports = function ViewportEdge(position, frame) {
    +	var QFrame = require("../q_frame.js");    // break circular dependency
    +	ensure.signature(arguments, [ String, QFrame ]);
    +
    +	if (position === LEFT || position === RIGHT) PositionDescriptor.x(this);
    +	else if (position === TOP || position === BOTTOM) PositionDescriptor.y(this);
    +	else ensure.unreachable("Unknown position: " + position);
    +
    +	this._position = position;
    +	this._frame = frame;
    +};
    +PositionDescriptor.extend(Me);
    +
    +Me.top = factoryFn(TOP);
    +Me.right = factoryFn(RIGHT);
    +Me.bottom = factoryFn(BOTTOM);
    +Me.left = factoryFn(LEFT);
    +
    +Me.prototype.value = function() {
    +	ensure.signature(arguments, []);
    +
    +	var scroll = this._frame.getRawScrollPosition();
    +	var x = Position.x(scroll.x);
    +	var y = Position.y(scroll.y);
    +
    +	var size = viewportSize(this._frame.get("html").toDomElement());
    +
    +	switch(this._position) {
    +		case TOP: return y;
    +		case RIGHT: return x.plus(Position.x(size.width));
    +		case BOTTOM: return y.plus(Position.y(size.height));
    +		case LEFT: return x;
    +
    +		default: ensure.unreachable();
    +	}
    +};
    +
    +Me.prototype.toString = function() {
    +	ensure.signature(arguments, []);
    +	return this._position + " edge of viewport";
    +};
    +
    +function factoryFn(position) {
    +	return function factory(frame) {
    +		return new Me(position, frame);
    +	};
    +}
    +
    +
    +
    +// USEFUL READING: http://www.quirksmode.org/mobile/viewports.html
    +// and http://www.quirksmode.org/mobile/viewports2.html
    +
    +// BROWSERS TESTED: Safari 6.2.0 (Mac OS X 10.8.5); Mobile Safari 7.0.0 (iOS 7.1); Firefox 32.0.0 (Mac OS X 10.8);
    +//    Firefox 33.0.0 (Windows 7); Chrome 38.0.2125 (Mac OS X 10.8.5); Chrome 38.0.2125 (Windows 7); IE 8, 9, 10, 11
    +
    +// Width techniques I've tried: (Note: results are different in quirks mode)
    +// body.clientWidth
    +// body.offsetWidth
    +// body.getBoundingClientRect().width
    +//    fails on all browsers: doesn't include margin
    +// body.scrollWidth
    +//    works on Safari, Mobile Safari, Chrome
    +//    fails on Firefox, IE 8, 9, 10, 11: doesn't include margin
    +// html.getBoundingClientRect().width
    +// html.offsetWidth
    +//    works on Safari, Mobile Safari, Chrome, Firefox
    +//    fails on IE 8, 9, 10: includes scrollbar
    +// html.scrollWidth
    +// html.clientWidth
    +//    WORKS! Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11
    +
    +// Height techniques I've tried: (Note that results are different in quirks mode)
    +// body.clientHeight
    +// body.offsetHeight
    +// body.getBoundingClientRect().height
    +//    fails on all browsers: only includes height of content
    +// body getComputedStyle("height")
    +//    fails on all browsers: IE8 returns "auto"; others only include height of content
    +// body.scrollHeight
    +//    works on Safari, Mobile Safari, Chrome;
    +//    fails on Firefox, IE 8, 9, 10, 11: only includes height of content
    +// html.getBoundingClientRect().height
    +// html.offsetHeight
    +//    works on IE 8, 9, 10
    +//    fails on IE 11, Safari, Mobile Safari, Chrome: only includes height of content
    +// html.scrollHeight
    +//    works on Firefox, IE 8, 9, 10, 11
    +//    fails on Safari, Mobile Safari, Chrome: only includes height of content
    +// html.clientHeight
    +//    WORKS! Safari, Mobile Safari, Chrome, Firefox, IE 8, 9, 10, 11
    +function viewportSize(htmlElement) {
    +	return {
    +		width: htmlElement.clientWidth,
    +		height: htmlElement.clientHeight
    +	};
    +}
    +
    +},{"../q_frame.js":19,"../util/ensure.js":23,"../values/position.js":27,"./position_descriptor.js":11}],17:[function(require,module,exports){
    +// Copyright (c) 2014-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("./util/ensure.js");
    +var shim = require("./util/shim.js");
    +var camelcase = require("../vendor/camelcase-1.0.1-modified.js");
    +var ElementRendered = require("./descriptors/element_rendered.js");
    +var ElementEdge = require("./descriptors/element_edge.js");
    +var Center = require("./descriptors/center.js");
    +var GenericSize = require("./descriptors/generic_size.js");
    +var Assertable = require("./assertable.js");
    +
    +var Me = module.exports = function QElement(domElement, frame, nickname) {
    +	var QFrame = require("./q_frame.js");    // break circular dependency
    +	ensure.signature(arguments, [Object, QFrame, String]);
    +
    +	this._domElement = domElement;
    +	this._nickname = nickname;
    +
    +	this.frame = frame;
    +
    +	// properties
    +	this.rendered = ElementRendered.create(this);
    +
    +	this.top = ElementEdge.top(this);
    +	this.right = ElementEdge.right(this);
    +	this.bottom = ElementEdge.bottom(this);
    +	this.left = ElementEdge.left(this);
    +
    +	this.center = Center.x(this.left, this.right, "center of " + this);
    +	this.middle = Center.y(this.top, this.bottom, "middle of " + this);
    +
    +	this.width = GenericSize.create(this.left, this.right, "width of " + this);
    +	this.height = GenericSize.create(this.top, this.bottom, "height of " + this);
    +};
    +Assertable.extend(Me);
    +
    +Me.prototype.getRawStyle = function(styleName) {
    +	ensure.signature(arguments, [String]);
    +
    +	var styles;
    +	var result;
    +
    +	// WORKAROUND IE 8: no getComputedStyle()
    +	if (window.getComputedStyle) {
    +		// WORKAROUND Firefox 40.0.3: must use frame's contentWindow (ref https://bugzilla.mozilla.org/show_bug.cgi?id=1204062)
    +		styles = this.frame.toDomElement().contentWindow.getComputedStyle(this._domElement);
    +		result = styles.getPropertyValue(styleName);
    +	}
    +	else {
    +		styles = this._domElement.currentStyle;
    +		result = styles[camelcase(styleName)];
    +	}
    +	if (result === null || result === undefined) result = "";
    +	return result;
    +};
    +
    +Me.prototype.getRawPosition = function() {
    +	ensure.signature(arguments, []);
    +
    +	// WORKAROUND IE 8: No TextRectangle.height or .width
    +	var rect = this._domElement.getBoundingClientRect();
    +	return {
    +		left: rect.left,
    +		right: rect.right,
    +		width: rect.width !== undefined ? rect.width : rect.right - rect.left,
    +
    +		top: rect.top,
    +		bottom: rect.bottom,
    +		height: rect.height !== undefined ? rect.height : rect.bottom - rect.top
    +	};
    +};
    +
    +Me.prototype.calculatePixelValue = function(sizeString) {
    +	var dom = this._domElement;
    +	if (dom.runtimeStyle !== undefined) return ie8Workaround();
    +
    +	var result;
    +	var style = dom.style;
    +	var oldPosition = style.position;
    +	var oldLeft = style.left;
    +
    +	style.position = "absolute";
    +	style.left = sizeString;
    +	result = parseFloat(this.getRawStyle("left"));    // parseInt strips of 'px' value
    +
    +	style.position = oldPosition;
    +	style.left = oldLeft;
    +	return result;
    +
    +	// WORKAROUND IE 8: getRawStyle() doesn't normalize values to px
    +	// Based on code by Dean Edwards: http://disq.us/p/myl99x
    +	function ie8Workaround() {
    +		var runtimeStyleLeft = dom.runtimeStyle.left;
    +		var styleLeft = dom.style.left;
    +
    +		dom.runtimeStyle.left = dom.currentStyle.left;
    +		dom.style.left = sizeString;
    +		result = dom.style.pixelLeft;
    +
    +		dom.runtimeStyle.left = runtimeStyleLeft;
    +		dom.style.left = styleLeft;
    +		return result;
    +	}
    +};
    +
    +Me.prototype.parent = function(nickname) {
    +	ensure.signature(arguments, [[ undefined, String ]]);
    +	if (nickname === undefined) nickname = "parent of " + this._nickname;
    +
    +	if (this.equals(this.frame.body())) return null;
    +
    +	var parent = this._domElement.parentElement;
    +	if (parent === null) return null;
    +
    +	return new Me(parent, this.frame, nickname);
    +};
    +
    +Me.prototype.add = function(html, nickname) {
    +	ensure.signature(arguments, [String, [undefined, String]]);
    +	if (nickname === undefined) nickname = html + " in " + this._nickname;
    +
    +	var tempElement = document.createElement("div");
    +	tempElement.innerHTML = shim.String.trim(html);
    +	ensure.that(
    +		tempElement.childNodes.length === 1,
    +		"Expected one element, but got " + tempElement.childNodes.length + " (" + html + ")"
    +	);
    +
    +	var insertedElement = tempElement.childNodes[0];
    +	this._domElement.appendChild(insertedElement);
    +	return new Me(insertedElement, this.frame, nickname);
    +};
    +
    +Me.prototype.remove = function() {
    +	ensure.signature(arguments, []);
    +	shim.Element.remove(this._domElement);
    +};
    +
    +Me.prototype.toDomElement = function() {
    +	ensure.signature(arguments, []);
    +	return this._domElement;
    +};
    +
    +Me.prototype.toString = function() {
    +	ensure.signature(arguments, []);
    +	return "'" + this._nickname + "'";
    +};
    +
    +Me.prototype.equals = function(that) {
    +	ensure.signature(arguments, [Me]);
    +	return this._domElement === that._domElement;
    +};
    +
    +},{"../vendor/camelcase-1.0.1-modified.js":32,"./assertable.js":2,"./descriptors/center.js":4,"./descriptors/element_edge.js":6,"./descriptors/element_rendered.js":7,"./descriptors/generic_size.js":9,"./q_frame.js":19,"./util/ensure.js":23,"./util/shim.js":25}],18:[function(require,module,exports){
    +// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("./util/ensure.js");
    +var QElement = require("./q_element.js");
    +
    +var Me = module.exports = function QElementList(nodeList, frame, nickname) {
    +	var QFrame = require("./q_frame.js");    // break circular dependency
    +	ensure.signature(arguments, [ Object, QFrame, String ]);
    +
    +	this._nodeList = nodeList;
    +	this._frame = frame;
    +	this._nickname = nickname;
    +};
    +
    +Me.prototype.length = function length() {
    +	ensure.signature(arguments, []);
    +
    +	return this._nodeList.length;
    +};
    +
    +Me.prototype.at = function at(requestedIndex, nickname) {
    +	ensure.signature(arguments, [ Number, [undefined, String] ]);
    +
    +	var index = requestedIndex;
    +	var length = this.length();
    +	if (index < 0) index = length + index;
    +
    +	ensure.that(
    +		index >= 0 && index < length,
    +		"'" + this._nickname + "'[" + requestedIndex + "] is out of bounds; list length is " + length
    +	);
    +	var element = this._nodeList[index];
    +
    +	if (nickname === undefined) nickname = this._nickname + "[" + index + "]";
    +	return new QElement(element, this._frame, nickname);
    +};
    +
    +Me.prototype.toString = function toString() {
    +	ensure.signature(arguments, []);
    +
    +	return "'" + this._nickname + "' list";
    +};
    +},{"./q_element.js":17,"./q_frame.js":19,"./util/ensure.js":23}],19:[function(require,module,exports){
    +// Copyright (c) 2014-2017 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
    +"use strict";
    +
    +var ensure = require("./util/ensure.js");
    +var shim = require("./util/shim.js");
    +var quixote = require("./quixote.js");
    +var QElement = require("./q_element.js");
    +var QElementList = require("./q_element_list.js");
    +var QViewport = require("./q_viewport.js");
    +var QPage = require("./q_page.js");
    +var async = require("../vendor/async-1.4.2.js");
    +
    +var Me = module.exports = function QFrame() {
    +	ensure.signature(arguments, []);
    +
    +	this._domElement = null;
    +	this._loaded = false;
    +	this._removed = false;
    +};
    +
    +function loaded(self, width, height, src, stylesheets) {
    +	self._loaded = true;
    +	self._document = self._domElement.contentDocument;
    +	self._originalBody = self._document.body.innerHTML;
    +	self._originalWidth = width;
    +	self._originalHeight = height;
    +	self._originalSrc = src;
    +	self._originalStylesheets = stylesheets;
    +}
    +
    +Me.create = function create(parentElement, options, callback) {
    +	ensure.signature(arguments, [Object, [Object, Function], [undefined, Function]]);
    +	if (callback === undefined) {
    +		callback = options;
    +		options = {};
    +	}
    +	var width = options.width || 2000;
    +	var height = options.height || 2000;
    +	var src = options.src;
    +	var stylesheets = options.stylesheet || [];
    +	var css = options.css;
    +	if (!shim.Array.isArray(stylesheets)) stylesheets = [ stylesheets ];
    +
    +	var frame = new Me();
    +	checkUrls(src, stylesheets, function(err) {
    +		if (err) return callback(err);
    +
    +		var iframe = insertIframe(parentElement, width, height);
    +		shim.EventTarget.addEventListener(iframe, "load", onFrameLoad);
    +		setIframeContent(iframe, src);
    +
    +		frame._domElement = iframe;
    +		setFrameLoadCallback(frame, callback);
    +	});
    +	return frame;
    +
    +	function onFrameLoad() {
    +		// WORKAROUND Mobile Safari 7.0.0, Safari 6.2.0, Chrome 38.0.2125: frame is loaded synchronously
    +		// We force it to be asynchronous here
    +		setTimeout(function() {
    +			loaded(frame, width, height, src, stylesheets);
    +			loadStylesheets(frame, stylesheets, function() {
    +				if (css) loadRawCSS(frame, options.css);
    +				frame._frameLoadCallback(null, frame);
    +			});
    +		}, 0);
    +	}
    +};
    +
    +function setFrameLoadCallback(frame, callback) {
    +	frame._frameLoadCallback = callback;
    +}
    +
    +function checkUrls(src, stylesheets, callback) {
    +	urlExists(src, function(err, srcExists) {
    +		if (err) return callback(err);
    +		if (!srcExists) return callback(error("src", src));
    +
    +		async.each(stylesheets, checkStylesheet, callback);
    +	});
    +
    +	function checkStylesheet(url, callback2) {
    +		urlExists(url, function(err, stylesheetExists) {
    +			if (err) return callback2(err);
    +
    +			if (!stylesheetExists) return callback2(error("stylesheet", url));
    +			else return callback2(null);
    +		});
    +	}
    +
    +	function error(name, url) {
    +		return new Error("404 error while loading " + name + " (" + url + ")");
    +	}
    +}
    +
    +function urlExists(url, callback) {
    +	var STATUS_AVAILABLE = 2;   // WORKAROUND IE 8: non-standard XMLHttpRequest constant names
    +
    +	if (url === undefined) {
    +		return callback(null, true);
    +	}
    +
    +	var http = new XMLHttpRequest();
    +	http.open("HEAD", url);
    +	http.onreadystatechange = function() {  // WORKAROUND IE 8: doesn't support .addEventListener() or .onload
    +		if (http.readyState === STATUS_AVAILABLE) {
    +			return callback(null, http.status !== 404);
    +		}
    +	};
    +	http.onerror = function() {     // onerror handler is not tested
    +		return callback("XMLHttpRequest error while using HTTP HEAD on URL '" + url + "': " + http.statusText);
    +	};
    +	http.send();
    +}
    +
    +function insertIframe(parentElement, width, height) {
    +	var iframe = document.createElement("iframe");
    +	iframe.setAttribute("width", width);
    +	iframe.setAttribute("height", height);
    +	iframe.setAttribute("frameborder", "0");    // WORKAROUND IE 8: don't include frame border in position calcs
    +	parentElement.appendChild(iframe);
    +	return iframe;
    +}
    +
    +function setIframeContent(iframe, src) {
    +	if (src === undefined) {
    +		writeStandardsModeHtml(iframe);
    +	}	else {
    +		setIframeSrc(iframe, src);
    +	}
    +}
    +
    +function setIframeSrc(iframe, src) {
    +	iframe.setAttribute("src", src);
    +}
    +
    +function writeStandardsModeHtml(iframe) {
    +	var standardsMode = "\n";
    +	iframe.contentWindow.document.open();
    +	iframe.contentWindow.document.write(standardsMode);
    +	iframe.contentWindow.document.close();
    +}
    +
    +function loadStylesheets(self, urls, callback) {
    +	async.each(urls, addLinkTag, callback);
    +
    +	function addLinkTag(url, onLinkLoad) {
    +		var link = document.createElement("link");
    +		shim.EventTarget.addEventListener(link, "load", function(event) { onLinkLoad(null); });
    +		link.setAttribute("rel", "stylesheet");
    +		link.setAttribute("type", "text/css");
    +		link.setAttribute("href", url);
    +		shim.Document.head(self._document).appendChild(link);
    +	}
    +}
    +
    +function loadRawCSS(self, css) {
    +	var style = document.createElement("style");
    +	style.setAttribute("type", "text/css");
    +	if (style.styleSheet) {
    +		// WORKAROUND IE 8: Throws 'unknown runtime error' if you set innerHTML on a 
    "); + + var text = frame.add("

    arbitrary text

    "); + frame.add("

    must have two p tags to work

    "); + + // WORKAROUND IE 8: need to force reflow or getting font-size may fail below + // This seems to occur when IE is running in a slow VirtualBox VM. There is no test for this line. + var forceReflow = text.offsetHeight; + + // WORKAROUND Safari 8.0.0: timeout required because font is enlarged asynchronously + setTimeout(function() { + var fontSize = text.getRawStyle("font-size"); + ensure.that(fontSize !== "", "Expected font-size to be a value"); + + // WORKAROUND IE 8: ignores
    "); + + var text = frame.add("

    arbitrary text

    "); + frame.add("

    must have two p tags to work

    "); + + // WORKAROUND IE 8: need to force reflow or getting font-size may fail below + // This seems to occur when IE is running in a slow VirtualBox VM. There is no test for this line. + var forceReflow = text.offsetHeight; + + // WORKAROUND Safari 8.0.0: timeout required because font is enlarged asynchronously + setTimeout(function() { + var fontSize = text.getRawStyle("font-size"); + ensure.that(fontSize !== "", "Expected font-size to be a value"); + + // WORKAROUND IE 8: ignores
    "); - - var text = frame.add("

    arbitrary text

    "); - frame.add("

    must have two p tags to work

    "); - - // WORKAROUND IE 8: need to force reflow or getting font-size may fail below - // This seems to occur when IE is running in a slow VirtualBox VM. There is no test for this line. - var forceReflow = text.offsetHeight; - - // WORKAROUND Safari 8.0.0: timeout required because font is enlarged asynchronously - setTimeout(function() { - var fontSize = text.getRawStyle("font-size"); - ensure.that(fontSize !== "", "Expected font-size to be a value"); - - // WORKAROUND IE 8: ignores
    "); - - var text = frame.add("

    arbitrary text

    "); - frame.add("

    must have two p tags to work

    "); - - // WORKAROUND IE 8: need to force reflow or getting font-size may fail below - // This seems to occur when IE is running in a slow VirtualBox VM. There is no test for this line. - var forceReflow = text.offsetHeight; - - // WORKAROUND Safari 8.0.0: timeout required because font is enlarged asynchronously - setTimeout(function() { - var fontSize = text.getRawStyle("font-size"); - ensure.that(fontSize !== "", "Expected font-size to be a value"); - - // WORKAROUND IE 8: ignores
    "); + + var text = frame.add("

    arbitrary text

    "); + frame.add("

    must have two p tags to work

    "); + + // WORKAROUND IE 8: need to force reflow or getting font-size may fail below + // This seems to occur when IE is running in a slow VirtualBox VM. There is no test for this line. + var forceReflow = text.offsetHeight; + + // WORKAROUND Safari 8.0.0: timeout required because font is enlarged asynchronously + setTimeout(function() { + var fontSize = text.getRawStyle("font-size"); + ensure.that(fontSize !== "", "Expected font-size to be a value"); + + // WORKAROUND IE 8: ignores