[Pkg-javascript-commits] [html2canvas] 01/02: New upstream version 0.5.0~beta4+ds1

Ximin Luo infinity0 at debian.org
Mon Nov 21 00:46:44 UTC 2016


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch master
in repository html2canvas.

commit 2384d90eb77667f166492c83364f4927c6e16922
Author: Ximin Luo <infinity0 at debian.org>
Date:   Sun Nov 20 01:23:02 2016 +0100

    New upstream version 0.5.0~beta4+ds1
---
 .editorconfig                               |  17 +
 .gitignore                                  |  12 +
 .gitmodules                                 |   3 +
 .jshintrc                                   |  19 +
 .npmignore                                  |  10 +
 .travis.yml                                 |  29 +
 CHANGELOG.md                                |  76 +++
 Gruntfile.js                                | 216 +++++++
 LICENSE                                     |  22 +
 bower.json                                  |   9 +
 examples/demo.html                          | 181 ++++++
 examples/demo2.html                         |  63 ++
 examples/existing_canvas.html               |  52 ++
 package.json                                |  51 ++
 readme.md                                   |  90 +++
 src/clone.js                                | 104 ++++
 src/color.js                                | 272 +++++++++
 src/core.js                                 | 155 +++++
 src/dummyimagecontainer.js                  |  22 +
 src/font.js                                 |  52 ++
 src/fontmetrics.js                          |  14 +
 src/framecontainer.js                       |  31 +
 src/gradientcontainer.js                    |  21 +
 src/imagecontainer.js                       |  19 +
 src/imageloader.js                          | 157 +++++
 src/lineargradientcontainer.js              | 102 ++++
 src/log.js                                  |   8 +
 src/nodecontainer.js                        | 296 ++++++++++
 src/nodeparser.js                           | 869 ++++++++++++++++++++++++++++
 src/proxy.js                                |  95 +++
 src/proxyimagecontainer.js                  |  21 +
 src/pseudoelementcontainer.js               |  38 ++
 src/renderer.js                             | 108 ++++
 src/renderers/canvas.js                     | 181 ++++++
 src/stackingcontext.js                      |  18 +
 src/support.js                              |  51 ++
 src/svgcontainer.js                         |  52 ++
 src/svgnodecontainer.js                     |  25 +
 src/textcontainer.js                        |  33 ++
 src/utils.js                                | 169 ++++++
 src/webkitgradientcontainer.js              |  10 +
 src/xhr.js                                  |  22 +
 tests/assets/iframe/frame1.html             |  16 +
 tests/assets/image.jpg                      | Bin 0 -> 2952 bytes
 tests/assets/image.svg                      |  46 ++
 tests/assets/image2.jpg                     | Bin 0 -> 3608 bytes
 tests/assets/image2_1.jpg                   | Bin 0 -> 3608 bytes
 tests/assets/image_1.jpg                    | Bin 0 -> 2952 bytes
 tests/cases/acid2.html                      | 141 +++++
 tests/cases/background/clip.html            |  66 +++
 tests/cases/background/encoded.html         |  52 ++
 tests/cases/background/linear-gradient.html | 139 +++++
 tests/cases/background/multi.html           |  50 ++
 tests/cases/background/position.html        |  66 +++
 tests/cases/background/radial-gradient.html | 161 ++++++
 tests/cases/background/repeat.html          |  57 ++
 tests/cases/background/size.html            |  61 ++
 tests/cases/border/dashed.html              |  47 ++
 tests/cases/border/dotted.html              |  47 ++
 tests/cases/border/double.html              |  47 ++
 tests/cases/border/inset.html               |  58 ++
 tests/cases/border/radius.html              |  80 +++
 tests/cases/border/solid.html               |  47 ++
 tests/cases/child-textnodes.html            |  22 +
 tests/cases/clip.html                       |  43 ++
 tests/cases/crossorigin-iframe.html         |  13 +
 tests/cases/forms.html                      |  81 +++
 tests/cases/google-maps.html                |  41 ++
 tests/cases/iframe.html                     |  10 +
 tests/cases/images/base.html                |  17 +
 tests/cases/images/canvas.html              |  27 +
 tests/cases/images/cross-origin.html        |  15 +
 tests/cases/images/empty.html               |  16 +
 tests/cases/images/images.html              |  27 +
 tests/cases/images/svg/base64.html          |  13 +
 tests/cases/images/svg/external.html        |  12 +
 tests/cases/images/svg/inline.html          |  14 +
 tests/cases/images/svg/native_only.html     |  25 +
 tests/cases/images/svg/node.html            |  20 +
 tests/cases/list/decimal-leading-zero.html  |  57 ++
 tests/cases/list/decimal.html               |  59 ++
 tests/cases/list/lower-alpha.html           |  59 ++
 tests/cases/list/upper-roman.html           |  59 ++
 tests/cases/overflow.html                   |  52 ++
 tests/cases/pseudoelements.html             |  70 +++
 tests/cases/text/chinese.html               |  36 ++
 tests/cases/text/fontawesome.html           |  46 ++
 tests/cases/text/linethrough.html           |  49 ++
 tests/cases/text/shadow.html                |  43 ++
 tests/cases/text/text.html                  | 114 ++++
 tests/cases/text/underline-lineheight.html  |  48 ++
 tests/cases/text/underline.html             |  49 ++
 tests/cases/transform/nested.html           |  45 ++
 tests/cases/transform/rotate.html           |  54 ++
 tests/cases/transform/translate.html        |  45 ++
 tests/cases/visibility.html                 |  24 +
 tests/cases/zindex/z-index1.html            |  81 +++
 tests/cases/zindex/z-index10.html           | 120 ++++
 tests/cases/zindex/z-index11.html           |  48 ++
 tests/cases/zindex/z-index12.html           |  48 ++
 tests/cases/zindex/z-index13.html           |  31 +
 tests/cases/zindex/z-index14.html           |  25 +
 tests/cases/zindex/z-index15.html           |  26 +
 tests/cases/zindex/z-index16.html           |  36 ++
 tests/cases/zindex/z-index17.html           |  25 +
 tests/cases/zindex/z-index18.html           |  79 +++
 tests/cases/zindex/z-index2.html            | 103 ++++
 tests/cases/zindex/z-index3.html            | 109 ++++
 tests/cases/zindex/z-index4.html            |  35 ++
 tests/cases/zindex/z-index5.html            |  34 ++
 tests/cases/zindex/z-index6.html            |  33 ++
 tests/cases/zindex/z-index7.html            | 102 ++++
 tests/cases/zindex/z-index8.html            | 104 ++++
 tests/cases/zindex/z-index9.html            | 108 ++++
 tests/mocha/.jshintrc                       |  18 +
 tests/mocha/background.html                 | 136 +++++
 tests/mocha/clone.js                        |   1 +
 tests/mocha/cropping.html                   | 145 +++++
 tests/mocha/css.js                          | 220 +++++++
 tests/mocha/form-rendering.html             | 166 ++++++
 tests/mocha/gradients.js                    | 158 +++++
 tests/mocha/ie9-clonenode-bug.html          |  67 +++
 tests/mocha/iframe1.htm                     |  23 +
 tests/mocha/iframe2.htm                     |  23 +
 tests/mocha/iframe3.htm                     |  21 +
 tests/mocha/multiple-renders.html           |  99 ++++
 tests/mocha/options.onclone.html            |  98 ++++
 tests/mocha/parsing.html                    | 235 ++++++++
 tests/mocha/proxy.htm                       |  89 +++
 tests/mocha/scrolling.html                  | 117 ++++
 tests/mocha/selenium.js                     |  73 +++
 tests/node/color.js                         | 119 ++++
 tests/node/package.js                       |  24 +
 tests/rangetest.html                        |  37 ++
 tests/results/.gitignore                    |   2 +
 tests/sauceconnect.js                       |  13 +
 tests/selenium.js                           | 114 ++++
 tests/test.js                               | 122 ++++
 tests/utils.js                              |  66 +++
 139 files changed, 9614 insertions(+)

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..64c61e1
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+# http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+indent_style = space
+indent_size = 4
+
+[{.travis.yml,package.json}]
+# The indent size used in the `package.json` file cannot be changed
+# https://github.com/npm/npm/pull/3180#issuecomment-16336516
+indent_size = 2
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d3905c4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+/nbproject/
+image.jpg
+/.project
+/.settings/
+node_modules/
+.envrc
+*.sublime-workspace
+*.baseline
+*.iml
+.idea/
+.DS_Store
+npm-debug.log
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..d630f8d
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "src/fabric"]
+	path = src/fabric
+	url = https://github.com/kangax/fabric.js.git
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..77ec1af
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,19 @@
+{
+    "curly": true,
+    "eqeqeq": true,
+    "immed": true,
+    "latedef": false,
+    "newcap": true,
+    "noarg": true,
+    "sub": true,
+    "undef": true,
+    "boss": true,
+    "eqnull": true,
+    "browser": true,
+    "node": true,
+    "indent": 4,
+    "globals": {
+      "jQuery": true
+    },
+    "predef": ["Promise", "define"]
+}
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..cca70c4
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,10 @@
+tests/
+examples/
+Gruntfile.js
+bower.json
+src/
+*.iml
+.idea/
+.npmignore
+.jshintrc
+.travis.yml
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..a61f795
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,29 @@
+language: node_js
+node_js:
+- '4.0'
+env:
+  global:
+  - secure: eW41gIqOizwO4pTgWnAAbW75AP7F+CK9qfSed/fSh4sJ9HWMIY1YRIaY8gjr+6jV/f7XVHcXuym6ZxgINYSkVKbF1JKxBJNLOXtSgNbVHSic58pYFvUjwxIBI9aPig9uux1+DbnpWqXFDTcACJSevQZE0xwmjdrSkDLgB0G34v8=
+  - secure: Y2Av+Gd3z9uQEB36GwdOOuGka0hx0/HeitASEo59z934O8RxnmN9eNTXS7dDT3XtKtwxIyLTOEpS7qlRdWahH28hr/dS4xJj6ao58C+1xMcDs6NAPGmDxUlcJWpcGEsnjmXjQCc3fBioSTdpIBrK/gdvgpNh77UKG74Sk7Z+YGk=
+  - secure: YI+YbTOGf2x4fPMKW+KhJiZWswoXT6xOKGwLfsQsVwmFX1LerJouil5D5iYOQuL4FE3pNaoJSNakIsokJQuGKJMmnPc8rdhMZuBJBk6MRghurE2Xe9qBHfuUBPlfD61nARESm4WDcyMwM0QVYaOKeY6aIpZ91qbUbyc60EEx3C4=
+addons:
+  sauce_connect: true
+before_script:
+- npm install -g grunt-cli
+- npm install -g uglify-js
+notifications:
+  webhooks:
+    urls:
+    - https://webhooks.gitter.im/e/2b007d4f86de89588804
+    on_success: always
+    on_failure: always
+    on_start: false
+deploy:
+  - provider: npm
+    email: niklasvh at gmail.com
+    api_key:
+      secure: G/Szpr8q4/D6hp+H/Z9yyluUXtHAwf7LLa1Y07X59/Enlj1h7V5fQ7AW4/iAVM3XbIsrCPWR3dJU9g/ZxpxFg4OovIHVpS2Jr/mahtPYWdHR3pWuSmMW8QD+Twnq2VAFwSgg5Oumq3QxhX3YbCOnZox6+6Uviqk8FO7Z5B0RwW4=
+    on:
+      tags: true
+      branch: master
+      repo: niklasvh/html2canvas
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..929abe0
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,76 @@
+### Changelog ###
+
+v0.5.0-beta4 - 23.1.2016
+ * Fix logger requiring access to window object
+ * Derequire browserify build
+ * Fix rendering of specific elements when window is scrolled and `type` isn't set to `view`
+
+v0.5.0-beta3 - 6.12.2015
+ * Handle color names in linear gradients
+
+v0.5.0-beta2 - 20.10.2015
+ * Remove Promise polyfill (use native or provide it yourself)
+
+v0.5.0-beta1 - 19.10.2015
+ * Fix bug with unmatched color stops in gradients
+ * Fix scrolling issues with iOS
+ * Correctly handle named colors in gradients
+ * Accept matrix3d transforms
+ * Fix transparent colors breaking gradients
+ * Preserve scrolling positions on render
+
+v0.5.0-alpha2 - 3.2.2015
+ * Switch to using browserify for building
+ * Fix (#517) Chrome stretches background images with 'auto' or single attributes
+
+v0.5.0-alpha - 19.1.2015
+ * Complete rewrite of library
+ * Switched interface to return Promise
+ * Uses hidden iframe window to perform rendering, allowing async rendering and doesn't force the viewport to be scrolled to the top anymore.
+ * Better support for unicode
+ * Checkbox/radio button rendering
+ * SVG rendering
+ * iframe rendering
+ * Changed format for proxy requests, permitting binary responses with CORS headers as well
+ * Fixed many layering issues (see z-index tests)
+
+v0.4.1 - 7.9.2013
+ * Added support for bower
+ * Improved z-index ordering
+ * Basic implementation for CSS transformations
+ * Fixed inline text in top element
+ * Basic implementation for text-shadow
+
+v0.4.0 - 30.1.2013
+ * Added rendering tests with <a href="https://github.com/niklasvh/webdriver.js">webdriver</a>
+ * Switched to using grunt for building
+ * Removed support for IE<9, including any FlashCanvas bits
+ * Support for border-radius
+ * Support for multiple background images, size, and clipping
+ * Support for :before and :after pseudo elements
+ * Support for placeholder rendering
+ * Reformatted all tests to small units to test specific features
+
+v0.3.4 - 26.6.2012
+
+* Removed (last?) jQuery dependencies (<a href="https://github.com/niklasvh/html2canvas/commit/343b86705fe163766fcf735eb0217130e4bd5b17">niklasvh</a>)
+* SVG-powered rendering (<a href="https://github.com/niklasvh/html2canvas/commit/67d3e0d0f59a5a654caf71a2e3be6494ff146c75">niklasvh</a>)
+* Radial gradients (<a href="https://github.com/niklasvh/html2canvas/commit/4f22c18043a73c0c3bbf3b5e4d62714c56acd3c7">SunboX</a>)
+* Split renderers to their own objects (<a href="https://github.com/niklasvh/html2canvas/commit/94f2f799a457cd29a21cc56ef8c06f1697866739">niklasvh</a>)
+* Simplified API, cleaned up code (<a href="https://github.com/niklasvh/html2canvas/commit/c7d526c9eaa6a4abf4754d205fe1dee360c7660e">niklasvh</a>)
+
+v0.3.3 - 2.3.2012
+
+* SVG taint fix, and additional taint testing options for rendering (<a href="https://github.com/niklasvh/html2canvas/commit/2dc8b9385e656696cb019d615bdfa1d98b17d5d4">niklasvh</a>)
+* Added support for CORS images and option to create canvas as tainted (<a href="https://github.com/niklasvh/html2canvas/commit/3ad49efa0032cde25c6ed32a39e35d1505d3b2ef">niklasvh</a>)
+* Improved minification saved ~1K! (<a href="https://github.com/cobexer/html2canvas/commit/b82be022b2b9240bd503e078ac980bde2b953e43">cobexer</a>)
+* Added integrated support for Flashcanvas (<a href="https://github.com/niklasvh/html2canvas/commit/e9257191519f67d74fd5e364d8dee3c0963ba5fc">niklasvh</a>)
+* Fixed a variety of legacy IE bugs (<a href="https://github.com/niklasvh/html2canvas/commit/b65357c55d0701017bafcd357bc654b54d458f8f">niklasvh</a>)
+
+v0.3.2 - 20.2.2012
+
+* Added changelog!
+* Added bookmarklet (<a href="https://github.com/niklasvh/html2canvas/commit/b320dd306e1a2d32a3bc5a71b6ebf6d8c060cde5">cobexer</a>)
+* Option to select single element to render (<a href="https://github.com/niklasvh/html2canvas/commit/0cb252ada91c84ef411288b317c03e97da1f12ad">niklasvh</a>)
+* Fixed closure compiler warnings (<a href="https://github.com/niklasvh/html2canvas/commit/36ff1ec7aadcbdf66851a0b77f0b9e87e4a8e4a1">cobexer</a>)
+* Enable profiling in FF (<a href="https://github.com/niklasvh/html2canvas/commit/bbd75286a8406cf9e5aea01fdb7950d547edefb9">cobexer</a>)
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 0000000..af90046
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,216 @@
+/*global module:false*/
+var _ =  require('lodash'), path = require('path');
+var proxy = require('html2canvas-proxy');
+
+module.exports = function(grunt) {
+
+    var meta = {
+        banner: '/*\n  <%= pkg.title || pkg.name %> <%= pkg.version %>' +
+            '<%= pkg.homepage ? " <" + pkg.homepage + ">" : "" %>' + '\n' +
+            '  Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>' +
+            '\n\n  Released under <%= _.pluck(pkg.licenses, "type").join(", ") %> License\n*/\n'
+    };
+
+    var browsers = {
+        chrome: {
+            browserName: "chrome",
+            platform: "Windows 7",
+            version: "39"
+        },
+        firefox: {
+            browserName: "firefox",
+            version: "15",
+            platform: "Windows 7"
+        },
+        ie9: {
+            browserName: "internet explorer",
+            version: "9",
+            platform: "Windows 7"
+        },
+        ie10: {
+            browserName: "internet explorer",
+            version: "10",
+            platform: "Windows 8"
+        },
+        ie11: {
+            browserName: "internet explorer",
+            version: "11",
+            platform: "Windows 8.1"
+        },
+        chromeOSX:{
+            browserName: "chrome",
+            platform: "OS X 10.8",
+            version: "39"
+        }
+    };
+    grunt.initConfig({
+
+        pkg: grunt.file.readJSON('package.json'),
+
+        browserify: {
+            dist: {
+                src: ['src/core.js'],
+                dest: 'dist/<%= pkg.name %>.js',
+                options: {
+                    browserifyOptions: {
+                        standalone: 'html2canvas'
+                    },
+                    banner: meta.banner,
+                    plugin: [
+                        [ "browserify-derequire" ]
+                    ]
+                }
+            },
+            svg: {
+                src: [
+                    'src/fabric/dist/fabric.js'
+                ],
+                dest: 'dist/<%= pkg.name %>.svg.js',
+                options:{
+                    browserifyOptions: {
+                        standalone: 'html2canvas.svg'
+                    },
+                    banner: meta.banner,
+                    plugin: [
+                        [ "browserify-derequire" ]
+                    ]
+                }
+            }
+        },
+        connect: {
+            server: {
+                options: {
+                    port: 8080,
+                    base: './',
+                    keepalive: true
+                }
+            },
+            altServer: {
+                options: {
+                    port: 8083,
+                    base: './'
+                }
+            },
+            cors: {
+                options: {
+                    port: 8081,
+                    base: './',
+                    middleware:  function(connect, options) {
+                        return [
+                            function(req, res, next) {
+                                if (req.url !== '/tests/assets/image2.jpg') {
+                                    next();
+                                    return;
+                                }
+                                res.setHeader("Access-Control-Allow-Origin", "*");
+                                res.end(require("fs").readFileSync('tests/assets/image2.jpg'));
+                            }
+                        ];
+                    }
+                }
+            },
+            proxy: {
+                options: {
+                    port: 8082,
+                    middleware:  function(connect, options) {
+                        return [
+                            function(req, res, next) {
+                                res.jsonp = function(content) {
+                                    res.end(req.query.callback +  "(" + JSON.stringify(content) + ")");
+                                };
+                                next();
+                            },
+                            proxy()
+                        ];
+                    }
+                }
+            },
+            ci: {
+                options: {
+                    port: 8080,
+                    base: './'
+                }
+            }
+        },
+        execute: {
+            fabric: {
+                options: {
+                    args: ['modules=' + ['text','serialization',
+                        'parser', 'gradient', 'pattern', 'shadow', 'freedrawing',
+                        'image_filters', 'serialization'].join(","), 'no-es5-compat', 'dest=' + path.resolve(__dirname, 'src/fabric/dist/') + '/']
+                },
+                src: ['src/fabric/build.js']
+            }
+        },
+        uglify: {
+            dist: {
+                src: ['<%= browserify.dist.dest %>'],
+                dest: 'dist/<%= pkg.name %>.min.js'
+            },
+            svg: {
+                src: ['<%= browserify.svg.dest %>'],
+                dest: 'dist/<%= pkg.name %>.svg.min.js'
+            },
+            options: {
+                banner: meta.banner
+            }
+        },
+        watch: {
+            files: ['src/**/*', '!src/fabric/**/*'],
+            tasks: ['jshint', 'build']
+        },
+        jshint: {
+            all: ['src/*.js', 'src/renderers/*.js'],
+            options: grunt.file.readJSON('./.jshintrc')
+        },
+        mochacli: {
+            options: {
+                reporter: 'spec'
+            },
+            all: ['tests/node/*.js']
+        },
+        mocha_phantomjs: {
+            all: ['tests/mocha/**/*.html']
+        },
+        mocha_webdriver: browsers,
+        webdriver: browsers
+    });
+
+    grunt.registerTask('webdriver', 'Browser render tests', function(browser, test) {
+        var selenium = require("./tests/selenium.js");
+        var done = this.async();
+        var browsers = (browser) ? [grunt.config.get(this.name + "." + browser)] : _.values(grunt.config.get(this.name));
+        selenium.tests(browsers, test).catch(function() {
+            done(false);
+        }).finally(function() {
+            console.log("Done");
+            done();
+        });
+    });
+
+    grunt.registerTask('mocha_webdriver', 'Browser mocha tests', function(browser, test) {
+        var selenium = require("./tests/mocha/selenium.js");
+        var done = this.async();
+        var browsers = (browser) ? [grunt.config.get(this.name + "." + browser)] : _.values(grunt.config.get(this.name));
+        selenium.tests(browsers, test).catch(function() {
+            done(false);
+        }).finally(function() {
+            done();
+        });
+    });
+
+    grunt.loadNpmTasks('grunt-browserify');
+    grunt.loadNpmTasks('grunt-mocha-phantomjs');
+    grunt.loadNpmTasks('grunt-contrib-watch');
+    grunt.loadNpmTasks('grunt-contrib-uglify');
+    grunt.loadNpmTasks('grunt-contrib-jshint');
+    grunt.loadNpmTasks('grunt-contrib-connect');
+    grunt.loadNpmTasks('grunt-execute');
+    grunt.loadNpmTasks('grunt-mocha-cli');
+
+    grunt.registerTask('server', ['connect:cors', 'connect:proxy', 'connect:altServer', 'connect:server']);
+    grunt.registerTask('build', ['execute', 'browserify', 'uglify']);
+    grunt.registerTask('default', ['jshint', 'build', 'mochacli', 'connect:altServer', 'mocha_phantomjs']);
+    grunt.registerTask('travis', ['jshint', 'build', 'connect:altServer', 'connect:ci', 'connect:proxy', 'connect:cors', 'mocha_phantomjs', 'webdriver']);
+
+};
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a73ffc9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2012 Niklas von Hertzen
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..10cc54b
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,9 @@
+{
+    "name": "html2canvas",
+    "description": "Screenshots with JavaScript",
+    "main": "dist/html2canvas.js",
+    "ignore": [
+        "tests",
+        ".travis.yml"
+    ]
+}
diff --git a/examples/demo.html b/examples/demo.html
new file mode 100644
index 0000000..54882cc
--- /dev/null
+++ b/examples/demo.html
@@ -0,0 +1,181 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
+<html>
+  <head>
+    <title>
+      display/box/float/clear test
+    </title>
+    <style type="text/css">
+      /* last modified: 1 Dec 98 */
+
+      html {
+        font: 10px/1 Verdana, sans-serif;
+        background-color: blue;
+        color: white;
+      }
+
+      body {
+        margin: 1.5em;
+        border: .5em solid black;
+        padding: 0;
+        width: 48em;
+        background-color: white;
+      }
+
+      dl {
+        margin: 0;
+        border: 0;
+        padding: .5em;
+      }
+
+      dt {
+        background-color: rgb(204,0,0);
+        margin: 0;
+        padding: 1em;
+        width: 10.638%; /* refers to parent element's width of 47em. = 5em or 50px */
+        height: 28em;
+        border: .5em solid black;
+        float: left;
+      }
+
+      dd {
+        float: right;
+        margin: 0 0 0 1em;
+        border: 1em solid black;
+        padding: 1em;
+        width: 34em;
+        height: 27em;
+      }
+
+      ul {
+        margin: 0;
+        border: 0;
+        padding: 0;
+      }
+
+      li {
+        display: block; /* i.e., suppress marker */
+        color: black;
+        height: 9em;
+        width: 5em;
+        margin: 0;
+        border: .5em solid black;
+        padding: 1em;
+        float: left;
+        background-color: #FC0;
+      }
+
+      #bar {
+        background-color: black;
+        color: white;
+        width: 41.17%; /* = 14em */
+        border: 0;
+        margin: 0 1em;
+      }
+
+      #baz {
+        margin: 1em 0;
+        border: 0;
+        padding: 1em;
+        width: 10em;
+        height: 10em;
+        background-color: black;
+        color: white;
+      }
+
+      form {
+        margin: 0;
+        display: inline;
+      }
+
+      p {
+        margin: 0;
+      }
+
+      form p {
+        line-height: 1.9;
+      }
+
+      blockquote {
+        margin: 1em 1em 1em 2em;
+        border-width: 1em 1.5em 2em .5em;
+        border-style: solid;
+        border-color: black;
+        padding: 1em 0;
+        width: 5em;
+        height: 9em;
+        float: left;
+        background-color: #FC0;
+        color: black;
+      }
+
+      address {
+        font-style: normal;
+      }
+
+      h1 {
+        background-color: black;
+        color: white;
+        float: left;
+        margin: 1em 0;
+        border: 0;
+        padding: 1em;
+        width: 10em;
+        height: 10em;
+        font-weight: normal;
+        font-size: 1em;
+      }
+    </style>
+  </head>
+  <body>
+    <dl>
+      <dt>
+        toggle
+      </dt>
+      <dd>
+        <ul>
+          <li>
+            the way
+          </li>
+          <li id="bar">
+            <p>
+              the world ends
+            </p>
+            <form action="./" method="get">
+              <p>
+                bang
+                <input type="radio" name="foo" value="off">
+              </p>
+              <p>
+                whimper
+                <input type="radio" name="foo2" value="on">
+              </p>
+            </form>
+          </li>
+          <li>
+            i grow old
+          </li>
+          <li id="baz">
+            pluot?
+          </li>
+        </ul>
+        <blockquote>
+          <address>
+            bar maids,
+          </address>
+        </blockquote>
+        <h1>
+          sing to me, erbarme dich
+        </h1>
+      </dd>
+    </dl>
+    <p style="color: black; font-size: 1em; line-height: 1.3em; clear: both">
+      This is a nonsensical document, but syntactically valid HTML 4.0. All 100% conformant CSS1 agents should be able to render the document elements above this paragraph <b>indistinguishably</b> (to the pixel) from this reference rendering, (except font rasterization and form widgets). All discrepancies should be traceable to CSS1 implementation shortcomings. Once you have finished evaluating this test, you can return to the <A HREF="sec5526c.htm" style="text-decoration:none">parent page</A>.
+    </p>
+    <script type="text/javascript" src="../dist/html2canvas.js"></script>
+    <script type="text/javascript">
+        html2canvas(document.body).then(function(canvas) {
+            document.body.appendChild(canvas);
+        });
+    </script>
+  </body>
+</html>
diff --git a/examples/demo2.html b/examples/demo2.html
new file mode 100644
index 0000000..0826f22
--- /dev/null
+++ b/examples/demo2.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title></title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <style>
+      .feedback-overlay-black{
+        background-color:#000;
+        opacity:0.5;
+        position:absolute;
+        top:0;
+        left:0;
+        width:100%;
+        height:100%;
+        margin:0;
+      }
+
+    </style>
+
+    <style>
+      div{
+        padding:20px;
+        margin:0 auto;
+        border:5px solid black;
+      }
+
+      h1{
+        border-bottom:2px solid white;
+      }
+
+      h2{
+        background: #efefef;
+        padding:10px;
+      }
+
+
+    </style>
+  </head>
+  <body>
+    <div style="background:red;">
+      <div style="background:green;">
+        <div style="background:blue;border-color:white;">
+          <div style="background:yellow;"><div style="background:orange;"><h1>Heading</h1>
+              Text that isn't wrapped in anything.
+              <p>Followed by some text wrapped in a <b><p> paragraph.</b> </p>
+              Maybe add a <a href="#">link</a> or a different style of <a href="#" style="background:white;" id="highlight">link with a highlight</a>.
+              <hr />
+              <h2>More content</h2>
+              <div style="width:10px;height:10px;border-width:10px;padding:0;">a</div>
+            </div></div>
+
+        </div>
+
+      </div>
+    </div>
+    <script type="text/javascript" src="../dist/html2canvas.js"></script>
+    <script type="text/javascript">
+        html2canvas(document.body).then(function(canvas) {
+            document.body.appendChild(canvas);
+        });
+    </script>
+  </body>
+</html>
diff --git a/examples/existing_canvas.html b/examples/existing_canvas.html
new file mode 100644
index 0000000..ac7be53
--- /dev/null
+++ b/examples/existing_canvas.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title>Using an existing canvas to draw on</title>
+    <style>
+        canvas {
+            border: 1px solid black;
+        }
+        button {
+            clear: both;
+            display: block;
+        }
+        #content {
+            background: rgba(100, 255, 255, 0.5);
+            padding: 50px 10px;
+        }
+    </style>
+</head>
+<body>
+<div><h1>HTML content to render:</h1>
+    <div id="content">Render the content in this element <strong>only</strong> onto the existing canvas element</div>
+</div>
+<h1>Existing canvas:</h1>
+<canvas width="500" height="200"></canvas>
+<script type="text/javascript" src="../dist/html2canvas.js"></script>
+<button>Run html2canvas</button>
+<script type="text/javascript">
+    var canvas = document.querySelector("canvas");
+    var ctx = canvas.getContext("2d");
+
+    var ctx = canvas.getContext('2d');
+
+    ctx.beginPath();
+    ctx.arc(75,75,50,0,Math.PI*2,true); // Outer circle
+    ctx.moveTo(110,75);
+    ctx.arc(75,75,35,0,Math.PI,false);   // Mouth (clockwise)
+    ctx.moveTo(65,65);
+    ctx.arc(60,65,5,0,Math.PI*2,true);  // Left eye
+    ctx.moveTo(95,65);
+    ctx.arc(90,65,5,0,Math.PI*2,true);  // Right eye
+    ctx.stroke();
+
+    document.querySelector("button").addEventListener("click", function() {
+        html2canvas(document.querySelector("#content"), {canvas: canvas}).then(function(canvas) {
+            console.log('Drew on the existing canvas');
+        });
+    }, false);
+
+</script>
+</body>
+</html>
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..abc62d4
--- /dev/null
+++ b/package.json
@@ -0,0 +1,51 @@
+{
+  "title": "html2canvas",
+  "name": "html2canvas",
+  "description": "Screenshots with JavaScript",
+  "main": "dist/html2canvas.js",
+  "version": "0.5.0-beta4",
+  "author": {
+    "name": "Niklas von Hertzen",
+    "email": "niklasvh at gmail.com",
+    "url": "http://hertzen.com"
+  },
+  "engines": {
+    "node": ">=4.0.0"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git at github.com:niklasvh/html2canvas.git"
+  },
+  "bugs": {
+    "url": "https://github.com/niklasvh/html2canvas/issues"
+  },
+  "devDependencies": {
+    "base64-arraybuffer": "^0.1.5",
+    "bluebird": "^3.0.6",
+    "browserify-derequire": "^0.9.4",
+    "grunt": "^0.4.5",
+    "grunt-browserify": "^4.0.1",
+    "grunt-cli": "^0.1.13",
+    "grunt-contrib-connect": "^0.11.2",
+    "grunt-contrib-jshint": "^0.11.3",
+    "grunt-contrib-uglify": "^0.11.0",
+    "grunt-contrib-watch": "^0.6.1",
+    "grunt-execute": "^0.2.2",
+    "grunt-mocha-cli": "^1.12.0",
+    "grunt-mocha-phantomjs": "^2.0.0",
+    "html2canvas-proxy": "0.0.5",
+    "humanize-duration": "^2.0.1",
+    "lodash": "^3.10.1",
+    "pngjs": "^2.2.0",
+    "requirejs": "^2.1.20",
+    "sauce-connect-launcher": "^0.13.0",
+    "wd": "^0.4.0"
+  },
+  "scripts": {
+    "test": "grunt travis --verbose",
+    "start": "grunt server",
+    "sauceconnect": "tests/sauceconnect.js"
+  },
+  "homepage": "http://html2canvas.hertzen.com",
+  "license": "MIT"
+}
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..972637f
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,90 @@
+html2canvas
+===========
+
+[Homepage](http://html2canvas.hertzen.com) | [Downloads](https://github.com/niklasvh/html2canvas/releases) | [Questions](http://stackoverflow.com/questions/tagged/html2canvas?sort=newest) | [Donate](https://www.gittip.com/niklasvh/)
+
+[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/niklasvh/html2canvas?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build Status](https://travis-ci.org/niklasvh/html2canvas.png)](https://travis-ci.org/niklasvh/html2canvas)
+
+#### JavaScript HTML renderer ####
+
+ The script allows you to take "screenshots" of webpages or parts of it, directly on the users browser. The screenshot is based on the DOM and as such may not be 100% accurate to the real representation as it does not make an actual screenshot, but builds the screenshot based on the information available on the page.
+
+
+###How does it work?###
+The script renders the current page as a canvas image, by reading the DOM and the different styles applied to the elements.
+
+It does **not require any rendering from the server**, as the whole image is created on the **clients browser**. However, as it is heavily dependent on the browser, this library is *not suitable* to be used in nodejs.
+It doesn't magically circumvent any browser content policy restrictions either, so rendering cross-origin content will require a [proxy](https://github.com/niklasvh/html2canvas/wiki/Proxies) to get the content to the [same origin](http://en.wikipedia.org/wiki/Same_origin_policy).
+
+The script is still in a **very experimental state**, so I don't recommend using it in a production environment nor start building applications with it yet, as there will be still major changes made.
+
+###Browser compatibility###
+
+The library should work fine on the following browsers (with `Promise` polyfill):
+
+* Firefox 3.5+
+* Google Chrome
+* Opera 12+
+* IE9+
+* Safari 6+
+
+As each CSS property needs to be manually built to be supported, there are a number of properties that are not yet supported.
+
+### Usage ###
+
+The html2canvas library utilizes `Promise`s and expects them to be available in the global context. If you wish to
+support [older browsers](http://caniuse.com/#search=promise) that do not natively support `Promise`s, please include a polyfill such as
+[es6-promise](https://github.com/jakearchibald/es6-promise) before including `html2canvas`.
+
+**Note!** These instructions are for using the current dev version of 0.5, for the latest release version (0.4.1), checkout the [old readme](https://github.com/niklasvh/html2canvas/blob/v0.4/readme.md).
+
+To render an `element` with html2canvas, simply call:
+` html2canvas(element[, options]);`
+
+The function returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) containing the `<canvas>` element. Simply add a promise fullfillment handler to the promise using `then`:
+
+    html2canvas(document.body).then(function(canvas) {
+        document.body.appendChild(canvas);
+    });
+
+### Building ###
+
+The library uses [grunt](http://gruntjs.com/) for building. Alternatively, you can download the latest build from [here](https://github.com/niklasvh/html2canvas/blob/master/dist/html2canvas.js).
+
+Clone git repository with submodules:
+
+    $ git clone --recursive git://github.com/niklasvh/html2canvas.git
+
+Install Grunt and uglifyjs:
+
+    $ npm install -g grunt-cli uglify-js
+
+Run the full build process (including lint, qunit and webdriver tests):
+
+    $ grunt
+
+Skip lint and tests and simply build from source:
+
+    $ grunt build
+
+### Running tests ###
+
+The library has two sets of tests. The first set is a number of qunit tests that check that different values parsed by browsers are correctly converted in html2canvas. To run these tests with grunt you'll need [phantomjs](http://phantomjs.org/).
+
+The other set of tests run Firefox, Chrome and Internet Explorer with [webdriver](https://github.com/niklasvh/webdriver.js). The selenium standalone server (runs on Java) is required for these tests and can be downloaded from [here](http://code.google.com/p/selenium/downloads/list). They capture an actual screenshot from the test pages and compare the image to the screenshot created by html2canvas and calculate the percentage differences. These tests generally aren't expected to provide  [...]
+
+Start by downloading the dependencies:
+
+    $ npm install
+
+Run qunit tests:
+
+    $ grunt test
+
+### Examples ###
+
+For more information and examples, please visit the [homepage](http://html2canvas.hertzen.com) or try the [test console](http://html2canvas.hertzen.com/screenshots.html).
+
+### Contributing ###
+
+If you wish to contribute to the project, please send the pull requests to the develop branch. Before submitting any changes, try and test that the changes work with all the support browsers. If some CSS property isn't supported or is incomplete, please create appropriate tests for it as well before submitting any code changes.
diff --git a/src/clone.js b/src/clone.js
new file mode 100644
index 0000000..bafaa09
--- /dev/null
+++ b/src/clone.js
@@ -0,0 +1,104 @@
+var log = require('./log');
+
+function restoreOwnerScroll(ownerDocument, x, y) {
+    if (ownerDocument.defaultView && (x !== ownerDocument.defaultView.pageXOffset || y !== ownerDocument.defaultView.pageYOffset)) {
+        ownerDocument.defaultView.scrollTo(x, y);
+    }
+}
+
+function cloneCanvasContents(canvas, clonedCanvas) {
+    try {
+        if (clonedCanvas) {
+            clonedCanvas.width = canvas.width;
+            clonedCanvas.height = canvas.height;
+            clonedCanvas.getContext("2d").putImageData(canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height), 0, 0);
+        }
+    } catch(e) {
+        log("Unable to copy canvas content from", canvas, e);
+    }
+}
+
+function cloneNode(node, javascriptEnabled) {
+    var clone = node.nodeType === 3 ? document.createTextNode(node.nodeValue) : node.cloneNode(false);
+
+    var child = node.firstChild;
+    while(child) {
+        if (javascriptEnabled === true || child.nodeType !== 1 || child.nodeName !== 'SCRIPT') {
+            clone.appendChild(cloneNode(child, javascriptEnabled));
+        }
+        child = child.nextSibling;
+    }
+
+    if (node.nodeType === 1) {
+        clone._scrollTop = node.scrollTop;
+        clone._scrollLeft = node.scrollLeft;
+        if (node.nodeName === "CANVAS") {
+            cloneCanvasContents(node, clone);
+        } else if (node.nodeName === "TEXTAREA" || node.nodeName === "SELECT") {
+            clone.value = node.value;
+        }
+    }
+
+    return clone;
+}
+
+function initNode(node) {
+    if (node.nodeType === 1) {
+        node.scrollTop = node._scrollTop;
+        node.scrollLeft = node._scrollLeft;
+
+        var child = node.firstChild;
+        while(child) {
+            initNode(child);
+            child = child.nextSibling;
+        }
+    }
+}
+
+module.exports = function(ownerDocument, containerDocument, width, height, options, x ,y) {
+    var documentElement = cloneNode(ownerDocument.documentElement, options.javascriptEnabled);
+    var container = containerDocument.createElement("iframe");
+
+    container.className = "html2canvas-container";
+    container.style.visibility = "hidden";
+    container.style.position = "fixed";
+    container.style.left = "-10000px";
+    container.style.top = "0px";
+    container.style.border = "0";
+    container.width = width;
+    container.height = height;
+    container.scrolling = "no"; // ios won't scroll without it
+    containerDocument.body.appendChild(container);
+
+    return new Promise(function(resolve) {
+        var documentClone = container.contentWindow.document;
+
+        /* Chrome doesn't detect relative background-images assigned in inline <style> sheets when fetched through getComputedStyle
+         if window url is about:blank, we can assign the url to current by writing onto the document
+         */
+        container.contentWindow.onload = container.onload = function() {
+            var interval = setInterval(function() {
+                if (documentClone.body.childNodes.length > 0) {
+                    initNode(documentClone.documentElement);
+                    clearInterval(interval);
+                    if (options.type === "view") {
+                        container.contentWindow.scrollTo(x, y);
+                        if ((/(iPad|iPhone|iPod)/g).test(navigator.userAgent) && (container.contentWindow.scrollY !== y || container.contentWindow.scrollX !== x)) {
+                            documentClone.documentElement.style.top = (-y) + "px";
+                            documentClone.documentElement.style.left = (-x) + "px";
+                            documentClone.documentElement.style.position = 'absolute';
+                        }
+                    }
+                    resolve(container);
+                }
+            }, 50);
+        };
+
+        documentClone.open();
+        documentClone.write("<!DOCTYPE html><html></html>");
+        // Chrome scrolls the parent document for some reason after the write to the cloned window???
+        restoreOwnerScroll(ownerDocument, x, y);
+        documentClone.replaceChild(documentClone.adoptNode(documentElement), documentClone.documentElement);
+        documentClone.close();
+    });
+};
diff --git a/src/color.js b/src/color.js
new file mode 100644
index 0000000..fc6d604
--- /dev/null
+++ b/src/color.js
@@ -0,0 +1,272 @@
+// http://dev.w3.org/csswg/css-color/
+
+function Color(value) {
+    this.r = 0;
+    this.g = 0;
+    this.b = 0;
+    this.a = null;
+    var result = this.fromArray(value) ||
+        this.namedColor(value) ||
+        this.rgb(value) ||
+        this.rgba(value) ||
+        this.hex6(value) ||
+        this.hex3(value);
+}
+
+Color.prototype.darken = function(amount) {
+    var a = 1 - amount;
+    return  new Color([
+        Math.round(this.r * a),
+        Math.round(this.g * a),
+        Math.round(this.b * a),
+        this.a
+    ]);
+};
+
+Color.prototype.isTransparent = function() {
+    return this.a === 0;
+};
+
+Color.prototype.isBlack = function() {
+    return this.r === 0 && this.g === 0 && this.b === 0;
+};
+
+Color.prototype.fromArray = function(array) {
+    if (Array.isArray(array)) {
+        this.r = Math.min(array[0], 255);
+        this.g = Math.min(array[1], 255);
+        this.b = Math.min(array[2], 255);
+        if (array.length > 3) {
+            this.a = array[3];
+        }
+    }
+
+    return (Array.isArray(array));
+};
+
+var _hex3 = /^#([a-f0-9]{3})$/i;
+
+Color.prototype.hex3 = function(value) {
+    var match = null;
+    if ((match = value.match(_hex3)) !== null) {
+        this.r = parseInt(match[1][0] + match[1][0], 16);
+        this.g = parseInt(match[1][1] + match[1][1], 16);
+        this.b = parseInt(match[1][2] + match[1][2], 16);
+    }
+    return match !== null;
+};
+
+var _hex6 = /^#([a-f0-9]{6})$/i;
+
+Color.prototype.hex6 = function(value) {
+    var match = null;
+    if ((match = value.match(_hex6)) !== null) {
+        this.r = parseInt(match[1].substring(0, 2), 16);
+        this.g = parseInt(match[1].substring(2, 4), 16);
+        this.b = parseInt(match[1].substring(4, 6), 16);
+    }
+    return match !== null;
+};
+
+
+var _rgb = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/;
+
+Color.prototype.rgb = function(value) {
+    var match = null;
+    if ((match = value.match(_rgb)) !== null) {
+        this.r = Number(match[1]);
+        this.g = Number(match[2]);
+        this.b = Number(match[3]);
+    }
+    return match !== null;
+};
+
+var _rgba = /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d?\.?\d+)\s*\)$/;
+
+Color.prototype.rgba = function(value) {
+    var match = null;
+    if ((match = value.match(_rgba)) !== null) {
+        this.r = Number(match[1]);
+        this.g = Number(match[2]);
+        this.b = Number(match[3]);
+        this.a = Number(match[4]);
+    }
+    return match !== null;
+};
+
+Color.prototype.toString = function() {
+    return this.a !== null && this.a !== 1 ?
+    "rgba(" + [this.r, this.g, this.b, this.a].join(",") + ")" :
+    "rgb(" + [this.r, this.g, this.b].join(",") + ")";
+};
+
+Color.prototype.namedColor = function(value) {
+    value = value.toLowerCase();
+    var color = colors[value];
+    if (color) {
+        this.r = color[0];
+        this.g = color[1];
+        this.b = color[2];
+    } else if (value === "transparent") {
+        this.r = this.g = this.b = this.a = 0;
+        return true;
+    }
+
+    return !!color;
+};
+
+Color.prototype.isColor = true;
+
+// JSON.stringify([].slice.call($$('.named-color-table tr'), 1).map(function(row) { return [row.childNodes[3].textContent, row.childNodes[5].textContent.trim().split(",").map(Number)] }).reduce(function(data, row) {data[row[0]] = row[1]; return data}, {}))
+var colors = {
+    "aliceblue": [240, 248, 255],
+    "antiquewhite": [250, 235, 215],
+    "aqua": [0, 255, 255],
+    "aquamarine": [127, 255, 212],
+    "azure": [240, 255, 255],
+    "beige": [245, 245, 220],
+    "bisque": [255, 228, 196],
+    "black": [0, 0, 0],
+    "blanchedalmond": [255, 235, 205],
+    "blue": [0, 0, 255],
+    "blueviolet": [138, 43, 226],
+    "brown": [165, 42, 42],
+    "burlywood": [222, 184, 135],
+    "cadetblue": [95, 158, 160],
+    "chartreuse": [127, 255, 0],
+    "chocolate": [210, 105, 30],
+    "coral": [255, 127, 80],
+    "cornflowerblue": [100, 149, 237],
+    "cornsilk": [255, 248, 220],
+    "crimson": [220, 20, 60],
+    "cyan": [0, 255, 255],
+    "darkblue": [0, 0, 139],
+    "darkcyan": [0, 139, 139],
+    "darkgoldenrod": [184, 134, 11],
+    "darkgray": [169, 169, 169],
+    "darkgreen": [0, 100, 0],
+    "darkgrey": [169, 169, 169],
+    "darkkhaki": [189, 183, 107],
+    "darkmagenta": [139, 0, 139],
+    "darkolivegreen": [85, 107, 47],
+    "darkorange": [255, 140, 0],
+    "darkorchid": [153, 50, 204],
+    "darkred": [139, 0, 0],
+    "darksalmon": [233, 150, 122],
+    "darkseagreen": [143, 188, 143],
+    "darkslateblue": [72, 61, 139],
+    "darkslategray": [47, 79, 79],
+    "darkslategrey": [47, 79, 79],
+    "darkturquoise": [0, 206, 209],
+    "darkviolet": [148, 0, 211],
+    "deeppink": [255, 20, 147],
+    "deepskyblue": [0, 191, 255],
+    "dimgray": [105, 105, 105],
+    "dimgrey": [105, 105, 105],
+    "dodgerblue": [30, 144, 255],
+    "firebrick": [178, 34, 34],
+    "floralwhite": [255, 250, 240],
+    "forestgreen": [34, 139, 34],
+    "fuchsia": [255, 0, 255],
+    "gainsboro": [220, 220, 220],
+    "ghostwhite": [248, 248, 255],
+    "gold": [255, 215, 0],
+    "goldenrod": [218, 165, 32],
+    "gray": [128, 128, 128],
+    "green": [0, 128, 0],
+    "greenyellow": [173, 255, 47],
+    "grey": [128, 128, 128],
+    "honeydew": [240, 255, 240],
+    "hotpink": [255, 105, 180],
+    "indianred": [205, 92, 92],
+    "indigo": [75, 0, 130],
+    "ivory": [255, 255, 240],
+    "khaki": [240, 230, 140],
+    "lavender": [230, 230, 250],
+    "lavenderblush": [255, 240, 245],
+    "lawngreen": [124, 252, 0],
+    "lemonchiffon": [255, 250, 205],
+    "lightblue": [173, 216, 230],
+    "lightcoral": [240, 128, 128],
+    "lightcyan": [224, 255, 255],
+    "lightgoldenrodyellow": [250, 250, 210],
+    "lightgray": [211, 211, 211],
+    "lightgreen": [144, 238, 144],
+    "lightgrey": [211, 211, 211],
+    "lightpink": [255, 182, 193],
+    "lightsalmon": [255, 160, 122],
+    "lightseagreen": [32, 178, 170],
+    "lightskyblue": [135, 206, 250],
+    "lightslategray": [119, 136, 153],
+    "lightslategrey": [119, 136, 153],
+    "lightsteelblue": [176, 196, 222],
+    "lightyellow": [255, 255, 224],
+    "lime": [0, 255, 0],
+    "limegreen": [50, 205, 50],
+    "linen": [250, 240, 230],
+    "magenta": [255, 0, 255],
+    "maroon": [128, 0, 0],
+    "mediumaquamarine": [102, 205, 170],
+    "mediumblue": [0, 0, 205],
+    "mediumorchid": [186, 85, 211],
+    "mediumpurple": [147, 112, 219],
+    "mediumseagreen": [60, 179, 113],
+    "mediumslateblue": [123, 104, 238],
+    "mediumspringgreen": [0, 250, 154],
+    "mediumturquoise": [72, 209, 204],
+    "mediumvioletred": [199, 21, 133],
+    "midnightblue": [25, 25, 112],
+    "mintcream": [245, 255, 250],
+    "mistyrose": [255, 228, 225],
+    "moccasin": [255, 228, 181],
+    "navajowhite": [255, 222, 173],
+    "navy": [0, 0, 128],
+    "oldlace": [253, 245, 230],
+    "olive": [128, 128, 0],
+    "olivedrab": [107, 142, 35],
+    "orange": [255, 165, 0],
+    "orangered": [255, 69, 0],
+    "orchid": [218, 112, 214],
+    "palegoldenrod": [238, 232, 170],
+    "palegreen": [152, 251, 152],
+    "paleturquoise": [175, 238, 238],
+    "palevioletred": [219, 112, 147],
+    "papayawhip": [255, 239, 213],
+    "peachpuff": [255, 218, 185],
+    "peru": [205, 133, 63],
+    "pink": [255, 192, 203],
+    "plum": [221, 160, 221],
+    "powderblue": [176, 224, 230],
+    "purple": [128, 0, 128],
+    "rebeccapurple": [102, 51, 153],
+    "red": [255, 0, 0],
+    "rosybrown": [188, 143, 143],
+    "royalblue": [65, 105, 225],
+    "saddlebrown": [139, 69, 19],
+    "salmon": [250, 128, 114],
+    "sandybrown": [244, 164, 96],
+    "seagreen": [46, 139, 87],
+    "seashell": [255, 245, 238],
+    "sienna": [160, 82, 45],
+    "silver": [192, 192, 192],
+    "skyblue": [135, 206, 235],
+    "slateblue": [106, 90, 205],
+    "slategray": [112, 128, 144],
+    "slategrey": [112, 128, 144],
+    "snow": [255, 250, 250],
+    "springgreen": [0, 255, 127],
+    "steelblue": [70, 130, 180],
+    "tan": [210, 180, 140],
+    "teal": [0, 128, 128],
+    "thistle": [216, 191, 216],
+    "tomato": [255, 99, 71],
+    "turquoise": [64, 224, 208],
+    "violet": [238, 130, 238],
+    "wheat": [245, 222, 179],
+    "white": [255, 255, 255],
+    "whitesmoke": [245, 245, 245],
+    "yellow": [255, 255, 0],
+    "yellowgreen": [154, 205, 50]
+};
+
+module.exports = Color;
diff --git a/src/core.js b/src/core.js
new file mode 100644
index 0000000..db0df18
--- /dev/null
+++ b/src/core.js
@@ -0,0 +1,155 @@
+var Support = require('./support');
+var CanvasRenderer = require('./renderers/canvas');
+var ImageLoader = require('./imageloader');
+var NodeParser = require('./nodeparser');
+var NodeContainer = require('./nodecontainer');
+var log = require('./log');
+var utils = require('./utils');
+var createWindowClone = require('./clone');
+var loadUrlDocument = require('./proxy').loadUrlDocument;
+var getBounds = utils.getBounds;
+
+var html2canvasNodeAttribute = "data-html2canvas-node";
+var html2canvasCloneIndex = 0;
+
+function html2canvas(nodeList, options) {
+    var index = html2canvasCloneIndex++;
+    options = options || {};
+    if (options.logging) {
+        log.options.logging = true;
+        log.options.start = Date.now();
+    }
+
+    options.async = typeof(options.async) === "undefined" ? true : options.async;
+    options.allowTaint = typeof(options.allowTaint) === "undefined" ? false : options.allowTaint;
+    options.removeContainer = typeof(options.removeContainer) === "undefined" ? true : options.removeContainer;
+    options.javascriptEnabled = typeof(options.javascriptEnabled) === "undefined" ? false : options.javascriptEnabled;
+    options.imageTimeout = typeof(options.imageTimeout) === "undefined" ? 10000 : options.imageTimeout;
+    options.renderer = typeof(options.renderer) === "function" ? options.renderer : CanvasRenderer;
+    options.strict = !!options.strict;
+
+    if (typeof(nodeList) === "string") {
+        if (typeof(options.proxy) !== "string") {
+            return Promise.reject("Proxy must be used when rendering url");
+        }
+        var width = options.width != null ? options.width : window.innerWidth;
+        var height = options.height != null ? options.height : window.innerHeight;
+        return loadUrlDocument(absoluteUrl(nodeList), options.proxy, document, width, height, options).then(function(container) {
+            return renderWindow(container.contentWindow.document.documentElement, container, options, width, height);
+        });
+    }
+
+    var node = ((nodeList === undefined) ? [document.documentElement] : ((nodeList.length) ? nodeList : [nodeList]))[0];
+    node.setAttribute(html2canvasNodeAttribute + index, index);
+    return renderDocument(node.ownerDocument, options, node.ownerDocument.defaultView.innerWidth, node.ownerDocument.defaultView.innerHeight, index).then(function(canvas) {
+        if (typeof(options.onrendered) === "function") {
+            log("options.onrendered is deprecated, html2canvas returns a Promise containing the canvas");
+            options.onrendered(canvas);
+        }
+        return canvas;
+    });
+}
+
+html2canvas.CanvasRenderer = CanvasRenderer;
+html2canvas.NodeContainer = NodeContainer;
+html2canvas.log = log;
+html2canvas.utils = utils;
+
+var html2canvasExport = (typeof(document) === "undefined" || typeof(Object.create) !== "function" || typeof(document.createElement("canvas").getContext) !== "function") ? function() {
+    return Promise.reject("No canvas support");
+} : html2canvas;
+
+module.exports = html2canvasExport;
+
+if (typeof(define) === 'function' && define.amd) {
+    define('html2canvas', [], function() {
+        return html2canvasExport;
+    });
+}
+
+function renderDocument(document, options, windowWidth, windowHeight, html2canvasIndex) {
+    return createWindowClone(document, document, windowWidth, windowHeight, options, document.defaultView.pageXOffset, document.defaultView.pageYOffset).then(function(container) {
+        log("Document cloned");
+        var attributeName = html2canvasNodeAttribute + html2canvasIndex;
+        var selector = "[" + attributeName + "='" + html2canvasIndex + "']";
+        document.querySelector(selector).removeAttribute(attributeName);
+        var clonedWindow = container.contentWindow;
+        var node = clonedWindow.document.querySelector(selector);
+        var oncloneHandler = (typeof(options.onclone) === "function") ? Promise.resolve(options.onclone(clonedWindow.document)) : Promise.resolve(true);
+        return oncloneHandler.then(function() {
+            return renderWindow(node, container, options, windowWidth, windowHeight);
+        });
+    });
+}
+
+function renderWindow(node, container, options, windowWidth, windowHeight) {
+    var clonedWindow = container.contentWindow;
+    var support = new Support(clonedWindow.document);
+    var imageLoader = new ImageLoader(options, support);
+    var bounds = getBounds(node);
+    var width = options.type === "view" ? windowWidth : documentWidth(clonedWindow.document);
+    var height = options.type === "view" ? windowHeight : documentHeight(clonedWindow.document);
+    var renderer = new options.renderer(width, height, imageLoader, options, document);
+    var parser = new NodeParser(node, renderer, support, imageLoader, options);
+    return parser.ready.then(function() {
+        log("Finished rendering");
+        var canvas;
+
+        if (options.type === "view") {
+            canvas = crop(renderer.canvas, {width: renderer.canvas.width, height: renderer.canvas.height, top: 0, left: 0, x: 0, y: 0});
+        } else if (node === clonedWindow.document.body || node === clonedWindow.document.documentElement || options.canvas != null) {
+            canvas = renderer.canvas;
+        } else {
+            canvas = crop(renderer.canvas, {width:  options.width != null ? options.width : bounds.width, height: options.height != null ? options.height : bounds.height, top: bounds.top, left: bounds.left, x: 0, y: 0});
+        }
+
+        cleanupContainer(container, options);
+        return canvas;
+    });
+}
+
+function cleanupContainer(container, options) {
+    if (options.removeContainer) {
+        container.parentNode.removeChild(container);
+        log("Cleaned up container");
+    }
+}
+
+function crop(canvas, bounds) {
+    var croppedCanvas = document.createElement("canvas");
+    var x1 = Math.min(canvas.width - 1, Math.max(0, bounds.left));
+    var x2 = Math.min(canvas.width, Math.max(1, bounds.left + bounds.width));
+    var y1 = Math.min(canvas.height - 1, Math.max(0, bounds.top));
+    var y2 = Math.min(canvas.height, Math.max(1, bounds.top + bounds.height));
+    croppedCanvas.width = bounds.width;
+    croppedCanvas.height =  bounds.height;
+    var width = x2-x1;
+    var height = y2-y1;
+    log("Cropping canvas at:", "left:", bounds.left, "top:", bounds.top, "width:", width, "height:", height);
+    log("Resulting crop with width", bounds.width, "and height", bounds.height, "with x", x1, "and y", y1);
+    croppedCanvas.getContext("2d").drawImage(canvas, x1, y1, width, height, bounds.x, bounds.y, width, height);
+    return croppedCanvas;
+}
+
+function documentWidth (doc) {
+    return Math.max(
+        Math.max(doc.body.scrollWidth, doc.documentElement.scrollWidth),
+        Math.max(doc.body.offsetWidth, doc.documentElement.offsetWidth),
+        Math.max(doc.body.clientWidth, doc.documentElement.clientWidth)
+    );
+}
+
+function documentHeight (doc) {
+    return Math.max(
+        Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight),
+        Math.max(doc.body.offsetHeight, doc.documentElement.offsetHeight),
+        Math.max(doc.body.clientHeight, doc.documentElement.clientHeight)
+    );
+}
+
+function absoluteUrl(url) {
+    var link = document.createElement("a");
+    link.href = url;
+    link.href = link.href;
+    return link;
+}
diff --git a/src/dummyimagecontainer.js b/src/dummyimagecontainer.js
new file mode 100644
index 0000000..4ee7401
--- /dev/null
+++ b/src/dummyimagecontainer.js
@@ -0,0 +1,22 @@
+var log = require('./log');
+var smallImage = require('./utils').smallImage;
+
+function DummyImageContainer(src) {
+    this.src = src;
+    log("DummyImageContainer for", src);
+    if (!this.promise || !this.image) {
+        log("Initiating DummyImageContainer");
+        DummyImageContainer.prototype.image = new Image();
+        var image = this.image;
+        DummyImageContainer.prototype.promise = new Promise(function(resolve, reject) {
+            image.onload = resolve;
+            image.onerror = reject;
+            image.src = smallImage();
+            if (image.complete === true) {
+                resolve(image);
+            }
+        });
+    }
+}
+
+module.exports = DummyImageContainer;
diff --git a/src/font.js b/src/font.js
new file mode 100644
index 0000000..5a5b052
--- /dev/null
+++ b/src/font.js
@@ -0,0 +1,52 @@
+var smallImage = require('./utils').smallImage;
+
+function Font(family, size) {
+    var container = document.createElement('div'),
+        img = document.createElement('img'),
+        span = document.createElement('span'),
+        sampleText = 'Hidden Text',
+        baseline,
+        middle;
+
+    container.style.visibility = "hidden";
+    container.style.fontFamily = family;
+    container.style.fontSize = size;
+    container.style.margin = 0;
+    container.style.padding = 0;
+
+    document.body.appendChild(container);
+
+    img.src = smallImage();
+    img.width = 1;
+    img.height = 1;
+
+    img.style.margin = 0;
+    img.style.padding = 0;
+    img.style.verticalAlign = "baseline";
+
+    span.style.fontFamily = family;
+    span.style.fontSize = size;
+    span.style.margin = 0;
+    span.style.padding = 0;
+
+    span.appendChild(document.createTextNode(sampleText));
+    container.appendChild(span);
+    container.appendChild(img);
+    baseline = (img.offsetTop - span.offsetTop) + 1;
+
+    container.removeChild(span);
+    container.appendChild(document.createTextNode(sampleText));
+
+    container.style.lineHeight = "normal";
+    img.style.verticalAlign = "super";
+
+    middle = (img.offsetTop-container.offsetTop) + 1;
+
+    document.body.removeChild(container);
+
+    this.baseline = baseline;
+    this.lineWidth = 1;
+    this.middle = middle;
+}
+
+module.exports = Font;
diff --git a/src/fontmetrics.js b/src/fontmetrics.js
new file mode 100644
index 0000000..5313b90
--- /dev/null
+++ b/src/fontmetrics.js
@@ -0,0 +1,14 @@
+var Font = require('./font');
+
+function FontMetrics() {
+    this.data = {};
+}
+
+FontMetrics.prototype.getMetrics = function(family, size) {
+    if (this.data[family + "-" + size] === undefined) {
+        this.data[family + "-" + size] = new Font(family, size);
+    }
+    return this.data[family + "-" + size];
+};
+
+module.exports = FontMetrics;
diff --git a/src/framecontainer.js b/src/framecontainer.js
new file mode 100644
index 0000000..475b9fa
--- /dev/null
+++ b/src/framecontainer.js
@@ -0,0 +1,31 @@
+var utils = require('./utils');
+var getBounds = utils.getBounds;
+var loadUrlDocument = require('./proxy').loadUrlDocument;
+
+function FrameContainer(container, sameOrigin, options) {
+    this.image = null;
+    this.src = container;
+    var self = this;
+    var bounds = getBounds(container);
+    this.promise = (!sameOrigin ? this.proxyLoad(options.proxy, bounds, options) : new Promise(function(resolve) {
+        if (container.contentWindow.document.URL === "about:blank" || container.contentWindow.document.documentElement == null) {
+            container.contentWindow.onload = container.onload = function() {
+                resolve(container);
+            };
+        } else {
+            resolve(container);
+        }
+    })).then(function(container) {
+        var html2canvas = require('./core');
+        return html2canvas(container.contentWindow.document.documentElement, {type: 'view', width: container.width, height: container.height, proxy: options.proxy, javascriptEnabled: options.javascriptEnabled, removeContainer: options.removeContainer, allowTaint: options.allowTaint, imageTimeout: options.imageTimeout / 2});
+    }).then(function(canvas) {
+        return self.image = canvas;
+    });
+}
+
+FrameContainer.prototype.proxyLoad = function(proxy, bounds, options) {
+    var container = this.src;
+    return loadUrlDocument(container.src, proxy, container.ownerDocument, bounds.width, bounds.height, options);
+};
+
+module.exports = FrameContainer;
diff --git a/src/gradientcontainer.js b/src/gradientcontainer.js
new file mode 100644
index 0000000..0c3ea61
--- /dev/null
+++ b/src/gradientcontainer.js
@@ -0,0 +1,21 @@
+function GradientContainer(imageData) {
+    this.src = imageData.value;
+    this.colorStops = [];
+    this.type = null;
+    this.x0 = 0.5;
+    this.y0 = 0.5;
+    this.x1 = 0.5;
+    this.y1 = 0.5;
+    this.promise = Promise.resolve(true);
+}
+
+GradientContainer.TYPES = {
+    LINEAR: 1,
+    RADIAL: 2
+};
+
+// TODO: support hsl[a], negative %/length values
+// TODO: support <angle> (e.g. -?\d{1,3}(?:\.\d+)deg, etc. : https://developer.mozilla.org/docs/Web/CSS/angle )
+GradientContainer.REGEXP_COLORSTOP = /^\s*(rgba?\(\s*\d{1,3},\s*\d{1,3},\s*\d{1,3}(?:,\s*[0-9\.]+)?\s*\)|[a-z]{3,20}|#[a-f0-9]{3,6})(?:\s+(\d{1,3}(?:\.\d+)?)(%|px)?)?(?:\s|$)/i;
+
+module.exports = GradientContainer;
diff --git a/src/imagecontainer.js b/src/imagecontainer.js
new file mode 100644
index 0000000..8710b56
--- /dev/null
+++ b/src/imagecontainer.js
@@ -0,0 +1,19 @@
+function ImageContainer(src, cors) {
+    this.src = src;
+    this.image = new Image();
+    var self = this;
+    this.tainted = null;
+    this.promise = new Promise(function(resolve, reject) {
+        self.image.onload = resolve;
+        self.image.onerror = reject;
+        if (cors) {
+            self.image.crossOrigin = "anonymous";
+        }
+        self.image.src = src;
+        if (self.image.complete === true) {
+            resolve(self.image);
+        }
+    });
+}
+
+module.exports = ImageContainer;
diff --git a/src/imageloader.js b/src/imageloader.js
new file mode 100644
index 0000000..3a504e1
--- /dev/null
+++ b/src/imageloader.js
@@ -0,0 +1,157 @@
+var log = require('./log');
+var ImageContainer = require('./imagecontainer');
+var DummyImageContainer = require('./dummyimagecontainer');
+var ProxyImageContainer = require('./proxyimagecontainer');
+var FrameContainer = require('./framecontainer');
+var SVGContainer = require('./svgcontainer');
+var SVGNodeContainer = require('./svgnodecontainer');
+var LinearGradientContainer = require('./lineargradientcontainer');
+var WebkitGradientContainer = require('./webkitgradientcontainer');
+var bind = require('./utils').bind;
+
+function ImageLoader(options, support) {
+    this.link = null;
+    this.options = options;
+    this.support = support;
+    this.origin = this.getOrigin(window.location.href);
+}
+
+ImageLoader.prototype.findImages = function(nodes) {
+    var images = [];
+    nodes.reduce(function(imageNodes, container) {
+        switch(container.node.nodeName) {
+        case "IMG":
+            return imageNodes.concat([{
+                args: [container.node.src],
+                method: "url"
+            }]);
+        case "svg":
+        case "IFRAME":
+            return imageNodes.concat([{
+                args: [container.node],
+                method: container.node.nodeName
+            }]);
+        }
+        return imageNodes;
+    }, []).forEach(this.addImage(images, this.loadImage), this);
+    return images;
+};
+
+ImageLoader.prototype.findBackgroundImage = function(images, container) {
+    container.parseBackgroundImages().filter(this.hasImageBackground).forEach(this.addImage(images, this.loadImage), this);
+    return images;
+};
+
+ImageLoader.prototype.addImage = function(images, callback) {
+    return function(newImage) {
+        newImage.args.forEach(function(image) {
+            if (!this.imageExists(images, image)) {
+                images.splice(0, 0, callback.call(this, newImage));
+                log('Added image #' + (images.length), typeof(image) === "string" ? image.substring(0, 100) : image);
+            }
+        }, this);
+    };
+};
+
+ImageLoader.prototype.hasImageBackground = function(imageData) {
+    return imageData.method !== "none";
+};
+
+ImageLoader.prototype.loadImage = function(imageData) {
+    if (imageData.method === "url") {
+        var src = imageData.args[0];
+        if (this.isSVG(src) && !this.support.svg && !this.options.allowTaint) {
+            return new SVGContainer(src);
+        } else if (src.match(/data:image\/.*;base64,/i)) {
+            return new ImageContainer(src.replace(/url\(['"]{0,}|['"]{0,}\)$/ig, ''), false);
+        } else if (this.isSameOrigin(src) || this.options.allowTaint === true || this.isSVG(src)) {
+            return new ImageContainer(src, false);
+        } else if (this.support.cors && !this.options.allowTaint && this.options.useCORS) {
+            return new ImageContainer(src, true);
+        } else if (this.options.proxy) {
+            return new ProxyImageContainer(src, this.options.proxy);
+        } else {
+            return new DummyImageContainer(src);
+        }
+    } else if (imageData.method === "linear-gradient") {
+        return new LinearGradientContainer(imageData);
+    } else if (imageData.method === "gradient") {
+        return new WebkitGradientContainer(imageData);
+    } else if (imageData.method === "svg") {
+        return new SVGNodeContainer(imageData.args[0], this.support.svg);
+    } else if (imageData.method === "IFRAME") {
+        return new FrameContainer(imageData.args[0], this.isSameOrigin(imageData.args[0].src), this.options);
+    } else {
+        return new DummyImageContainer(imageData);
+    }
+};
+
+ImageLoader.prototype.isSVG = function(src) {
+    return src.substring(src.length - 3).toLowerCase() === "svg" || SVGContainer.prototype.isInline(src);
+};
+
+ImageLoader.prototype.imageExists = function(images, src) {
+    return images.some(function(image) {
+        return image.src === src;
+    });
+};
+
+ImageLoader.prototype.isSameOrigin = function(url) {
+    return (this.getOrigin(url) === this.origin);
+};
+
+ImageLoader.prototype.getOrigin = function(url) {
+    var link = this.link || (this.link = document.createElement("a"));
+    link.href = url;
+    link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/
+    return link.protocol + link.hostname + link.port;
+};
+
+ImageLoader.prototype.getPromise = function(container) {
+    return this.timeout(container, this.options.imageTimeout)['catch'](function() {
+        var dummy = new DummyImageContainer(container.src);
+        return dummy.promise.then(function(image) {
+            container.image = image;
+        });
+    });
+};
+
+ImageLoader.prototype.get = function(src) {
+    var found = null;
+    return this.images.some(function(img) {
+        return (found = img).src === src;
+    }) ? found : null;
+};
+
+ImageLoader.prototype.fetch = function(nodes) {
+    this.images = nodes.reduce(bind(this.findBackgroundImage, this), this.findImages(nodes));
+    this.images.forEach(function(image, index) {
+        image.promise.then(function() {
+            log("Succesfully loaded image #"+ (index+1), image);
+        }, function(e) {
+            log("Failed loading image #"+ (index+1), image, e);
+        });
+    });
+    this.ready = Promise.all(this.images.map(this.getPromise, this));
+    log("Finished searching images");
+    return this;
+};
+
+ImageLoader.prototype.timeout = function(container, timeout) {
+    var timer;
+    var promise = Promise.race([container.promise, new Promise(function(res, reject) {
+        timer = setTimeout(function() {
+            log("Timed out loading image", container);
+            reject(container);
+        }, timeout);
+    })]).then(function(container) {
+        clearTimeout(timer);
+        return container;
+    });
+    promise['catch'](function() {
+        clearTimeout(timer);
+    });
+    return promise;
+};
+
+module.exports = ImageLoader;
diff --git a/src/lineargradientcontainer.js b/src/lineargradientcontainer.js
new file mode 100644
index 0000000..b1ce32f
--- /dev/null
+++ b/src/lineargradientcontainer.js
@@ -0,0 +1,102 @@
+var GradientContainer = require('./gradientcontainer');
+var Color = require('./color');
+
+function LinearGradientContainer(imageData) {
+    GradientContainer.apply(this, arguments);
+    this.type = GradientContainer.TYPES.LINEAR;
+
+    var hasDirection = LinearGradientContainer.REGEXP_DIRECTION.test( imageData.args[0] ) ||
+        !GradientContainer.REGEXP_COLORSTOP.test( imageData.args[0] );
+
+    if (hasDirection) {
+        imageData.args[0].split(/\s+/).reverse().forEach(function(position, index) {
+            switch(position) {
+            case "left":
+                this.x0 = 0;
+                this.x1 = 1;
+                break;
+            case "top":
+                this.y0 = 0;
+                this.y1 = 1;
+                break;
+            case "right":
+                this.x0 = 1;
+                this.x1 = 0;
+                break;
+            case "bottom":
+                this.y0 = 1;
+                this.y1 = 0;
+                break;
+            case "to":
+                var y0 = this.y0;
+                var x0 = this.x0;
+                this.y0 = this.y1;
+                this.x0 = this.x1;
+                this.x1 = x0;
+                this.y1 = y0;
+                break;
+            case "center":
+                break; // centered by default
+            // Firefox internally converts position keywords to percentages:
+            // http://www.w3.org/TR/2010/WD-CSS2-20101207/colors.html#propdef-background-position
+            default: // percentage or absolute length
+                // TODO: support absolute start point positions (e.g., use bounds to convert px to a ratio)
+                var ratio = parseFloat(position, 10) * 1e-2;
+                if (isNaN(ratio)) { // invalid or unhandled value
+                    break;
+                }
+                if (index === 0) {
+                    this.y0 = ratio;
+                    this.y1 = 1 - this.y0;
+                } else {
+                    this.x0 = ratio;
+                    this.x1 = 1 - this.x0;
+                }
+                break;
+            }
+        }, this);
+    } else {
+        this.y0 = 0;
+        this.y1 = 1;
+    }
+
+    this.colorStops = imageData.args.slice(hasDirection ? 1 : 0).map(function(colorStop) {
+        var colorStopMatch = colorStop.match(GradientContainer.REGEXP_COLORSTOP);
+        var value = +colorStopMatch[2];
+        var unit = value === 0 ? "%" : colorStopMatch[3]; // treat "0" as "0%"
+        return {
+            color: new Color(colorStopMatch[1]),
+            // TODO: support absolute stop positions (e.g., compute gradient line length & convert px to ratio)
+            stop: unit === "%" ? value / 100 : null
+        };
+    });
+
+    if (this.colorStops[0].stop === null) {
+        this.colorStops[0].stop = 0;
+    }
+
+    if (this.colorStops[this.colorStops.length - 1].stop === null) {
+        this.colorStops[this.colorStops.length - 1].stop = 1;
+    }
+
+    // calculates and fills-in explicit stop positions when omitted from rule
+    this.colorStops.forEach(function(colorStop, index) {
+        if (colorStop.stop === null) {
+            this.colorStops.slice(index).some(function(find, count) {
+                if (find.stop !== null) {
+                    colorStop.stop = ((find.stop - this.colorStops[index - 1].stop) / (count + 1)) + this.colorStops[index - 1].stop;
+                    return true;
+                } else {
+                    return false;
+                }
+            }, this);
+        }
+    }, this);
+}
+
+LinearGradientContainer.prototype = Object.create(GradientContainer.prototype);
+
+// TODO: support <angle> (e.g. -?\d{1,3}(?:\.\d+)deg, etc. : https://developer.mozilla.org/docs/Web/CSS/angle )
+LinearGradientContainer.REGEXP_DIRECTION = /^\s*(?:to|left|right|top|bottom|center|\d{1,3}(?:\.\d+)?%?)(?:\s|$)/i;
+
+module.exports = LinearGradientContainer;
diff --git a/src/log.js b/src/log.js
new file mode 100644
index 0000000..a1118fd
--- /dev/null
+++ b/src/log.js
@@ -0,0 +1,8 @@
+var logger = function() {
+    if (logger.options.logging && window.console && window.console.log) {
+        Function.prototype.bind.call(window.console.log, (window.console)).apply(window.console, [(Date.now() - logger.options.start) + "ms", "html2canvas:"].concat([].slice.call(arguments, 0)));
+    }
+};
+
+logger.options = {logging: false};
+module.exports = logger;
diff --git a/src/nodecontainer.js b/src/nodecontainer.js
new file mode 100644
index 0000000..a7a1299
--- /dev/null
+++ b/src/nodecontainer.js
@@ -0,0 +1,296 @@
+var Color = require('./color');
+var utils = require('./utils');
+var getBounds = utils.getBounds;
+var parseBackgrounds = utils.parseBackgrounds;
+var offsetBounds = utils.offsetBounds;
+
+function NodeContainer(node, parent) {
+    this.node = node;
+    this.parent = parent;
+    this.stack = null;
+    this.bounds = null;
+    this.borders = null;
+    this.clip = [];
+    this.backgroundClip = [];
+    this.offsetBounds = null;
+    this.visible = null;
+    this.computedStyles = null;
+    this.colors = {};
+    this.styles = {};
+    this.backgroundImages = null;
+    this.transformData = null;
+    this.transformMatrix = null;
+    this.isPseudoElement = false;
+    this.opacity = null;
+}
+
+NodeContainer.prototype.cloneTo = function(stack) {
+    stack.visible = this.visible;
+    stack.borders = this.borders;
+    stack.bounds = this.bounds;
+    stack.clip = this.clip;
+    stack.backgroundClip = this.backgroundClip;
+    stack.computedStyles = this.computedStyles;
+    stack.styles = this.styles;
+    stack.backgroundImages = this.backgroundImages;
+    stack.opacity = this.opacity;
+};
+
+NodeContainer.prototype.getOpacity = function() {
+    return this.opacity === null ? (this.opacity = this.cssFloat('opacity')) : this.opacity;
+};
+
+NodeContainer.prototype.assignStack = function(stack) {
+    this.stack = stack;
+    stack.children.push(this);
+};
+
+NodeContainer.prototype.isElementVisible = function() {
+    return this.node.nodeType === Node.TEXT_NODE ? this.parent.visible : (
+        this.css('display') !== "none" &&
+        this.css('visibility') !== "hidden" &&
+        !this.node.hasAttribute("data-html2canvas-ignore") &&
+        (this.node.nodeName !== "INPUT" || this.node.getAttribute("type") !== "hidden")
+    );
+};
+
+NodeContainer.prototype.css = function(attribute) {
+    if (!this.computedStyles) {
+        this.computedStyles = this.isPseudoElement ? this.parent.computedStyle(this.before ? ":before" : ":after") : this.computedStyle(null);
+    }
+
+    return this.styles[attribute] || (this.styles[attribute] = this.computedStyles[attribute]);
+};
+
+NodeContainer.prototype.prefixedCss = function(attribute) {
+    var prefixes = ["webkit", "moz", "ms", "o"];
+    var value = this.css(attribute);
+    if (value === undefined) {
+        prefixes.some(function(prefix) {
+            value = this.css(prefix + attribute.substr(0, 1).toUpperCase() + attribute.substr(1));
+            return value !== undefined;
+        }, this);
+    }
+    return value === undefined ? null : value;
+};
+
+NodeContainer.prototype.computedStyle = function(type) {
+    return this.node.ownerDocument.defaultView.getComputedStyle(this.node, type);
+};
+
+NodeContainer.prototype.cssInt = function(attribute) {
+    var value = parseInt(this.css(attribute), 10);
+    return (isNaN(value)) ? 0 : value; // borders in old IE are throwing 'medium' for demo.html
+};
+
+NodeContainer.prototype.color = function(attribute) {
+    return this.colors[attribute] || (this.colors[attribute] = new Color(this.css(attribute)));
+};
+
+NodeContainer.prototype.cssFloat = function(attribute) {
+    var value = parseFloat(this.css(attribute));
+    return (isNaN(value)) ? 0 : value;
+};
+
+NodeContainer.prototype.fontWeight = function() {
+    var weight = this.css("fontWeight");
+    switch(parseInt(weight, 10)){
+    case 401:
+        weight = "bold";
+        break;
+    case 400:
+        weight = "normal";
+        break;
+    }
+    return weight;
+};
+
+NodeContainer.prototype.parseClip = function() {
+    var matches = this.css('clip').match(this.CLIP);
+    if (matches) {
+        return {
+            top: parseInt(matches[1], 10),
+            right: parseInt(matches[2], 10),
+            bottom: parseInt(matches[3], 10),
+            left: parseInt(matches[4], 10)
+        };
+    }
+    return null;
+};
+
+NodeContainer.prototype.parseBackgroundImages = function() {
+    return this.backgroundImages || (this.backgroundImages = parseBackgrounds(this.css("backgroundImage")));
+};
+
+NodeContainer.prototype.cssList = function(property, index) {
+    var value = (this.css(property) || '').split(',');
+    value = value[index || 0] || value[0] || 'auto';
+    value = value.trim().split(' ');
+    if (value.length === 1) {
+        value = [value[0], isPercentage(value[0]) ? 'auto' : value[0]];
+    }
+    return value;
+};
+
+NodeContainer.prototype.parseBackgroundSize = function(bounds, image, index) {
+    var size = this.cssList("backgroundSize", index);
+    var width, height;
+
+    if (isPercentage(size[0])) {
+        width = bounds.width * parseFloat(size[0]) / 100;
+    } else if (/contain|cover/.test(size[0])) {
+        var targetRatio = bounds.width / bounds.height, currentRatio = image.width / image.height;
+        return (targetRatio < currentRatio ^ size[0] === 'contain') ?  {width: bounds.height * currentRatio, height: bounds.height} : {width: bounds.width, height: bounds.width / currentRatio};
+    } else {
+        width = parseInt(size[0], 10);
+    }
+
+    if (size[0] === 'auto' && size[1] === 'auto') {
+        height = image.height;
+    } else if (size[1] === 'auto') {
+        height = width / image.width * image.height;
+    } else if (isPercentage(size[1])) {
+        height =  bounds.height * parseFloat(size[1]) / 100;
+    } else {
+        height = parseInt(size[1], 10);
+    }
+
+    if (size[0] === 'auto') {
+        width = height / image.height * image.width;
+    }
+
+    return {width: width, height: height};
+};
+
+NodeContainer.prototype.parseBackgroundPosition = function(bounds, image, index, backgroundSize) {
+    var position = this.cssList('backgroundPosition', index);
+    var left, top;
+
+    if (isPercentage(position[0])){
+        left = (bounds.width - (backgroundSize || image).width) * (parseFloat(position[0]) / 100);
+    } else {
+        left = parseInt(position[0], 10);
+    }
+
+    if (position[1] === 'auto') {
+        top = left / image.width * image.height;
+    } else if (isPercentage(position[1])){
+        top =  (bounds.height - (backgroundSize || image).height) * parseFloat(position[1]) / 100;
+    } else {
+        top = parseInt(position[1], 10);
+    }
+
+    if (position[0] === 'auto') {
+        left = top / image.height * image.width;
+    }
+
+    return {left: left, top: top};
+};
+
+NodeContainer.prototype.parseBackgroundRepeat = function(index) {
+    return this.cssList("backgroundRepeat", index)[0];
+};
+
+NodeContainer.prototype.parseTextShadows = function() {
+    var textShadow = this.css("textShadow");
+    var results = [];
+
+    if (textShadow && textShadow !== 'none') {
+        var shadows = textShadow.match(this.TEXT_SHADOW_PROPERTY);
+        for (var i = 0; shadows && (i < shadows.length); i++) {
+            var s = shadows[i].match(this.TEXT_SHADOW_VALUES);
+            results.push({
+                color: new Color(s[0]),
+                offsetX: s[1] ? parseFloat(s[1].replace('px', '')) : 0,
+                offsetY: s[2] ? parseFloat(s[2].replace('px', '')) : 0,
+                blur: s[3] ? s[3].replace('px', '') : 0
+            });
+        }
+    }
+    return results;
+};
+
+NodeContainer.prototype.parseTransform = function() {
+    if (!this.transformData) {
+        if (this.hasTransform()) {
+            var offset = this.parseBounds();
+            var origin = this.prefixedCss("transformOrigin").split(" ").map(removePx).map(asFloat);
+            origin[0] += offset.left;
+            origin[1] += offset.top;
+            this.transformData = {
+                origin: origin,
+                matrix: this.parseTransformMatrix()
+            };
+        } else {
+            this.transformData = {
+                origin: [0, 0],
+                matrix: [1, 0, 0, 1, 0, 0]
+            };
+        }
+    }
+    return this.transformData;
+};
+
+NodeContainer.prototype.parseTransformMatrix = function() {
+    if (!this.transformMatrix) {
+        var transform = this.prefixedCss("transform");
+        var matrix = transform ? parseMatrix(transform.match(this.MATRIX_PROPERTY)) : null;
+        this.transformMatrix = matrix ? matrix : [1, 0, 0, 1, 0, 0];
+    }
+    return this.transformMatrix;
+};
+
+NodeContainer.prototype.parseBounds = function() {
+    return this.bounds || (this.bounds = this.hasTransform() ? offsetBounds(this.node) : getBounds(this.node));
+};
+
+NodeContainer.prototype.hasTransform = function() {
+    return this.parseTransformMatrix().join(",") !== "1,0,0,1,0,0" || (this.parent && this.parent.hasTransform());
+};
+
+NodeContainer.prototype.getValue = function() {
+    var value = this.node.value || "";
+    if (this.node.tagName === "SELECT") {
+        value = selectionValue(this.node);
+    } else if (this.node.type === "password") {
+        value = Array(value.length + 1).join('\u2022'); // jshint ignore:line
+    }
+    return value.length === 0 ? (this.node.placeholder || "") : value;
+};
+
+NodeContainer.prototype.MATRIX_PROPERTY = /(matrix|matrix3d)\((.+)\)/;
+NodeContainer.prototype.TEXT_SHADOW_PROPERTY = /((rgba|rgb)\([^\)]+\)(\s-?\d+px){0,})/g;
+NodeContainer.prototype.TEXT_SHADOW_VALUES = /(-?\d+px)|(#.+)|(rgb\(.+\))|(rgba\(.+\))/g;
+NodeContainer.prototype.CLIP = /^rect\((\d+)px,? (\d+)px,? (\d+)px,? (\d+)px\)$/;
+
+function selectionValue(node) {
+    var option = node.options[node.selectedIndex || 0];
+    return option ? (option.text || "") : "";
+}
+
+function parseMatrix(match) {
+    if (match && match[1] === "matrix") {
+        return match[2].split(",").map(function(s) {
+            return parseFloat(s.trim());
+        });
+    } else if (match && match[1] === "matrix3d") {
+        var matrix3d = match[2].split(",").map(function(s) {
+          return parseFloat(s.trim());
+        });
+        return [matrix3d[0], matrix3d[1], matrix3d[4], matrix3d[5], matrix3d[12], matrix3d[13]];
+    }
+}
+
+function isPercentage(value) {
+    return value.toString().indexOf("%") !== -1;
+}
+
+function removePx(str) {
+    return str.replace("px", "");
+}
+
+function asFloat(str) {
+    return parseFloat(str);
+}
+
+module.exports = NodeContainer;
diff --git a/src/nodeparser.js b/src/nodeparser.js
new file mode 100644
index 0000000..683f9bd
--- /dev/null
+++ b/src/nodeparser.js
@@ -0,0 +1,869 @@
+var log = require('./log');
+var punycode = require('punycode');
+var NodeContainer = require('./nodecontainer');
+var TextContainer = require('./textcontainer');
+var PseudoElementContainer = require('./pseudoelementcontainer');
+var FontMetrics = require('./fontmetrics');
+var Color = require('./color');
+var StackingContext = require('./stackingcontext');
+var utils = require('./utils');
+var bind = utils.bind;
+var getBounds = utils.getBounds;
+var parseBackgrounds = utils.parseBackgrounds;
+var offsetBounds = utils.offsetBounds;
+
+function NodeParser(element, renderer, support, imageLoader, options) {
+    log("Starting NodeParser");
+    this.renderer = renderer;
+    this.options = options;
+    this.range = null;
+    this.support = support;
+    this.renderQueue = [];
+    this.stack = new StackingContext(true, 1, element.ownerDocument, null);
+    var parent = new NodeContainer(element, null);
+    if (options.background) {
+        renderer.rectangle(0, 0, renderer.width, renderer.height, new Color(options.background));
+    }
+    if (element === element.ownerDocument.documentElement) {
+        // http://www.w3.org/TR/css3-background/#special-backgrounds
+        var canvasBackground = new NodeContainer(parent.color('backgroundColor').isTransparent() ? element.ownerDocument.body : element.ownerDocument.documentElement, null);
+        renderer.rectangle(0, 0, renderer.width, renderer.height, canvasBackground.color('backgroundColor'));
+    }
+    parent.visibile = parent.isElementVisible();
+    this.createPseudoHideStyles(element.ownerDocument);
+    this.disableAnimations(element.ownerDocument);
+    this.nodes = flatten([parent].concat(this.getChildren(parent)).filter(function(container) {
+        return container.visible = container.isElementVisible();
+    }).map(this.getPseudoElements, this));
+    this.fontMetrics = new FontMetrics();
+    log("Fetched nodes, total:", this.nodes.length);
+    log("Calculate overflow clips");
+    this.calculateOverflowClips();
+    log("Start fetching images");
+    this.images = imageLoader.fetch(this.nodes.filter(isElement));
+    this.ready = this.images.ready.then(bind(function() {
+        log("Images loaded, starting parsing");
+        log("Creating stacking contexts");
+        this.createStackingContexts();
+        log("Sorting stacking contexts");
+        this.sortStackingContexts(this.stack);
+        this.parse(this.stack);
+        log("Render queue created with " + this.renderQueue.length + " items");
+        return new Promise(bind(function(resolve) {
+            if (!options.async) {
+                this.renderQueue.forEach(this.paint, this);
+                resolve();
+            } else if (typeof(options.async) === "function") {
+                options.async.call(this, this.renderQueue, resolve);
+            } else if (this.renderQueue.length > 0){
+                this.renderIndex = 0;
+                this.asyncRenderer(this.renderQueue, resolve);
+            } else {
+                resolve();
+            }
+        }, this));
+    }, this));
+}
+
+NodeParser.prototype.calculateOverflowClips = function() {
+    this.nodes.forEach(function(container) {
+        if (isElement(container)) {
+            if (isPseudoElement(container)) {
+                container.appendToDOM();
+            }
+            container.borders = this.parseBorders(container);
+            var clip = (container.css('overflow') === "hidden") ? [container.borders.clip] : [];
+            var cssClip = container.parseClip();
+            if (cssClip && ["absolute", "fixed"].indexOf(container.css('position')) !== -1) {
+                clip.push([["rect",
+                        container.bounds.left + cssClip.left,
+                        container.bounds.top + cssClip.top,
+                        cssClip.right - cssClip.left,
+                        cssClip.bottom - cssClip.top
+                ]]);
+            }
+            container.clip = hasParentClip(container) ? container.parent.clip.concat(clip) : clip;
+            container.backgroundClip = (container.css('overflow') !== "hidden") ? container.clip.concat([container.borders.clip]) : container.clip;
+            if (isPseudoElement(container)) {
+                container.cleanDOM();
+            }
+        } else if (isTextNode(container)) {
+            container.clip = hasParentClip(container) ? container.parent.clip : [];
+        }
+        if (!isPseudoElement(container)) {
+            container.bounds = null;
+        }
+    }, this);
+};
+
+function hasParentClip(container) {
+    return container.parent && container.parent.clip.length;
+}
+
+NodeParser.prototype.asyncRenderer = function(queue, resolve, asyncTimer) {
+    asyncTimer = asyncTimer || Date.now();
+    this.paint(queue[this.renderIndex++]);
+    if (queue.length === this.renderIndex) {
+        resolve();
+    } else if (asyncTimer + 20 > Date.now()) {
+        this.asyncRenderer(queue, resolve, asyncTimer);
+    } else {
+        setTimeout(bind(function() {
+            this.asyncRenderer(queue, resolve);
+        }, this), 0);
+    }
+};
+
+NodeParser.prototype.createPseudoHideStyles = function(document) {
+    this.createStyles(document, '.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + ':before { content: "" !important; display: none !important; }' +
+        '.' + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER + ':after { content: "" !important; display: none !important; }');
+};
+
+NodeParser.prototype.disableAnimations = function(document) {
+    this.createStyles(document, '* { -webkit-animation: none !important; -moz-animation: none !important; -o-animation: none !important; animation: none !important; ' +
+        '-webkit-transition: none !important; -moz-transition: none !important; -o-transition: none !important; transition: none !important;}');
+};
+
+NodeParser.prototype.createStyles = function(document, styles) {
+    var hidePseudoElements = document.createElement('style');
+    hidePseudoElements.innerHTML = styles;
+    document.body.appendChild(hidePseudoElements);
+};
+
+NodeParser.prototype.getPseudoElements = function(container) {
+    var nodes = [[container]];
+    if (container.node.nodeType === Node.ELEMENT_NODE) {
+        var before = this.getPseudoElement(container, ":before");
+        var after = this.getPseudoElement(container, ":after");
+
+        if (before) {
+            nodes.push(before);
+        }
+
+        if (after) {
+            nodes.push(after);
+        }
+    }
+    return flatten(nodes);
+};
+
+function toCamelCase(str) {
+    return str.replace(/(\-[a-z])/g, function(match){
+        return match.toUpperCase().replace('-','');
+    });
+}
+
+NodeParser.prototype.getPseudoElement = function(container, type) {
+    var style = container.computedStyle(type);
+    if(!style || !style.content || style.content === "none" || style.content === "-moz-alt-content" || style.display === "none") {
+        return null;
+    }
+
+    var content = stripQuotes(style.content);
+    var isImage = content.substr(0, 3) === 'url';
+    var pseudoNode = document.createElement(isImage ? 'img' : 'html2canvaspseudoelement');
+    var pseudoContainer = new PseudoElementContainer(pseudoNode, container, type);
+
+    for (var i = style.length-1; i >= 0; i--) {
+        var property = toCamelCase(style.item(i));
+        pseudoNode.style[property] = style[property];
+    }
+
+    pseudoNode.className = PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE + " " + PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER;
+
+    if (isImage) {
+        pseudoNode.src = parseBackgrounds(content)[0].args[0];
+        return [pseudoContainer];
+    } else {
+        var text = document.createTextNode(content);
+        pseudoNode.appendChild(text);
+        return [pseudoContainer, new TextContainer(text, pseudoContainer)];
+    }
+};
+
+
+NodeParser.prototype.getChildren = function(parentContainer) {
+    return flatten([].filter.call(parentContainer.node.childNodes, renderableNode).map(function(node) {
+        var container = [node.nodeType === Node.TEXT_NODE ? new TextContainer(node, parentContainer) : new NodeContainer(node, parentContainer)].filter(nonIgnoredElement);
+        return node.nodeType === Node.ELEMENT_NODE && container.length && node.tagName !== "TEXTAREA" ? (container[0].isElementVisible() ? container.concat(this.getChildren(container[0])) : []) : container;
+    }, this));
+};
+
+NodeParser.prototype.newStackingContext = function(container, hasOwnStacking) {
+    var stack = new StackingContext(hasOwnStacking, container.getOpacity(), container.node, container.parent);
+    container.cloneTo(stack);
+    var parentStack = hasOwnStacking ? stack.getParentStack(this) : stack.parent.stack;
+    parentStack.contexts.push(stack);
+    container.stack = stack;
+};
+
+NodeParser.prototype.createStackingContexts = function() {
+    this.nodes.forEach(function(container) {
+        if (isElement(container) && (this.isRootElement(container) || hasOpacity(container) || isPositionedForStacking(container) || this.isBodyWithTransparentRoot(container) || container.hasTransform())) {
+            this.newStackingContext(container, true);
+        } else if (isElement(container) && ((isPositioned(container) && zIndex0(container)) || isInlineBlock(container) || isFloating(container))) {
+            this.newStackingContext(container, false);
+        } else {
+            container.assignStack(container.parent.stack);
+        }
+    }, this);
+};
+
+NodeParser.prototype.isBodyWithTransparentRoot = function(container) {
+    return container.node.nodeName === "BODY" && container.parent.color('backgroundColor').isTransparent();
+};
+
+NodeParser.prototype.isRootElement = function(container) {
+    return container.parent === null;
+};
+
+NodeParser.prototype.sortStackingContexts = function(stack) {
+    stack.contexts.sort(zIndexSort(stack.contexts.slice(0)));
+    stack.contexts.forEach(this.sortStackingContexts, this);
+};
+
+NodeParser.prototype.parseTextBounds = function(container) {
+    return function(text, index, textList) {
+        if (container.parent.css("textDecoration").substr(0, 4) !== "none" || text.trim().length !== 0) {
+            if (this.support.rangeBounds && !container.parent.hasTransform()) {
+                var offset = textList.slice(0, index).join("").length;
+                return this.getRangeBounds(container.node, offset, text.length);
+            } else if (container.node && typeof(container.node.data) === "string") {
+                var replacementNode = container.node.splitText(text.length);
+                var bounds = this.getWrapperBounds(container.node, container.parent.hasTransform());
+                container.node = replacementNode;
+                return bounds;
+            }
+        } else if(!this.support.rangeBounds || container.parent.hasTransform()){
+            container.node = container.node.splitText(text.length);
+        }
+        return {};
+    };
+};
+
+NodeParser.prototype.getWrapperBounds = function(node, transform) {
+    var wrapper = node.ownerDocument.createElement('html2canvaswrapper');
+    var parent = node.parentNode,
+        backupText = node.cloneNode(true);
+
+    wrapper.appendChild(node.cloneNode(true));
+    parent.replaceChild(wrapper, node);
+    var bounds = transform ? offsetBounds(wrapper) : getBounds(wrapper);
+    parent.replaceChild(backupText, wrapper);
+    return bounds;
+};
+
+NodeParser.prototype.getRangeBounds = function(node, offset, length) {
+    var range = this.range || (this.range = node.ownerDocument.createRange());
+    range.setStart(node, offset);
+    range.setEnd(node, offset + length);
+    return range.getBoundingClientRect();
+};
+
+function ClearTransform() {}
+
+NodeParser.prototype.parse = function(stack) {
+    // http://www.w3.org/TR/CSS21/visuren.html#z-index
+    var negativeZindex = stack.contexts.filter(negativeZIndex); // 2. the child stacking contexts with negative stack levels (most negative first).
+    var descendantElements = stack.children.filter(isElement);
+    var descendantNonFloats = descendantElements.filter(not(isFloating));
+    var nonInlineNonPositionedDescendants = descendantNonFloats.filter(not(isPositioned)).filter(not(inlineLevel)); // 3 the in-flow, non-inline-level, non-positioned descendants.
+    var nonPositionedFloats = descendantElements.filter(not(isPositioned)).filter(isFloating); // 4. the non-positioned floats.
+    var inFlow = descendantNonFloats.filter(not(isPositioned)).filter(inlineLevel); // 5. the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
+    var stackLevel0 = stack.contexts.concat(descendantNonFloats.filter(isPositioned)).filter(zIndex0); // 6. the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
+    var text = stack.children.filter(isTextNode).filter(hasText);
+    var positiveZindex = stack.contexts.filter(positiveZIndex); // 7. the child stacking contexts with positive stack levels (least positive first).
+    negativeZindex.concat(nonInlineNonPositionedDescendants).concat(nonPositionedFloats)
+        .concat(inFlow).concat(stackLevel0).concat(text).concat(positiveZindex).forEach(function(container) {
+            this.renderQueue.push(container);
+            if (isStackingContext(container)) {
+                this.parse(container);
+                this.renderQueue.push(new ClearTransform());
+            }
+        }, this);
+};
+
+NodeParser.prototype.paint = function(container) {
+    try {
+        if (container instanceof ClearTransform) {
+            this.renderer.ctx.restore();
+        } else if (isTextNode(container)) {
+            if (isPseudoElement(container.parent)) {
+                container.parent.appendToDOM();
+            }
+            this.paintText(container);
+            if (isPseudoElement(container.parent)) {
+                container.parent.cleanDOM();
+            }
+        } else {
+            this.paintNode(container);
+        }
+    } catch(e) {
+        log(e);
+        if (this.options.strict) {
+            throw e;
+        }
+    }
+};
+
+NodeParser.prototype.paintNode = function(container) {
+    if (isStackingContext(container)) {
+        this.renderer.setOpacity(container.opacity);
+        this.renderer.ctx.save();
+        if (container.hasTransform()) {
+            this.renderer.setTransform(container.parseTransform());
+        }
+    }
+
+    if (container.node.nodeName === "INPUT" && container.node.type === "checkbox") {
+        this.paintCheckbox(container);
+    } else if (container.node.nodeName === "INPUT" && container.node.type === "radio") {
+        this.paintRadio(container);
+    } else {
+        this.paintElement(container);
+    }
+};
+
+NodeParser.prototype.paintElement = function(container) {
+    var bounds = container.parseBounds();
+    this.renderer.clip(container.backgroundClip, function() {
+        this.renderer.renderBackground(container, bounds, container.borders.borders.map(getWidth));
+    }, this);
+
+    this.renderer.clip(container.clip, function() {
+        this.renderer.renderBorders(container.borders.borders);
+    }, this);
+
+    this.renderer.clip(container.backgroundClip, function() {
+        switch (container.node.nodeName) {
+        case "svg":
+        case "IFRAME":
+            var imgContainer = this.images.get(container.node);
+            if (imgContainer) {
+                this.renderer.renderImage(container, bounds, container.borders, imgContainer);
+            } else {
+                log("Error loading <" + container.node.nodeName + ">", container.node);
+            }
+            break;
+        case "IMG":
+            var imageContainer = this.images.get(container.node.src);
+            if (imageContainer) {
+                this.renderer.renderImage(container, bounds, container.borders, imageContainer);
+            } else {
+                log("Error loading <img>", container.node.src);
+            }
+            break;
+        case "CANVAS":
+            this.renderer.renderImage(container, bounds, container.borders, {image: container.node});
+            break;
+        case "SELECT":
+        case "INPUT":
+        case "TEXTAREA":
+            this.paintFormValue(container);
+            break;
+        }
+    }, this);
+};
+
+NodeParser.prototype.paintCheckbox = function(container) {
+    var b = container.parseBounds();
+
+    var size = Math.min(b.width, b.height);
+    var bounds = {width: size - 1, height: size - 1, top: b.top, left: b.left};
+    var r = [3, 3];
+    var radius = [r, r, r, r];
+    var borders = [1,1,1,1].map(function(w) {
+        return {color: new Color('#A5A5A5'), width: w};
+    });
+
+    var borderPoints = calculateCurvePoints(bounds, radius, borders);
+
+    this.renderer.clip(container.backgroundClip, function() {
+        this.renderer.rectangle(bounds.left + 1, bounds.top + 1, bounds.width - 2, bounds.height - 2, new Color("#DEDEDE"));
+        this.renderer.renderBorders(calculateBorders(borders, bounds, borderPoints, radius));
+        if (container.node.checked) {
+            this.renderer.font(new Color('#424242'), 'normal', 'normal', 'bold', (size - 3) + "px", 'arial');
+            this.renderer.text("\u2714", bounds.left + size / 6, bounds.top + size - 1);
+        }
+    }, this);
+};
+
+NodeParser.prototype.paintRadio = function(container) {
+    var bounds = container.parseBounds();
+
+    var size = Math.min(bounds.width, bounds.height) - 2;
+
+    this.renderer.clip(container.backgroundClip, function() {
+        this.renderer.circleStroke(bounds.left + 1, bounds.top + 1, size, new Color('#DEDEDE'), 1, new Color('#A5A5A5'));
+        if (container.node.checked) {
+            this.renderer.circle(Math.ceil(bounds.left + size / 4) + 1, Math.ceil(bounds.top + size / 4) + 1, Math.floor(size / 2), new Color('#424242'));
+        }
+    }, this);
+};
+
+NodeParser.prototype.paintFormValue = function(container) {
+    var value = container.getValue();
+    if (value.length > 0) {
+        var document = container.node.ownerDocument;
+        var wrapper = document.createElement('html2canvaswrapper');
+        var properties = ['lineHeight', 'textAlign', 'fontFamily', 'fontWeight', 'fontSize', 'color',
+            'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom',
+            'width', 'height', 'borderLeftStyle', 'borderTopStyle', 'borderLeftWidth', 'borderTopWidth',
+            'boxSizing', 'whiteSpace', 'wordWrap'];
+
+        properties.forEach(function(property) {
+            try {
+                wrapper.style[property] = container.css(property);
+            } catch(e) {
+                // Older IE has issues with "border"
+                log("html2canvas: Parse: Exception caught in renderFormValue: " + e.message);
+            }
+        });
+        var bounds = container.parseBounds();
+        wrapper.style.position = "fixed";
+        wrapper.style.left = bounds.left + "px";
+        wrapper.style.top = bounds.top + "px";
+        wrapper.textContent = value;
+        document.body.appendChild(wrapper);
+        this.paintText(new TextContainer(wrapper.firstChild, container));
+        document.body.removeChild(wrapper);
+    }
+};
+
+NodeParser.prototype.paintText = function(container) {
+    container.applyTextTransform();
+    var characters = punycode.ucs2.decode(container.node.data);
+    var textList = (!this.options.letterRendering || noLetterSpacing(container)) && !hasUnicode(container.node.data) ? getWords(characters) : characters.map(function(character) {
+        return punycode.ucs2.encode([character]);
+    });
+
+    var weight = container.parent.fontWeight();
+    var size = container.parent.css('fontSize');
+    var family = container.parent.css('fontFamily');
+    var shadows = container.parent.parseTextShadows();
+
+    this.renderer.font(container.parent.color('color'), container.parent.css('fontStyle'), container.parent.css('fontVariant'), weight, size, family);
+    if (shadows.length) {
+        // TODO: support multiple text shadows
+        this.renderer.fontShadow(shadows[0].color, shadows[0].offsetX, shadows[0].offsetY, shadows[0].blur);
+    } else {
+        this.renderer.clearShadow();
+    }
+
+    this.renderer.clip(container.parent.clip, function() {
+        textList.map(this.parseTextBounds(container), this).forEach(function(bounds, index) {
+            if (bounds) {
+                this.renderer.text(textList[index], bounds.left, bounds.bottom);
+                this.renderTextDecoration(container.parent, bounds, this.fontMetrics.getMetrics(family, size));
+            }
+        }, this);
+    }, this);
+};
+
+NodeParser.prototype.renderTextDecoration = function(container, bounds, metrics) {
+    switch(container.css("textDecoration").split(" ")[0]) {
+    case "underline":
+        // Draws a line at the baseline of the font
+        // TODO As some browsers display the line as more than 1px if the font-size is big, need to take that into account both in position and size
+        this.renderer.rectangle(bounds.left, Math.round(bounds.top + metrics.baseline + metrics.lineWidth), bounds.width, 1, container.color("color"));
+        break;
+    case "overline":
+        this.renderer.rectangle(bounds.left, Math.round(bounds.top), bounds.width, 1, container.color("color"));
+        break;
+    case "line-through":
+        // TODO try and find exact position for line-through
+        this.renderer.rectangle(bounds.left, Math.ceil(bounds.top + metrics.middle + metrics.lineWidth), bounds.width, 1, container.color("color"));
+        break;
+    }
+};
+
+var borderColorTransforms = {
+    inset: [
+        ["darken", 0.60],
+        ["darken", 0.10],
+        ["darken", 0.10],
+        ["darken", 0.60]
+    ]
+};
+
+NodeParser.prototype.parseBorders = function(container) {
+    var nodeBounds = container.parseBounds();
+    var radius = getBorderRadiusData(container);
+    var borders = ["Top", "Right", "Bottom", "Left"].map(function(side, index) {
+        var style = container.css('border' + side + 'Style');
+        var color = container.color('border' + side + 'Color');
+        if (style === "inset" && color.isBlack()) {
+            color = new Color([255, 255, 255, color.a]); // this is wrong, but
+        }
+        var colorTransform = borderColorTransforms[style] ? borderColorTransforms[style][index] : null;
+        return {
+            width: container.cssInt('border' + side + 'Width'),
+            color: colorTransform ? color[colorTransform[0]](colorTransform[1]) : color,
+            args: null
+        };
+    });
+    var borderPoints = calculateCurvePoints(nodeBounds, radius, borders);
+
+    return {
+        clip: this.parseBackgroundClip(container, borderPoints, borders, radius, nodeBounds),
+        borders: calculateBorders(borders, nodeBounds, borderPoints, radius)
+    };
+};
+
+function calculateBorders(borders, nodeBounds, borderPoints, radius) {
+    return borders.map(function(border, borderSide) {
+        if (border.width > 0) {
+            var bx = nodeBounds.left;
+            var by = nodeBounds.top;
+            var bw = nodeBounds.width;
+            var bh = nodeBounds.height - (borders[2].width);
+
+            switch(borderSide) {
+            case 0:
+                // top border
+                bh = borders[0].width;
+                border.args = drawSide({
+                        c1: [bx, by],
+                        c2: [bx + bw, by],
+                        c3: [bx + bw - borders[1].width, by + bh],
+                        c4: [bx + borders[3].width, by + bh]
+                    }, radius[0], radius[1],
+                    borderPoints.topLeftOuter, borderPoints.topLeftInner, borderPoints.topRightOuter, borderPoints.topRightInner);
+                break;
+            case 1:
+                // right border
+                bx = nodeBounds.left + nodeBounds.width - (borders[1].width);
+                bw = borders[1].width;
+
+                border.args = drawSide({
+                        c1: [bx + bw, by],
+                        c2: [bx + bw, by + bh + borders[2].width],
+                        c3: [bx, by + bh],
+                        c4: [bx, by + borders[0].width]
+                    }, radius[1], radius[2],
+                    borderPoints.topRightOuter, borderPoints.topRightInner, borderPoints.bottomRightOuter, borderPoints.bottomRightInner);
+                break;
+            case 2:
+                // bottom border
+                by = (by + nodeBounds.height) - (borders[2].width);
+                bh = borders[2].width;
+                border.args = drawSide({
+                        c1: [bx + bw, by + bh],
+                        c2: [bx, by + bh],
+                        c3: [bx + borders[3].width, by],
+                        c4: [bx + bw - borders[3].width, by]
+                    }, radius[2], radius[3],
+                    borderPoints.bottomRightOuter, borderPoints.bottomRightInner, borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner);
+                break;
+            case 3:
+                // left border
+                bw = borders[3].width;
+                border.args = drawSide({
+                        c1: [bx, by + bh + borders[2].width],
+                        c2: [bx, by],
+                        c3: [bx + bw, by + borders[0].width],
+                        c4: [bx + bw, by + bh]
+                    }, radius[3], radius[0],
+                    borderPoints.bottomLeftOuter, borderPoints.bottomLeftInner, borderPoints.topLeftOuter, borderPoints.topLeftInner);
+                break;
+            }
+        }
+        return border;
+    });
+}
+
+NodeParser.prototype.parseBackgroundClip = function(container, borderPoints, borders, radius, bounds) {
+    var backgroundClip = container.css('backgroundClip'),
+        borderArgs = [];
+
+    switch(backgroundClip) {
+    case "content-box":
+    case "padding-box":
+        parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftInner, borderPoints.topRightInner, bounds.left + borders[3].width, bounds.top + borders[0].width);
+        parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightInner, borderPoints.bottomRightInner, bounds.left + bounds.width - borders[1].width, bounds.top + borders[0].width);
+        parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightInner, borderPoints.bottomLeftInner, bounds.left + bounds.width - borders[1].width, bounds.top + bounds.height - borders[2].width);
+        parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftInner, borderPoints.topLeftInner, bounds.left + borders[3].width, bounds.top + bounds.height - borders[2].width);
+        break;
+
+    default:
+        parseCorner(borderArgs, radius[0], radius[1], borderPoints.topLeftOuter, borderPoints.topRightOuter, bounds.left, bounds.top);
+        parseCorner(borderArgs, radius[1], radius[2], borderPoints.topRightOuter, borderPoints.bottomRightOuter, bounds.left + bounds.width, bounds.top);
+        parseCorner(borderArgs, radius[2], radius[3], borderPoints.bottomRightOuter, borderPoints.bottomLeftOuter, bounds.left + bounds.width, bounds.top + bounds.height);
+        parseCorner(borderArgs, radius[3], radius[0], borderPoints.bottomLeftOuter, borderPoints.topLeftOuter, bounds.left, bounds.top + bounds.height);
+        break;
+    }
+
+    return borderArgs;
+};
+
+function getCurvePoints(x, y, r1, r2) {
+    var kappa = 4 * ((Math.sqrt(2) - 1) / 3);
+    var ox = (r1) * kappa, // control point offset horizontal
+        oy = (r2) * kappa, // control point offset vertical
+        xm = x + r1, // x-middle
+        ym = y + r2; // y-middle
+    return {
+        topLeft: bezierCurve({x: x, y: ym}, {x: x, y: ym - oy}, {x: xm - ox, y: y}, {x: xm, y: y}),
+        topRight: bezierCurve({x: x, y: y}, {x: x + ox,y: y}, {x: xm, y: ym - oy}, {x: xm, y: ym}),
+        bottomRight: bezierCurve({x: xm, y: y}, {x: xm, y: y + oy}, {x: x + ox, y: ym}, {x: x, y: ym}),
+        bottomLeft: bezierCurve({x: xm, y: ym}, {x: xm - ox, y: ym}, {x: x, y: y + oy}, {x: x, y:y})
+    };
+}
+
+function calculateCurvePoints(bounds, borderRadius, borders) {
+    var x = bounds.left,
+        y = bounds.top,
+        width = bounds.width,
+        height = bounds.height,
+
+        tlh = borderRadius[0][0] < width / 2 ? borderRadius[0][0] : width / 2,
+        tlv = borderRadius[0][1] < height / 2 ? borderRadius[0][1] : height / 2,
+        trh = borderRadius[1][0] < width / 2 ? borderRadius[1][0] : width / 2,
+        trv = borderRadius[1][1] < height / 2 ? borderRadius[1][1] : height / 2,
+        brh = borderRadius[2][0] < width / 2 ? borderRadius[2][0] : width / 2,
+        brv = borderRadius[2][1] < height / 2 ? borderRadius[2][1] : height / 2,
+        blh = borderRadius[3][0] < width / 2 ? borderRadius[3][0] : width / 2,
+        blv = borderRadius[3][1] < height / 2 ? borderRadius[3][1] : height / 2;
+
+    var topWidth = width - trh,
+        rightHeight = height - brv,
+        bottomWidth = width - brh,
+        leftHeight = height - blv;
+
+    return {
+        topLeftOuter: getCurvePoints(x, y, tlh, tlv).topLeft.subdivide(0.5),
+        topLeftInner: getCurvePoints(x + borders[3].width, y + borders[0].width, Math.max(0, tlh - borders[3].width), Math.max(0, tlv - borders[0].width)).topLeft.subdivide(0.5),
+        topRightOuter: getCurvePoints(x + topWidth, y, trh, trv).topRight.subdivide(0.5),
+        topRightInner: getCurvePoints(x + Math.min(topWidth, width + borders[3].width), y + borders[0].width, (topWidth > width + borders[3].width) ? 0 :trh - borders[3].width, trv - borders[0].width).topRight.subdivide(0.5),
+        bottomRightOuter: getCurvePoints(x + bottomWidth, y + rightHeight, brh, brv).bottomRight.subdivide(0.5),
+        bottomRightInner: getCurvePoints(x + Math.min(bottomWidth, width - borders[3].width), y + Math.min(rightHeight, height + borders[0].width), Math.max(0, brh - borders[1].width),  brv - borders[2].width).bottomRight.subdivide(0.5),
+        bottomLeftOuter: getCurvePoints(x, y + leftHeight, blh, blv).bottomLeft.subdivide(0.5),
+        bottomLeftInner: getCurvePoints(x + borders[3].width, y + leftHeight, Math.max(0, blh - borders[3].width), blv - borders[2].width).bottomLeft.subdivide(0.5)
+    };
+}
+
+function bezierCurve(start, startControl, endControl, end) {
+    var lerp = function (a, b, t) {
+        return {
+            x: a.x + (b.x - a.x) * t,
+            y: a.y + (b.y - a.y) * t
+        };
+    };
+
+    return {
+        start: start,
+        startControl: startControl,
+        endControl: endControl,
+        end: end,
+        subdivide: function(t) {
+            var ab = lerp(start, startControl, t),
+                bc = lerp(startControl, endControl, t),
+                cd = lerp(endControl, end, t),
+                abbc = lerp(ab, bc, t),
+                bccd = lerp(bc, cd, t),
+                dest = lerp(abbc, bccd, t);
+            return [bezierCurve(start, ab, abbc, dest), bezierCurve(dest, bccd, cd, end)];
+        },
+        curveTo: function(borderArgs) {
+            borderArgs.push(["bezierCurve", startControl.x, startControl.y, endControl.x, endControl.y, end.x, end.y]);
+        },
+        curveToReversed: function(borderArgs) {
+            borderArgs.push(["bezierCurve", endControl.x, endControl.y, startControl.x, startControl.y, start.x, start.y]);
+        }
+    };
+}
+
+function drawSide(borderData, radius1, radius2, outer1, inner1, outer2, inner2) {
+    var borderArgs = [];
+
+    if (radius1[0] > 0 || radius1[1] > 0) {
+        borderArgs.push(["line", outer1[1].start.x, outer1[1].start.y]);
+        outer1[1].curveTo(borderArgs);
+    } else {
+        borderArgs.push([ "line", borderData.c1[0], borderData.c1[1]]);
+    }
+
+    if (radius2[0] > 0 || radius2[1] > 0) {
+        borderArgs.push(["line", outer2[0].start.x, outer2[0].start.y]);
+        outer2[0].curveTo(borderArgs);
+        borderArgs.push(["line", inner2[0].end.x, inner2[0].end.y]);
+        inner2[0].curveToReversed(borderArgs);
+    } else {
+        borderArgs.push(["line", borderData.c2[0], borderData.c2[1]]);
+        borderArgs.push(["line", borderData.c3[0], borderData.c3[1]]);
+    }
+
+    if (radius1[0] > 0 || radius1[1] > 0) {
+        borderArgs.push(["line", inner1[1].end.x, inner1[1].end.y]);
+        inner1[1].curveToReversed(borderArgs);
+    } else {
+        borderArgs.push(["line", borderData.c4[0], borderData.c4[1]]);
+    }
+
+    return borderArgs;
+}
+
+function parseCorner(borderArgs, radius1, radius2, corner1, corner2, x, y) {
+    if (radius1[0] > 0 || radius1[1] > 0) {
+        borderArgs.push(["line", corner1[0].start.x, corner1[0].start.y]);
+        corner1[0].curveTo(borderArgs);
+        corner1[1].curveTo(borderArgs);
+    } else {
+        borderArgs.push(["line", x, y]);
+    }
+
+    if (radius2[0] > 0 || radius2[1] > 0) {
+        borderArgs.push(["line", corner2[0].start.x, corner2[0].start.y]);
+    }
+}
+
+function negativeZIndex(container) {
+    return container.cssInt("zIndex") < 0;
+}
+
+function positiveZIndex(container) {
+    return container.cssInt("zIndex") > 0;
+}
+
+function zIndex0(container) {
+    return container.cssInt("zIndex") === 0;
+}
+
+function inlineLevel(container) {
+    return ["inline", "inline-block", "inline-table"].indexOf(container.css("display")) !== -1;
+}
+
+function isStackingContext(container) {
+    return (container instanceof StackingContext);
+}
+
+function hasText(container) {
+    return container.node.data.trim().length > 0;
+}
+
+function noLetterSpacing(container) {
+    return (/^(normal|none|0px)$/.test(container.parent.css("letterSpacing")));
+}
+
+function getBorderRadiusData(container) {
+    return ["TopLeft", "TopRight", "BottomRight", "BottomLeft"].map(function(side) {
+        var value = container.css('border' + side + 'Radius');
+        var arr = value.split(" ");
+        if (arr.length <= 1) {
+            arr[1] = arr[0];
+        }
+        return arr.map(asInt);
+    });
+}
+
+function renderableNode(node) {
+    return (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ELEMENT_NODE);
+}
+
+function isPositionedForStacking(container) {
+    var position = container.css("position");
+    var zIndex = (["absolute", "relative", "fixed"].indexOf(position) !== -1) ? container.css("zIndex") : "auto";
+    return zIndex !== "auto";
+}
+
+function isPositioned(container) {
+    return container.css("position") !== "static";
+}
+
+function isFloating(container) {
+    return container.css("float") !== "none";
+}
+
+function isInlineBlock(container) {
+    return ["inline-block", "inline-table"].indexOf(container.css("display")) !== -1;
+}
+
+function not(callback) {
+    var context = this;
+    return function() {
+        return !callback.apply(context, arguments);
+    };
+}
+
+function isElement(container) {
+    return container.node.nodeType === Node.ELEMENT_NODE;
+}
+
+function isPseudoElement(container) {
+    return container.isPseudoElement === true;
+}
+
+function isTextNode(container) {
+    return container.node.nodeType === Node.TEXT_NODE;
+}
+
+function zIndexSort(contexts) {
+    return function(a, b) {
+        return (a.cssInt("zIndex") + (contexts.indexOf(a) / contexts.length)) - (b.cssInt("zIndex") + (contexts.indexOf(b) / contexts.length));
+    };
+}
+
+function hasOpacity(container) {
+    return container.getOpacity() < 1;
+}
+
+function asInt(value) {
+    return parseInt(value, 10);
+}
+
+function getWidth(border) {
+    return border.width;
+}
+
+function nonIgnoredElement(nodeContainer) {
+    return (nodeContainer.node.nodeType !== Node.ELEMENT_NODE || ["SCRIPT", "HEAD", "TITLE", "OBJECT", "BR", "OPTION"].indexOf(nodeContainer.node.nodeName) === -1);
+}
+
+function flatten(arrays) {
+    return [].concat.apply([], arrays);
+}
+
+function stripQuotes(content) {
+    var first = content.substr(0, 1);
+    return (first === content.substr(content.length - 1) && first.match(/'|"/)) ? content.substr(1, content.length - 2) : content;
+}
+
+function getWords(characters) {
+    var words = [], i = 0, onWordBoundary = false, word;
+    while(characters.length) {
+        if (isWordBoundary(characters[i]) === onWordBoundary) {
+            word = characters.splice(0, i);
+            if (word.length) {
+                words.push(punycode.ucs2.encode(word));
+            }
+            onWordBoundary =! onWordBoundary;
+            i = 0;
+        } else {
+            i++;
+        }
+
+        if (i >= characters.length) {
+            word = characters.splice(0, i);
+            if (word.length) {
+                words.push(punycode.ucs2.encode(word));
+            }
+        }
+    }
+    return words;
+}
+
+function isWordBoundary(characterCode) {
+    return [
+        32, // <space>
+        13, // \r
+        10, // \n
+        9, // \t
+        45 // -
+    ].indexOf(characterCode) !== -1;
+}
+
+function hasUnicode(string) {
+    return (/[^\u0000-\u00ff]/).test(string);
+}
+
+module.exports = NodeParser;
diff --git a/src/proxy.js b/src/proxy.js
new file mode 100644
index 0000000..8a41ac6
--- /dev/null
+++ b/src/proxy.js
@@ -0,0 +1,95 @@
+var XHR = require('./xhr');
+var utils = require('./utils');
+var log = require('./log');
+var createWindowClone = require('./clone');
+var decode64 = utils.decode64;
+
+function Proxy(src, proxyUrl, document) {
+    var supportsCORS = ('withCredentials' in new XMLHttpRequest());
+    if (!proxyUrl) {
+        return Promise.reject("No proxy configured");
+    }
+    var callback = createCallback(supportsCORS);
+    var url = createProxyUrl(proxyUrl, src, callback);
+
+    return supportsCORS ? XHR(url) : (jsonp(document, url, callback).then(function(response) {
+        return decode64(response.content);
+    }));
+}
+var proxyCount = 0;
+
+function ProxyURL(src, proxyUrl, document) {
+    var supportsCORSImage = ('crossOrigin' in new Image());
+    var callback = createCallback(supportsCORSImage);
+    var url = createProxyUrl(proxyUrl, src, callback);
+    return (supportsCORSImage ? Promise.resolve(url) : jsonp(document, url, callback).then(function(response) {
+        return "data:" + response.type + ";base64," + response.content;
+    }));
+}
+
+function jsonp(document, url, callback) {
+    return new Promise(function(resolve, reject) {
+        var s = document.createElement("script");
+        var cleanup = function() {
+            delete window.html2canvas.proxy[callback];
+            document.body.removeChild(s);
+        };
+        window.html2canvas.proxy[callback] = function(response) {
+            cleanup();
+            resolve(response);
+        };
+        s.src = url;
+        s.onerror = function(e) {
+            cleanup();
+            reject(e);
+        };
+        document.body.appendChild(s);
+    });
+}
+
+function createCallback(useCORS) {
+    return !useCORS ? "html2canvas_" + Date.now() + "_" + (++proxyCount) + "_" + Math.round(Math.random() * 100000) : "";
+}
+
+function createProxyUrl(proxyUrl, src, callback) {
+    return proxyUrl + "?url=" + encodeURIComponent(src) + (callback.length ? "&callback=html2canvas.proxy." + callback : "");
+}
+
+function documentFromHTML(src) {
+    return function(html) {
+        var parser = new DOMParser(), doc;
+        try {
+            doc = parser.parseFromString(html, "text/html");
+        } catch(e) {
+            log("DOMParser not supported, falling back to createHTMLDocument");
+            doc = document.implementation.createHTMLDocument("");
+            try {
+                doc.open();
+                doc.write(html);
+                doc.close();
+            } catch(ee) {
+                log("createHTMLDocument write not supported, falling back to document.body.innerHTML");
+                doc.body.innerHTML = html; // ie9 doesnt support writing to documentElement
+            }
+        }
+
+        var b = doc.querySelector("base");
+        if (!b || !b.href.host) {
+            var base = doc.createElement("base");
+            base.href = src;
+            doc.head.insertBefore(base, doc.head.firstChild);
+        }
+
+        return doc;
+    };
+}
+
+function loadUrlDocument(src, proxy, document, width, height, options) {
+    return new Proxy(src, proxy, window.document).then(documentFromHTML(src)).then(function(doc) {
+        return createWindowClone(doc, document, width, height, options, 0, 0);
+    });
+}
+
+exports.Proxy = Proxy;
+exports.ProxyURL = ProxyURL;
+exports.loadUrlDocument = loadUrlDocument;
diff --git a/src/proxyimagecontainer.js b/src/proxyimagecontainer.js
new file mode 100644
index 0000000..1cac032
--- /dev/null
+++ b/src/proxyimagecontainer.js
@@ -0,0 +1,21 @@
+var ProxyURL = require('./proxy').ProxyURL;
+
+function ProxyImageContainer(src, proxy) {
+    var link = document.createElement("a");
+    link.href = src;
+    src = link.href;
+    this.src = src;
+    this.image = new Image();
+    var self = this;
+    this.promise = new Promise(function(resolve, reject) {
+        self.image.crossOrigin = "Anonymous";
+        self.image.onload = resolve;
+        self.image.onerror = reject;
+
+        new ProxyURL(src, proxy, document).then(function(url) {
+            self.image.src = url;
+        })['catch'](reject);
+    });
+}
+
+module.exports = ProxyImageContainer;
diff --git a/src/pseudoelementcontainer.js b/src/pseudoelementcontainer.js
new file mode 100644
index 0000000..36c672b
--- /dev/null
+++ b/src/pseudoelementcontainer.js
@@ -0,0 +1,38 @@
+var NodeContainer = require('./nodecontainer');
+
+function PseudoElementContainer(node, parent, type) {
+    NodeContainer.call(this, node, parent);
+    this.isPseudoElement = true;
+    this.before = type === ":before";
+}
+
+PseudoElementContainer.prototype.cloneTo = function(stack) {
+    PseudoElementContainer.prototype.cloneTo.call(this, stack);
+    stack.isPseudoElement = true;
+    stack.before = this.before;
+};
+
+PseudoElementContainer.prototype = Object.create(NodeContainer.prototype);
+
+PseudoElementContainer.prototype.appendToDOM = function() {
+    if (this.before) {
+        this.parent.node.insertBefore(this.node, this.parent.node.firstChild);
+    } else {
+        this.parent.node.appendChild(this.node);
+    }
+    this.parent.node.className += " " + this.getHideClass();
+};
+
+PseudoElementContainer.prototype.cleanDOM = function() {
+    this.node.parentNode.removeChild(this.node);
+    this.parent.node.className = this.parent.node.className.replace(this.getHideClass(), "");
+};
+
+PseudoElementContainer.prototype.getHideClass = function() {
+    return this["PSEUDO_HIDE_ELEMENT_CLASS_" + (this.before ? "BEFORE" : "AFTER")];
+};
+
+PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_BEFORE = "___html2canvas___pseudoelement_before";
+PseudoElementContainer.prototype.PSEUDO_HIDE_ELEMENT_CLASS_AFTER = "___html2canvas___pseudoelement_after";
+
+module.exports = PseudoElementContainer;
diff --git a/src/renderer.js b/src/renderer.js
new file mode 100644
index 0000000..11e9617
--- /dev/null
+++ b/src/renderer.js
@@ -0,0 +1,108 @@
+var log = require('./log');
+
+function Renderer(width, height, images, options, document) {
+    this.width = width;
+    this.height = height;
+    this.images = images;
+    this.options = options;
+    this.document = document;
+}
+
+Renderer.prototype.renderImage = function(container, bounds, borderData, imageContainer) {
+    var paddingLeft = container.cssInt('paddingLeft'),
+        paddingTop = container.cssInt('paddingTop'),
+        paddingRight = container.cssInt('paddingRight'),
+        paddingBottom = container.cssInt('paddingBottom'),
+        borders = borderData.borders;
+
+    var width = bounds.width - (borders[1].width + borders[3].width + paddingLeft + paddingRight);
+    var height = bounds.height - (borders[0].width + borders[2].width + paddingTop + paddingBottom);
+    this.drawImage(
+        imageContainer,
+        0,
+        0,
+        imageContainer.image.width || width,
+        imageContainer.image.height || height,
+        bounds.left + paddingLeft + borders[3].width,
+        bounds.top + paddingTop + borders[0].width,
+        width,
+        height
+    );
+};
+
+Renderer.prototype.renderBackground = function(container, bounds, borderData) {
+    if (bounds.height > 0 && bounds.width > 0) {
+        this.renderBackgroundColor(container, bounds);
+        this.renderBackgroundImage(container, bounds, borderData);
+    }
+};
+
+Renderer.prototype.renderBackgroundColor = function(container, bounds) {
+    var color = container.color("backgroundColor");
+    if (!color.isTransparent()) {
+        this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, color);
+    }
+};
+
+Renderer.prototype.renderBorders = function(borders) {
+    borders.forEach(this.renderBorder, this);
+};
+
+Renderer.prototype.renderBorder = function(data) {
+    if (!data.color.isTransparent() && data.args !== null) {
+        this.drawShape(data.args, data.color);
+    }
+};
+
+Renderer.prototype.renderBackgroundImage = function(container, bounds, borderData) {
+    var backgroundImages = container.parseBackgroundImages();
+    backgroundImages.reverse().forEach(function(backgroundImage, index, arr) {
+        switch(backgroundImage.method) {
+        case "url":
+            var image = this.images.get(backgroundImage.args[0]);
+            if (image) {
+                this.renderBackgroundRepeating(container, bounds, image, arr.length - (index+1), borderData);
+            } else {
+                log("Error loading background-image", backgroundImage.args[0]);
+            }
+            break;
+        case "linear-gradient":
+        case "gradient":
+            var gradientImage = this.images.get(backgroundImage.value);
+            if (gradientImage) {
+                this.renderBackgroundGradient(gradientImage, bounds, borderData);
+            } else {
+                log("Error loading background-image", backgroundImage.args[0]);
+            }
+            break;
+        case "none":
+            break;
+        default:
+            log("Unknown background-image type", backgroundImage.args[0]);
+        }
+    }, this);
+};
+
+Renderer.prototype.renderBackgroundRepeating = function(container, bounds, imageContainer, index, borderData) {
+    var size = container.parseBackgroundSize(bounds, imageContainer.image, index);
+    var position = container.parseBackgroundPosition(bounds, imageContainer.image, index, size);
+    var repeat = container.parseBackgroundRepeat(index);
+    switch (repeat) {
+    case "repeat-x":
+    case "repeat no-repeat":
+        this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + borderData[3], bounds.top + position.top + borderData[0], 99999, size.height, borderData);
+        break;
+    case "repeat-y":
+    case "no-repeat repeat":
+        this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left + borderData[3], bounds.top + borderData[0], size.width, 99999, borderData);
+        break;
+    case "no-repeat":
+        this.backgroundRepeatShape(imageContainer, position, size, bounds, bounds.left + position.left + borderData[3], bounds.top + position.top + borderData[0], size.width, size.height, borderData);
+        break;
+    default:
+        this.renderBackgroundRepeat(imageContainer, position, size, {top: bounds.top, left: bounds.left}, borderData[3], borderData[0]);
+        break;
+    }
+};
+
+module.exports = Renderer;
diff --git a/src/renderers/canvas.js b/src/renderers/canvas.js
new file mode 100644
index 0000000..225a148
--- /dev/null
+++ b/src/renderers/canvas.js
@@ -0,0 +1,181 @@
+var Renderer = require('../renderer');
+var LinearGradientContainer = require('../lineargradientcontainer');
+var log = require('../log');
+
+function CanvasRenderer(width, height) {
+    Renderer.apply(this, arguments);
+    this.canvas = this.options.canvas || this.document.createElement("canvas");
+    if (!this.options.canvas) {
+        this.canvas.width = width;
+        this.canvas.height = height;
+    }
+    this.ctx = this.canvas.getContext("2d");
+    this.taintCtx = this.document.createElement("canvas").getContext("2d");
+    this.ctx.textBaseline = "bottom";
+    this.variables = {};
+    log("Initialized CanvasRenderer with size", width, "x", height);
+}
+
+CanvasRenderer.prototype = Object.create(Renderer.prototype);
+
+CanvasRenderer.prototype.setFillStyle = function(fillStyle) {
+    this.ctx.fillStyle = typeof(fillStyle) === "object" && !!fillStyle.isColor ? fillStyle.toString() : fillStyle;
+    return this.ctx;
+};
+
+CanvasRenderer.prototype.rectangle = function(left, top, width, height, color) {
+    this.setFillStyle(color).fillRect(left, top, width, height);
+};
+
+CanvasRenderer.prototype.circle = function(left, top, size, color) {
+    this.setFillStyle(color);
+    this.ctx.beginPath();
+    this.ctx.arc(left + size / 2, top + size / 2, size / 2, 0, Math.PI*2, true);
+    this.ctx.closePath();
+    this.ctx.fill();
+};
+
+CanvasRenderer.prototype.circleStroke = function(left, top, size, color, stroke, strokeColor) {
+    this.circle(left, top, size, color);
+    this.ctx.strokeStyle = strokeColor.toString();
+    this.ctx.stroke();
+};
+
+CanvasRenderer.prototype.drawShape = function(shape, color) {
+    this.shape(shape);
+    this.setFillStyle(color).fill();
+};
+
+CanvasRenderer.prototype.taints = function(imageContainer) {
+    if (imageContainer.tainted === null) {
+        this.taintCtx.drawImage(imageContainer.image, 0, 0);
+        try {
+            this.taintCtx.getImageData(0, 0, 1, 1);
+            imageContainer.tainted = false;
+        } catch(e) {
+            this.taintCtx = document.createElement("canvas").getContext("2d");
+            imageContainer.tainted = true;
+        }
+    }
+
+    return imageContainer.tainted;
+};
+
+CanvasRenderer.prototype.drawImage = function(imageContainer, sx, sy, sw, sh, dx, dy, dw, dh) {
+    if (!this.taints(imageContainer) || this.options.allowTaint) {
+        this.ctx.drawImage(imageContainer.image, sx, sy, sw, sh, dx, dy, dw, dh);
+    }
+};
+
+CanvasRenderer.prototype.clip = function(shapes, callback, context) {
+    this.ctx.save();
+    shapes.filter(hasEntries).forEach(function(shape) {
+        this.shape(shape).clip();
+    }, this);
+    callback.call(context);
+    this.ctx.restore();
+};
+
+CanvasRenderer.prototype.shape = function(shape) {
+    this.ctx.beginPath();
+    shape.forEach(function(point, index) {
+        if (point[0] === "rect") {
+            this.ctx.rect.apply(this.ctx, point.slice(1));
+        } else {
+            this.ctx[(index === 0) ? "moveTo" : point[0] + "To" ].apply(this.ctx, point.slice(1));
+        }
+    }, this);
+    this.ctx.closePath();
+    return this.ctx;
+};
+
+CanvasRenderer.prototype.font = function(color, style, variant, weight, size, family) {
+    this.setFillStyle(color).font = [style, variant, weight, size, family].join(" ").split(",")[0];
+};
+
+CanvasRenderer.prototype.fontShadow = function(color, offsetX, offsetY, blur) {
+    this.setVariable("shadowColor", color.toString())
+        .setVariable("shadowOffsetY", offsetX)
+        .setVariable("shadowOffsetX", offsetY)
+        .setVariable("shadowBlur", blur);
+};
+
+CanvasRenderer.prototype.clearShadow = function() {
+    this.setVariable("shadowColor", "rgba(0,0,0,0)");
+};
+
+CanvasRenderer.prototype.setOpacity = function(opacity) {
+    this.ctx.globalAlpha = opacity;
+};
+
+CanvasRenderer.prototype.setTransform = function(transform) {
+    this.ctx.translate(transform.origin[0], transform.origin[1]);
+    this.ctx.transform.apply(this.ctx, transform.matrix);
+    this.ctx.translate(-transform.origin[0], -transform.origin[1]);
+};
+
+CanvasRenderer.prototype.setVariable = function(property, value) {
+    if (this.variables[property] !== value) {
+        this.variables[property] = this.ctx[property] = value;
+    }
+
+    return this;
+};
+
+CanvasRenderer.prototype.text = function(text, left, bottom) {
+    this.ctx.fillText(text, left, bottom);
+};
+
+CanvasRenderer.prototype.backgroundRepeatShape = function(imageContainer, backgroundPosition, size, bounds, left, top, width, height, borderData) {
+    var shape = [
+        ["line", Math.round(left), Math.round(top)],
+        ["line", Math.round(left + width), Math.round(top)],
+        ["line", Math.round(left + width), Math.round(height + top)],
+        ["line", Math.round(left), Math.round(height + top)]
+    ];
+    this.clip([shape], function() {
+        this.renderBackgroundRepeat(imageContainer, backgroundPosition, size, bounds, borderData[3], borderData[0]);
+    }, this);
+};
+
+CanvasRenderer.prototype.renderBackgroundRepeat = function(imageContainer, backgroundPosition, size, bounds, borderLeft, borderTop) {
+    var offsetX = Math.round(bounds.left + backgroundPosition.left + borderLeft), offsetY = Math.round(bounds.top + backgroundPosition.top + borderTop);
+    this.setFillStyle(this.ctx.createPattern(this.resizeImage(imageContainer, size), "repeat"));
+    this.ctx.translate(offsetX, offsetY);
+    this.ctx.fill();
+    this.ctx.translate(-offsetX, -offsetY);
+};
+
+CanvasRenderer.prototype.renderBackgroundGradient = function(gradientImage, bounds) {
+    if (gradientImage instanceof LinearGradientContainer) {
+        var gradient = this.ctx.createLinearGradient(
+            bounds.left + bounds.width * gradientImage.x0,
+            bounds.top + bounds.height * gradientImage.y0,
+            bounds.left +  bounds.width * gradientImage.x1,
+            bounds.top +  bounds.height * gradientImage.y1);
+        gradientImage.colorStops.forEach(function(colorStop) {
+            gradient.addColorStop(colorStop.stop, colorStop.color.toString());
+        });
+        this.rectangle(bounds.left, bounds.top, bounds.width, bounds.height, gradient);
+    }
+};
+
+CanvasRenderer.prototype.resizeImage = function(imageContainer, size) {
+    var image = imageContainer.image;
+    if(image.width === size.width && image.height === size.height) {
+        return image;
+    }
+
+    var ctx, canvas = document.createElement('canvas');
+    canvas.width = size.width;
+    canvas.height = size.height;
+    ctx = canvas.getContext("2d");
+    ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, size.width, size.height );
+    return canvas;
+};
+
+function hasEntries(array) {
+    return array.length > 0;
+}
+
+module.exports = CanvasRenderer;
diff --git a/src/stackingcontext.js b/src/stackingcontext.js
new file mode 100644
index 0000000..5606f6f
--- /dev/null
+++ b/src/stackingcontext.js
@@ -0,0 +1,18 @@
+var NodeContainer = require('./nodecontainer');
+
+function StackingContext(hasOwnStacking, opacity, element, parent) {
+    NodeContainer.call(this, element, parent);
+    this.ownStacking = hasOwnStacking;
+    this.contexts = [];
+    this.children = [];
+    this.opacity = (this.parent ? this.parent.stack.opacity : 1) * opacity;
+}
+
+StackingContext.prototype = Object.create(NodeContainer.prototype);
+
+StackingContext.prototype.getParentStack = function(context) {
+    var parentStack = (this.parent) ? this.parent.stack : null;
+    return parentStack ? (parentStack.ownStacking ? parentStack : parentStack.getParentStack(context)) : context.stack;
+};
+
+module.exports = StackingContext;
diff --git a/src/support.js b/src/support.js
new file mode 100644
index 0000000..b188c00
--- /dev/null
+++ b/src/support.js
@@ -0,0 +1,51 @@
+function Support(document) {
+    this.rangeBounds = this.testRangeBounds(document);
+    this.cors = this.testCORS();
+    this.svg = this.testSVG();
+}
+
+Support.prototype.testRangeBounds = function(document) {
+    var range, testElement, rangeBounds, rangeHeight, support = false;
+
+    if (document.createRange) {
+        range = document.createRange();
+        if (range.getBoundingClientRect) {
+            testElement = document.createElement('boundtest');
+            testElement.style.height = "123px";
+            testElement.style.display = "block";
+            document.body.appendChild(testElement);
+
+            range.selectNode(testElement);
+            rangeBounds = range.getBoundingClientRect();
+            rangeHeight = rangeBounds.height;
+
+            if (rangeHeight === 123) {
+                support = true;
+            }
+            document.body.removeChild(testElement);
+        }
+    }
+
+    return support;
+};
+
+Support.prototype.testCORS = function() {
+    return typeof((new Image()).crossOrigin) !== "undefined";
+};
+
+Support.prototype.testSVG = function() {
+    var img = new Image();
+    var canvas = document.createElement("canvas");
+    var ctx =  canvas.getContext("2d");
+    img.src = "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>";
+
+    try {
+        ctx.drawImage(img, 0, 0);
+        canvas.toDataURL();
+    } catch(e) {
+        return false;
+    }
+    return true;
+};
+
+module.exports = Support;
diff --git a/src/svgcontainer.js b/src/svgcontainer.js
new file mode 100644
index 0000000..67280cd
--- /dev/null
+++ b/src/svgcontainer.js
@@ -0,0 +1,52 @@
+var XHR = require('./xhr');
+var decode64 = require('./utils').decode64;
+
+function SVGContainer(src) {
+    this.src = src;
+    this.image = null;
+    var self = this;
+
+    this.promise = this.hasFabric().then(function() {
+        return (self.isInline(src) ? Promise.resolve(self.inlineFormatting(src)) : XHR(src));
+    }).then(function(svg) {
+        return new Promise(function(resolve) {
+            window.html2canvas.svg.fabric.loadSVGFromString(svg, self.createCanvas.call(self, resolve));
+        });
+    });
+}
+
+SVGContainer.prototype.hasFabric = function() {
+    return !window.html2canvas.svg || !window.html2canvas.svg.fabric ? Promise.reject(new Error("html2canvas.svg.js is not loaded, cannot render svg")) : Promise.resolve();
+};
+
+SVGContainer.prototype.inlineFormatting = function(src) {
+    return (/^data:image\/svg\+xml;base64,/.test(src)) ? this.decode64(this.removeContentType(src)) : this.removeContentType(src);
+};
+
+SVGContainer.prototype.removeContentType = function(src) {
+    return src.replace(/^data:image\/svg\+xml(;base64)?,/,'');
+};
+
+SVGContainer.prototype.isInline = function(src) {
+    return (/^data:image\/svg\+xml/i.test(src));
+};
+
+SVGContainer.prototype.createCanvas = function(resolve) {
+    var self = this;
+    return function (objects, options) {
+        var canvas = new window.html2canvas.svg.fabric.StaticCanvas('c');
+        self.image = canvas.lowerCanvasEl;
+        canvas
+            .setWidth(options.width)
+            .setHeight(options.height)
+            .add(window.html2canvas.svg.fabric.util.groupSVGElements(objects, options))
+            .renderAll();
+        resolve(canvas.lowerCanvasEl);
+    };
+};
+
+SVGContainer.prototype.decode64 = function(str) {
+    return (typeof(window.atob) === "function") ? window.atob(str) : decode64(str);
+};
+
+module.exports = SVGContainer;
diff --git a/src/svgnodecontainer.js b/src/svgnodecontainer.js
new file mode 100644
index 0000000..9d5f1c3
--- /dev/null
+++ b/src/svgnodecontainer.js
@@ -0,0 +1,25 @@
+var SVGContainer = require('./svgcontainer');
+
+function SVGNodeContainer(node, _native) {
+    this.src = node;
+    this.image = null;
+    var self = this;
+
+    this.promise = _native ? new Promise(function(resolve, reject) {
+        self.image = new Image();
+        self.image.onload = resolve;
+        self.image.onerror = reject;
+        self.image.src = "data:image/svg+xml," + (new XMLSerializer()).serializeToString(node);
+        if (self.image.complete === true) {
+            resolve(self.image);
+        }
+    }) : this.hasFabric().then(function() {
+        return new Promise(function(resolve) {
+            window.html2canvas.svg.fabric.parseSVGDocument(node, self.createCanvas.call(self, resolve));
+        });
+    });
+}
+
+SVGNodeContainer.prototype = Object.create(SVGContainer.prototype);
+
+module.exports = SVGNodeContainer;
diff --git a/src/textcontainer.js b/src/textcontainer.js
new file mode 100644
index 0000000..eb047e6
--- /dev/null
+++ b/src/textcontainer.js
@@ -0,0 +1,33 @@
+var NodeContainer = require('./nodecontainer');
+
+function TextContainer(node, parent) {
+    NodeContainer.call(this, node, parent);
+}
+
+TextContainer.prototype = Object.create(NodeContainer.prototype);
+
+TextContainer.prototype.applyTextTransform = function() {
+    this.node.data = this.transform(this.parent.css("textTransform"));
+};
+
+TextContainer.prototype.transform = function(transform) {
+    var text = this.node.data;
+    switch(transform){
+        case "lowercase":
+            return text.toLowerCase();
+        case "capitalize":
+            return text.replace(/(^|\s|:|-|\(|\))([a-z])/g, capitalize);
+        case "uppercase":
+            return text.toUpperCase();
+        default:
+            return text;
+    }
+};
+
+function capitalize(m, p1, p2) {
+    if (m.length > 0) {
+        return p1 + p2.toUpperCase();
+    }
+}
+
+module.exports = TextContainer;
diff --git a/src/utils.js b/src/utils.js
new file mode 100644
index 0000000..5daf9bc
--- /dev/null
+++ b/src/utils.js
@@ -0,0 +1,169 @@
+exports.smallImage = function smallImage() {
+    return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
+};
+
+exports.bind = function(callback, context) {
+    return function() {
+        return callback.apply(context, arguments);
+    };
+};
+
+/*
+ * base64-arraybuffer
+ * https://github.com/niklasvh/base64-arraybuffer
+ *
+ * Copyright (c) 2012 Niklas von Hertzen
+ * Licensed under the MIT license.
+ */
+
+exports.decode64 = function(base64) {
+    var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+    var len = base64.length, i, encoded1, encoded2, encoded3, encoded4, byte1, byte2, byte3;
+
+    var output = "";
+
+    for (i = 0; i < len; i+=4) {
+        encoded1 = chars.indexOf(base64[i]);
+        encoded2 = chars.indexOf(base64[i+1]);
+        encoded3 = chars.indexOf(base64[i+2]);
+        encoded4 = chars.indexOf(base64[i+3]);
+
+        byte1 = (encoded1 << 2) | (encoded2 >> 4);
+        byte2 = ((encoded2 & 15) << 4) | (encoded3 >> 2);
+        byte3 = ((encoded3 & 3) << 6) | encoded4;
+        if (encoded3 === 64) {
+            output += String.fromCharCode(byte1);
+        } else if (encoded4 === 64 || encoded4 === -1) {
+            output += String.fromCharCode(byte1, byte2);
+        } else{
+            output += String.fromCharCode(byte1, byte2, byte3);
+        }
+    }
+
+    return output;
+};
+
+exports.getBounds = function(node) {
+    if (node.getBoundingClientRect) {
+        var clientRect = node.getBoundingClientRect();
+        var width = node.offsetWidth == null ? clientRect.width : node.offsetWidth;
+        return {
+            top: clientRect.top,
+            bottom: clientRect.bottom || (clientRect.top + clientRect.height),
+            right: clientRect.left + width,
+            left: clientRect.left,
+            width:  width,
+            height: node.offsetHeight == null ? clientRect.height : node.offsetHeight
+        };
+    }
+    return {};
+};
+
+exports.offsetBounds = function(node) {
+    var parent = node.offsetParent ? exports.offsetBounds(node.offsetParent) : {top: 0, left: 0};
+
+    return {
+        top: node.offsetTop + parent.top,
+        bottom: node.offsetTop + node.offsetHeight + parent.top,
+        right: node.offsetLeft + parent.left + node.offsetWidth,
+        left: node.offsetLeft + parent.left,
+        width: node.offsetWidth,
+        height: node.offsetHeight
+    };
+};
+
+exports.parseBackgrounds = function(backgroundImage) {
+    var whitespace = ' \r\n\t',
+        method, definition, prefix, prefix_i, block, results = [],
+        mode = 0, numParen = 0, quote, args;
+    var appendResult = function() {
+        if(method) {
+            if (definition.substr(0, 1) === '"') {
+                definition = definition.substr(1, definition.length - 2);
+            }
+            if (definition) {
+                args.push(definition);
+            }
+            if (method.substr(0, 1) === '-' && (prefix_i = method.indexOf('-', 1 ) + 1) > 0) {
+                prefix = method.substr(0, prefix_i);
+                method = method.substr(prefix_i);
+            }
+            results.push({
+                prefix: prefix,
+                method: method.toLowerCase(),
+                value: block,
+                args: args,
+                image: null
+            });
+        }
+        args = [];
+        method = prefix = definition = block = '';
+    };
+    args = [];
+    method = prefix = definition = block = '';
+    backgroundImage.split("").forEach(function(c) {
+        if (mode === 0 && whitespace.indexOf(c) > -1) {
+            return;
+        }
+        switch(c) {
+        case '"':
+            if(!quote) {
+                quote = c;
+            } else if(quote === c) {
+                quote = null;
+            }
+            break;
+        case '(':
+            if(quote) {
+                break;
+            } else if(mode === 0) {
+                mode = 1;
+                block += c;
+                return;
+            } else {
+                numParen++;
+            }
+            break;
+        case ')':
+            if (quote) {
+                break;
+            } else if(mode === 1) {
+                if(numParen === 0) {
+                    mode = 0;
+                    block += c;
+                    appendResult();
+                    return;
+                } else {
+                    numParen--;
+                }
+            }
+            break;
+
+        case ',':
+            if (quote) {
+                break;
+            } else if(mode === 0) {
+                appendResult();
+                return;
+            } else if (mode === 1) {
+                if (numParen === 0 && !method.match(/^url$/i)) {
+                    args.push(definition);
+                    definition = '';
+                    block += c;
+                    return;
+                }
+            }
+            break;
+        }
+
+        block += c;
+        if (mode === 0) {
+            method += c;
+        } else {
+            definition += c;
+        }
+    });
+
+    appendResult();
+    return results;
+};
diff --git a/src/webkitgradientcontainer.js b/src/webkitgradientcontainer.js
new file mode 100644
index 0000000..36fceab
--- /dev/null
+++ b/src/webkitgradientcontainer.js
@@ -0,0 +1,10 @@
+var GradientContainer = require('./gradientcontainer');
+
+function WebkitGradientContainer(imageData) {
+    GradientContainer.apply(this, arguments);
+    this.type = imageData.args[0] === "linear" ? GradientContainer.TYPES.LINEAR : GradientContainer.TYPES.RADIAL;
+}
+
+WebkitGradientContainer.prototype = Object.create(GradientContainer.prototype);
+
+module.exports = WebkitGradientContainer;
diff --git a/src/xhr.js b/src/xhr.js
new file mode 100644
index 0000000..e7ae375
--- /dev/null
+++ b/src/xhr.js
@@ -0,0 +1,22 @@
+function XHR(url) {
+    return new Promise(function(resolve, reject) {
+        var xhr = new XMLHttpRequest();
+        xhr.open('GET', url);
+
+        xhr.onload = function() {
+            if (xhr.status === 200) {
+                resolve(xhr.responseText);
+            } else {
+                reject(new Error(xhr.statusText));
+            }
+        };
+
+        xhr.onerror = function() {
+            reject(new Error("Network Error"));
+        };
+
+        xhr.send();
+    });
+}
+
+module.exports = XHR;
diff --git a/tests/assets/iframe/frame1.html b/tests/assets/iframe/frame1.html
new file mode 100644
index 0000000..386b8b7
--- /dev/null
+++ b/tests/assets/iframe/frame1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title>frame 1</title>
+    <style>
+        body {
+            background: green;
+            color: red;
+        }
+    </style>
+</head>
+<body>
+this is the content of frame1
+</body>
+</html>
diff --git a/tests/assets/image.jpg b/tests/assets/image.jpg
new file mode 100644
index 0000000..5c6cd3c
Binary files /dev/null and b/tests/assets/image.jpg differ
diff --git a/tests/assets/image.svg b/tests/assets/image.svg
new file mode 100644
index 0000000..b5f18ee
--- /dev/null
+++ b/tests/assets/image.svg
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" height="252.89000pt" width="493.28000pt" version="1.0" y="0.00000000" x="0.00000000" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <defs>
+    <linearGradient id="lg0">
+      <stop style="stop-color:#ff0000;stop-opacity:1.0000000" offset="0.00000000"/>
+      <stop style="stop-color:#00ff00;stop-opacity:1.0000000" offset="0.50000000"/>
+      <stop style="stop-color:#0000ff;stop-opacity:1.0000000" offset="1.0000000"/>
+    </linearGradient>
+    <linearGradient id="lg1">
+      <stop style="stop-color:#ff0000;stop-opacity:0.27450982" offset="0.00000000"/>
+      <stop style="stop-color:#ff0000;stop-opacity:1.0000000" offset="1.0000000"/>
+    </linearGradient>
+    <radialGradient id="rd0" fx="550.28571" fy="155.11731" xlink:href="#lg1" gradientUnits="userSpaceOnUse" cy="155.11731" cx="550.28571" gradientTransform="matrix(0.652228,-1.522906,1.403595,0.601129,-26.34767,869.2927)" r="127.00000"/>
+    <radialGradient id="rd1" fx="492.85715" fy="379.50504" xlink:href="#lg3" gradientUnits="userSpaceOnUse" cy="379.50504" cx="492.85715" gradientTransform="matrix(0.944964,4.150569e-2,-4.340623e-2,0.988234,43.59757,-15.99113)" r="184.96443"/>
+    <radialGradient id="rd2" fx="449.12918" fy="345.23175" xlink:href="#lg2" gradientUnits="userSpaceOnUse" cy="345.23175" cx="449.12918" gradientTransform="matrix(1.06455,-4.457048e-3,4.186833e-3,1.000012,-30.43703,1.997764)" r="184.96443"/>
+    <linearGradient id="lg2">
+      <stop style="stop-color:#fa4;stop-opacity:1" offset="0"/>
+      <stop style="stop-color:#c3791f;stop-opacity:1" offset="0.5"/>
+      <stop style="stop-color:#935000;stop-opacity:1" offset="1"/>
+    </linearGradient>
+    <linearGradient id="lg3">
+      <stop style="stop-color:black;stop-opacity:1" offset="0"/>
+      <stop style="stop-color:black;stop-opacity:1" offset="0.5"/>
+      <stop style="stop-color:black;stop-opacity:1" offset="0.75"/>
+      <stop style="stop-color:black;stop-opacity:0.72164947" offset="0.875"/>
+      <stop style="stop-color:black;stop-opacity:0.50515461" offset="0.9375"/>
+      <stop style="stop-color:black;stop-opacity:0.3298969" offset="0.96875"/>
+      <stop style="stop-color:black;stop-opacity:0" offset="1"/>
+    </linearGradient>
+  </defs>
+  <path d="M300 252.36218C300 307.59065 255.22847 352.36218 200 352.36218 144.77153 352.36218 100 307.59065 100 252.36218 100 197.13371 144.77153 152.36218 200 152.36218 255.22847 152.36218 300 197.13371 300 252.36218L300 252.36218z" style="fill:#00ffff;fill-opacity:0.49999997;fill-rule:evenodd;stroke:#00ffff;stroke-width:4.0000000;stroke-linecap:butt;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.00000000;stroke-opacity:1.0000000" transform="translate(-91.79890,-1 [...]
+  <path d="M500 252.36218C500 307.59065 455.22847 352.36218 400 352.36218 344.77153 352.36218 300 307.59065 300 252.36218 300 197.13371 344.77153 152.36218 400 152.36218 455.22847 152.36218 500 197.13371 500 252.36218L500 252.36218z" style="fill:#ffff00;fill-opacity:0.49999997;fill-rule:evenodd;stroke:#ffff00;stroke-width:4.0000000;stroke-linecap:butt;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.00000000;stroke-opacity:1.0000000" transform="translate(-242.7989,-4 [...]
+  <path d="M400 452.36218C400 507.59065 355.22847 552.36218 300 552.36218 244.77153 552.36218 200 507.59065 200 452.36218 200 397.13371 244.77153 352.36218 300 352.36218 355.22847 352.36218 400 397.13371 400 452.36218L400 452.36218z" style="fill:#ff00ff;fill-opacity:0.49999997;fill-rule:evenodd;stroke:#ff00ff;stroke-width:4.0000000;stroke-linecap:butt;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.00000000;stroke-opacity:1.0000000" transform="translate(-90.79890,-3 [...]
+  <rect style="fill:url(#rd0);fill-opacity:1.0000000;fill-rule:evenodd;stroke:#000;stroke-width:4.0000000;stroke-linecap:butt;stroke-miterlimit:4.0000000;stroke-dasharray:8.0000000 4.0000000;stroke-dashoffset:0.00000000;stroke-opacity:1.0000000" rx="41.428570" ry="41.428570" transform="translate(6.201104,5.167586)" width="250.00000" y="2.3621826" x="351.00000" height="150.00000"/>
+  <text style="font-size:72px;font-style:oblique;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125.00000%;writing-mode:lr;text-anchor:start;fill:#fff;fill-opacity:0.49999997;stroke:#000;stroke-width:3.0000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dasharray:6.0000000 3.0000000;stroke-dashoffset:0.00000000;stroke-opacity:1.0000000;font-family:Bitstream Vera Sans" xml:space="preserve" transform="translate(6.20110 [...]
+  <g transform="matrix(0.403355,0.000000,0.000000,0.403355,284.7118,53.56855)">
+    <path style="fill:url(#rd1);fill-opacity:1.0000000;stroke:none;stroke-width:4.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.00000000;stroke-opacity:1.0000000" d="M675.82158 379.50504A182.96443 182.96443 0 1 0 309.89272 379.50504 182.96443 182.96443 0 1 0 675.82158 379.50504z" transform="translate(25.71677,42.14162)"/>
+    <path style="fill:url(#rd2);fill-opacity:1.0000000;stroke:none;stroke-width:4.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.00000000;stroke-opacity:1.0000000" d="M675.82158 379.50504A182.96443 182.96443 0 1 0 309.89272 379.50504 182.96443 182.96443 0 1 0 675.82158 379.50504z" transform="translate(3.000000,1.000000)"/>
+    <path style="color:#000;fill:#000;fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:4.0000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.00000000;stroke-opacity:1.0000000;marker:none" d="M448.21432 203.83901C450.36313 204.6315 453.75174 205.94795 456.34375 207.71875 458.93576 209.48955 460.70727 211.5991 460.84375 214 461.1565 219.5018 462.73056 224.22855 456.3125 234.21875 449.89444 244.20895 435.16134 259.07637 402.75 2 [...]
+    <path style="color:#000;fill:#000;fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:4.0000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.00000000;stroke-opacity:1.0000000;marker:none" d="M509.01528 198.02937C499.53358 209.87282 477.91722 245.5091 465.15625 336.75 449.39628 449.43374 450.70852 546.83082 450.91598 557.84038L454.9375 558.75C454.9375 558.75 452.43678 456.60195 469.125 337.28125 482.74755 239.88008 506.43369 2 [...]
+    <path style="color:#000;fill:#000;fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:4.0000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.00000000;stroke-opacity:1.0000000;marker:none" d="M556.6875 211.625C547.0438 211.8095 537.01703 214.51544 529.96875 222.90625 520.74474 233.88721 520.91652 245.76284 524.5 256.1875 528.08348 266.61216 534.88069 275.92531 538.96875 282.875 541.20112 286.67003 547.45814 295.57779 555.4375  [...]
+    <path style="color:#000;fill:#000;fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:4.0000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-dashoffset:0.00000000;stroke-opacity:1.0000000;marker:none" d="M458.15625 224.96875C424.33124 226.01594 399.1972 233.81099 381.5 242.40625 376.05652 245.05007 371.36415 247.88263 367.2855 250.51507 362.72383 255.08465 357.48708 260.80582 350.5625 269.40625 350.5625 269.40625 360.3001 257.14641 383.25 [...]
+
+  </g>
+</svg>
diff --git a/tests/assets/image2.jpg b/tests/assets/image2.jpg
new file mode 100644
index 0000000..3cb8bd6
Binary files /dev/null and b/tests/assets/image2.jpg differ
diff --git a/tests/assets/image2_1.jpg b/tests/assets/image2_1.jpg
new file mode 100644
index 0000000..3cb8bd6
Binary files /dev/null and b/tests/assets/image2_1.jpg differ
diff --git a/tests/assets/image_1.jpg b/tests/assets/image_1.jpg
new file mode 100644
index 0000000..5c6cd3c
Binary files /dev/null and b/tests/assets/image_1.jpg differ
diff --git a/tests/cases/acid2.html b/tests/cases/acid2.html
new file mode 100644
index 0000000..9273ad7
--- /dev/null
+++ b/tests/cases/acid2.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<html>
+<head>
+    <title>The Second Acid Test</title>
+    <style type="text/css">
+        /* section numbers refer to CSS2.1 */
+
+        /* page setup */
+        html { font: 12px sans-serif; margin: 0; padding: 0; overflow: hidden; /* hides scrollbars on viewport, see 11.1.1:3 */ background: white; color: red; }
+        body { margin: 0; padding: 0; }
+
+        /* introduction message */
+        .intro { font: 2em sans-serif; margin: 3.5em 2em; padding: 0.5em; border: solid thin; background: white; color: black; position: relative; z-index: 2; /* should cover the black and red bars that are fixed-positioned */ }
+        .intro * { font: inherit; margin: 0; padding: 0; }
+        .intro h1 { font-size: 1em; font-weight: bolder; margin: 0; padding: 0; }
+        .intro :link { color: blue; }
+        .intro :visited { color: purple; }
+
+        /* picture setup */
+        #top { margin: 100em 3em 0; padding: 2em 0 0 .5em; text-align: left; font: 2em/24px sans-serif; color: navy; white-space: pre; } /* "Hello World!" text */
+        .picture { position: relative; border: 1em solid transparent; margin: 0 0 100em 3em; } /* containing block for face */
+        .picture { background: red; } /* overriden by preferred stylesheet below */
+
+        /* top line of face (scalp): fixed positioning and min/max height/width */
+        .picture p { position: fixed; margin: 0; padding: 0; border: 0; top: 9em; left: 11em; width: 140%; max-width: 4em; height: 8px; min-height: 1em; max-height: 2mm; /* min-height overrides max-height, see 10.7 */ background: black; border-bottom: 0.5em yellow solid; }
+
+        /* bits that shouldn't be part of the top line (and shouldn't be visible at all): HTML parsing, "+" combinator, stacking order */
+        .picture p.bad { border-bottom: red solid; /* shouldn't matter, because the "p + table + p" rule below should match it too, thus hiding it */ }
+        .picture p + p { background: maroon; z-index: 1; } /* shouldn't match anything */
+        .picture p + table + p { margin-top: 3em; /* should end up under the absolutely positioned table below, and thus not be visible */ }
+
+        /* second line of face: attribute selectors, float positioning */
+        [class~=one].first.one { position: absolute; top: 0; margin: 36px 0 0 60px; padding: 0; border: black 2em; border-style: none solid; /* shrink wraps around float */ }
+        [class~=one][class~=first] [class=second\ two][class="second two"] { float: right; width: 48px; height: 12px; background: yellow; margin: 0; padding: 0; } /* only content of abs pos block */
+
+        /* third line of face: width and overflow */
+        .forehead { margin: 4em; width: 8em; border-left: solid black 1em; border-right: solid black 1em; background: red url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4%2F58BAAT%2FAf9jgNErAAAAAElFTkSuQmCC); /* that's a 1x1 yellow pixel PNG */ }
+        .forehead * { width: 12em; line-height: 1em; }
+
+        /* class selectors headache */
+        .two.error.two { background: maroon; } /* shouldn't match */
+        .forehead.error.forehead { background: red; } /* shouldn't match */
+        [class=second two] { background: red; } /* this should be ignored (invalid selector -- grammar says it only accepts IDENTs or STRINGs) */
+
+        /* fourth and fifth lines of face, with eyes: paint order test (see appendix E) and fixed backgrounds */
+        /* the two images are identical: 2-by-2 squares with the top left
+           and bottom right pixels set to yellow and the other two set to
+           transparent. Since they are offset by one pixel from each other,
+           the second one paints exactly over the transparent parts of the
+           first one, thus creating a solid yellow block. */
+        .eyes { position: absolute; top: 5em; left: 3em; margin: 0; padding: 0; background: red; }
+        #eyes-a { height: 0; line-height: 2em; text-align: right; } /* contents should paint top-most because they're inline */
+        #eyes-a object { display: inline; vertical-align: bottom; }
+        #eyes-a object[type] { width: 7.5em; height: 2.5em; } /* should have no effect since that object should fallback to being inline (height/width don't apply to inlines) */
+        #eyes-a object object object { border-right: solid 1em black; padding: 0 12px 0 11px; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAABnRSTlMAAAAAAABupgeRAAAABmJLR0QA%2FwD%2FAP%2BgvaeTAAAAEUlEQVR42mP4%2F58BCv7%2FZwAAHfAD%2FabwPj4AAAAASUVORK5CYII%3D) fixed 1px 0; }
+        #eyes-b { float: left; width: 10em; height: 2em; background: fixed url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAABnRSTlMAAAAAAABupgeRAAAABmJLR0QA%2FwD%2FAP%2BgvaeTAAAAEUlEQVR42mP4%2F58BCv7%2FZwAAHfAD%2FabwPj4AAAAASUVORK5CYII%3D); border-left: solid 1em black; border-right: solid 1em red; } /* should paint in the middle layer because it is a float */
+        #eyes-c { display: block; background: red; border-left: 2em solid yellow; width: 10em; height: 2em; } /* should paint bottom most because it is a block */
+
+        /* lines six to nine, with nose: auto margins */
+        .nose { float: left; margin: -2em 2em -1em; border: solid 1em black; border-top: 0; min-height: 80%; height: 60%; max-height: 3em; /* percentages become auto (see 10.5 and 10.7) and intrinsic height is more than 3em, so 3em wins */ padding: 0; width: 12em; }
+        .nose > div { padding: 1em 1em 3em; height: 0; background: yellow; }
+        .nose div div { width: 2em; height: 2em; background: red; margin: auto; }
+        .nose :hover div { border-color: blue; }
+        .nose div:hover :before { border-bottom-color: inherit; }
+        .nose div:hover :after { border-top-color: inherit; }
+        .nose div div:before { display: block; border-style: none solid solid; border-color: red yellow black yellow; border-width: 1em; content: ''; height: 0; }
+        .nose div    :after { display: block; border-style: solid solid none; border-color: black yellow red yellow; border-width: 1em; content: ''; height: 0; }
+
+        /* between lines nine and ten: margin collapsing with 'float' and 'clear' */
+        .empty { margin: 6.25em; height: 10%; /* computes to auto which makes it empty per 8.3.1:7 (own margins) */ }
+        .empty div { margin: 0 2em -6em 4em; }
+        .smile { margin: 5em 3em; clear: both; /* clearance is negative (see 8.3.1 and 9.5.1) */ }
+
+        /* line ten and eleven: containing block for abs pos */
+        .smile div { margin-top: 0.25em; background: black; width: 12em; height: 2em; position: relative; bottom: -1em; }
+        .smile div div { position: absolute; top: 0; right: 1em; width: auto; height: 0; margin: 0; border: yellow solid 1em; }
+
+        /* smile (over lines ten and eleven): backgrounds behind borders, inheritance of 'float', nested floats, negative heights */
+        .smile div div span { display: inline; margin: -1em 0 0 0; border: solid 1em transparent; border-style: none solid; float: right; background: black; height: 1em; }
+        .smile div div span em { float: inherit; border-top: solid yellow 1em; border-bottom: solid black 1em; } /* zero-height block; width comes from (zero-height) child. */
+        .smile div div span em strong { width: 6em; display: block; margin-bottom: -1em; /* should have no effect, since parent has top&bottom borders, so this margin doesn't collapse */ }
+
+        /* line twelve: line-height */
+        .chin { margin: -4em 4em 0; width: 8em; line-height: 1em; border-left: solid 1em black; border-right: solid 1em black; background: yellow url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAFSDNYfAAAAaklEQVR42u3XQQrAIAwAQeP%2F%2F6wf8CJBJTK9lnQ7FpHGaOurt1I34nfH9pMMZAZ8BwMGEvvh%2BBsJCAgICLwIOA8EBAQEBAQEBAQEBK79H5RfIQAAAAAAAAAAAAAAAAAAAAAAAAAAAID%2FABMSqAfj%2FsLmvAAAAABJRU5ErkJggg%3D%3D) /* 64x64 red square */ no-repeat fixed /* shouldn't be visible unless the smiley is  [...]
+        .chin div { display: inline; font: 2px/4px serif; }
+
+        /* line thirteen: cascade and selector tests */
+        .parser-container div { color: maroon; border: solid; color: orange; } /* setup */
+        div.parser-container * { border-color: black; /* overrides (implied) border-color on previous line */ } /* setup */
+        * div.parser { border-width: 0 2em; /* overrides (implied) declarations on earlier line */ } /* setup */
+
+        /* line thirteen continued: parser tests */
+        .parser { /* comment parsing test -- comment ends before the end of this line, the backslash should have no effect: \*/ }
+        .parser { margin: 0 5em 1em; padding: 0 1em; width: 2em; height: 1em; error: \}; background: yellow; } /* setup with parsing test */
+        * html .parser {  background: gray; }
+        \.parser { padding: 2em; }
+        .parser { m\argin: 2em; };
+        .parser { height: 3em; }
+        .parser { width: 200; }
+        .parser { border: 5em solid red ! error; }
+        .parser { background: red pink; }
+
+        /* line fourteen (last line of face): table */
+        ul { display: table; padding: 0; margin: -1em 7em 0; background: red; }
+        ul li { padding: 0; margin: 0; }
+        ul li.first-part { display: table-cell; height: 1em; width: 1em; background: black; }
+        ul li.second-part { display: table; height: 1em; width: 1em; background: black; } /* anonymous table cell wraps around this */
+        ul li.third-part { display: table-cell; height: 0.5em; /* gets stretched to fit row */ width: 1em; background: black; }
+        ul li.fourth-part { list-style: none; height: 1em; width: 1em; background: black; } /* anonymous table cell wraps around this */
+
+        /* bits that shouldn't appear: inline alignment in cells */
+        .image-height-test { height: 10px; overflow: hidden; font: 20em serif; } /* only the area between the top of the line box and the top of the image should be visible */
+        table { margin: 0; border-spacing: 0; }
+        td { padding: 0; }
+
+    </style>
+    <link rel="appendix stylesheet" href="data:text/css,.picture%20%7B%20background%3A%20none%3B%20%7D"> <!-- this stylesheet should be applied by default -->
+    <script type="text/javascript" src="../test.js"></script>
+</head>
+<body>
+<div style="margin: 70px;"></div>
+<div class="picture">
+    <p><table><tr><td></table><p class="bad"> <!-- <table> closes <p> per the HTML4 DTD -->
+    <blockquote class="first one"><address class="second two"></address></blockquote>
+    <div class="forehead"><div>                              </div></div>
+    <div class="eyes"><div id="eyes-a"><object data="data:application/x-unknown,ERROR"><object data="http://www.damowmow.com/404/" type="text/html"><object data="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAYCAYAAAFy7sgCAAAGsUlEQVRo3u2ZbWwcZxHHf3s%2B7LNbO3ZjXBtowprGODRX0qpNQCjmJKuVKhMl1P2AkCwhFOIKkCBSm9IXavGFKAixIAECwkmWo5MrhRI3Ub40IEwQgp6aIDg3Cd6eEqyIHEteah%2B1E69vhw%2BZtTaX8704ZzkKjHS6271nZ56ZZ%2BY%2F%2F%2BdZKF%2FCwYshx3EkkggLsD1v4FQkEZZYLCbAKyG9%2Ba9EIsG6hnUAf8x74K3aUC3j4%2BM5 [...]
+    <div class="nose"><div><div></div></div></div>
+    <div class="empty"><div></div></div>
+    <div class="smile"><div><div><span><em><strong></strong></em></span></div></div></div>
+    <div class="chin"><div> </div></div>
+    <div class="parser-container"><div class="parser"><!-- ->ERROR<!- --></div></div> <!-- two dashes is what delimits a comment, so the text "->ERROR<!-" earlier on this line is actually part of a comment -->
+    <ul>
+        <li class="first-part"></li>
+        <li class="second-part"></li>
+        <li class="third-part"></li>
+        <li class="fourth-part"></li>
+    </ul>
+    <div class="image-height-test"><table><tr><td><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAFSDNYfAAAAaklEQVR42u3XQQrAIAwAQeP%2F%2F6wf8CJBJTK9lnQ7FpHGaOurt1I34nfH9pMMZAZ8BwMGEvvh%2BBsJCAgICLwIOA8EBAQEBAQEBAQEBK79H5RfIQAAAAAAAAAAAAAAAAAAAAAAAAAAAID%2FABMSqAfj%2FsLmvAAAAABJRU5ErkJggg%3D%3D" alt=""></td></tr></table></div>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/cases/background/clip.html b/tests/cases/background/clip.html
new file mode 100644
index 0000000..4e6b2c8
--- /dev/null
+++ b/tests/cases/background/clip.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Background attribute tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../../test.js"></script>
+        <style>
+            html {
+                background-color: red;
+            }
+            body {
+                background-color: lime;
+            }
+            .small div{
+                width:100px;
+                height:100px;
+                float:left;
+                margin:10px;
+                border:1px solid #000;
+            }
+
+            .medium div{
+                width:200px;
+                height:200px;
+                float:left;
+                margin:10px;
+                border:20px solid transparent;
+                border-width: 10px 20px 30px 40px;
+                background: green;
+            }
+
+            .small, .medium{
+                clear:both;
+            }
+
+            div{
+                display:block;
+            }
+
+        </style>
+
+    </head>
+    <body>
+        <div class="medium">
+            <div style="background:url(../../assets/image.jpg);background-clip: border-box;"></div>
+            <div style="background:url(../../assets/image.jpg);background-clip: padding-box;"></div>
+            <div style="background:url(../../assets/image.jpg);background-clip: content-box;"></div>
+            <div style="background:url(../../assets/image.jpg);"></div>
+        </div>
+
+        <div class="medium">
+            <div style="background:url(../../assets/image.jpg);background-clip: border-box; background-repeat: no-repeat;"></div>
+            <div style="background:url(../../assets/image.jpg);background-clip: padding-box; background-repeat: repeat-y;"></div>
+            <div style="background:url(../../assets/image.jpg);background-clip: content-box; background-repeat: repeat-x;"></div>
+            <div style="background:url(../../assets/image.jpg); background-repeat: no-repeat;"></div>
+        </div>
+
+        <div class="medium">
+            <div style="background-clip: border-box;"></div>
+            <div style="background-clip: padding-box;"></div>
+            <div style="background-clip: content-box;"></div>
+            <div style=""></div>
+        </div>
+
+    </body>
+</html>
diff --git a/tests/cases/background/encoded.html b/tests/cases/background/encoded.html
new file mode 100644
index 0000000..0c49250
--- /dev/null
+++ b/tests/cases/background/encoded.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Background attribute tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../../test.js"></script>
+        <style>
+            html {
+                background-color: red;
+            }
+            body {
+                background-color: lime;
+            }
+            .small div{
+                width:100px;
+                height:100px;
+                float:left;
+                margin:10px;
+                border:1px solid #000;
+            }
+
+            .medium div{
+                width:200px;
+                height:200px;
+                float:left;
+                margin:10px;
+                border:1px solid #000;
+            }
+
+            .small, .medium{
+                clear:both;
+            }
+
+            div{
+                display:block;
+            }
+
+            .encoded {
+                background:url("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCABLAEsDAREAAhEBAxEB/8QAHAAAAgMBAQEBAAAAAAAAAAAABQcEBggCAwAJ/8QANxAAAgECBQEHAQUIAwEAAAAAAQIDBBEABQYSITEHExQiQVFhcTKBkbHBCCNCUnLR4fAWYqLx/8QAGwEAAQUBAQAAAAAAAAAAAAAAAQIDBAUGAAf/xAAzEQACAgECAwQHCAMAAAAAAAAAAQIDEQQhEjFBIlFhc [...]
+            }
+
+        </style>
+
+    </head>
+    <body>
+
+        <div class="medium">
+            <div class="encoded"></div>
+        </div>
+
+    </body>
+</html>
diff --git a/tests/cases/background/linear-gradient.html b/tests/cases/background/linear-gradient.html
new file mode 100644
index 0000000..6e1fc80
--- /dev/null
+++ b/tests/cases/background/linear-gradient.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Background attribute tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../../test.js"></script>
+        <style>
+            html {
+                background-color: red;
+            }
+            .small div{
+                width:100px;
+                height:100px;
+                float:left;
+                margin:10px;
+                border:1px solid #000;
+            }
+
+            .medium div{
+                width:200px;
+                height:200px;
+                float:left;
+                margin:10px;
+                border:1px solid #000;
+            }
+
+            .small, .medium{
+                clear:both;
+            }
+
+            div{
+                display:block;
+            }
+
+            .linearGradient {
+                /*background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(255, 0, 0)), to(rgb(0, 0, 255)));*/
+                background: -webkit-linear-gradient(top left, #f00, #00f, #BADA55, rgba(0, 0, 255,0.5));
+                background: -moz-linear-gradient(top right, #f00, #00f, #BADA55, rgba(0, 0, 255,0.5));
+            }
+
+            .linearGradient2 {
+                background: -webkit-gradient(linear, 0% 0, 0% 100%, from(rgb(252, 252, 252)), to(rgb(232, 232, 232)));
+            }
+
+            .linearGradient3 {
+                /* FF 3.6+ */
+                background: -moz-linear-gradient(left, #ff0000, #ffff00, #00ff00);
+                /* Chrome,Safari4+ */
+                background: -webkit-gradient(linear, left top, right top, color-stop(#ff0000), color-stop(#ffff00), color-stop(#00ff00));
+                /* Chrome 10+, Safari 5.1+ */
+                background: -webkit-linear-gradient(left, #ff0000, #ffff00, #00ff00);
+                /* Opera 11.10+ */
+                background: -o-linear-gradient(left, #ff0000, #ffff00, #00ff00);
+                /* IE 10+ */
+                background: -ms-linear-gradient(left, #ff0000, #ffff00, #00ff00);
+                /* W3C */
+                background: linear-gradient(left, #ff0000, #ffff00, #00ff00);
+            }
+
+            .linearGradient4 {
+                /* FF 3.6+ */
+                background: -moz-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%);
+                /* Chrome, Safari 4+ */
+                background: -webkit-gradient(linear, left top, right top, color-stop(0%, #cedbe9), color-stop(17%, #aac5de), color-stop(50%, #6199c7), color-stop(51%, #3a84c3), color-stop(59%, #419ad6), color-stop(71%, #4bb8f0), color-stop(84%, #3a8bc2), color-stop(100%, #26558b));
+                /* Chrome 10+, Safari 5.1+ */
+                background: -webkit-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%);
+                /* Opera 11.10+ */
+                background: -o-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%);
+                /* IE10+ */
+                background: -ms-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%);
+                /* W3C */
+                background: linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%);
+            }
+
+            .linearGradient5 {
+                /* FF 3.6+ */
+                background: -moz-linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%);
+                /* Chrome, Safari 4+ */
+                background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f0b7a1), color-stop(50%, #8c3310), color-stop(51%, #752201), color-stop(100%, #bf6e4e));
+                /* Chrome 10+, Safari 5.1+ */
+                background: -webkit-linear-gradient(top,  #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%);
+                /* Opera 11.10+ */
+                background: -o-linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%);
+                /* IE 10+ */
+                background: -ms-linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%);
+                /* W3C */
+                background: linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%);
+            }
+
+            .linearGradient6 {
+                /* FF 3.6+ */
+                background: -moz-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6, #26558b 100%);
+                /* Chrome 10+, Safari 5.1+ */
+                background: -webkit-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6, #26558b 100%);
+                /* Opera 11.10+ */
+                background: -o-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6, #26558b 100%);
+                /* IE10+ */
+                background: -ms-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6, #26558b 100%);
+                /* W3C */
+                background: linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6, #26558b 100%);
+            }
+            .linearGradient7 {
+                background: #cce5f4;
+                background: -moz-linear-gradient(top, #cce5f4 0%, #00263c 100%);
+                background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #cce5f4), color-stop(100%, #00263c));
+                background: -webkit-linear-gradient(top, #cce5f4 0%, #00263c 100%);
+                background: -o-linear-gradient(top, #cce5f4 0%, #00263c 100%);
+                background: -ms-linear-gradient(top, #cce5f4 0%, #00263c 100%);
+                background: linear-gradient(to bottom, #cce5f4 0%, #00263c 100%);
+            }
+
+            .linearGradient8 {
+                background: linear-gradient(to top left, #fff 0%, #00263c 100%);
+            }
+            .linearGradient9 {
+                background: linear-gradient(to top left, white 0%, black 100%);
+            }
+
+            .linearGradient10 {
+                background: linear-gradient(to left top, #0000Ff, rgb(255, 0,0) 50px, green 199px, rgba(0, 0, 0, 0.5) 100.0%);
+            }
+        </style>
+
+    </head>
+    <body>
+        <div class="medium">
+            <div class="linearGradient"> </div>
+            <div class="linearGradient2"> </div>
+            <div class="linearGradient3"> </div>
+            <div class="linearGradient4"> </div>
+            <div class="linearGradient5"> </div>
+            <div class="linearGradient6"> </div>
+            <div class="linearGradient7"> </div>
+            <div class="linearGradient8"> </div>
+            <div class="linearGradient9"> </div>
+            <div class="linearGradient10"> </div>
+        </div>
+    </body>
+</html>
diff --git a/tests/cases/background/multi.html b/tests/cases/background/multi.html
new file mode 100644
index 0000000..e5093c5
--- /dev/null
+++ b/tests/cases/background/multi.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Background attribute tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style>
+      html {
+        background-color: red;
+      }
+      body {
+        background-color: lime;
+      }
+      .small div{
+        width:100px;
+        height:100px;
+        float:left;
+        margin:10px;
+        border:1px solid #000;
+      }
+
+      .medium div{
+        width:200px;
+        height:200px;
+        float:left;
+        margin:10px;
+        border:1px solid #000;
+      }
+
+      .small, .medium{
+        clear:both;
+      }
+
+      div{
+        display:block;
+      }
+
+    </style>
+
+  </head>
+  <body>
+
+    <div class="medium">
+      <div style="background-image:url(../../assets/image.jpg), url(../../assets/image2.jpg); background-repeat: repeat-x; background-position: 50px 50px, 100px 130px;"></div>
+      <div style="background-image:url(../../assets/image.jpg), url(../../assets/image2.jpg); background-repeat: repeat-x; background-position: 50px 50px, 20px -20px;"></div>
+      <div style="background-image:url(../../assets/image.jpg), url(../../assets/image2.jpg); background-repeat: repeat-x; background-position: 50px 50px, 450px 100px;"></div>
+    </div>
+
+  </body>
+</html>
diff --git a/tests/cases/background/position.html b/tests/cases/background/position.html
new file mode 100644
index 0000000..2d53bb7
--- /dev/null
+++ b/tests/cases/background/position.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Background attribute tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style>
+      html {
+        background-color: red;
+      }
+      body {
+        background-color: lime;
+      }
+      .small div{
+        width:100px;
+        height:100px;
+        float:left;
+        margin:10px;
+        border:1px solid #000;
+      }
+
+      .medium div{
+        width:200px;
+        height:200px;
+        float:left;
+        margin:10px;
+        border:1px solid #000;
+      }
+
+      .small, .medium{
+        clear:both;
+      }
+
+      div{
+        display:block;
+      }
+
+    </style>
+
+  </head>
+  <body>
+
+    <div class="medium">
+      <div style='background:url("../../assets/image.jpg") center center;'></div>
+      <div style="background:url('../../assets/image.jpg') repeat-x center center;"></div>
+      <div style="background:url(../../assets/image.jpg) repeat-y center center;"></div>
+      <div style="background:url(../../assets/image.jpg) no-repeat center center;"></div>
+    </div>
+
+
+    <div class="small">
+      <div style="background:url(../../assets/image.jpg) center center;"></div>
+      <div style="background:url(../../assets/image.jpg) repeat-x center center;"></div>
+      <div style="background:url(../../assets/image.jpg) repeat-y center center;"></div>
+      <div style="background:url(../../assets/image.jpg) no-repeat center center;"></div>
+    </div>
+
+    <div class="medium">
+      <div style="background:url(../../assets/image.jpg) -15% 25px;"></div>
+      <div style="background:url(../../assets/image.jpg) repeat-x 50px 50px;"></div>
+      <div style="background:url(../../assets/image.jpg) repeat-y 50px 50px;"></div>
+      <div style="background:url(../../assets/image.jpg) no-repeat 50px 50px;"></div>
+    </div>
+
+  </body>
+</html>
diff --git a/tests/cases/background/radial-gradient.html b/tests/cases/background/radial-gradient.html
new file mode 100644
index 0000000..b09abc8
--- /dev/null
+++ b/tests/cases/background/radial-gradient.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Background attribute tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../../test.js"></script>
+        <style>
+            html {
+                background-color: red;
+            }
+            body {
+                background-color: lime;
+            }
+            .small div{
+                width:100px;
+                height:100px;
+                float:left;
+                margin:10px;
+                border:1px solid #000;
+            }
+
+            .medium div{
+                width:200px;
+                height:200px;
+                float:left;
+                margin:10px;
+                border:1px solid #000;
+            }
+
+            .small, .medium{
+                clear:both;
+            }
+
+            div{
+                display:block;
+            }
+
+            .radialGradient {
+                /* FF 3.6+ */
+                background: -moz-radial-gradient(center, ellipse cover,  #959595 0%, #0d0d0d 48%, #2f7bd8 50%, #0a0a0a 64%, #4e4e4e 80%, #383838 87%, #1b1b1b 100%);
+                /* Chrome, Safari 4+ */
+                background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,#959595), color-stop(48%,#0d0d0d), color-stop(50%,#2f7bd8), color-stop(64%,#0a0a0a), color-stop(80%,#4e4e4e), color-stop(87%,#383838), color-stop(100%,#1b1b1b));
+                /* Chrome 10+, Safari 5.1+ */
+                background: -webkit-radial-gradient(center, ellipse cover,  #959595 0%, #0d0d0d 48%,#2f7bd8 50%,#0a0a0a 64%,#4e4e4e 80%,#383838 87%,#1b1b1b 100%);
+                /* Opera 12+ */
+                background: -o-radial-gradient(center, ellipse cover,  #959595 0%, #0d0d0d 48%,#2f7bd8 50%,#0a0a0a 64%,#4e4e4e 80%,#383838 87%,#1b1b1b 100%);
+                /* IE 10+ */
+                background: -ms-radial-gradient(center, ellipse cover,  #959595 0%,#0d0d0d 48%,#2f7bd8 50%,#0a0a0a 64%,#4e4e4e 80%,#383838 87%,#1b1b1b 100%);
+                /* W3C */
+                background: radial-gradient(center, ellipse cover,  #959595 0%,#0d0d0d 48%,#2f7bd8 50%,#0a0a0a 64%,#4e4e4e 80%,#383838 87%,#1b1b1b 100%);
+            }
+
+            .radialGradient2 {
+                /* thx to https://twitter.com/#!/markjmclaren/status/13067366701 */
+
+                background-color: #FFF;
+
+                background-image:
+                    -webkit-gradient(radial, 45 45, 10, 52 50, 30, from(#A7D30C), to(rgba(1,159,98,0)), color-stop(90%, #019F62)),
+                    -webkit-gradient(radial, 105 105, 20, 112 120, 50, from(#ff5f98), to(rgba(255,1,136,0)), color-stop(75%, #ff0188)),
+                    -webkit-gradient(radial, 95 15, 15, 102 20, 40, from(#00c9ff), to(rgba(0,201,255,0)), color-stop(80%, #00b5e2)),
+                    -webkit-gradient(radial, 0 150, 50, 0 140, 90, from(#f4f201), to(rgba(228, 199,0,0)), color-stop(80%, #e4c700));
+
+                background-image:
+                    -moz-radial-gradient(42px 42px, circle farthest-corner, #A7D30C 0%, #A7D30C 3%, rgba(1,159,98,0) 11%),
+                    -moz-radial-gradient(45px 45px, circle farthest-corner, #019F62 0%, #019F62 13%, rgba(255,255,255,0) 16%, rgba(255,255,255,0) 100%),
+
+                    -moz-radial-gradient(102px 102px, circle farthest-corner, #ff5f98 0%, #ff5f98 15%,  rgba(255,1,136,0) 27%),
+                    -moz-radial-gradient(105px 105px, circle farthest-corner, #FF0188 0%, #FF0188 28%,  rgba(255,255,255,0) 32%, rgba(255,255,255,0) 100%),
+
+                    -moz-radial-gradient(92px 12px, circle farthest-corner, #00c9ff 0%, #00c9ff 10%, rgba(0,181,226,0) 26%),
+                    -moz-radial-gradient(95px 15px, circle farthest-corner, #00b5e2 0%, #00b5e2 28%, rgba(255,255,255,0) 31%, rgba(255,255,255,0) 100%),
+
+                    -moz-radial-gradient(0px 150px, circle farthest-corner, #f4f201 0%, #f4f201 25%, rgba(228,199,0,0) 45%),
+                    -moz-radial-gradient(0px 150px, circle farthest-corner, #e4c700 0%, #e4c700 47%, rgba(255,255,255,0) 50%, rgba(255,255,255,0) 100%);
+            }
+
+            .radialGradient3 {
+                background: -moz-radial-gradient(75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -webkit-radial-gradient(75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -o-radial-gradient(75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -ms-radial-gradient(75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%);
+                background: radial-gradient(75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%);
+            }
+
+            .radialGradient4 {
+                background: -moz-radial-gradient(75% 19%, ellipse closest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -webkit-radial-gradient(75% 19%, ellipse closest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -o-radial-gradient(75% 19%, ellipse closest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -ms-radial-gradient(75% 19%, ellipse closest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+                background: radial-gradient(75% 19%, ellipse closest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+            }
+            .radialGradient5 {
+                background: -moz-radial-gradient(75% 19%, ellipse farthest-side, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -webkit-radial-gradient(75% 19%, ellipse farthest-side, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -o-radial-gradient(75% 19%, ellipse farthest-side, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -ms-radial-gradient(75% 19%, ellipse farthest-side, #ababab, #0000ff 33%,#991f1f 100%);
+                background: radial-gradient(75% 19%, ellipse farthest-side, #ababab, #0000ff 33%,#991f1f 100%)
+            }
+            .radialGradient6 {
+                background: -moz-radial-gradient(75% 19%, ellipse farthest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -webkit-radial-gradient(75% 19%, ellipse farthest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -o-radial-gradient(75% 19%, ellipse farthest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -ms-radial-gradient(75% 19%, ellipse farthest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+                background: radial-gradient(75% 19%, ellipse farthest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+            }
+            .radialGradient7 {
+                background: -moz-radial-gradient(75% 19%, circle contain, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -webkit-radial-gradient(75% 19%, circle contain, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -o-radial-gradient(75% 19%, circle contain, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -ms-radial-gradient(75% 19%, circle contain, #ababab, #0000ff 33%,#991f1f 100%);
+                background: radial-gradient(75% 19%, circle contain, #ababab, #0000ff 33%,#991f1f 100%);
+            }
+            .radialGradient8 {
+                background: -moz-radial-gradient(75% 19%, circle cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -webkit-radial-gradient(75% 19%, circle cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -o-radial-gradient(75% 19%, circle cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -ms-radial-gradient(75% 19%, circle cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: radial-gradient(75% 19%, circle cover, #ababab, #0000ff 33%,#991f1f 100%);
+            }
+            .radialGradient9 {
+                background: -moz-radial-gradient(right 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -webkit-radial-gradient(right 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -o-radial-gradient(right 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -ms-radial-gradient(right 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: radial-gradient(right 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+            }
+            .radialGradient10 {
+                background: -moz-radial-gradient(left 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -webkit-radial-gradient(left 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -o-radial-gradient(left 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -ms-radial-gradient(left 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: radial-gradient(left 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+            }
+            .radialGradient11 {
+                background: -moz-radial-gradient(left top, circle cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -webkit-radial-gradient(left top, circle cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -o-radial-gradient(left top, circle cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: -ms-radial-gradient(left top, circle cover, #ababab, #0000ff 33%,#991f1f 100%);
+                background: radial-gradient(left top, circle cover, #ababab, #0000ff 33%,#991f1f 100%);
+            }
+        </style>
+
+    </head>
+    <body>
+        <div class="medium">
+            <div class="radialGradient"> </div>
+            <div class="radialGradient2"> </div>
+            <div class="radialGradient3"> </div>
+            <div class="radialGradient4"> </div>
+            <div class="radialGradient5"> </div>
+            <div class="radialGradient6"> </div>
+            <div class="radialGradient7"> </div>
+            <div class="radialGradient8"> </div>
+            <div class="radialGradient9"> </div>
+            <div class="radialGradient10"> </div>
+            <div class="radialGradient11"> </div>
+        </div>
+
+    </body>
+</html>
diff --git a/tests/cases/background/repeat.html b/tests/cases/background/repeat.html
new file mode 100644
index 0000000..ed3ae6a
--- /dev/null
+++ b/tests/cases/background/repeat.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Background attribute tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../../test.js"></script>
+        <style>
+            html {
+                background-color: red;
+            }
+            body {
+                background-color: lime;
+            }
+            .small div{
+                width:100px;
+                height:100px;
+                float:left;
+                margin:10px;
+                border:1px solid #000;
+            }
+
+            .medium div{
+                width:200px;
+                height:200px;
+                float:left;
+                margin:10px;
+                border:1px solid #000;
+            }
+
+            .small, .medium{
+                clear:both;
+            }
+
+            div{
+                display:block;
+            }
+
+        </style>
+
+    </head>
+    <body>
+
+        <div class="medium">
+            <div style="background:url(../../assets/image.jpg);"></div>
+            <div style="background:url(../../assets/image.jpg) repeat-x;"></div>
+            <div style="background:url(../../assets/image.jpg) repeat-y;"></div>
+            <div style="background:url(../../assets/image.jpg) no-repeat;"></div>
+        </div>
+        <div class="small">
+            <div style="background:url(../../assets/image.jpg);"></div>
+            <div style="background:url(../../assets/image.jpg) repeat-x;"></div>
+            <div style="background:url(../../assets/image.jpg) repeat-y;"></div>
+            <div style="background:url(../../assets/image.jpg) no-repeat;"></div>
+        </div>
+
+    </body>
+</html>
diff --git a/tests/cases/background/size.html b/tests/cases/background/size.html
new file mode 100644
index 0000000..d437616
--- /dev/null
+++ b/tests/cases/background/size.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Background size tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style>
+        .horizontal div, .vertical div {
+            display: block;
+            background:url("../../assets/image.jpg") center center;
+        }
+
+        .vertical {
+            float: right;
+        }
+
+        .horizontal {
+            float: left;
+        }
+
+        .horizontal div {
+            width: 400px; height: 100px;
+        }
+
+        .vertical div {
+            width: 200px; height: 200px;
+        }
+
+        .container {
+            float: left;
+            border: 1px solid black;
+            width: 150px;
+            height: 200px;
+            background-image: url(../../assets/image.jpg);
+            background-size: 34px;
+            background-repeat: no-repeat;
+            background-position: center;
+        }
+    </style>
+</head>
+<body>
+<div class="container"></div>
+<div class="container" style="background-repeat: repeat-y"></div>
+<div class="container" style="background-repeat: repeat-x"></div>
+<div class="container" style="background-size: 150% auto"></div>
+<div class="horizontal">
+    <div style='background-size: cover;'></div>
+    <div style='background-size: contain;'></div>
+    <div style='background-size: auto 100%;'></div>
+    <div style='background-size: auto;'></div>
+</div>
+
+<div class="vertical">
+    <div style='background-size: cover;'></div>
+    <div style='background-size: contain;'></div>
+    <div style='background-size: auto 100%;'></div>
+    <div style='background-size: auto;'></div>
+</div>
+
+</body>
+</html>
diff --git a/tests/cases/border/dashed.html b/tests/cases/border/dashed.html
new file mode 100644
index 0000000..74e95de
--- /dev/null
+++ b/tests/cases/border/dashed.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Borders tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+      div {
+        width: 200px;
+        height: 200px;
+        display: inline-block;
+        margin: 10px;
+        background:#6F428C;
+        border-style: dashed;
+      }
+
+      .box1 {
+        border-width: 1px;
+        border-color: #00b5e2;
+      }
+
+      .box2 {
+        border-width: 3px;
+        border-color: red;
+      }
+
+      .box3 {
+        border-width: 10px;
+      }
+
+      .box4 {
+        border-width: 50px;
+        border-color: green;
+      }
+
+      html {
+        background: #3a84c3;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="box1"> </div>
+    <div class="box2"> </div>
+    <div class="box3"> </div>
+    <div class="box4"> </div>
+  </body>
+</html>
diff --git a/tests/cases/border/dotted.html b/tests/cases/border/dotted.html
new file mode 100644
index 0000000..77f1c35
--- /dev/null
+++ b/tests/cases/border/dotted.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Borders tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+      div {
+        width: 200px;
+        height: 200px;
+        display: inline-block;
+        margin: 10px;
+        background:#6F428C;
+        border-style: dotted;
+      }
+
+      .box1 {
+        border-width: 1px;
+        border-color: #00b5e2;
+      }
+
+      .box2 {
+        border-width: 3px;
+        border-color: red;
+      }
+
+      .box3 {
+        border-width: 10px;
+      }
+
+      .box4 {
+        border-width: 50px;
+        border-color: green;
+      }
+
+      html {
+        background: #3a84c3;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="box1"> </div>
+    <div class="box2"> </div>
+    <div class="box3"> </div>
+    <div class="box4"> </div>
+  </body>
+</html>
diff --git a/tests/cases/border/double.html b/tests/cases/border/double.html
new file mode 100644
index 0000000..daa3852
--- /dev/null
+++ b/tests/cases/border/double.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Borders tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+      div {
+        width: 200px;
+        height: 200px;
+        display: inline-block;
+        margin: 10px;
+        background:#6F428C;
+        border-style:double;
+      }
+
+      .box1 {
+        border-width: 1px;
+        border-color: #00b5e2;
+      }
+
+      .box2 {
+        border-width: 3px;
+        border-color: red;
+      }
+
+      .box3 {
+        border-width: 10px;
+      }
+
+      .box4 {
+        border-width: 50px;
+        border-color: green;
+      }
+
+      html {
+        background: #3a84c3;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="box1"> </div>
+    <div class="box2"> </div>
+    <div class="box3"> </div>
+    <div class="box4"> </div>
+  </body>
+</html>
diff --git a/tests/cases/border/inset.html b/tests/cases/border/inset.html
new file mode 100644
index 0000000..29e8483
--- /dev/null
+++ b/tests/cases/border/inset.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Borders tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+        div {
+            width: 200px;
+            height: 200px;
+            display: inline-block;
+            margin: 10px;
+            background:#6F428C;
+            border-style: inset;
+        }
+
+        .box1 {
+            border-width: 1px;
+            border-color: #00b5e2;
+        }
+
+        .box2 {
+            border-width: 3px;
+            border-color: red;
+        }
+
+        .box3 {
+            border-width: 10px;
+        }
+
+        .box4 {
+            border-width: 50px;
+            border-color: green;
+        }
+
+        .box5 {
+            border-width: 50px;
+            border-color: rgb(0, 0, 0);
+        }
+
+        input {
+            border-width: 50px;
+        }
+
+        html {
+            background: #3a84c3;
+        }
+    </style>
+</head>
+<body>
+<div class="box1"> </div>
+<div class="box2"> </div>
+<div class="box3"> </div>
+<div class="box4"> </div>
+<div class="box5"> </div>
+<input type="text" />
+</body>
+</html>
diff --git a/tests/cases/border/radius.html b/tests/cases/border/radius.html
new file mode 100644
index 0000000..35634f0
--- /dev/null
+++ b/tests/cases/border/radius.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Borders tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+      div {
+        width: 200px;
+        height: 200px;
+        display: inline-block;
+        margin: 10px;
+        background:#6F428C;
+        border-style: solid;
+        border-radius: 50px;
+      }
+
+      .box1 {
+        border-width: 1px;
+        border-left-color: #00b5e2;
+        border-top-color: red;
+        border-right-color: green;
+      }
+
+      .box2 {
+        border-width: 3px;
+        border-left-color: #00b5e2;
+        border-top-color: red;
+        border-right-color: green;
+      }
+
+      .box3 {
+        border-width: 10px;
+        border-left-color: transparent;
+        border-top-color: red;
+        border-right-color: green;
+      }
+
+      .box4 {
+        border-width: 50px;
+        border-left-color: transparent;
+        border-top-color: red;
+        border-top-width: 10px;
+        border-right-color: green;
+        border-bottom-right-radius: 190px;
+        background-clip: padding-box;
+      }
+
+      .box5 {
+          margin: 100px;
+          border-width: 50px;
+          border-left-color: transparent;
+          border-top-color: red;
+          border-top-width: 10px;
+          border-right-color: green;
+          border-radius: 25px;
+          background-clip: padding-box;
+      }
+
+      .box6 {
+        height: 200px;
+        width: 200px;
+        border-radius: 200px;
+      }
+
+      html {
+        background: #3a84c3;
+      }
+    </style>
+  </head>
+  <body>
+
+    <div class="box1"> </div>
+    <div class="box2"> </div>
+    <div class="box3"> </div>
+    <div class="box4"> </div>
+    <div class="box5"> </div>
+    <div class="box6"> </div>
+  </body>
+</html>
diff --git a/tests/cases/border/solid.html b/tests/cases/border/solid.html
new file mode 100644
index 0000000..faab8a7
--- /dev/null
+++ b/tests/cases/border/solid.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Borders tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+      div {
+        width: 200px;
+        height: 200px;
+        display: inline-block;
+        margin: 10px;
+        background:#6F428C;
+        border-style: solid;
+      }
+
+      .box1 {
+        border-width: 1px;
+        border-color: #00b5e2;
+      }
+
+      .box2 {
+        border-width: 3px;
+        border-color: red;
+      }
+
+      .box3 {
+        border-width: 10px;
+      }
+
+      .box4 {
+        border-width: 50px;
+        border-color: green;
+      }
+
+      html {
+        background: #3a84c3;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="box1"> </div>
+    <div class="box2"> </div>
+    <div class="box3"> </div>
+    <div class="box4"> </div>
+  </body>
+</html>
diff --git a/tests/cases/child-textnodes.html b/tests/cases/child-textnodes.html
new file mode 100644
index 0000000..ea5cafe
--- /dev/null
+++ b/tests/cases/child-textnodes.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Inline text in the top element</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../test.js"></script>
+        <style>
+        span {
+            color:blue;
+        }
+        p {
+            background-color: green;
+        }
+        </style>
+
+    </head>
+    <body>
+        Some inline text <span> followed by text in span </span> followed by more inline text.
+        <p>Then a block level element.</p>
+        Then more inline text.
+    </body>
+</html>
diff --git a/tests/cases/clip.html b/tests/cases/clip.html
new file mode 100644
index 0000000..4d8cc95
--- /dev/null
+++ b/tests/cases/clip.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Inline text in the top element</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../test.js"></script>
+    <style>
+        span {
+            color:blue;
+        }
+        p {
+            background-color: green;
+        }
+        div {
+            background: red;
+            border: 5px solid blue;
+        }
+    </style>
+
+</head>
+<body>
+<div style="clip: rect(0px, 400px, 50px, 200px); ">Some inline text <span> followed by text in span </span> followed by more inline text.
+    <p>Then a block level element.</p>
+    Then more inline text.</div>
+
+<div style="clip: rect(0px, 400px, 50px, 200px); position: relative; ">Some inline text <span> followed by text in span </span> followed by more inline text.
+    <p>Then a block level element.</p>
+    Then more inline text.</div>
+
+<div style="clip: rect(0px, 400px, 50px, 200px); position: fixed; ">Some inline text <span> followed by text in span </span> followed by more inline text.
+    <p>Then a block level element.</p>
+    Then more inline text.</div>
+
+<div style="clip: rect(0px, 400px, 50px, 200px); position: absolute; top: 250px; left: 500px;">Some inline text <span> followed by text in span </span> followed by more inline text.
+    <p>Then a block level element.</p>
+    Then more inline text.</div>
+</body>
+
+<div style="clip: rect(0px, 400px, 50px, 200px); position: absolute; top: 500px;">Some inline text <span> followed by text in span </span> followed by more inline text.
+    <p>Then a block level element.</p>
+    Then more inline text.</div>
+</body>
+</html>
diff --git a/tests/cases/crossorigin-iframe.html b/tests/cases/crossorigin-iframe.html
new file mode 100644
index 0000000..2453146
--- /dev/null
+++ b/tests/cases/crossorigin-iframe.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>cross-origin iframe test</title>
+    <script>
+        var h2cOptions = {proxy: "http://localhost:8082"};
+    </script>
+    <script type="text/javascript" src="../test.js"></script>
+</head>
+<body>
+<iframe src="http://hertzen.com/" width="800" height="800"></iframe>
+</body>
+</html>
diff --git a/tests/cases/forms.html b/tests/cases/forms.html
new file mode 100644
index 0000000..424a028
--- /dev/null
+++ b/tests/cases/forms.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Form tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../test.js"></script>
+        <style>
+            input[type="radio"], input[type="checkbox"] {
+                margin: 10px;
+                display: inline-block;
+            }
+        </style>
+
+    </head>
+    <body>
+        <input type="hidden" value="THIS SHOULD NOT BE VISIBLE!" />
+        <input type="text" value="textbox" />
+        <input type="password" value="textbox" />
+        <input type="text" value="textbox" style="border:5px solid navy;" />
+        <input type="text" value="textbox" style="border:5px solid navy;height:40px;" />
+
+        <input type="text" value="textbox" style="border:5px solid navy;height:40px;padding:10px;" />
+
+        <input type="text" value="textbox" style="padding:10px;" />
+        <input type="text" value="textbox" style="padding:10px;text-align:right;" />
+        <hr />
+        <select>
+            <option value="1">Value 1</option>
+            <option value="2">Value 2</option>
+            <option value="3">Value 3</option>
+        </select>
+        <select>
+
+        </select>
+        <select>
+            <option value="">2</option>
+        </select>
+
+
+        <select>
+            <option value="1">Value 1</option>
+            <option value="2" selected>Value 2 with something else</option>
+            <option value="3">Value 3</option>
+        </select>
+        <hr />
+        <input type="submit" value="Submit" />
+        <input type="Button" value="Button" />
+        <input type="Reset" value="Reset" />
+
+
+        <input type="submit" value="Submit" style="width:200px;" />
+        <input type="Button" value="Button" style="width:200px;height:50px;"  />
+        <input type="Reset" value="Reset" style="width:200px;height:50px;text-align:left;"  />
+
+        <hr />
+
+        <textarea>  </textarea>
+        <textarea style="border-width:10px;"></textarea>
+
+                <textarea> text </textarea>
+        <textarea style="border-width:10px;">text</textarea>
+        <hr />
+        <input type="radio" value="radio1" />
+        <input type="radio" value="radio2" style="transform:scale(3)" />
+        <input type="RADIO" value="radio3" checked />
+        <input type="radio" value="radio4" style="transform:scale(3)" checked />
+        <input type="radio" value="radio5" style="transform:scale(3); background: red;" />
+        <input type="radio" value="radio6" style="width: 200px;" />
+        <input type="radio" value="radio6" style="width: 200px; height: 200px;" />
+        <input type="radio" value="radio6" style="width: 200px; height: 200px;" checked />
+        <input type="checkbox" value="checkbox1" />
+        <input type="chECKbox" value="checkbox2" style="transform:scale(3)" />
+        <input type="checkbox" value="checkbox3" checked />
+        <input type="checkbox" value="checkbox4" style="transform:scale(3)" checked />
+        <input type="checkbox" value="checkbox5" style="transform:scale(3); background: red;" />
+        <input type="checkbox" value="checkbox6" style="width: 200px;" />
+        <input type="checkbox" value="checkbox6" style="width: 200px; height: 200px;" />
+        <input type="checkbox" value="checkbox6" style="width: 200px; height: 200px;" checked />
+        <div style="position: absolute; width: 10px; height: 10px; border:1px solid rgb(165,165,165); border-radius: 3px; background: rgb(222,222,222)"></div>
+    </body>
+</html>
diff --git a/tests/cases/google-maps.html b/tests/cases/google-maps.html
new file mode 100644
index 0000000..88d377a
--- /dev/null
+++ b/tests/cases/google-maps.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title>google maps</title>
+    <script>
+        var dontRun = true;
+    </script>
+    <script type="text/javascript" src="../test.js"></script>
+    <script src="https://maps.googleapis.com/maps/api/js?sensor=false"></script>
+    <style>
+        #map {
+            width: 100%;
+            height: 500px;
+            position: absolute;
+            top: 0;
+        }
+        body, html {
+            width: 100%;
+            height: 100%;
+        }
+    </style>
+</head>
+<body>
+<div>
+    <h2>Google maps</h2>
+    <div id="map"></div>
+    <script>
+        var mapOptions = { zoom: 13, center: new google.maps.LatLng(60.1656623, 24.9477731) };
+        var map = new google.maps.Map(document.querySelector('#map'), mapOptions);
+        google.maps.event.addListenerOnce(map, 'idle', function(){
+            if (typeof(window.run) === "function") {
+                window.run();
+            } else {
+                dontRun = undefined;
+            }
+        });
+    </script>
+</div>
+</body>
+</html>
diff --git a/tests/cases/iframe.html b/tests/cases/iframe.html
new file mode 100644
index 0000000..e6a5cf1
--- /dev/null
+++ b/tests/cases/iframe.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>iframe test</title>
+    <script type="text/javascript" src="../test.js"></script>
+</head>
+<body>
+<iframe src="/tests/assets/iframe/frame1.html" width="500" height="500"></iframe>
+</body>
+</html>
diff --git a/tests/cases/images/base.html b/tests/cases/images/base.html
new file mode 100644
index 0000000..b3c4f5e
--- /dev/null
+++ b/tests/cases/images/base.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>External content tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+
+    <base href="http://www.google.com/" />
+</head>
+<body>
+<h1>External image</h1>
+<img src="http://www.google.com/logos/2011/gregormendel11-hp.jpg" style="border:5px solid black;" />
+
+<h1>External image (using <base> href)</h1>
+<img src="/logos/2011/gregormendel11-res.jpg" />
+</body>
+</html>
diff --git a/tests/cases/images/canvas.html b/tests/cases/images/canvas.html
new file mode 100644
index 0000000..2a876c4
--- /dev/null
+++ b/tests/cases/images/canvas.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Image tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+
+    <script type="text/javascript" src="../../test.js"></script>
+    <script type="text/javascript">
+      function setUp() {
+        if ($('#testcanvas')[0].getContext) {
+          var ctx = $('#testcanvas')[0].getContext('2d');
+
+          ctx.fillStyle = "rgb(200,0,0)";
+          ctx.fillRect (10, 10, 55, 50);
+
+          ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
+          ctx.fillRect (30, 30, 55, 50);
+        } else {
+          $('#testcanvas').remove();
+        }
+      };
+    </script>
+  </head>
+  <body>
+    <canvas id="testcanvas" style="width:700px;height:300px;"></canvas>
+  </body>
+</html>
diff --git a/tests/cases/images/cross-origin.html b/tests/cases/images/cross-origin.html
new file mode 100644
index 0000000..f29cc68
--- /dev/null
+++ b/tests/cases/images/cross-origin.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>External content tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script>
+            h2cOptions = {useCORS: true, proxy: null};
+        </script>
+        <script type="text/javascript" src="../../test.js"></script>
+    </head>
+    <body>
+        <h1>External image (CORS)</h1>
+        <img src="http://localhost:8081/tests/assets/image2.jpg" />
+    </body>
+</html>
diff --git a/tests/cases/images/empty.html b/tests/cases/images/empty.html
new file mode 100644
index 0000000..b22cf79
--- /dev/null
+++ b/tests/cases/images/empty.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Image tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+  </head>
+  <body>
+    Image without src attribute, should not crash:
+    <img style="width:50px;height:50px;border:1px solid red;display:block;" />
+    Image with broken src attribute, should not crash:
+    <img src="404" style="width:50px;height:50px;border:1px solid red;display:block;" />
+    <img src="404_2" style="width:50px;height:50px;border:1px solid red;display:block;" />
+
+  </body>
+</html>
diff --git a/tests/cases/images/images.html b/tests/cases/images/images.html
new file mode 100644
index 0000000..25b7dea
--- /dev/null
+++ b/tests/cases/images/images.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Image tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+  </head>
+  <body>
+    <img src="../../assets/image.jpg" />
+    <img src="../../assets/image.jpg" style="width:50px;height:400px;" />
+    <img src="../../assets/image.jpg" style="width:500px;"  />
+    <img src="../../assets/image.jpg" style="width:100px;border-radius:50px;"  />
+    <img src="../../assets/image.jpg" style="width:500px;height:40px;" />
+
+
+    <img src="../../assets/image.jpg" style="padding:20px;border:5px solid black;" />
+
+    <img src="../../assets/image.jpg" style="padding-bottom:20px;border-top:5px solid black;clear:both;" />
+
+
+    <img src="../../assets/image.jpg" style="padding-top:20px;border-top:5px solid black;clear:both;width:50px;" />
+    <img src="../../assets/image.jpg" style="padding-top:20px;border-top:5px solid black;clear:both;width:50px;height:25px;" />
+
+    <img src="../../assets/image.jpg" style="width:0px;height:0px;border:1px solid black" />
+    <img src="../../assets/image.jpg" style="width:0px;height:0px;" />
+  </body>
+</html>
diff --git a/tests/cases/images/svg/base64.html b/tests/cases/images/svg/base64.html
new file mode 100644
index 0000000..e52b478
--- /dev/null
+++ b/tests/cases/images/svg/base64.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Base64 svg</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../../test.js"></script>
+</head>
+<body>
+<div>
+    Inline svg image: <br />
+    <img width="200" height="200" src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjMwNiIgaGVpZ2h0PSIyOTYiPjxkZWZzIGlkPSJkZWZzNCIgLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTYyLjQ2OTk1LC00NzcuMjg2MykiIGlkPSJsYXllcjEiPjxwYXRoIGQ9Im0gMzE0LjE1NzQ1LDQ4MS42OTU1OCBjIC01OS4yMDA4OSwwLjUzNzc0IC0xMTQuODA5NzksMzYuNzIyMTkgLTEzNy4zMTI1LDk1LjM0Mzc1IC0yOS4zOTEyOSw3Ni41NjY5MyA4LjgzOTMyLDE2Mi40NTI [...]
+</body>
+</html>
diff --git a/tests/cases/images/svg/external.html b/tests/cases/images/svg/external.html
new file mode 100644
index 0000000..0ad3c68
--- /dev/null
+++ b/tests/cases/images/svg/external.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Image tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../../test.js"></script>
+  </head>
+  <body>
+    SVG taints image:<br /> <!-- http://fi.wikipedia.org/wiki/Tiedosto:Svg.svg -->
+    <img src="../../../assets/image.svg" />
+  </body>
+</html>
diff --git a/tests/cases/images/svg/inline.html b/tests/cases/images/svg/inline.html
new file mode 100644
index 0000000..8fb0f8d
--- /dev/null
+++ b/tests/cases/images/svg/inline.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Inline svg</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../../test.js"></script>
+</head>
+<body>
+<div>
+    Inline svg image: <br />
+    <img width="200" height="200" src='data:image/svg+xml,<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="306" height="296"><defs id="defs4" /><g transform="translate(-162.46995,-477.2863)" id="layer1"><path d="m 314.15745,481.69558 c -59.20089,0.53774 -114.80979,36.72219 -137.3125,95.34375 -29.39129,76.56693 8.83932,162.45246 85.40625,191.84375 l 34.03125,-88.6875 c -20.0678,-7.71358 -34.3125,-27.15324 -34.3125,-49.9375 0,-29.54723 23. [...]
+</div>
+</body>
+</html>
diff --git a/tests/cases/images/svg/native_only.html b/tests/cases/images/svg/native_only.html
new file mode 100644
index 0000000..14a45fe
--- /dev/null
+++ b/tests/cases/images/svg/native_only.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Native svg only</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script>
+        var noFabric = true;
+    </script>
+    <script type="text/javascript" src="../../../test.js"></script>
+</head>
+<body>
+<div>
+    <img src="../../../assets/image.svg" />
+    <img width="200" height="200" src='data:image/svg+xml,<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="306" height="296"><defs id="defs4" /><g transform="translate(-162.46995,-477.2863)" id="layer1"><path d="m 314.15745,481.69558 c -59.20089,0.53774 -114.80979,36.72219 -137.3125,95.34375 -29.39129,76.56693 8.83932,162.45246 85.40625,191.84375 l 34.03125,-88.6875 c -20.0678,-7.71358 -34.3125,-27.15324 -34.3125,-49.9375 0,-29.54723 23. [...]
+    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="306" height="296" id="svg2">
+        <defs id="defs4"/>
+        <g transform="translate(-162.46995,-477.2863)" id="layer1">
+            <path d="m 314.15745,481.69558 c -59.20089,0.53774 -114.80979,36.72219 -137.3125,95.34375 -29.39129,76.56693 8.83932,162.45246 85.40625,191.84375 l 34.03125,-88.6875 c -20.0678,-7.71358 -34.3125,-27.15324 -34.3125,-49.9375 0,-29.54723 23.95277,-53.5 53.5,-53.5 29.54723,0 53.5,23.95277 53.5,53.5 0,22.78426 -14.2447,42.22392 -34.3125,49.9375 l 34.03125,88.6875 c 39.29085,-15.08234 70.3239,-46.1154 85.40625,-85.40625 29.39129,-76.56693 -8.83932,-162.48371 -85.40625,-191.875 -17. [...]
+                  id="path2830"
+                  style="fill:#40aa54;fill-opacity:1;stroke:#20552a;stroke-width:7.99999952;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
+        </g>
+    </svg>
+    <img width="200" height="200" src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjMwNiIgaGVpZ2h0PSIyOTYiPjxkZWZzIGlkPSJkZWZzNCIgLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTYyLjQ2OTk1LC00NzcuMjg2MykiIGlkPSJsYXllcjEiPjxwYXRoIGQ9Im0gMzE0LjE1NzQ1LDQ4MS42OTU1OCBjIC01OS4yMDA4OSwwLjUzNzc0IC0xMTQuODA5NzksMzYuNzIyMTkgLTEzNy4zMTI1LDk1LjM0Mzc1IC0yOS4zOTEyOSw3Ni41NjY5MyA4LjgzOTMyLDE2Mi40NTI [...]
+</body>
+</html>
diff --git a/tests/cases/images/svg/node.html b/tests/cases/images/svg/node.html
new file mode 100644
index 0000000..5faa7a5
--- /dev/null
+++ b/tests/cases/images/svg/node.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>SVG node</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../../test.js"></script>
+</head>
+<body><div>
+    SVG node image: <br/>
+    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="306" height="296" id="svg2">
+        <defs id="defs4"/>
+        <g transform="translate(-162.46995,-477.2863)" id="layer1">
+            <path d="m 314.15745,481.69558 c -59.20089,0.53774 -114.80979,36.72219 -137.3125,95.34375 -29.39129,76.56693 8.83932,162.45246 85.40625,191.84375 l 34.03125,-88.6875 c -20.0678,-7.71358 -34.3125,-27.15324 -34.3125,-49.9375 0,-29.54723 23.95277,-53.5 53.5,-53.5 29.54723,0 53.5,23.95277 53.5,53.5 0,22.78426 -14.2447,42.22392 -34.3125,49.9375 l 34.03125,88.6875 c 39.29085,-15.08234 70.3239,-46.1154 85.40625,-85.40625 29.39129,-76.56693 -8.83932,-162.48371 -85.40625,-191.875 -17. [...]
+                  id="path2830"
+                  style="fill:#40aa54;fill-opacity:1;stroke:#20552a;stroke-width:7.99999952;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
+        </g>
+    </svg>
+</div>
+</body>
+</html>
diff --git a/tests/cases/list/decimal-leading-zero.html b/tests/cases/list/decimal-leading-zero.html
new file mode 100644
index 0000000..7704a42
--- /dev/null
+++ b/tests/cases/list/decimal-leading-zero.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>List tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+
+    <script type="text/javascript">
+      function setUp() {
+        var supportedTypes = ["decimal","decimal-leading-zero","upper-roman","lower-roman","lower-alpha","upper-alpha"];
+        for (var i = 1;i<=100;i++){
+          $('#dynamic').append($('<li />').text(i).css('list-style-type',supportedTypes[1]));
+        }
+      }
+    </script>
+
+    <style>
+      #dynamic{
+        list-style-type:decimal;
+        list-style-position: inside;
+        font-size:20px;
+        line-height:50px;
+
+      }
+
+      .small{
+        font-size:14px;
+      }
+
+      .medium{
+        font-size:18px;
+      }
+      .large{
+        font-size:24px;
+      }
+      div{
+        float:left;
+      }
+      h2 {
+        clear:both;
+      }
+      li{
+        border:1px solid black;
+        width:100px;
+        margin:0;
+      }
+      ol{
+        margin:0;
+
+      }
+    </style>
+
+  </head>
+  <body>
+    <ol id="dynamic"></ol>
+  </body>
+</html>
diff --git a/tests/cases/list/decimal.html b/tests/cases/list/decimal.html
new file mode 100644
index 0000000..b92a401
--- /dev/null
+++ b/tests/cases/list/decimal.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>List tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+
+    <script type="text/javascript">
+      function setUp() {
+        var supportedTypes = ["decimal","decimal-leading-zero","upper-roman","lower-roman","lower-alpha","upper-alpha"];
+        for (var i = 1;i<=100;i++){
+          $('#dynamic').append($('<li />').text(i).css('list-style-type',supportedTypes[0]));
+        }
+      }
+    </script>
+
+    <style>
+      #dynamic{
+        list-style-type:decimal;
+        list-style-position: inside;
+        font-size:20px;
+        line-height:50px;
+
+      }
+
+      .small{
+        font-size:14px;
+      }
+
+      .medium{
+        font-size:18px;
+      }
+      .large{
+        font-size:24px;
+      }
+      div{
+        float:left;
+      }
+      h2 {
+        clear:both;
+      }
+      li{
+        border:1px solid black;
+        width:100px;
+        margin:0;
+      }
+      ol{
+        margin:0;
+
+      }
+    </style>
+
+  </head>
+  <body>
+
+    <ol id="dynamic"></ol>
+
+  </body>
+</html>
diff --git a/tests/cases/list/lower-alpha.html b/tests/cases/list/lower-alpha.html
new file mode 100644
index 0000000..7343393
--- /dev/null
+++ b/tests/cases/list/lower-alpha.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>List tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+
+    <script type="text/javascript">
+      function setUp() {
+        var supportedTypes = ["decimal","decimal-leading-zero","upper-roman","lower-roman","lower-alpha","upper-alpha"];
+        for (var i = 1;i<=100;i++){
+          $('#dynamic').append($('<li />').text(i).css('list-style-type',supportedTypes[4]));
+        }
+      }
+    </script>
+
+    <style>
+      #dynamic{
+        list-style-type:decimal;
+        list-style-position: inside;
+        font-size:20px;
+        line-height:50px;
+
+      }
+
+      .small{
+        font-size:14px;
+      }
+
+      .medium{
+        font-size:18px;
+      }
+      .large{
+        font-size:24px;
+      }
+      div{
+        float:left;
+      }
+      h2 {
+        clear:both;
+      }
+      li{
+        border:1px solid black;
+        width:100px;
+        margin:0;
+      }
+      ol{
+        margin:0;
+
+      }
+    </style>
+
+  </head>
+  <body>
+
+    <ol id="dynamic"></ol>
+
+  </body>
+</html>
diff --git a/tests/cases/list/upper-roman.html b/tests/cases/list/upper-roman.html
new file mode 100644
index 0000000..8eb3ddc
--- /dev/null
+++ b/tests/cases/list/upper-roman.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>List tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+
+    <script type="text/javascript">
+      function setUp() {
+        var supportedTypes = ["decimal","decimal-leading-zero","upper-roman","lower-roman","lower-alpha","upper-alpha"];
+        for (var i = 1;i<=100;i++){
+          $('#dynamic').append($('<li />').text(i).css('list-style-type',supportedTypes[2]));
+        }
+      }
+    </script>
+
+    <style>
+      #dynamic{
+        list-style-type:decimal;
+        list-style-position: inside;
+        font-size:20px;
+        line-height:50px;
+
+      }
+
+      .small{
+        font-size:14px;
+      }
+
+      .medium{
+        font-size:18px;
+      }
+      .large{
+        font-size:24px;
+      }
+      div{
+        float:left;
+      }
+      h2 {
+        clear:both;
+      }
+      li{
+        border:1px solid black;
+        width:100px;
+        margin:0;
+      }
+      ol{
+        margin:0;
+
+      }
+    </style>
+
+  </head>
+  <body>
+
+    <ol id="dynamic"></ol>
+
+  </body>
+</html>
diff --git a/tests/cases/overflow.html b/tests/cases/overflow.html
new file mode 100644
index 0000000..db6f086
--- /dev/null
+++ b/tests/cases/overflow.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Overflow tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../test.js"></script>
+        <style>
+            .small{
+                font-size:14px;
+            }
+
+            .medium{
+                font-size:18px;
+            }
+            .large{
+                font-size:24px;
+            }
+            div{
+                background: #ccc;
+                border:6px solid black;
+                width:300px;
+                height:200px;
+                margin: 0 0 60px 0;
+            }
+            h1 {
+                margin:0;
+            }
+        </style>
+
+    </head>
+    <body>
+        <h1>Overflow: visible</h1>
+        <div>              Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passage [...]
+        </div>
+
+        <h1>Overflow: hidden</h1>
+        <div style="overflow:hidden;">
+            Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s
+
+            with the release of <div style="border-width:10px 0 5px 0;background:green;">a</div>Letraset sheets containing Lorem Ipsum passages, <img src="../assets/image.jpg" /> and more recently with desktop publishing software like <b>Aldus PageMaker</b> including versions of Lorem Ipsum.
+
+
+            <div style="overflow:visible;position:relative;"><u>position:relative within a overflow:hidden element</u><br /> <br />
+
+                Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more [...]
+
+            </div>
+        </div>
+
+
+    </body>
+</html>
diff --git a/tests/cases/pseudoelements.html b/tests/cases/pseudoelements.html
new file mode 100644
index 0000000..1227cde
--- /dev/null
+++ b/tests/cases/pseudoelements.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Pseudoelement tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../test.js"></script>
+    <style>
+      :root .text *::before {
+        content:" root before!";
+      }
+
+      .text *::before {
+        content:" before!";
+      }
+
+      .text *::after {
+        content:" after!";
+      }
+
+      .img *::before{
+        content: url(../assets/image.jpg);
+      }
+
+      .img *::after{
+        content: url(../assets/image2.jpg);
+      }
+
+      .background *::before{
+        background: url(../assets/image_1.jpg);
+        border: 5px solid red;
+      }
+
+      .background *::after{
+        background: url(../assets/image2_1.jpg);
+      }
+
+      .none *::before {
+        display:none;
+      }
+
+      .none *::after {
+        display:none;
+      }
+
+    </style>
+
+  </head>
+  <body>
+    <div class="text">
+      <span>Content 1</span>
+      <span>Content 2</span>
+    </div>
+
+    <div class="text none">
+      <span>Content 1</span>
+      <span>Content 2</span>
+    </div>
+
+    <div class="text img">
+      <span>Content 1</span>
+      <span>Content 2</span>
+    </div>
+
+    <div class="text background">
+      <span>Content 1</span>
+      <span>Content 2</span>
+    </div>
+
+  </body>
+</html>
diff --git a/tests/cases/text/chinese.html b/tests/cases/text/chinese.html
new file mode 100644
index 0000000..8b56736
--- /dev/null
+++ b/tests/cases/text/chinese.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Chinese text</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style>
+      .chn-text-block {
+        width: 500px;
+        font-family: serif;
+        float: left;
+      }
+
+    </style>
+  </head>
+  <body>
+
+    <div id="text" class="chn-text-block">
+      <p>    注    释
+      </p><p>    〔1〕 见本书第一卷《实践论》注〔6〕。
+      </p><p>    〔2〕 见列宁《党的组织和党的出版物》。列宁在这篇论文中说:“这将是自由的写作,因为把一批又一批新生力量吸引到写作队伍中来的,不是私利贪欲,也不是名誉地位,而是社会主义思想和对劳动人民的同情。这将是自由的写作,因为它不是为饱食终日的贵妇人服务,不是为百无聊赖、胖得发愁的‘一万个上层分子’服务,而是为千千万万劳动人民,为这些国家的精华、国家的力量、国家的未来服务。这将是自由的写作,它要用社会主义无产阶级的经验和生气勃勃的工作去丰富人类革命思想的最新成就,它要使过去的经验(从原始空想的社会主义发展而成的科学社会主义)和现在的经验(工人同志们当前的斗争)之间经常发生相互作用。”(《列宁全集》第12卷,人民出版社1987年版,第96—97页)
+      </p><p>    〔3〕 梁实秋(一九○三——一九八七),北京人。新月社主要成员。先后在复旦大学、北京大学等校任教。曾写过一些文艺评论,长时期致力于文学翻译工作和散文的写作。鲁迅对梁实秋的批评,见《三闲集·新月社批评家的任务》、《二心集·“硬译”与“文学的阶级性”》等文。(《鲁迅全集》第4卷,人民文学出版社1981年版,第159、195—212页)
+      </p><p>    〔4〕 周作人(一八八五——一九六七),浙江绍兴人。曾在北京大学、燕京大学等校任教。五四运动时从事新文学写作。他的著述很多,有大量的散文集、文学专著和翻译作品。张资平(一八九三——一九五九),广东梅县人。他写过很多小说,曾在暨南大学、大夏大学兼任教职。周作人、张资平于一九三八年和一九三九年先后在北平、上海依附侵略中国的日本占领者。
+      </p><p>    〔5〕 见鲁迅《二心集·对于左翼作家联盟的意见》(《鲁迅全集》第4卷,人民文学出版社1981年版,第237—238页)。
+      </p><p>    〔6〕 参见鲁迅《且介亭杂文末编·附集·死》(《鲁迅全集》第6卷,人民文学出版社1981年版,第612页)。
+      </p><p>    〔7〕 “小放牛”是中国一出传统的小歌舞剧。全剧只有两个角色,男角是牧童,女角是乡村小姑娘,以互相对唱的方式表现剧的内容。抗日战争初期,革命的文艺工作者利用这个歌舞剧的形式,变动其原来的词句,宣传抗日,一时颇为流行。
+      </p><p>    〔8〕 “人、手、口、刀、牛、羊”是笔画比较简单的汉字,旧时一些小学国语读本把这几个字编在第一册的最初几课里。
+      </p><p>    〔9〕 “阳春白雪”和“下里巴人”,都是公元前三世纪楚国的歌曲。“阳春白雪”是供少数人欣赏的较高级的歌曲;“下里巴人”是流传很广的民间歌曲。《文选·宋玉对楚王问》记载一个故事,说有人在楚都唱歌,唱“阳春白雪”时,“国中属而和者(跟着唱的),不过数十人”;但唱“下里巴人”时,“国中属而和者数千人”。
+      </p><p>    〔10〕 见列宁《党的组织和党的出版物》。列宁在这篇论文中说:“写作事业应当成为整个无产阶级事业的一部分,成为由整个工人阶级的整个觉悟的先锋队所开动的一部巨大的社会民主主义机器的‘齿轮和螺丝钉’。”(《列宁全集》第12卷,人民出版社1987年版,第93页)
+      </p><p>    〔11〕 亭子间是上海里弄房子中的一种小房间,位置在房子后部的楼梯中侧,狭小黑暗,因此租金比较低廉。解放以前,贫苦的作家、艺术家、知识分子和机关小职员,多半租这种房间居住。
+      </p><p>    〔12〕 见本书第二卷《和中央社、扫荡报、新民报三记者的谈话》注〔3〕。
+      </p><p>    〔13〕 法捷耶夫(一九○一——一九五六),苏联名作家。他所作的小说《毁灭》于一九二七年出版,内容是描写苏联国内战争时期由苏联远东滨海边区工人、农民和革命知识分子所组成的一支游击队同国内反革命白卫军以及日本武装干涉军进行斗争的故事。这部小说曾由鲁迅译为汉文。
+      </p><p>    〔14〕 见鲁迅《集外集·自嘲》(《鲁迅全集》第7卷,人民文学出版社1981年版,第147页)。</p>
+    </div>
+  </body>
+</html>
diff --git a/tests/cases/text/fontawesome.html b/tests/cases/text/fontawesome.html
new file mode 100644
index 0000000..081ab26
--- /dev/null
+++ b/tests/cases/text/fontawesome.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title>fontawesome icons</title>
+    <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
+    <script type="text/javascript" src="../../test.js"></script>
+</head>
+<body>
+<div>
+    Fontawesome icons
+    <hr />
+    <i class="fa fa-camera-retro fa-5x"></i> fa-5x
+
+    <ul class="fa-ul">
+        <li><i class="fa-li fa fa-check-square"></i>List icons</li>
+        <li><i class="fa-li fa fa-check-square"></i>can be used</li>
+        <li><i class="fa-li fa fa-spinner fa-spin"></i>as bullets</li>
+        <li><i class="fa-li fa fa-square"></i>in lists</li>
+    </ul>
+
+    <div>
+        <span class="fa-stack fa-lg">
+          <i class="fa fa-square-o fa-stack-2x"></i>
+          <i class="fa fa-twitter fa-stack-1x"></i>
+        </span>
+        fa-twitter on fa-square-o<br>
+        <span class="fa-stack fa-lg">
+          <i class="fa fa-circle fa-stack-2x"></i>
+          <i class="fa fa-flag fa-stack-1x fa-inverse"></i>
+        </span>
+        fa-flag on fa-circle<br>
+        <span class="fa-stack fa-lg">
+          <i class="fa fa-square fa-stack-2x"></i>
+          <i class="fa fa-terminal fa-stack-1x fa-inverse"></i>
+        </span>
+        fa-terminal on fa-square<br>
+        <span class="fa-stack fa-lg">
+          <i class="fa fa-camera fa-stack-1x"></i>
+          <i class="fa fa-ban fa-stack-2x text-danger"></i>
+        </span>
+        fa-ban on fa-camera
+    </div>
+</div>
+</body>
+</html>
diff --git a/tests/cases/text/linethrough.html b/tests/cases/text/linethrough.html
new file mode 100644
index 0000000..968862c
--- /dev/null
+++ b/tests/cases/text/linethrough.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Text-decoration:line-through tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../../test.js"></script>
+        <script type="text/javascript">
+            function setUp() {
+                $('body').empty();
+                $.each(['arial','verdana','tahoma','courier new'],function(i,e){
+                    var div = $('<div />').css('font-family',e).appendTo('body');
+                    for(var i=0;i<=10;i++){
+                        $('<div />').text('Testing texts').css('margin-top',1).css('border','1px solid black').css('font-size',(16+i*6)).appendTo(div);
+                    }
+                });
+            }
+        </script>
+
+        <style>
+            .small{
+                font-size:14px;
+            }
+
+            .medium{
+                font-size:18px;
+            }
+            .large{
+                font-size:24px;
+            }
+
+            div{
+                text-decoration:line-through;
+
+            }
+
+            .lineheight{
+                line-height:40px;
+            }
+
+            h2 {
+                clear:both;
+            }
+        </style>
+
+    </head>
+    <body>
+        Creating content through JavaScript
+    </body>
+</html>
diff --git a/tests/cases/text/shadow.html b/tests/cases/text/shadow.html
new file mode 100644
index 0000000..f353bbf
--- /dev/null
+++ b/tests/cases/text/shadow.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Text shadow tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style>
+        .shadow1 span{
+            text-shadow: 1px 1px 3px #888;
+        }
+        .shadow1 strong {
+            text-shadow: 1px 1px 2px black, 0 0 1em blue, 0 0 0.2em blue;
+        }
+
+        .shadow2 {
+            font-size: 48px;
+        }
+
+        .shadow2 span{
+            color: transparent;
+            text-shadow: 0 0 5px #00f, 2px 2px 0 #f00;
+        }
+
+        .shadow2 strong {
+            color: rgba(0, 255, 0, 0.5);
+            text-shadow: 0 0 5px #00f, 2px 2px 0 #f00;
+            text-decoration: underline;
+        }
+
+    </style>
+
+</head>
+<body>
+<div class="shadow1">
+    Some text <span> followed by text with shadow </span> followed by more text without shadow.
+    <strong>Multi text shadow</strong> and some more text without shadow
+</div>
+<div class="shadow2">
+    <span>testing with transparent</span>
+    <strong>testing with low opacity</strong>
+</div>
+</body>
+</html>
diff --git a/tests/cases/text/text.html b/tests/cases/text/text.html
new file mode 100644
index 0000000..2cacb95
--- /dev/null
+++ b/tests/cases/text/text.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Text tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../../test.js"></script>
+        <style>
+            .small{
+                font-size:14px;
+            }
+
+            .medium{
+                font-size:18px;
+            }
+            .large{
+                font-size:24px;
+            }
+            div{
+                float:left;
+            }
+            h2 {
+                clear:both;
+            }
+        </style>
+
+    </head>
+    <body>           <h1><h1> text-decoration</h1>
+        <div style="font-family:Arial;">
+            <h2>Arial</h2>
+            <ol class="small">
+                <li style="text-decoration:none;">text-decoration:none;</li>
+                <li style="text-decoration:underline;">text-decoration:underline;</li>
+                <li style="text-decoration:overline;">text-decoration:overline;</li>
+                <li style="text-decoration:line-through;">text-decoration:line-through;</li>
+            </ol>
+
+            <ol class="medium">
+                <li style="text-decoration:none;">text-decoration:none;</li>
+                <li style="text-decoration:underline;">text-decoration:underline;</li>
+                <li style="text-decoration:overline;">text-decoration:overline;</li>
+                <li style="text-decoration:line-through;">text-decoration:line-through;</li>
+            </ol>
+            <ol class="large">
+                <li style="text-decoration:none;">text-decoration:none;</li>
+                <li style="text-decoration:underline;">text-decoration:underline;</li>
+                <li style="text-decoration:overline;">text-decoration:overline;</li>
+                <li style="text-decoration:line-through;">text-decoration:line-through;</li>
+            </ol>
+        </div>
+
+        <div style="font-family:Verdana;">
+            <h2>Verdana</h2>
+            <ol class="small">
+                <li style="text-decoration:none;">text-decoration:none;</li>
+                <li style="text-decoration:underline;">text-decoration:underline;</li>
+                <li style="text-decoration:overline;">text-decoration:overline;</li>
+                <li style="text-decoration:line-through;">text-decoration:line-through;</li>
+            </ol>
+
+            <ol class="medium">
+                <li style="text-decoration:none;">text-decoration:none;</li>
+                <li style="text-decoration:underline;">text-decoration:underline;</li>
+                <li style="text-decoration:overline;">text-decoration:overline;</li>
+                <li style="text-decoration:line-through;">text-decoration:line-through;</li>
+            </ol>
+            <ol class="large">
+                <li style="text-decoration:none;">text-decoration:none;</li>
+                <li style="text-decoration:underline;">text-decoration:underline;</li>
+                <li style="text-decoration:overline;">text-decoration:overline;</li>
+                <li style="text-decoration:line-through;">text-decoration:line-through;</li>
+            </ol>
+        </div>
+
+        <div style="font-family:Tahoma;">
+            <h2>Tahoma</h2>
+            <ol class="small">
+                <li style="text-decoration:none;">text-decoration:none;</li>
+                <li style="text-decoration:underline;">text-decoration:underline;</li>
+                <li style="text-decoration:overline;">text-decoration:overline;</li>
+                <li style="text-decoration:line-through;">text-decoration:line-through;</li>
+            </ol>
+
+            <ol class="medium">
+                <li style="text-decoration:none;">text-decoration:none;</li>
+                <li style="text-decoration:underline;">text-decoration:underline;</li>
+                <li style="text-decoration:overline;">text-decoration:overline;</li>
+                <li style="text-decoration:line-through;">text-decoration:line-through;</li>
+            </ol>
+            <ol class="large">
+                <li style="text-decoration:none;">text-decoration:none;</li>
+                <li style="text-decoration:underline;">text-decoration:underline;</li>
+                <li style="text-decoration:overline;">text-decoration:overline;</li>
+                <li style="text-decoration:line-through;">text-decoration:line-through;</li>
+            </ol>
+        </div>
+
+        <h2><h2> text-transform</h2>
+        <ul>
+            <li style="text-transform:none;">text-transform:none;</li>
+            <li style="text-transform:capitalize;">text-transform: capitalize; (including foreign characters such as Öaäå)</li>
+            <li style="text-transform:uppercase;">text-transform:uppercase;</li>
+            <li style="text-transform:lowercase;">text-transform:lowercase;</li>
+        </ul>
+        <h3><h3> misc text alignments</h3>
+        <ul>
+            <li style="word-spacing:5px;">word-spacing:5px; (as each letter is rendered individually, the bounds will always be correct)</li>
+            <li style="line-height:35px;">line-height:35px; <br />(same goes for line-height)</li>
+            <li style="letter-spacing:5px;">letter-spacing:5px;</li>
+            <li style="text-align:right;width:300px;">text-align:right;width:300px;</li>
+            <li style="font-variant:small-caps;">font-variant:small-caps; </li>
+        </ul>
+
+    </body>
+</html>
diff --git a/tests/cases/text/underline-lineheight.html b/tests/cases/text/underline-lineheight.html
new file mode 100644
index 0000000..b556b9c
--- /dev/null
+++ b/tests/cases/text/underline-lineheight.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Text-decoration:underline tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../../test.js"></script>
+        <script type="text/javascript">
+            function setUp() {
+                $('body').empty();
+                $.each(['arial','verdana','tahoma','courier new'],function(i,e){
+                    var div = $('<div />').css('font-family',e).appendTo('body');
+                    for(var i=0;i<=10;i++){
+                        $('<div />').text('Testing texts').css('margin-top',1).css('border','1px solid black').css('font-size',(16+i*6)).appendTo(div);
+                    }
+
+                });
+            }
+        </script>
+
+        <style>
+            .small{
+                font-size:14px;
+            }
+
+            .medium{
+                font-size:18px;
+            }
+            .large{
+                font-size:24px;
+            }
+
+            div{
+                text-decoration:underline;
+                line-height:40px;
+            }
+
+
+
+            h2 {
+                clear:both;
+            }
+        </style>
+
+    </head>
+    <body>
+        Creating content through JavaScript
+    </body>
+</html>
diff --git a/tests/cases/text/underline.html b/tests/cases/text/underline.html
new file mode 100644
index 0000000..7d39ac9
--- /dev/null
+++ b/tests/cases/text/underline.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Text-decoration:underline tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../../test.js"></script>
+        <script type="text/javascript">
+            function setUp() {
+                $('body').empty();
+                $.each(['arial','verdana','tahoma','courier new'],function(i,e){
+                    var div = $('<div />').css('font-family',e).appendTo('body');
+                    for(var i=0;i<=10;i++){
+                        $('<div />').text('Testing texts').css('margin-top',1).css('border','1px solid black').css('font-size',(16+i*6)).appendTo(div);
+                    }
+
+                });
+            }
+        </script>
+
+        <style>
+            .small{
+                font-size:14px;
+            }
+
+            .medium{
+                font-size:18px;
+            }
+            .large{
+                font-size:24px;
+            }
+
+            div{
+                text-decoration:underline;
+            }
+
+            .lineheight{
+                line-height:40px;
+            }
+
+            h2 {
+                clear:both;
+            }
+        </style>
+
+    </head>
+    <body>
+        Creating content through JavaScript
+    </body>
+</html>
diff --git a/tests/cases/transform/nested.html b/tests/cases/transform/nested.html
new file mode 100644
index 0000000..b523ad7
--- /dev/null
+++ b/tests/cases/transform/nested.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Nested transform tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style>
+        #first {
+            background: indianred;
+            margin-top: 100px;
+        }
+        #second {
+            border: 15px solid red;
+            background: darkseagreen;
+            -webkit-transform: rotate(7.5deg);  /* Chrome, Safari 3.1+ */
+            -moz-transform: rotate(7.5deg);  /* Firefox 3.5-15 */
+            -ms-transform: rotate(7.5deg);  /* IE 9 */
+            -o-transform: rotate(7.5deg);  /* Opera 10.50-12.00 */
+            transform: rotate(7.5deg);
+        }
+        #third {
+            background: cadetblue;
+            -webkit-transform: rotate(-70.5deg);  /* Chrome, Safari 3.1+ */
+            -moz-transform: rotate(-70.5deg);  /* Firefox 3.5-15 */
+            -ms-transform: rotate(-70.5deg);  /* IE 9 */
+            -o-transform: rotate(-70.5deg);  /* Opera 10.50-12.00 */
+            transform: rotate(-70.5deg);  /* Firefox 16+, IE 10+, Opera 12.10+ */
+
+        }
+        #fourth {
+            background: #bc8f8f;
+        }
+
+        div {
+            display: inline-block;
+
+        }
+    </style>
+
+</head>
+<body>
+<div id="first">First level content <div id="second">with second level content <div id="third">and third level content</div>, ending second</div>, ending first</div>
+<div id="fourth">something else</div>
+</body>
+</html>
diff --git a/tests/cases/transform/rotate.html b/tests/cases/transform/rotate.html
new file mode 100644
index 0000000..dc2f158
--- /dev/null
+++ b/tests/cases/transform/rotate.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Rotation transform tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style>
+        .container {
+            position: relative;
+        }
+        .image1 {
+            position: absolute;
+            left: 100px;
+            -webkit-transform: rotate(-45deg);  /* Chrome, Safari 3.1+ */
+            -moz-transform: rotate(-45deg);  /* Firefox 3.5-15 */
+            -ms-transform: rotate(-45deg);   /* IE 9 */
+            -o-transform: rotate(-45deg);   /* Opera 10.50-12.00 */
+            transform:rotate(-45deg);
+        }
+        .image2 {
+            position: absolute;
+            left: 634px;
+            -webkit-transform: rotate(90deg);  /* Chrome, Safari 3.1+ */
+            -moz-transform: rotate(90deg);  /* Firefox 3.5-15 */
+            -ms-transform: rotate(90deg);   /* IE 9 */
+            -o-transform: rotate(90deg);   /* Opera 10.50-12.00 */
+            transform:rotate(90deg);
+        }
+        .image3 {
+            position: absolute;
+            top: 250px;
+            left: 100px;
+            -webkit-transform: rotate(45deg);  /* Chrome, Safari 3.1+ */
+            -moz-transform: rotate(45deg);  /* Firefox 3.5-15 */
+            -ms-transform: rotate(45deg);   /* IE 9 */
+            -o-transform: rotate(45deg);   /* Opera 10.50-12.00 */
+            transform:rotate(45deg);
+        }
+    </style
+</head>
+<body>
+<div class="container">
+    <div class="image1">
+        <img src="../../assets/image.jpg" style="width: 200px; height: 200px;">
+    </div>
+    <div class="image2">
+        <img src="../../assets/image2.jpg" style="width: 50px; height: 200px;">
+    </div>
+    <div class="image3">
+        <img src="../../assets/image.jpg" style="width: 100px; height: 200px;">
+    </div>
+</div>
+</body>
+</html>
diff --git a/tests/cases/transform/translate.html b/tests/cases/transform/translate.html
new file mode 100644
index 0000000..86301b3
--- /dev/null
+++ b/tests/cases/transform/translate.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Nested transform tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style>
+        #first {
+            background: indianred;
+            margin-top: 100px;
+        }
+        #second {
+            border: 10px solid red;
+            background: darkseagreen;
+            -webkit-transform: translate(125px);  /* Chrome, Safari 3.1+ */
+            -moz-transform: translate(125px);  /* Firefox 3.5-15 */
+            -ms-transform: translate(125px);  /* IE 9 */
+            -o-transform: translate(125px);  /* Opera 10.50-12.00 */
+            transform: translate(125px);
+        }
+        #third {
+            background: cadetblue;
+            -webkit-transform: translate(-100px, -25px);  /* Chrome, Safari 3.1+ */
+            -moz-transform: translate(100px, -25px);  /* Firefox 3.5-15 */
+            -ms-transform: translate(100px, -25px);  /* IE 9 */
+            -o-transform: translate(100px, -25px);  /* Opera 10.50-12.00 */
+            transform: translate(100px, -25px);
+            -webkit-transform-origin: 100px 50px;
+            -moz-transform-origin: 100px 50px;
+            -ms-transform-origin: 100px 50px;
+            -o-transform-origin: 100px 50px;
+            transform-origin: 100px 50px;
+
+        }
+        div {
+            display: inline-block;
+            padding: 10px;
+        }
+    </style>
+
+</head>
+<body>
+<div id="first">First level content <div id="second">with second level content <div id="third">and third level content</div>, ending second</div>, ending first</div>
+</body>
+</html>
diff --git a/tests/cases/visibility.html b/tests/cases/visibility.html
new file mode 100644
index 0000000..9c09a63
--- /dev/null
+++ b/tests/cases/visibility.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Visible elements tests</title>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <script type="text/javascript" src="../test.js"></script>
+        <style>
+            div{
+                border:2px solid black;
+            }
+        </style>
+
+    </head>
+    <body>
+        <h1>Display:none and visible:hidden tests</h1>
+        <div>This should be visible </div>
+        <div style="display:none">display:none, This should be <b>hidden</b></div>
+        <div style="visibility:hidden">visibility:hidden, This should be <b>hidden</b></div>
+        <hr />
+        <div style="display:none">display:none, This should be <b>hidden</b></div>
+        <div style="visibility:hidden">visibility:hidden, This should be <b>hidden</b></div>
+
+    </body>
+</html>
diff --git a/tests/cases/zindex/z-index1.html b/tests/cases/zindex/z-index1.html
new file mode 100644
index 0000000..075ded7
--- /dev/null
+++ b/tests/cases/zindex/z-index1.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>z-index tests #1</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style>
+
+      div { font: 12px Arial; }
+
+      span.bold { font-weight: bold; }
+
+      #div1,#div3 {
+        height: 80px;
+        position: relative;
+        border: 1px solid #669966;
+        background-color: #ccffcc;
+        padding-left: 5px;
+      }
+
+      #div2 {
+        opacity: 0.8;
+        z-index: 1;
+        position: absolute;
+        width: 150px;
+        height: 200px;
+        top: 20px;
+        left: 170px;
+        border: 1px solid #990000;
+        background-color: #ffdddd;
+        text-align: center;
+      }
+
+      #div4 {
+        opacity: 0.8;
+        z-index: 2;
+        position: absolute;
+        width: 200px;
+        height: 70px;
+        top: 65px;
+        left: 50px;
+        border: 1px solid #000099;
+        background-color: #ddddff;
+        text-align: left;
+        padding-left: 10px;
+      }
+
+    </style>
+
+  </head>
+  <body>
+
+    <div>
+      <br />
+
+      No z-indexed content
+      <div id="div1">
+        <br /><span class="bold">DIV #1</span>
+        <br />position: relative;
+        <div id="div2">
+          <br /><span class="bold">DIV #2</span>
+          <br />position: absolute;
+          <br />z-index: 1;
+        </div>
+      </div>
+
+      <br />
+
+      <div id="div3">
+        <br /><span class="bold">DIV #3</span>
+        <br />position: relative;
+        <div id="div4">
+          <br /><span class="bold">DIV #4</span>
+          <br />position: absolute;
+          <br />z-index: 2;
+        </div>
+      </div>
+      Some more non-zindexed content
+    </div>
+  </body>
+</html>
diff --git a/tests/cases/zindex/z-index10.html b/tests/cases/zindex/z-index10.html
new file mode 100644
index 0000000..485d2e7
--- /dev/null
+++ b/tests/cases/zindex/z-index10.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html>
+  <head>
+<!-- https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context?redirectlocale=en-US&redirectslug=CSS%2FUnderstanding_z-index%2FThe_stacking_context
+ -->    
+    <title>Understanding CSS z-index: The Stacking Context: Example Source</title>
+    <script type="text/javascript" src="../../test.js"></script>
+
+    <style type="text/css">
+      * {
+        margin: 0;
+        }
+      html {
+        padding: 20px;
+        font: 12px/20px Arial, sans-serif;
+        }
+      div {
+        opacity: 0.7;
+        position: relative;
+        }
+      h1 {
+        font: inherit;
+        font-weight: bold;
+        }
+      #div1, #div2 {
+        border: 1px solid #696;
+        padding: 10px;
+        background-color: #cfc;
+        }
+      #div1 {
+        z-index: 5;
+        margin-bottom: 190px;
+        }
+      #div2 {
+        z-index: 2;
+        }
+      #div3 {
+        z-index: 4;
+        opacity: 1;
+        position: absolute;
+        top: 40px;
+        left: 180px;
+        width: 330px;
+        border: 1px solid #900;
+        background-color: #fdd;
+        padding: 40px 20px 20px;
+        }
+      #div4, #div5 {
+        border: 1px solid #996;
+        background-color: #ffc;
+        }
+      #div4 {
+        z-index: 6;
+        margin-bottom: 15px;
+        padding: 25px 10px 5px;
+        }
+      #div5 {
+        z-index: 1;
+        margin-top: 15px;
+        padding: 5px 10px;
+        }
+      #div6 {
+        z-index: 3;
+        position: absolute;
+        top: 20px;
+        left: 180px;
+        width: 150px;
+        height: 125px;
+        border: 1px solid #009;
+        padding-top: 125px;
+        background-color: #ddf;
+        text-align: center;
+        }
+    </style>
+
+  </head>
+  <body>
+
+    <div id="div1">
+      <h1>Division Element #1</h1>
+      <code>position: relative;<br/>
+      z-index: 5;</code>
+    </div>
+
+    <div id="div2">
+      <h1>Division Element #2</h1>
+      <code>position: relative;<br/>
+      z-index: 2;</code>
+    </div>
+
+    <div id="div3">
+
+      <div id="div4">
+        <h1>Division Element #4</h1>
+        <code>position: relative;<br/>
+        z-index: 6;</code>
+      </div>
+
+      <h1>Division Element #3</h1>
+      <code>position: absolute;<br/>
+      z-index: 4;</code>
+
+      <div id="div5">
+        <h1>Division Element #5</h1>
+        <code>position: relative;<br/>
+        z-index: 1;</code>
+      </div>
+   
+      <div id="div6">
+        <h1>Division Element #6</h1>
+        <code>position: absolute;<br/>
+        z-index: 3;</code>
+      </div>
+
+    </div>
+
+  </body>
+</html>
\ No newline at end of file
diff --git a/tests/cases/zindex/z-index11.html b/tests/cases/zindex/z-index11.html
new file mode 100644
index 0000000..f649d41
--- /dev/null
+++ b/tests/cases/zindex/z-index11.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html>
+<head>
+    <title>static position inside position relative</title>
+    <script type="text/javascript" src="../../test.js"></script>
+
+    <style type="text/css">
+        html {
+            padding: 20px;
+            font: 12px/20px Arial, sans-serif;
+        }
+        #div1 {
+            padding: 10px;
+            background: #9bfff8;
+            position: relative;
+        }
+
+        #div2 {
+            background: #7cb659;
+            display: table;
+        }
+
+        #div3 {
+            float:left;
+        }
+
+    </style>
+
+</head>
+<body>
+<div id="div1">
+    <h1>Div Element #1</h1>
+    <code>position: relative;</code>
+    <div id="div2">
+        <h1>Div Element #2</h1>
+        <code>position: static;</code>
+        <div id="div3">
+            <div>
+                <h1>Div Element #3</h1>
+                <code>float: left;</code>
+            </div>
+        </div>
+    </div>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/cases/zindex/z-index12.html b/tests/cases/zindex/z-index12.html
new file mode 100644
index 0000000..87c5c17
--- /dev/null
+++ b/tests/cases/zindex/z-index12.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html>
+<head>
+    <title>Negative z-indexes</title>
+    <script type="text/javascript" src="../../test.js"></script>
+
+    <style type="text/css">
+        html {
+            padding: 20px;
+            font: 12px/20px Arial, sans-serif;
+        }
+        #div1 {
+            padding: 10px;
+            position: relative;
+        }
+
+        #div2 {
+            background: #7cb659;
+            position: absolute;
+            z-index:-999998;
+            left: 0px;
+            top: 0px;
+        }
+
+        #div3 {
+            background: #b69f1a;
+            position: absolute;
+            z-index: -999999;
+            left: 0px;
+            top: 0px;
+        }
+    </style>
+</head>
+<body>
+<div id="div1">
+    <div id="div2">
+        <h1>Div Element #2</h1>
+        <div id="div3">
+            <div>
+                <h1>Div Element #3</h1>
+            </div>
+        </div>
+    </div>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/cases/zindex/z-index13.html b/tests/cases/zindex/z-index13.html
new file mode 100644
index 0000000..884f792
--- /dev/null
+++ b/tests/cases/zindex/z-index13.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>text above children with negative z-index; z-index tests #13</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+    .outer {
+        background-color:cyan;
+        width:200px;
+        height:200px;
+        z-index:0;
+        position:relative;
+    }
+
+    .inner {
+        background-color:green;
+        width:100px;
+        height:100px;
+        z-index:-1;
+        position:absolute;
+        top:0;left:0;
+    }
+    </style></head>
+
+  <body>
+    <div class="outer">outer
+      <div class="inner"></div>
+  </div>
+  </body>
+</html>
diff --git a/tests/cases/zindex/z-index14.html b/tests/cases/zindex/z-index14.html
new file mode 100644
index 0000000..6bfa9c5
--- /dev/null
+++ b/tests/cases/zindex/z-index14.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>body text above children with negative index but body bgcolor below</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+    * {margin:0;padding:0;}
+    body {
+        background-color: green;
+    }
+    #div1 {
+        background-color:cyan;
+        width:200px;
+        height:200px;
+        z-index:-1;
+        position:absolute;
+        top:0; left:0;
+    }
+    </style></head>
+
+  <body>body
+    <div id="div1"></div>
+  </body>
+</html>
diff --git a/tests/cases/zindex/z-index15.html b/tests/cases/zindex/z-index15.html
new file mode 100644
index 0000000..f8dbece
--- /dev/null
+++ b/tests/cases/zindex/z-index15.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>body text and bgcolor above children with negative z-index</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+    * {margin:0;padding:0;}
+    html {background-color: gray;}
+    body {
+        background-color: green;
+    }
+    #div1 {
+        background-color:cyan;
+        width:200px;
+        height:200px;
+        z-index:-1;
+        position:absolute;
+        top:0; left:0;
+    }
+    </style></head>
+
+  <body>body
+    <div id="div1"></div>
+  </body>
+</html>
diff --git a/tests/cases/zindex/z-index16.html b/tests/cases/zindex/z-index16.html
new file mode 100644
index 0000000..c9030ea
--- /dev/null
+++ b/tests/cases/zindex/z-index16.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+<HTML>
+<HEAD>
+    <TITLE>Z-order positioning</TITLE>
+    <STYLE type="text/css">
+        .pile {
+            position: absolute;
+            left: 2in;
+            top: 2in;
+            width: 3in;
+            height: 3in;
+        }
+    </STYLE>
+    <script type="text/javascript" src="../../test.js"></script>
+</HEAD>
+<BODY>
+<P>
+    <IMG id="image" class="pile"
+         src="../../assets/image.jpg" alt="A butterfly image"
+         style="z-index: 1">
+
+<DIV id="text1" class="pile"
+     style="z-index: 3">
+    This text will overlay the butterfly image.
+</DIV>
+
+<DIV id="text2">
+    This text will be beneath everything.
+</DIV>
+
+<DIV id="text3" class="pile"
+     style="z-index: 2">
+    This text will underlay text1, but overlay the butterfly image
+</DIV>
+</BODY>
+</HTML>
diff --git a/tests/cases/zindex/z-index17.html b/tests/cases/zindex/z-index17.html
new file mode 100644
index 0000000..4023e3e
--- /dev/null
+++ b/tests/cases/zindex/z-index17.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title>z-index17</title>
+    <style>
+        .z {
+            background: darkolivegreen;
+            position: fixed;
+            right: 0;
+            left: 0;
+            height: 100px;
+            z-index: 10;
+            top: 0;
+        }
+        body {
+            background: violet;
+        }
+    </style>
+    <script type="text/javascript" src="../../test.js"></script>
+</head>
+<body>
+<div class="z">fixed z-index 10</div>
+</body>
+</html>
diff --git a/tests/cases/zindex/z-index18.html b/tests/cases/zindex/z-index18.html
new file mode 100644
index 0000000..080b236
--- /dev/null
+++ b/tests/cases/zindex/z-index18.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title>z-index18</title>
+    <style>
+        body {
+            background: violet;
+        }
+
+        #container {
+            position: relative;
+            height: 300px;
+        }
+
+        .base {
+            background: green;
+        }
+
+        #container div {
+            width: 800px;
+            height: 300px;
+        }
+
+        .div1 {
+            background: red;
+        }
+
+        #container .div2 {
+            width: 400px;
+            background: blue;
+        }
+
+        #container .div3 {
+            background: orange;
+            width: 200px;
+            height: 200px;
+        }
+
+        #container .highlight1 {
+            background: purple;
+            height: 100px;
+        }
+
+        #container .highlight2 {
+            background: pink;
+            height: 100px;
+        }
+
+        #container .highlight3 {
+            background: navy;
+            height: 100px;
+        }
+
+        #container .last {
+            background: brown;
+            width: 100px;
+            height: 100px;
+        }
+    </style>
+    <script type="text/javascript" src="../../test.js"></script>
+</head>
+<body>
+<div id="container" class="jqplot-target" style="position: relative; height: 300px;">
+    <div class="base" style="position: absolute; left: 0px; top: 0px;">a</div>
+    <div class="base" style="position: absolute; left: 0px; top: 0px;">b</div>
+    <div style="position: absolute; left: 16px; top: 10px;">c</div>
+    <div style="position: absolute; left: 16px; top: 10px;">d</div>
+    <div style="position: absolute; left: 16px; top: 10px;">e</div>
+    <div class="div1" style="position: absolute; left: 16px; top: 10px;">f</div>
+    <div class="div2" style="position: absolute; left: 16px; top: 10px;">g</div>
+    <div class="div3" style="position: absolute; left: 16px; top: 10px;">h</div>
+    <div class="highlight1" style="position: absolute; right: 16px; top: 10px;">i</div>
+    <div class="highlight2" style="position: absolute; left: 16px; top: 60px;">j</div>
+    <div class="highlight3" style="position: absolute; left: 56px; top: 90px;">k</div>
+    <div class="last" style="position: absolute; left: 16px; top: 10px;">l</div>
+</div>
+</body>
+</html>
diff --git a/tests/cases/zindex/z-index2.html b/tests/cases/zindex/z-index2.html
new file mode 100644
index 0000000..2dabb11
--- /dev/null
+++ b/tests/cases/zindex/z-index2.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>z-index tests #2</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+
+      div { font: 12px Arial; }
+
+      span.bold { font-weight: bold; }
+
+      #div2 { z-index: 2; }
+      #div3 { z-index: 1; }
+      #div4 { z-index: 10; }
+
+      #div1,#div3 {
+        height: 80px;
+        position: relative;
+        border: 1px solid #669966;
+        background-color: #ccffcc;
+        padding-left: 5px;
+      }
+
+      #div2 {
+        opacity: 0.8;
+        position: absolute;
+        width: 150px;
+        height: 200px;
+        top: 20px;
+        left: 170px;
+        border: 1px solid #990000;
+        background-color: #ffdddd;
+        text-align: center;
+      }
+
+      #div4 {
+        opacity: 0.8;
+        position: absolute;
+        width: 200px;
+        height: 70px;
+        top: 65px;
+        left: 50px;
+        border: 1px solid #000099;
+        background-color: #ddddff;
+        text-align: left;
+        padding-left: 10px;
+      }
+
+      #div5{
+        border: 1px solid #669966;
+        background-color: #ccffcc;
+        padding-left: 5px;
+        position:relative;
+        margin-bottom:-15px;
+        height:50px;
+        margin-top:10px;
+
+      }
+
+      #div6{
+        border: 1px solid #000099;
+        background-color: #ddddff;
+        text-align: left;
+        padding-left: 10px;
+
+      }
+
+    </style></head>
+
+  <body>
+
+    <br />
+
+    <div id="div1">
+      <br /><span class="bold">DIV #1</span>
+      <br />position: relative;
+      <div id="div2">
+        <br /><span class="bold">DIV #2</span>
+        <br />position: absolute;
+        <br />z-index: 2;
+      </div>
+    </div>
+
+    <br />
+
+    <div id="div3">
+      <br /><span class="bold">DIV #3</span>
+      <br />position: relative;
+      <br />z-index: 1;
+      <div id="div4">
+        <br /><span class="bold">DIV #4</span>
+        <br />position: absolute;
+        <br />z-index: 10;
+      </div>
+    </div>
+
+    <div id="div5"><br />DIV #5<br />position:relative;<br /></div>
+
+    <div id ="div6"><br />DIV #6<br />position:static;<br /></div>
+
+  </body>
+</html>
diff --git a/tests/cases/zindex/z-index3.html b/tests/cases/zindex/z-index3.html
new file mode 100644
index 0000000..b9147f4
--- /dev/null
+++ b/tests/cases/zindex/z-index3.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>z-index tests #3</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+
+      div { font: 12px Arial; }
+
+      span.bold { font-weight: bold; }
+
+      div.lev1 {
+        width: 250px;
+        height: 70px;
+        position: relative;
+        border: 2px solid #669966;
+        background-color: #ccffcc;
+        padding-left: 5px;
+
+      }
+
+      #container1 {
+        z-index: 1;
+        position: absolute;
+        top: 30px;
+        left: 75px;
+      }
+
+      div.lev2 {
+        opacity: 0.9;
+        width: 200px;
+        height: 60px;
+        position: relative;
+        border: 2px solid #990000;
+        background-color: #ffdddd;
+        padding-left: 5px;
+      }
+
+      #container2 {
+        z-index: 1;
+        position: absolute;
+        top: 20px;
+        left: 110px;
+      }
+
+      div.lev3 {
+        z-index: 10;
+        width: 100px;
+        position: relative;
+        border: 2px outset #000099;
+        background-color: #ddddff;
+        padding-left: 5px;
+      }
+
+    </style></head>
+
+  <body>
+
+    <br />
+
+    <div class="lev1">
+      <span class="bold">LEVEL #1</span>
+
+      <div id="container1">
+
+        <div class="lev2">
+          <br /><span class="bold">LEVEL #2</span>
+          <br />z-index: 1;
+
+          <div id="container2">
+
+            <div class="lev3"><span class="bold">LEVEL #3</span></div>
+            <div class="lev3"><span class="bold">LEVEL #3</span></div>
+            <div class="lev3"><span class="bold">LEVEL #3</span></div>
+            <div class="lev3"><span class="bold">LEVEL #3</span></div>
+            <div class="lev3"><span class="bold">LEVEL #3</span></div>
+            <div class="lev3"><span class="bold">LEVEL #3</span></div>
+            <div class="lev3"><span class="bold">LEVEL #3</span></div>
+            <div class="lev3"><span class="bold">LEVEL #3</span></div>
+            <div class="lev3"><span class="bold">LEVEL #3</span></div>
+            <div class="lev3"><span class="bold">LEVEL #3</span></div>
+            <div class="lev3"><span class="bold">LEVEL #3</span></div>
+
+          </div>
+
+        </div>
+
+        <div class="lev2">
+          <br /><span class="bold">LEVEL #2</span>
+          <br />z-index: 1;
+        </div>
+
+      </div>
+    </div>
+
+    <div class="lev1" style="position:static">
+      <span class="bold">LEVEL #1</span>
+    </div>
+
+    <div class="lev1">
+      <span class="bold">LEVEL #1</span>
+    </div>
+
+    <div class="lev1">
+      <span class="bold">LEVEL #1</span>
+    </div>
+  </body>
+</html>
diff --git a/tests/cases/zindex/z-index4.html b/tests/cases/zindex/z-index4.html
new file mode 100644
index 0000000..3e0b95b
--- /dev/null
+++ b/tests/cases/zindex/z-index4.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>z-index tests #4</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+      div.lev1 {
+        width: 250px;
+        height: 70px;
+        position: relative;
+        border: 2px solid #669966;
+        background-color: #ccffcc;
+        padding-left: 5px;
+      }
+      div.background {
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        width: 250px;
+        background-color: #ffdddd;
+        z-index: -1;
+      }
+    </style></head>
+
+  <body>
+    <div class="lev1">
+      <span>LEVEL #1</span>
+    </div>
+    <div class="background"></div>
+    <div class="lev1">
+      <span>LEVEL #1</span>
+    </div>
+  </body>
+</html>
diff --git a/tests/cases/zindex/z-index5.html b/tests/cases/zindex/z-index5.html
new file mode 100644
index 0000000..960c7fc
--- /dev/null
+++ b/tests/cases/zindex/z-index5.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>z-index tests #5</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+      div.lev1 {
+        width: 250px;
+        height: 70px;
+        position: relative;
+        border: 2px solid #669966;
+        background-color: #ccffcc;
+        padding-left: 5px;
+      }
+      div.background {
+        position: fixed;
+        top: 0;
+        bottom: 0;
+        width: 250px;
+        background-color: #ffdddd;
+      }
+    </style></head>
+
+  <body>
+    <div class="lev1">
+      LEVEL #1
+    </div>
+    <div class="background"></div>
+    <div class="lev1">
+      <span>LEVEL #1</span>
+    </div>
+  </body>
+</html>
diff --git a/tests/cases/zindex/z-index6.html b/tests/cases/zindex/z-index6.html
new file mode 100644
index 0000000..2b7073f
--- /dev/null
+++ b/tests/cases/zindex/z-index6.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>z-index tests #6</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <script type="text/javascript" src="../../test.js"></script>
+    <style type="text/css">
+      div {
+        width: 250px;
+        height: 70px;
+        position: relative;
+        border: 2px solid #669966;
+        background-color: #ccffcc;
+        padding-left: 5px;
+      }
+      div.z0 {
+        z-index: 0;
+        top:105px;
+        left:20px;
+        background-color: #ffdddd;
+      }
+
+      div.z1 {
+        z-index: 1;
+      }
+    </style></head>
+
+  <body>
+    <div class="z0"><span>z-index:0</span></div>
+    <div>default z-index</div>
+    <div class="z1">z-index:1</div>
+  </body>
+</html>
diff --git a/tests/cases/zindex/z-index7.html b/tests/cases/zindex/z-index7.html
new file mode 100644
index 0000000..dae7f4a
--- /dev/null
+++ b/tests/cases/zindex/z-index7.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+<title>Stacking without z-index</title>
+<!-- https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/Stacking_without_z-index?redirectlocale=en-US&redirectslug=CSS%2FUnderstanding_z-index%2FStacking_without_z-index -->
+<script type="text/javascript" src="../../test.js"></script>
+<style type="text/css">
+
+div {
+   font: 12px Arial;
+}
+
+span.bold { font-weight: bold; }
+
+#normdiv {
+   height: 70px;
+   border: 1px solid #999966;
+   background-color: #ffffcc;
+   margin: 0px 50px 0px 50px;
+   text-align: center;
+}
+
+#reldiv1 {
+   opacity: 0.7;
+   height: 100px;
+   position: relative;
+   top: 30px;
+   border: 1px solid #669966;
+   background-color: #ccffcc;
+   margin: 0px 50px 0px 50px;
+   text-align: center;
+}
+
+#reldiv2 {
+   opacity: 0.7;
+   height: 100px;
+   position: relative;
+   top: 15px;
+   left: 20px;
+   border: 1px solid #669966;
+   background-color: #ccffcc;
+   margin: 0px 50px 0px 50px;
+   text-align: center;
+}
+
+#absdiv1 {
+   opacity: 0.7;
+   position: absolute;
+   width: 150px;
+   height: 350px;
+   top: 10px;
+   left: 10px;
+   border: 1px solid #990000;
+   background-color: #ffdddd;
+   text-align: center;
+}
+
+#absdiv2 {
+   opacity: 0.7;
+   position: absolute;
+   width: 150px;
+   height: 350px;
+   top: 10px;
+   right: 10px;
+   border: 1px solid #990000;
+   background-color: #ffdddd;
+   text-align: center;
+}
+
+</style></head>
+
+<body>
+
+<br /><br />
+
+<div id="absdiv1">
+   <br /><span class="bold">DIV #1</span>
+   <br />position: absolute;
+</div>
+
+<div id="reldiv1">
+   <br /><span class="bold">DIV #2</span>
+   <br />position: relative;
+</div>
+
+<div id="reldiv2">
+   <br /><span class="bold">DIV #3</span>
+   <br />position: relative;
+</div>
+
+<div id="absdiv2">
+   <br /><span class="bold">DIV #4</span>
+   <br />position: absolute;
+</div>
+
+<div id="normdiv">
+   <br /><span class="bold">DIV #5</span>
+   <br />no positioning
+</div>
+
+</body></html>
\ No newline at end of file
diff --git a/tests/cases/zindex/z-index8.html b/tests/cases/zindex/z-index8.html
new file mode 100644
index 0000000..777ec6f
--- /dev/null
+++ b/tests/cases/zindex/z-index8.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head>
+<title>Stacking and float</title>
+<!-- https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/Stacking_and_float?redirectlocale=en-US&redirectslug=CSS%2FUnderstanding_z-index%2FStacking_and_float -->
+<script type="text/javascript" src="../../test.js"></script>
+<style type="text/css">
+
+div {
+   font: 12px Arial;
+}
+
+span.bold { font-weight: bold; }
+
+#absdiv1 {
+   opacity: 0.7;
+   position: absolute;
+   width: 150px;
+   height: 200px;
+   top: 10px;
+   right: 140px;
+   border: 1px solid #990000;
+   background-color: #ffdddd;
+   text-align: center;
+}
+
+#normdiv {
+   /*opacity: 0.7;*/
+   height: 100px;
+   border: 1px solid #999966;
+   background-color: #ffffcc;
+   margin: 0px 10px 0px 10px;
+   text-align: left;
+}
+
+#flodiv1 {
+   opacity: 0.7;
+   margin: 0px 10px 0px 20px;
+   float: left;
+   width: 150px;
+   height: 200px;
+   border: 1px solid #009900;
+   background-color: #ccffcc;
+   text-align: center;
+}
+
+#flodiv2 {
+   opacity: 0.7;
+   margin: 0px 20px 0px 10px;
+   float: right;
+   width: 150px;
+   height: 200px;
+   border: 1px solid #009900;
+   background-color: #ccffcc;
+   text-align: center;
+}
+
+#absdiv2 {
+   opacity: 0.7;
+   position: absolute;
+   width: 150px;
+   height: 100px;
+   top: 130px;
+   left: 100px;
+   border: 1px solid #990000;
+   background-color: #ffdddd;
+   text-align: center;
+}
+
+</style></head>
+
+<body>
+
+<br /><br />
+
+<div id="absdiv1">
+<br /><span class="bold">DIV #1</span>
+<br />position: absolute;
+</div>
+
+<div id="flodiv1">
+<br /><span class="bold">DIV #2</span>
+<br />float: left;
+</div>
+
+<div id="flodiv2">
+<br /><span class="bold">DIV #3</span>
+<br />float: right;
+</div>
+
+<br />
+
+<div id="normdiv">
+<br /><span class="bold">DIV #4</span>
+<br />no positioning
+</div>
+
+<div id="absdiv2">
+<br /><span class="bold">DIV #5</span>
+<br />position: absolute;
+</div>
+
+</body></html>
\ No newline at end of file
diff --git a/tests/cases/zindex/z-index9.html b/tests/cases/zindex/z-index9.html
new file mode 100644
index 0000000..7fa2761
--- /dev/null
+++ b/tests/cases/zindex/z-index9.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html>
+<head><title>Adding z-index</title>
+<!-- https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/Adding_z-index?redirectlocale=en-US&redirectslug=CSS%2FUnderstanding_z-index%2FAdding_z-index -->
+<script type="text/javascript" src="../../test.js"></script>
+<style type="text/css">
+
+div {
+   opacity: 0.7;
+   font: 12px Arial;
+}
+
+span.bold { font-weight: bold; }
+
+#normdiv {
+   z-index: 8;
+   height: 70px;
+   border: 1px solid #999966;
+   background-color: #ffffcc;
+   margin: 0px 50px 0px 50px;
+   text-align: center;
+}
+
+#reldiv1 {
+   z-index: 3;
+   height: 100px;
+   position: relative;
+   top: 30px;
+   border: 1px solid #669966;
+   background-color: #ccffcc;
+   margin: 0px 50px 0px 50px;
+   text-align: center;
+}
+
+#reldiv2 {
+   z-index: 2;
+   height: 100px;
+   position: relative;
+   top: 15px;
+   left: 20px;
+   border: 1px solid #669966;
+   background-color: #ccffcc;
+   margin: 0px 50px 0px 50px;
+   text-align: center;
+}
+
+#absdiv1 {
+   z-index: 5;
+   position: absolute;
+   width: 150px;
+   height: 350px;
+   top: 10px;
+   left: 10px;
+   border: 1px solid #990000;
+   background-color: #ffdddd;
+   text-align: center;
+}
+
+#absdiv2 {
+   z-index: 1;
+   position: absolute;
+   width: 150px;
+   height: 350px;
+   top: 10px;
+   right: 10px;
+   border: 1px solid #990000;
+   background-color: #ffdddd;
+   text-align: center;
+}
+
+</style></head>
+
+<body>
+
+<br /><br />
+
+<div id="absdiv1">
+   <br /><span class="bold">DIV #1</span>
+   <br />position: absolute;
+   <br />z-index: 5;
+</div>
+
+<div id="reldiv1">
+   <br /><span class="bold">DIV #2</span>
+   <br />position: relative;
+   <br />z-index: 3;
+</div>
+
+<div id="reldiv2">
+   <br /><span class="bold">DIV #3</span>
+   <br />position: relative;
+   <br />z-index: 2;
+</div>
+
+<div id="absdiv2">
+   <br /><span class="bold">DIV #4</span>
+   <br />position: absolute;
+   <br />z-index: 1;
+</div>
+
+<div id="normdiv">
+   <br /><span class="bold">DIV #5</span>
+   <br />no positioning
+   <br />z-index: 8;
+</div>
+
+</body></html>
\ No newline at end of file
diff --git a/tests/mocha/.jshintrc b/tests/mocha/.jshintrc
new file mode 100644
index 0000000..5a34e66
--- /dev/null
+++ b/tests/mocha/.jshintrc
@@ -0,0 +1,18 @@
+{
+    "curly": true,
+    "eqeqeq": true,
+    "immed": true,
+    "latedef": false,
+    "newcap": true,
+    "noarg": true,
+    "sub": true,
+    "undef": true,
+    "boss": true,
+    "eqnull": true,
+    "browser": true,
+    "globals": {
+        "jQuery": true
+    },
+    "predef": ["deepEqual", "module", "test", "$", "QUnit", "NodeParser", "NodeContainer", "StackingContext", "TextContainer", "ImageLoader", "CanvasRenderer", "Renderer", "Support", "bind", "Promise",
+        "ImageContainer", "ProxyImageContainer", "DummyImageContainer", "Font", "FontMetrics", "GradientContainer", "LinearGradientContainer", "WebkitGradientContainer", "log", "smallImage", "parseBackgrounds"]
+}
diff --git a/tests/mocha/background.html b/tests/mocha/background.html
new file mode 100644
index 0000000..626a557
--- /dev/null
+++ b/tests/mocha/background.html
@@ -0,0 +1,136 @@
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Mocha Tests</title>
+    <link rel="stylesheet" href="lib/mocha.css" />
+    <script src="../../node_modules/bluebird/js/browser/bluebird.js"></script>
+    <script src="../../dist/html2canvas.js"></script>
+    <script src="../assets/jquery-1.6.2.js"></script>
+    <script src="lib/expect.js"></script>
+    <script src="lib/mocha.js"></script>
+    <style>
+        #block {
+            width: 200px;
+            height: 200px;
+        }
+        #green-block {
+            width: 200px;
+            height: 200px;
+            background: green;
+        }
+        #background-block {
+            width: 200px;
+            height: 200px;
+            background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNgaGD4DwAChAGAJVtEDQAAAABJRU5ErkJggg==) red;
+        }
+        #gradient-block {
+            width: 200px;
+            height: 200px;
+            background-image: -webkit-linear-gradient(top, #008000, #008000);
+            background-image:    -moz-linear-gradient(to bottom, #008000, #008000);
+            background-image:         linear-gradient(to bottom, #008000, #008000);
+        }
+    </style>
+</head>
+<body>
+<div id="mocha"></div>
+<script>mocha.setup('bdd')</script>
+<div id="block"></div>
+<div id="green-block"></div>
+<div id="background-block"></div>
+<div id="gradient-block"></div>
+<script>
+    describe("options.background", function() {
+        it("with hexcolor", function(done) {
+            html2canvas(document.querySelector("#block"), {background: '#008000'}).then(function(canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it("with named color", function(done) {
+            html2canvas(document.querySelector("#block"), {background: 'green'}).then(function(canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it("with element background", function(done) {
+            html2canvas(document.querySelector("#green-block"), {background: 'red'}).then(function(canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+    });
+
+    describe('element background', function() {
+        it('with background-color', function(done) {
+            html2canvas(document.querySelector("#green-block")).then(function(canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it('with background-image', function(done) {
+            html2canvas(document.querySelector("#background-block")).then(function(canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it('with gradient background-image', function(done) {
+            html2canvas(document.querySelector("#gradient-block")).then(function(canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+    });
+
+    function validCanvasPixels(canvas) {
+        var ctx = canvas.getContext("2d");
+        var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
+        for (var i = 0, len = data.length; i < len; i+=4) {
+            if (data[i] !== 0 || data[i+1] !== 128 || data[i+2] !== 0 || data[i+3] !== 255) {
+                expect().fail("Invalid canvas data");
+            }
+        }
+    }
+
+    mocha.checkLeaks();
+    mocha.globals(['jQuery']);
+    if (window.mochaPhantomJS) {
+        mochaPhantomJS.run();
+    }
+    else {
+        mocha.run();
+    }
+    mocha.suite.afterAll(function() {
+        document.body.setAttribute('data-complete', 'true');
+    });
+</script>
+</body>
+</html>
diff --git a/tests/mocha/clone.js b/tests/mocha/clone.js
new file mode 100644
index 0000000..576d4ba
--- /dev/null
+++ b/tests/mocha/clone.js
@@ -0,0 +1 @@
+document.querySelector("#block").className += "class";
diff --git a/tests/mocha/cropping.html b/tests/mocha/cropping.html
new file mode 100644
index 0000000..2a6d4fc
--- /dev/null
+++ b/tests/mocha/cropping.html
@@ -0,0 +1,145 @@
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Mocha Tests</title>
+    <link rel="stylesheet" href="lib/mocha.css" />
+    <script src="../../node_modules/bluebird/js/browser/bluebird.js"></script>
+    <script src="../../dist/html2canvas.js"></script>
+    <script src="../assets/jquery-1.6.2.js"></script>
+    <script src="lib/expect.js"></script>
+    <script src="lib/mocha.js"></script>
+</head>
+<body>
+<div id="mocha"></div>
+<script>mocha.setup('bdd')</script>
+<div style="background: green; width: 40px; height:40px;" id="block"></div>
+<div style="visibility: hidden">
+    <iframe src="iframe1.htm" style="height: 350px; width: 450px; border: 0;" scrolling="no" id="frame1"></iframe>
+    <iframe src="iframe2.htm" style="height: 350px; width: 450px; border: 0;" scrolling="yes" id="frame2"></iframe>
+</div>
+<script>
+    describe("Cropping", function() {
+        it("window view with body", function(done) {
+            html2canvas(document.body, {type: 'view'}).then(function(canvas) {
+                expect(canvas.width).to.equal(window.innerWidth);
+                expect(canvas.height).to.equal(window.innerHeight);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it("window view with documentElement", function(done) {
+            html2canvas(document.documentElement, {type: 'view'}).then(function(canvas) {
+                expect(canvas.width).to.equal(window.innerWidth);
+                expect(canvas.height).to.equal(window.innerHeight);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it("iframe with body", function(done) {
+            html2canvas(document.querySelector("#frame1").contentWindow.document.body, {type: 'view'}).then(function(canvas) {
+                expect(canvas.width).to.equal(450);
+                expect(canvas.height).to.equal(350);
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it("iframe with document element", function(done) {
+            html2canvas(document.querySelector("#frame1").contentWindow.document.documentElement, {type: 'view'}).then(function(canvas) {
+                expect(canvas.width).to.equal(450);
+                expect(canvas.height).to.equal(350);
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it("with node", function(done) {
+            html2canvas(document.querySelector("#block")).then(function(canvas) {
+                expect(canvas.width).to.equal(40);
+                expect(canvas.height).to.equal(40);
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it("with node and size", function(done) {
+            html2canvas(document.querySelector("#block"), {width: 20, height: 20}).then(function(canvas) {
+                expect(canvas.width).to.equal(20);
+                expect(canvas.height).to.equal(20);
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        document.querySelector("#frame2").addEventListener("load", function() {
+
+            document.querySelector("#frame2").contentWindow.scrollTo(0, 350);
+            describe("with scrolled content", function() {
+                it("iframe with body", function(done) {
+                    html2canvas(document.querySelector("#frame2").contentWindow.document.body, {type: 'view'}).then(function(canvas) {
+                        // phantomjs issue https://github.com/ariya/phantomjs/issues/10581
+                        if (canvas.height !== 1200) {
+                            expect(canvas.width).to.equal(450);
+                            expect(canvas.height).to.equal(350);
+                            validCanvasPixels(canvas);
+                        }
+                        done();
+                    }).catch(function(error) {
+                        done(error);
+                    });
+                });
+
+                it("iframe with document element", function(done) {
+                    html2canvas(document.querySelector("#frame2").contentWindow.document.documentElement, {type: 'view'}).then(function(canvas) {
+                        // phantomjs issue https://github.com/ariya/phantomjs/issues/10581
+                        if (canvas.height !== 1200) {
+                            expect(canvas.width).to.equal(450);
+                            expect(canvas.height).to.equal(350);
+                            validCanvasPixels(canvas);
+                        }
+                        done();
+                    }).catch(function(error) {
+                        done(error);
+                    });
+                });
+            });
+        }, false);
+    });
+
+
+    function validCanvasPixels(canvas) {
+        var ctx = canvas.getContext("2d");
+        var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
+        for (var i = 0, len = data.length; i < len; i+=4) {
+            if (data[i] !== 0 || data[i+1] !== 128 || data[i+2] !== 0 || data[i+3] !== 255) {
+                expect().fail("Invalid canvas data");
+            }
+        }
+    }
+
+    mocha.checkLeaks();
+    mocha.globals(['jQuery']);
+    if (window.mochaPhantomJS) {
+        mochaPhantomJS.run();
+    }
+    else {
+        mocha.run();
+    }
+    mocha.suite.afterAll(function() {
+        document.body.setAttribute('data-complete', 'true');
+    });
+</script>
+</body>
+</html>
diff --git a/tests/mocha/css.js b/tests/mocha/css.js
new file mode 100644
index 0000000..885a4bc
--- /dev/null
+++ b/tests/mocha/css.js
@@ -0,0 +1,220 @@
+var NodeContainer = html2canvas.NodeContainer;
+
+describe('Borders', function() {
+    $('#borders div').each(function(i, node) {
+        it($(this).attr('style'), function() {
+            ["borderTopWidth", "borderRightWidth", "borderBottomWidth", "borderLeftWidth"].forEach(function(prop) {
+                var result = $(node).css(prop);
+                // older IE's don't necessarily return px even with jQuery
+                if (result === "thin") {
+                    result = "1px";
+                } else if (result === "medium") {
+                    result = "3px";
+                } else if (result === "thick") {
+                    result = "5px";
+                }
+                var container = new NodeContainer(node, null);
+                expect(container.css(prop)).to.be(result);
+            });
+        });
+    });
+});
+
+describe('Padding', function() {
+    $('#padding div').each(function(i, node) {
+        it($(this).attr('style'), function() {
+            ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"].forEach(function(prop) {
+                var container = new NodeContainer(node, null);
+                var result = container.css(prop);
+                expect(result).to.contain("px");
+                expect(result, $(node).css(prop));
+            });
+        });
+    });
+});
+
+describe('Background-position', function() {
+    $('#backgroundPosition div').each(function(i, node) {
+        it($(this).attr('style'), function() {
+            var prop = "backgroundPosition";
+            var img =  new Image();
+            img.width = 50;
+            img.height = 50;
+
+            var container = new NodeContainer(node, null);
+            var item = container.css(prop),
+                backgroundPosition = container.parseBackgroundPosition(html2canvas.utils.getBounds(node), img),
+                split = (window.getComputedStyle) ? $(node).css(prop).split(" ") : [$(node).css(prop+"X"), $(node).css(prop+"Y")];
+
+            var testEl = $('<div />').css({
+                'position': 'absolute',
+                'left': split[0],
+                'top': split[1]
+            });
+
+            testEl.appendTo(node);
+
+            expect(backgroundPosition.left).to.equal(Math.floor(parseFloat(testEl.css('left'))));
+            expect(backgroundPosition.top).to.equal(Math.floor(parseFloat(testEl.css('top'))));
+
+            testEl.remove();
+        });
+    });
+});
+
+describe('Text-shadow', function() {
+    $('#textShadows div').each(function(i, node) {
+        var index = i + 1;
+        var container = new NodeContainer(node, null);
+        var shadows = container.parseTextShadows();
+        it(node.style.textShadow, function() {
+            if (i === 0) {
+                expect(shadows.length).to.equal(0);
+            } else {
+                expect(shadows.length).to.equal(i >= 6 ? 2 : 1);
+                expect(shadows[0].offsetX).to.equal(i);
+                expect(shadows[0].offsetY).to.equal(i);
+                if (i < 2) {
+                    expect(shadows[0].color.toString()).to.equal('rgba(0,0,0,0)');
+                } else if (i % 2 === 0) {
+                    expect(shadows[0].color.toString()).to.equal('rgb(2,2,2)');
+                } else {
+                    var opacity = '0.2';
+                    expect(shadows[0].color.toString()).to.match(/rgba\(2,2,2,(0.2|0\.199219)\)/);
+                }
+
+                // only testing blur once
+                if (i === 1) {
+                    expect(shadows[0].blur).to.equal('1');
+                }
+            }
+        });
+    });
+});
+
+describe('Background-image', function() {
+    test_parse_background_image(
+        'url("te)st")',
+        {
+            prefix: '',
+            method: 'url',
+            value: 'url("te)st")',
+            args: ['te)st'],
+            image: null
+        },
+        'test quoted'
+    );
+
+    test_parse_background_image(
+        'url("te,st")',
+        {
+            prefix: '',
+            method: 'url',
+            value: 'url("te,st")',
+            args: ['te,st'],
+            image: null
+        },
+        'test quoted'
+    );
+
+    test_parse_background_image(
+        'url(te,st)',
+        {
+            prefix: '',
+            method: 'url',
+            value: 'url(te,st)',
+            args: ['te,st'],
+            image: null
+        },
+        'test quoted'
+    );
+
+    test_parse_background_image(
+        'url(test)',
+        {
+            prefix: '',
+            method: 'url',
+            value: 'url(test)',
+            args: ['test'],
+            image: null
+        },
+        'basic url'
+    );
+
+    test_parse_background_image(
+        'url("test")',
+        {
+            prefix: '',
+            method: 'url',
+            value: 'url("test")',
+            args: ['test'],
+            image: null
+        },
+        'quoted url'
+    );
+
+    test_parse_background_image(
+        'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)',
+        {
+            prefix: '',
+            method: 'url',
+            value: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)',
+            args: ['data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'],
+            image: null
+        },
+        'data url'
+    );
+
+    test_parse_background_image(
+        'linear-gradient(red,black)',
+        {
+            prefix: '',
+            method: 'linear-gradient',
+            value: 'linear-gradient(red,black)',
+            args: ['red','black'],
+            image: null
+        },
+        'linear-gradient'
+    );
+
+    test_parse_background_image(
+        'linear-gradient(top,rgb(255,0,0),rgb(0,0,0))',
+        {
+            prefix: '',
+            method: 'linear-gradient',
+            value: 'linear-gradient(top,rgb(255,0,0),rgb(0,0,0))',
+            args: ['top', 'rgb(255,0,0)', 'rgb(0,0,0)'],
+            image: null
+        },
+        'linear-gradient w/ rgb()'
+    );
+
+    test_parse_background_image(
+        '-webkit-linear-gradient(red,black)',
+        {
+            prefix: '-webkit-',
+            method: 'linear-gradient',
+            value: '-webkit-linear-gradient(red,black)',
+            args: ['red','black'],
+            image: null
+        },
+        'prefixed linear-gradient'
+    );
+
+    test_parse_background_image(
+        'linear-gradient(red,black), url(test), url("test"),\n none, ', [
+            { prefix: '', method: 'linear-gradient', value: 'linear-gradient(red,black)', args: ['red','black'], image: null },
+            { prefix: '', method: 'url', value: 'url(test)', args: ['test'], image: null  },
+            { prefix: '', method: 'url', value: 'url("test")', args: ['test'], image: null  },
+            { prefix: '', method: 'none', value: 'none', args: [], image: null }
+        ],
+        'multiple backgrounds'
+    );
+
+
+    function test_parse_background_image(value, expected, name) {
+        it(name, function() {
+            expect(html2canvas.utils.parseBackgrounds(value)).to.eql(Array.isArray(expected) ? expected : [expected]);
+        });
+    }
+});
diff --git a/tests/mocha/form-rendering.html b/tests/mocha/form-rendering.html
new file mode 100644
index 0000000..363b836
--- /dev/null
+++ b/tests/mocha/form-rendering.html
@@ -0,0 +1,166 @@
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Mocha Tests</title>
+    <link rel="stylesheet" href="lib/mocha.css" />
+    <script src="../../node_modules/bluebird/js/browser/bluebird.js"></script>
+    <script src="../../dist/html2canvas.js"></script>
+    <script src="../assets/jquery-1.6.2.js"></script>
+    <script src="lib/expect.js"></script>
+    <script src="lib/mocha.js"></script>
+    <style>
+        .block {
+            width: 200px;
+            height: 200px;
+        }
+    </style>
+</head>
+<body>
+<div id="mocha"></div>
+<script>mocha.setup('bdd')</script>
+<div id="block1" class="block">
+    <input type="text" value="text" />
+</div>
+<div id="block2" class="block">
+    <input type="password" value="password" />
+</div>
+<div id="block3" class="block">
+    <input type="text" value="text" />
+</div>
+
+<div id="block4" class="block">
+    <textarea>text</textarea>
+</div>
+
+<div id="block5" class="block">
+    <select>
+        <option value="1">1</option>
+        <option value="2" selected>2</option>
+        <option value="3">3</option>
+    </select>
+</div>
+
+<div id="green-block"></div>
+<script>
+    var CanvasRenderer = html2canvas.CanvasRenderer;
+
+    describe("Rendering input values", function() {
+        it("uses default value for input[type='text']", function(done) {
+            CanvasRenderer.prototype.text = function(text) {
+                expect(text).to.equal('text');
+            };
+            html2canvas(document.querySelector("#block1"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it("uses transformed value for input[type='password']", function(done) {
+            var count = 0;
+            CanvasRenderer.prototype.text = function(text) {
+                expect(text).to.equal('•');
+                count++;
+            };
+            html2canvas(document.querySelector("#block2"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                expect(count).to.equal("password".length);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it("used property and not attribute for rendering", function(done) {
+            document.querySelector("#block3 input").value = 'updated';
+
+            CanvasRenderer.prototype.text = function(text) {
+                expect(text).to.equal('updated');
+            };
+            html2canvas(document.querySelector("#block3"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        describe("Rendering textarea values", function() {
+            it("uses default value correctly", function(done) {
+                CanvasRenderer.prototype.text = function(text) {
+                    expect(text).to.equal('text');
+                };
+                html2canvas(document.querySelector("#block4"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
+                    expect(canvas.width).to.equal(200);
+                    expect(canvas.height).to.equal(200);
+                    done();
+                }).catch(function(error) {
+                    done(error);
+                });
+            });
+
+            it("used property and not attribute for rendering", function(done) {
+                document.querySelector("#block4 textarea").value = 'updated';
+
+                CanvasRenderer.prototype.text = function(text) {
+                    expect(text).to.equal('updated');
+                };
+                html2canvas(document.querySelector("#block4"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
+                    expect(canvas.width).to.equal(200);
+                    expect(canvas.height).to.equal(200);
+                    done();
+                }).catch(function(error) {
+                    done(error);
+                });
+            });
+        });
+
+        describe("Select values", function() {
+            it("uses default value correctly", function(done) {
+                CanvasRenderer.prototype.text = function(text) {
+                    expect(text).to.equal('2');
+                };
+                html2canvas(document.querySelector("#block5"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
+                    expect(canvas.width).to.equal(200);
+                    expect(canvas.height).to.equal(200);
+                    done();
+                }).catch(function(error) {
+                    done(error);
+                });
+            });
+
+            it("used property and not attribute for rendering", function(done) {
+                document.querySelector("#block5 select").value = '3';
+
+                CanvasRenderer.prototype.text = function(text) {
+                    expect(text).to.equal('3');
+                };
+                html2canvas(document.querySelector("#block5"), {renderer: CanvasRenderer, strict: true}).then(function(canvas) {
+                    expect(canvas.width).to.equal(200);
+                    expect(canvas.height).to.equal(200);
+                    done();
+                }).catch(function(error) {
+                    done(error);
+                });
+            });
+        });
+    });
+
+    mocha.checkLeaks();
+    mocha.globals(['jQuery']);
+    if (window.mochaPhantomJS) {
+        mochaPhantomJS.run();
+    }
+    else {
+        mocha.run();
+    }
+    mocha.suite.afterAll(function() {
+        document.body.setAttribute('data-complete', 'true');
+    });
+</script>
+</body>
+</html>
diff --git a/tests/mocha/gradients.js b/tests/mocha/gradients.js
new file mode 100644
index 0000000..8f616f0
--- /dev/null
+++ b/tests/mocha/gradients.js
@@ -0,0 +1,158 @@
+describe("Gradients", function() {
+    var expected = [
+        {
+            method: "linear-gradient",
+            args: [
+                "left",
+                " rgb(255, 0, 0)",
+                " rgb(255, 255, 0)",
+                " rgb(0, 255, 0)"
+            ]
+        },
+        {
+            method: "linear-gradient",
+            args: [
+                "left",
+                " red",
+                " rgb(255, 255, 0)",
+                " rgb(0, 255, 0)"
+            ]
+        },
+        {
+            method: 'linear-gradient',
+            args: [
+                "left",
+                " rgb(206, 219, 233) 0%",
+                " rgb(170, 197, 222) 17%",
+                " rgb(97, 153, 199) 50%",
+                " rgb(58, 132, 195) 51%",
+                " rgb(65, 154, 214) 59%",
+                " rgb(75, 184, 240) 71%",
+                " rgb(58, 139, 194) 84%",
+                " rgb(38, 85, 139) 100%"
+            ]
+        },
+        {
+            method: 'linear-gradient',
+            args: [
+                "left",
+                " rgb(206, 219, 233) 0%",
+                " rgb(170, 197, 222) 17px",
+                " rgb(97, 153, 199) 50%",
+                " rgb(58, 132, 195) 51px",
+                " rgb(65, 154, 214) 59%",
+                " rgb(75, 184, 240) 71px",
+                " rgb(58, 139, 194) 84%",
+                " rgb(38, 85, 139) 100px"
+            ]
+        },
+        {
+            method: "gradient",
+            args: [
+                "linear",
+                " 50% 0%",
+                " 50% 100%",
+                " from(rgb(240, 183, 161))",
+                " color-stop(0.5, rgb(140, 51, 16))",
+                " color-stop(0.51, rgb(117, 34, 1))",
+                " to(rgb(191, 110, 78))"
+            ]
+        },
+        {
+            method: "gradient",
+            args: [
+                "linear",
+                " 50% 0%",
+                " 50% 100%",
+                " from(rgb(255, 0, 0))",
+                " color-stop(0.314159, green)",
+                " color-stop(0.51, rgb(0, 0, 255))",
+                // temporary workaround for Blink/WebKit bug: crbug.com/453414
+                //" to(rgba(0, 0, 0, 0.5))"
+                " to(rgba(0, 0, 0, 0))"
+            ]
+        },
+        {
+            method: 'linear-gradient',
+            args: [
+                "0deg",
+                " rgb(221, 221, 221)",
+                " rgb(221, 221, 221) 50%",
+                " transparent 50%"
+            ]
+        },
+        {
+            method: "radial-gradient",
+            args: [
+                "75% 19%",
+                " ellipse closest-side",
+                " rgb(171, 171, 171)",
+                " rgb(0, 0, 255) 33%",
+                " rgb(153, 31, 31) 100%"
+            ]
+        },
+        {
+            method: "radial-gradient",
+            args: [
+                "75% 19%",
+                " ellipse closest-corner",
+                " rgb(171, 171, 171)",
+                " rgb(0, 0, 255) 33%",
+                " rgb(153, 31, 31) 100%"
+            ]
+        },
+        {
+            method: "radial-gradient",
+            args: [
+                "75% 19%",
+                " ellipse farthest-side",
+                " rgb(171, 171, 171)",
+                " rgb(0, 0, 255) 33%",
+                " rgb(153, 31, 31) 100%"
+            ]
+        },
+        {
+            method: "radial-gradient",
+            args: [
+                "75% 19%",
+                " ellipse farthest-corner",
+                " rgb(171, 171, 171)",
+                " rgb(0, 0, 255) 33%",
+                " rgb(153, 31, 31) 100%"
+            ]
+        },
+        {
+            method: "radial-gradient",
+            args: [
+                "75% 19%",
+                " ellipse contain",
+                " rgb(171, 171, 171)",
+                " rgb(0, 0, 255) 33%",
+                " rgb(153, 31, 31) 100%"
+            ]
+        },
+        {
+            method: "radial-gradient",
+            args: [
+                "75% 19%",
+                " ellipse cover",
+                " rgb(171, 171, 171)",
+                " rgb(0, 0, 255) 33%",
+                " rgb(153, 31, 31) 100%"
+            ]
+        }
+    ];
+
+    [].slice.call(document.querySelectorAll('#backgroundGradients div'), 0).forEach(function(node, i) {
+        var container = new html2canvas.NodeContainer(node, null);
+        var value = container.css("backgroundImage");
+        it(value, function() {
+            var parsedBackground = html2canvas.utils.parseBackgrounds(value);
+            if (parsedBackground[0].args[0] === "0% 50%") {
+                parsedBackground[0].args[0] = 'left';
+            }
+            expect(parsedBackground[0].args).to.eql(expected[i].args);
+            expect(parsedBackground[0].method).to.eql(expected[i].method);
+        });
+    });
+});
diff --git a/tests/mocha/ie9-clonenode-bug.html b/tests/mocha/ie9-clonenode-bug.html
new file mode 100644
index 0000000..7949018
--- /dev/null
+++ b/tests/mocha/ie9-clonenode-bug.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title>Proxy tests</title>
+    <link rel="stylesheet" href="lib/mocha.css" />
+    <script src="../../node_modules/bluebird/js/browser/bluebird.js"></script>
+    <script src="../assets/jquery-1.6.2.js"></script>
+    <script src="lib/expect.js"></script>
+    <script src="lib/mocha.js"></script>
+    <style>
+        #block {
+            background: red;
+        }
+
+        #block.class {
+            background: green;
+        }
+    </style>
+</head>
+<body>
+<div style="width: 200px; height:200px;" id="block"></div>
+<script src="../../dist/html2canvas.js"></script>
+<script src="clone.js"></script>
+<div id="mocha"></div>
+<script>mocha.setup('bdd')</script>
+<script>
+    // https://github.com/niklasvh/html2canvas/issues/503
+    describe("Document clone should not re-execute javascript", function() {
+        it("with mutating className", function (done) {
+            this.timeout(10000);
+            html2canvas(document.querySelector("#block")).then(function (canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function (error) {
+                done(error);
+            });
+        });
+
+    });
+
+    function validCanvasPixels(canvas) {
+        var ctx = canvas.getContext("2d");
+        var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
+        for (var i = 0, len = data.length; i < len; i+=4) {
+            if (data[i] !== 0 || data[i+1] !== 128 || data[i+2] !== 0 || data[i+3] !== 255) {
+                expect().fail("Invalid canvas data");
+            }
+        }
+    }
+
+    mocha.checkLeaks();
+    mocha.globals(['jQuery']);
+    if (window.mochaPhantomJS) {
+        mochaPhantomJS.run();
+    }
+    else {
+        mocha.run();
+    }
+    mocha.suite.afterAll(function() {
+        document.body.setAttribute('data-complete', 'true');
+    });
+</script>
+</body>
+</html>
diff --git a/tests/mocha/iframe1.htm b/tests/mocha/iframe1.htm
new file mode 100644
index 0000000..c45bc8f
--- /dev/null
+++ b/tests/mocha/iframe1.htm
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title></title>
+    <style>
+        html, body {
+            background: green;
+            margin: 0;
+            padding: 0;
+        }
+        .invalid {
+            margin-top: 350px;
+            height: 500px;
+            background: red;
+            display: block;
+        }
+    </style>
+</head>
+<body>
+ <div class="invalid"> </div>
+</body>
+</html>
diff --git a/tests/mocha/iframe2.htm b/tests/mocha/iframe2.htm
new file mode 100644
index 0000000..5655a37
--- /dev/null
+++ b/tests/mocha/iframe2.htm
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title></title>
+    <style>
+
+        html, body {
+            background: red;
+            margin: 0;
+            padding: 0;
+        }
+        .valid {
+            margin-top: 350px;
+            height: 350px;
+            background: green;
+            display: block;
+            margin-bottom: 500px;
+        }
+    </style>
+</head>
+<body><div class="valid"> </div></body>
+</html>
diff --git a/tests/mocha/iframe3.htm b/tests/mocha/iframe3.htm
new file mode 100644
index 0000000..3eda83f
--- /dev/null
+++ b/tests/mocha/iframe3.htm
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title></title>
+    <style>
+
+        html, body {
+            background: red;
+            margin: 0;
+            padding: 0;
+        }
+        .valid {
+            height: 350px;
+            background: green;
+            display: block;
+        }
+    </style>
+</head>
+<body><div class="valid"> </div></body>
+</html>
diff --git a/tests/mocha/multiple-renders.html b/tests/mocha/multiple-renders.html
new file mode 100644
index 0000000..ff30a22
--- /dev/null
+++ b/tests/mocha/multiple-renders.html
@@ -0,0 +1,99 @@
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Mocha Tests</title>
+    <link rel="stylesheet" href="lib/mocha.css" />
+    <script src="../../node_modules/bluebird/js/browser/bluebird.js"></script>
+    <script src="../../dist/html2canvas.js"></script>
+    <script src="../assets/jquery-1.6.2.js"></script>
+    <script src="lib/expect.js"></script>
+    <script src="lib/mocha.js"></script>
+    <style>
+        #block {
+            width: 200px;
+            height: 200px;
+        }
+        #green-block {
+            width: 200px;
+            height: 200px;
+            background: green;
+        }
+    </style>
+</head>
+<body>
+<div id="mocha"></div>
+<div id="block"></div>
+<div id="green-block"></div>
+<script>mocha.setup('bdd')</script>
+<script>
+    describe("Multiple renders", function() {
+        it("render correctly", function(done) {
+            this.timeout(10000);
+            var d = 0;
+            var count = 3;
+            for (var i = 0; i < count; i++) {
+                html2canvas(document.querySelector('#green-block')).then(function (canvas) {
+                    expect(canvas.width).to.equal(200);
+                    expect(canvas.height).to.equal(200);
+                    validCanvasPixels(canvas);
+                    canvas.width = canvas.height = 10;
+                    d++;
+                    if (d === count) {
+                        done();
+                    }
+                });
+            }
+        });
+
+        it("render correctly when non sequential", function(done) {
+            this.timeout(10000);
+            var d = 0;
+            var count = 3;
+            for (var i = 0; i < count; i++) {
+                html2canvas(document.querySelector('#block'), {onclone: function(document) {
+                    return new Promise(function(resolve) {
+                        document.querySelector('#block').style.backgroundColor = 'green';
+                        setTimeout(function() {
+                            resolve();
+                        }, 100);
+                    });
+                }}).then(function (canvas) {
+                    expect(canvas.width).to.equal(200);
+                    expect(canvas.height).to.equal(200);
+                    validCanvasPixels(canvas);
+                    canvas.width = canvas.height = 10;
+                    d++;
+                    if (d === count) {
+                        done();
+                    }
+                });
+            }
+        });
+    });
+
+    function validCanvasPixels(canvas) {
+        var ctx = canvas.getContext("2d");
+        var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
+        for (var i = 0, len = 200*199*4; i < len; i+=4) {
+            if (data[i] !== 0 || data[i+1] !== 128 || data[i+2] !== 0 || data[i+3] !== 255) {
+                console.log(i, data[i], data[i+1], data[i+2], data[i+3]);
+                expect().fail("Invalid canvas data");
+            }
+        }
+    }
+
+    mocha.suite.afterAll(function() {
+        document.body.setAttribute('data-complete', 'true');
+    });
+
+    mocha.checkLeaks();
+    mocha.globals(['jQuery']);
+    if (window.mochaPhantomJS) {
+        mochaPhantomJS.run();
+    }
+    else {
+        mocha.run();
+    }
+</script>
+</body>
+</html>
diff --git a/tests/mocha/options.onclone.html b/tests/mocha/options.onclone.html
new file mode 100644
index 0000000..da1f578
--- /dev/null
+++ b/tests/mocha/options.onclone.html
@@ -0,0 +1,98 @@
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Mocha Tests</title>
+    <link rel="stylesheet" href="lib/mocha.css" />
+    <script src="../../node_modules/bluebird/js/browser/bluebird.js"></script>
+    <script src="../../dist/html2canvas.js"></script>
+    <script src="../assets/jquery-1.6.2.js"></script>
+    <script src="lib/expect.js"></script>
+    <script src="lib/mocha.js"></script>
+    <style>
+        #block2 {
+            background: red;
+        }
+
+        .my-css-class #block2 {
+            background: green;
+        }
+    </style>
+</head>
+<body>
+<div id="mocha"></div>
+<script>mocha.setup('bdd')</script>
+<div style="background: red; width: 200px; height:200px;" id="block"></div>
+<div style="width: 200px; height:200px;" id="block2"></div>
+<script>
+    describe("options.onclone", function() {
+        it("with a function", function(done) {
+            html2canvas(document.querySelector("#block"), {onclone: function(document) {
+                document.querySelector("#block").style.background = "green";
+            }}).then(function(canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                expect(document.querySelector("#block").style.backgroundColor).to.equal("red");
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it("with a promise", function(done) {
+            html2canvas(document.querySelector("#block"), {onclone: function(document) {
+                return new Promise(function(resolve) {
+                    setTimeout(function() {
+                        document.querySelector("#block").style.background = "green";
+                        resolve();
+                    }, 500);
+                });
+            }}).then(function(canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                expect(document.querySelector("#block").style.backgroundColor).to.equal("red");
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+
+        it("add class to node", function(done) {
+            html2canvas(document.querySelector("#block2"), {onclone: function(document) {
+                document.documentElement.className = "my-css-class";
+            }}).then(function(canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function(error) {
+                done(error);
+            });
+        });
+    });
+
+    function validCanvasPixels(canvas) {
+        var ctx = canvas.getContext("2d");
+        var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
+        for (var i = 0, len = data.length; i < len; i+=4) {
+            if (data[i] !== 0 || data[i+1] !== 128 || data[i+2] !== 0 || data[i+3] !== 255) {
+                expect().fail("Invalid canvas data");
+            }
+        }
+    }
+
+    mocha.checkLeaks();
+    mocha.globals(['jQuery']);
+    if (window.mochaPhantomJS) {
+        mochaPhantomJS.run();
+    }
+    else {
+        mocha.run();
+    }
+    mocha.suite.afterAll(function() {
+        document.body.setAttribute('data-complete', 'true');
+    });
+</script>
+</body>
+</html>
diff --git a/tests/mocha/parsing.html b/tests/mocha/parsing.html
new file mode 100644
index 0000000..36c84f4
--- /dev/null
+++ b/tests/mocha/parsing.html
@@ -0,0 +1,235 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>Mocha Tests</title>
+    <link rel="stylesheet" href="lib/mocha.css" />
+    <script src="../../dist/html2canvas.js"></script>
+    <script src="../assets/jquery-1.6.2.js"></script>
+    <script src="lib/expect.js"></script>
+    <script src="lib/mocha.js"></script>
+</head>
+<body>
+<div id="mocha"></div>
+<script>mocha.setup('bdd')</script>
+<div style="visibility: hidden">
+    <div id="borders">Yep, here's some more.
+        <div style="border-width: 1px 0;">Div 1</div>
+        <div style="border-width: 1em 0;">Div 2</div>
+        <div style="border-width: thin medium thick;">Some more divs</div>
+        <div style="border-width: 5% 6px 12%;"></div> <!-- percentages aren't valid -->
+        <div style="border-width: 5em 5ex 5in 5cm;"></div>
+        <div style="border-width: 500em 500ex 500in 500cm;"></div>
+        <div style="border-width: 5mm 5pt 5pc 5px;"></div>
+        <div style="border-width: auto inherit;"></div>
+        <div style="border-width: 500mm 500pt 500pc 500px;"></div>
+    </div>
+
+    <div id="padding">
+        <div style="padding: 1px 0;"></div>
+        <div style="padding: 1em 0;"></div>
+        <div style="padding: thin medium thick;"></div>
+        <div style="padding: 5em 5ex 5in 5cm;"></div>
+        <div style="padding: 500em 500ex 500in 500cm;"></div>
+        <div style="padding: 5mm 5pt 5pc 5px;"></div>
+        <div style="padding: 500mm 500pt 500pc 500px;"></div>
+        <div style="padding: 1px 5%;"></div>
+        <div style="padding: 15% 0 3%;"></div>
+    </div>
+
+    <div id="textShadows">
+        <div style=""></div>
+        <div style="text-shadow: 1px 1px 1px"></div>
+        <div style="text-shadow: 2px 2px rgb(2, 2, 2)"></div>
+        <div style="text-shadow: 3px 3px rgba(2, 2, 2, .2)"></div>
+        <div style="text-shadow: rgb(2, 2, 2) 4px 4px"></div>
+        <div style="text-shadow: rgba(2, 2, 2, .2) 5px 5px"></div>
+        <div style="text-shadow: rgb(2, 2, 2) 6px 6px, #222222 2px 2px"></div>
+        <div style="text-shadow: 7px 7px rgba(2, 2, 2, .2), #222222 2px 2px"></div>
+    </div>
+
+    <div id="backgroundPosition">
+        <div style="background-position: 1px 0;"></div>
+        <div style="background-position: 1em 0;"></div>
+        <div style="background-position: thin medium;"></div>
+        <div style="background-position: 5em 5ex;"></div>
+        <div style="background-position: 5in 5cm;"></div>
+        <div style="background-position: 500in 500cm;"></div>
+        <div style="background-position: 500em 500ex;"></div>
+        <div style="background-position: 5pc 5px;"></div>
+        <div style="background-position: 500pc 500px;"></div>
+        <div style="background-position: 5mm 5pt;"></div>
+        <div style="background-position: 500mm 500pt;"></div>
+    </div>
+
+    <div id="backgroundPositionPercentage">
+        <div style="background-position: 5% 6px;"></div>
+        <div style="background-position: center center;"></div>
+        <div style="background-position: left bottom;"></div>
+    </div>
+
+    <div id="backgroundGradients">
+        <style scoped>
+        .linearGradientSimple {
+            /* FF 3.6+ */
+            background: -moz-linear-gradient(left, #ff0000, #ffff00, #00ff00);
+            /* Chrome,Safari4+ */
+            background: -webkit-gradient(linear, left center, right center, color-stop(#ff0000), color-stop(#ffff00), color-stop(#00ff00));
+            /* Chrome 10+, Safari 5.1+ */
+            background: -webkit-linear-gradient(left, #ff0000, #ffff00, #00ff00);
+            /* Opera 11.10+ */
+            background: -o-linear-gradient(left, #ff0000, #ffff00, #00ff00);
+            /* IE 10+ */
+            background: -ms-linear-gradient(left, #ff0000, #ffff00, #00ff00);
+            /* W3C */
+            background: linear-gradient(left, #ff0000, #ffff00, #00ff00);
+        }
+        .linearGradientSimple2 {
+            /* FF 3.6+ */
+            background: -moz-linear-gradient(left, red, #ff0, #0f0);
+            /* Chrome,Safari4+ */
+            background: -webkit-gradient(linear, left center, right center, color-stop(red), color-stop(#ff0), color-stop(#0f0));
+            /* Chrome 10+, Safari 5.1+ */
+            background: -webkit-linear-gradient(left, red, #ff0, #0f0);
+            /* Opera 11.10+ */
+            background: -o-linear-gradient(left, red, #ff0, #0f0);
+            /* IE 10+ */
+            background: -ms-linear-gradient(left, red, #ff0, #0f0);
+            /* W3C */
+            background: linear-gradient(left, red, #ff0, #0f0);
+        }
+        .linearGradientWithStops {
+            /* FF 3.6+ */
+            background: -moz-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%);
+            /* Chrome, Safari 4+ */
+            background: -webkit-gradient(linear, left center, right center, color-stop(0%, #cedbe9), color-stop(17%, #aac5de), color-stop(50%, #6199c7), color-stop(51%, #3a84c3), color-stop(59%, #419ad6), color-stop(71%, #4bb8f0), color-stop(84%, #3a8bc2), color-stop(100%, #26558b));
+            /* Chrome 10+, Safari 5.1+ */
+            background: -webkit-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%);
+            /* Opera 11.10+ */
+            background: -o-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%);
+            /* IE 10+ */
+            background: -ms-linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%);
+            /* W3C */
+            background: linear-gradient(left, #cedbe9 0%, #aac5de 17%, #6199c7 50%, #3a84c3 51%, #419ad6 59%, #4bb8f0 71%, #3a8bc2 84%, #26558b 100%);
+        }
+        .linearGradientWithPixelLengthStops {
+            width: 100px;
+            height: 100px;
+            /* FF 3.6+ */
+            background: -moz-linear-gradient(left, #cedbe9 0%, #aac5de 17px, #6199c7 50%, #3a84c3 51px, #419ad6 59%, #4bb8f0 71px, #3a8bc2 84%, #26558b 100px);
+            /* Chrome, Safari 4+ */
+            background: -webkit-gradient(linear, left center, right center, color-stop(0%, #cedbe9), color-stop(17px, #aac5de), color-stop(50%, #6199c7), color-stop(51%, #3a84c3), color-stop(59%, #419ad6), color-stop(71px, #4bb8f0), color-stop(84%, #3a8bc2), color-stop(100px, #26558b));
+            /* Chrome 10+, Safari 5.1+ */
+            background: -webkit-linear-gradient(left, #cedbe9 0%, #aac5de 17px, #6199c7 50%, #3a84c3 51px, #419ad6 59%, #4bb8f0 71px, #3a8bc2 84%, #26558b 100px);
+            /* Opera 11.10+ */
+            background: -o-linear-gradient(left, #cedbe9 0%, #aac5de 17px, #6199c7 50%, #3a84c3 51px, #419ad6 59%, #4bb8f0 71px, #3a8bc2 84%, #26558b 100px);
+            /* IE 10+ */
+            background: -ms-linear-gradient(left, #cedbe9 0%, #aac5de 17px, #6199c7 50%, #3a84c3 51px, #419ad6 59%, #4bb8f0 71px, #3a8bc2 84%, #26558b 100px);
+            /* W3C */
+            background: linear-gradient(left, #cedbe9 0%, #aac5de 17px, #6199c7 50%, #3a84c3 51px, #419ad6 59%, #4bb8f0 71px, #3a8bc2 84%, #26558b 100px);
+        }
+        .linearGradient5 {
+            /* FF 3.6+ */
+            background: -moz-linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%);
+            /* Chrome, Safari 4+ */
+            background: -webkit-gradient(linear, center top, center bottom, color-stop(0%, #f0b7a1), color-stop(50%, #8c3310), color-stop(51%, #752201), color-stop(100%, #bf6e4e));
+            /* Opera 11.10+ */
+            background: -o-linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%);
+            /* IE 10+ */
+            background: -ms-linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%);
+            /* W3C */
+            background: linear-gradient(top, #f0b7a1 0%, #8c3310 50%, #752201 51%, #bf6e4e 100%);
+        }
+        .linearGradient6 {
+            /* FF 3.6+ */
+            background: -moz-linear-gradient(top, #F00 0, green 31.4159%, #0000fF 51%, rgba(0, 0, 0, 0.0) 100%);
+            /* Chrome, Safari 4+ */
+            background: -webkit-gradient(linear, center top, center bottom, color-stop(0, #F00), color-stop(31.4159%, green), color-stop(51%, #0000fF), color-stop(100%, rgba(0, 0, 0, 0.0)));
+            /* Opera 11.10+ */
+            background: -o-linear-gradient(top, #F00 0, green 31.4159%, #0000fF 51%, rgba(0, 0, 0, 0.0) 100%);
+            /* IE 10+ */
+            background: -ms-linear-gradient(top, #F00 0, green 31.4159%, #0000fF 51%, rgba(0, 0, 0, 0.0) 100%);
+            /* W3C */
+            background: linear-gradient(top, #F00 0, green 31.4159%, #0000fF 51%, rgba(0, 0, 0, 0.0) 100%);
+        }
+        .linearGradient7 {
+            background: -webkit-linear-gradient(0deg, #ddd, #ddd 50%, transparent 50%);
+            background: linear-gradient(0deg, #ddd, #ddd 50%, transparent 50%);
+        }
+
+        .radialGradient {
+            background: -moz-radial-gradient(75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -webkit-radial-gradient(75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -o-radial-gradient(75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -ms-radial-gradient(75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%);
+            background: radial-gradient(75% 19%, ellipse closest-side, #ababab, #0000ff 33%,#991f1f 100%);
+        }
+
+        .radialGradient2 {
+            background: -moz-radial-gradient(75% 19%, ellipse closest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -webkit-radial-gradient(75% 19%, ellipse closest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -o-radial-gradient(75% 19%, ellipse closest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -ms-radial-gradient(75% 19%, ellipse closest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+            background: radial-gradient(75% 19%, ellipse closest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+        }
+        .radialGradient3 {
+            background: -moz-radial-gradient(75% 19%, ellipse farthest-side, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -webkit-radial-gradient(75% 19%, ellipse farthest-side, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -o-radial-gradient(75% 19%, ellipse farthest-side, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -ms-radial-gradient(75% 19%, ellipse farthest-side, #ababab, #0000ff 33%,#991f1f 100%);
+            background: radial-gradient(75% 19%, ellipse farthest-side, #ababab, #0000ff 33%,#991f1f 100%)
+        }
+        .radialGradient4 {
+            background: -moz-radial-gradient(75% 19%, ellipse farthest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -webkit-radial-gradient(75% 19%, ellipse farthest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -o-radial-gradient(75% 19%, ellipse farthest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -ms-radial-gradient(75% 19%, ellipse farthest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+            background: radial-gradient(75% 19%, ellipse farthest-corner, #ababab, #0000ff 33%,#991f1f 100%);
+        }
+        .radialGradient5 {
+            background: -moz-radial-gradient(75% 19%, ellipse contain, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -webkit-radial-gradient(75% 19%, ellipse contain, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -o-radial-gradient(75% 19%, ellipse contain, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -ms-radial-gradient(75% 19%, ellipse contain, #ababab, #0000ff 33%,#991f1f 100%);
+            background: radial-gradient(75% 19%, ellipse contain, #ababab, #0000ff 33%,#991f1f 100%);
+        }
+        .radialGradient6 {
+            background: -moz-radial-gradient(75% 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -webkit-radial-gradient(75% 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -o-radial-gradient(75% 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+            background: -ms-radial-gradient(75% 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+            background: radial-gradient(75% 19%, ellipse cover, #ababab, #0000ff 33%,#991f1f 100%);
+        }
+        </style>
+        <div class="linearGradientSimple"></div>
+        <div class="linearGradientSimple2"></div>
+        <div class="linearGradientWithStops"></div>
+        <div class="linearGradientWithPixelLengthStops"></div>
+        <div class="linearGradient5"></div>
+        <div class="linearGradient6"></div>
+        <div class="linearGradient7"></div>
+        <div class="radialGradient"></div>
+        <div class="radialGradient2"></div>
+        <div class="radialGradient3"></div>
+        <div class="radialGradient4"></div>
+        <div class="radialGradient5"></div>
+        <div class="radialGradient6"></div>
+    </div>
+</div>
+<script src="css.js"></script>
+<script src="gradients.js"></script>
+<script>
+    mocha.checkLeaks();
+    mocha.globals(['jQuery']);
+    if (window.mochaPhantomJS) {
+        mochaPhantomJS.run();
+    }
+    else {
+        mocha.run();
+    }
+    mocha.suite.afterAll(function() {
+        document.body.setAttribute('data-complete', 'true');
+    });
+</script>
+</body>
+</html>
diff --git a/tests/mocha/proxy.htm b/tests/mocha/proxy.htm
new file mode 100644
index 0000000..3494e90
--- /dev/null
+++ b/tests/mocha/proxy.htm
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title>Proxy tests</title>
+    <link rel="stylesheet" href="lib/mocha.css" />
+    <script src="../../dist/html2canvas.js"></script>
+    <script src="../assets/jquery-1.6.2.js"></script>
+    <script src="lib/expect.js"></script>
+    <script src="lib/mocha.js"></script>
+    <style>
+        #content {
+            width: 200px;
+            height: 200px;
+        }
+    </style>
+</head>
+<body>
+<div id="mocha"></div>
+<script>mocha.setup('bdd')</script>
+<div id="content" style="display: inline-block;background: red;">
+    <iframe src="http://localhost:8083/tests/mocha/iframe3.htm" style="border: 0; width: 200px; height: 200px;" scrolling="no"></iframe>
+</div>
+<script>
+    describe("Proxy", function() {
+        it("with iframe through proxy", function (done) {
+            this.timeout(10000);
+            html2canvas(document.querySelector("#content"), {proxy: 'http://localhost:8082'}).then(function (canvas) {
+                validCanvasPixels(canvas);
+                done();
+            }).catch(function (error) {
+                done(error);
+            });
+        });
+
+        it("with iframe without proxy", function (done) {
+            html2canvas(document.querySelector("#content")).then(function (canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                done();
+            }).catch(function (error) {
+                done(error);
+            });
+        });
+
+        it("with url using proxy", function (done) {
+            html2canvas("http://localhost:8083/tests/mocha/iframe3.htm", {proxy: 'http://localhost:8082', width: 200, height: 200, type: 'view'}).then(function (canvas) {
+                expect(canvas.width).to.equal(200);
+                expect(canvas.height).to.equal(200);
+                done();
+            }).catch(function (error) {
+                done(error);
+            });
+        });
+
+        it("with url without proxy", function (done) {
+            html2canvas("http://localhost:8083/tests/mocha/iframe3.htm").then(function () {
+                done("Should throw error");
+            }).catch(function (error) {
+                expect(error).to.equal("Proxy must be used when rendering url");
+                done();
+            });
+        });
+    });
+
+    function validCanvasPixels(canvas) {
+        var ctx = canvas.getContext("2d");
+        var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
+        for (var i = 0, len = data.length; i < len; i+=4) {
+            if (data[i] !== 0 || data[i+1] !== 128 || data[i+2] !== 0 || data[i+3] !== 255) {
+                expect().fail("Invalid canvas data");
+            }
+        }
+    }
+
+    mocha.checkLeaks();
+    mocha.globals(['jQuery']);
+    if (window.mochaPhantomJS) {
+        mochaPhantomJS.run();
+    }
+    else {
+        mocha.run();
+    }
+    mocha.suite.afterAll(function() {
+        document.body.setAttribute('data-complete', 'true');
+    });
+</script>
+</body>
+</html>
diff --git a/tests/mocha/scrolling.html b/tests/mocha/scrolling.html
new file mode 100644
index 0000000..ced49c7
--- /dev/null
+++ b/tests/mocha/scrolling.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<html>
+<head lang="en">
+    <meta charset="UTF-8">
+    <title>Scrolling tests</title>
+    <link rel="stylesheet" href="lib/mocha.css" />
+    <script src="../../node_modules/bluebird/js/browser/bluebird.js"></script>
+    <script src="../../dist/html2canvas.js"></script>
+    <script src="../assets/jquery-1.6.2.js"></script>
+    <script src="lib/expect.js"></script>
+    <script src="lib/mocha.js"></script>
+</head>
+<body>
+<div id="mocha"></div>
+<script>mocha.setup('bdd')</script>
+<div id="scroll-render" style="height: 200px; width: 200px;">
+    <div style="height: 500px; width: 400px;overflow: scroll;" id="scrollable">
+        <div style="height: 500px;background: red;"></div>
+        <div style="height: 650px; background: green"></div>
+    </div>
+</div>
+<div style="height: 2200px"></div>
+<div style="height: 500px;background: green;" id="bottom-content"><a name="content"> </a></div>
+<script>
+    describe("Scrolling", function() {
+        it("with random scroll", function (done) {
+            $(window).scrollTop(123);
+            setTimeout(function() {
+                html2canvas(document.body, {type: 'view'}).then(function () {
+                    expect($(window).scrollTop()).to.equal(123);
+                    done();
+                }).catch(function (error) {
+                    done(error);
+                });
+            }, 0);
+        });
+
+        it("with url anchor", function (done) {
+            window.location.hash = "#content";
+            setTimeout(function() {
+                var top = $(window).scrollTop();
+                html2canvas(document.body, {type: 'view'}).then(function () {
+                    var currentTop = $(window).scrollTop();
+                    window.location.hash = "";
+                    expect(currentTop).to.be.greaterThan(1500);
+                    if ((currentTop - top) !== 200) { // phantomjs issue
+                        expect(currentTop).to.equal(top);
+                    }
+                    done();
+                }).catch(function (error) {
+                    done(error);
+                });
+            }, 0);
+        });
+
+        it("with content scroll", function (done) {
+            $("#scrollable").scrollTop(500);
+            setTimeout(function() {
+                html2canvas(document.querySelector("#scroll-render")).then(function (canvas) {
+                    expect($("#scrollable").scrollTop()).to.equal(500);
+                    expect(canvas.width).to.equal(200);
+                    expect(canvas.height).to.equal(200);
+                    validCanvasPixels(canvas);
+                    done();
+                }).catch(function (error) {
+                    done(error);
+                });
+            }, 0);
+        });
+
+
+        it("with window scroll", function (done) {
+            $(window).scrollTop(500);
+            setTimeout(function() {
+                console.log(document.querySelector("#bottom-content").getBoundingClientRect().top);
+                html2canvas(document.querySelector("#bottom-content")).then(function (canvas) {
+                    expect($(window).scrollTop()).to.equal(500);
+                    validCanvasPixels(canvas);
+                    done();
+                }).catch(function (error) {
+                    done(error);
+                });
+            }, 0);
+        });
+    });
+
+    function validCanvasPixels(canvas) {
+        var ctx = canvas.getContext("2d");
+        var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
+        for (var i = 0, len = 200*199*4; i < len; i+=4) {
+            if (data[i] !== 0 || data[i+1] !== 128 || data[i+2] !== 0 || data[i+3] !== 255) {
+                console.log(i, data[i], data[i+1], data[i+2], data[i+3]);
+                expect().fail("Invalid canvas data");
+            }
+        }
+    }
+
+    after(function() {
+        if (history) {
+            history.pushState("", document.title, window.location.pathname + window.location.search);
+        }
+    });
+
+    mocha.checkLeaks();
+    mocha.globals(['jQuery']);
+    if (window.mochaPhantomJS) {
+        mochaPhantomJS.run();
+    }
+    else {
+        mocha.run();
+    }
+    mocha.suite.afterAll(function() {
+        document.body.setAttribute('data-complete', 'true');
+    });
+</script>
+</body>
+</html>
diff --git a/tests/mocha/selenium.js b/tests/mocha/selenium.js
new file mode 100644
index 0000000..79e152f
--- /dev/null
+++ b/tests/mocha/selenium.js
@@ -0,0 +1,73 @@
+var wd = require('wd');
+var http = require("http");
+var https = require("https");
+var url = require("url");
+var path = require("path");
+var Promise = require('bluebird');
+var _ = require('lodash');
+var humanizeDuration = require("humanize-duration");
+var utils = require('../utils');
+var colors = utils.colors;
+var port = 8080;
+
+function runTestWithRetries(browser, test, retries) {
+    retries = retries || 0;
+    return runTest(browser, test)
+        .timeout(30000)
+        .catch(Promise.TimeoutError, function() {
+            if (retries < 3) {
+                console.log(colors.violet, "Retry", (retries + 1), test);
+                return runTestWithRetries(browser, test, retries + 1);
+            } else {
+                throw new Error("Couldn't run test after 3 retries");
+            }
+        });
+}
+
+function getResults(browser) {
+    return function() {
+        return Promise.props({
+            dataUrl: browser.waitForElementByCss("body[data-complete='true']", 90000).then(function() {
+                return browser.elementsByCssSelector('.test.fail');
+            }).then(function(nodes) {
+                return Array.isArray(nodes) ? Promise.map(nodes, function(node) {
+                    return browser.text(node).then(function(error) {
+                        return Promise.reject(error);
+                    });
+                }) : Promise.resolve([]);
+            })
+        });
+    };
+}
+
+function runTest(browser, test) {
+    return Promise.resolve(browser
+        .then(utils.loadTestPage(browser, test, port))
+        .then(getResults(browser))
+    ).cancellable();
+}
+
+exports.tests = function(browsers, singleTest) {
+    var path = "tests/mocha";
+    return (singleTest ? Promise.resolve([singleTest]) : utils.getTests(path)).then(function(tests) {
+        return Promise.map(browsers, function(settings) {
+            var name = [settings.browserName, settings.version, settings.platform].join("-");
+            var count = 0;
+            var browser = utils.initBrowser(settings);
+            return Promise.using(browser, function() {
+                return Promise.map(tests, function(test, index, total) {
+                    console.log(colors.green, "STARTING", "(" + (++count) + "/" + total + ")", name, test, colors.clear);
+                    var start = Date.now();
+                    return runTestWithRetries(browser, test).then(function() {
+                        console.log(colors.green, "COMPLETE", humanizeDuration(Date.now() - start), "(" + count + "/" + total + ")", name, colors.clear);
+                    });
+                }, {concurrency: 1})
+                    .settle()
+                    .catch(function(error) {
+                        console.error(colors.red, "ERROR", name, error);
+                        throw error;
+                    });
+            });
+        }, {concurrency: 3});
+    });
+};
diff --git a/tests/node/color.js b/tests/node/color.js
new file mode 100644
index 0000000..25e1bac
--- /dev/null
+++ b/tests/node/color.js
@@ -0,0 +1,119 @@
+var Color = require('../../src/color');
+var assert = require('assert');
+
+describe("Colors", function() {
+    describe("named colors", function() {
+        it("bisque", function () {
+            var c = new Color("bisque");
+            assertColor(c, 255, 228, 196, null);
+            assert.equal(c.isTransparent(), false);
+        });
+
+        it("BLUE", function () {
+            var c = new Color("BLUE");
+            assertColor(c, 0, 0, 255, null);
+            assert.equal(c.isTransparent(), false);
+        });
+    });
+
+    describe("rgb()", function() {
+        it("rgb(1,3,5)", function () {
+            var c = new Color("rgb(1,3,5)");
+            assertColor(c, 1, 3, 5, null);
+            assert.equal(c.isTransparent(), false);
+        });
+
+        it("rgb(222, 111, 50)", function () {
+            var c = new Color("rgb(222, 111, 50)");
+            assertColor(c, 222, 111, 50, null);
+            assert.equal(c.isTransparent(), false);
+        });
+
+        it("rgb( 222, 111 , 50)", function () {
+            var c = new Color("rgb(222 , 111 , 50)");
+            assertColor(c, 222, 111, 50, null);
+            assert.equal(c.isTransparent(), false);
+        });
+    });
+
+    describe("rgba()", function() {
+        it("rgba(200,3,5,1)", function () {
+            var c = new Color("rgba(200,3,5,1)");
+            assertColor(c, 200, 3, 5, 1);
+            assert.equal(c.isTransparent(), false);
+        });
+
+        it("rgba(222, 111, 50, 0.22)", function () {
+            var c = new Color("rgba(222, 111, 50, 0.22)");
+            assertColor(c, 222, 111, 50, 0.22);
+            assert.equal(c.isTransparent(), false);
+        });
+
+        it("rgba( 222, 111 , 50, 0.123 )", function () {
+            var c = new Color("rgba(222 , 111 , 50, 0.123)");
+            assertColor(c, 222, 111, 50, 0.123);
+            assert.equal(c.isTransparent(), false);
+        });
+    });
+
+    describe("hex", function() {
+        it("#7FFFD4", function () {
+            var c = new Color("#7FFFD4");
+            assertColor(c, 127, 255, 212, null);
+            assert.equal(c.isTransparent(), false);
+        });
+
+        it("#f0ffff", function () {
+            var c = new Color("#f0ffff");
+            assertColor(c, 240, 255, 255, null);
+            assert.equal(c.isTransparent(), false);
+        });
+
+        it("#fff", function () {
+            var c = new Color("#fff");
+            assertColor(c, 255, 255, 255, null);
+            assert.equal(c.isTransparent(), false);
+        });
+    });
+
+    describe("from array", function() {
+        it("[1,2,3]", function () {
+            var c = new Color([1,2,3]);
+            assertColor(c, 1, 2, 3, null);
+            assert.equal(c.isTransparent(), false);
+        });
+
+        it("[5,6,7,1]", function () {
+            var c = new Color([5,6,7, 1]);
+            assertColor(c, 5, 6, 7, 1);
+            assert.equal(c.isTransparent(), false);
+        });
+
+        it("[5,6,7,0]", function () {
+            var c = new Color([5,6,7, 0]);
+            assertColor(c, 5, 6, 7, 0);
+            assert.equal(c.isTransparent(), true);
+        });
+    });
+
+    describe("transparency", function() {
+        it("transparent", function () {
+            var c = new Color("transparent");
+            assertColor(c, 0, 0, 0, 0);
+            assert.equal(c.isTransparent(), true);
+        });
+
+        it("rgba(255,255,255,0)", function () {
+            var c = new Color("rgba(255,255,255,0)");
+            assertColor(c, 255, 255, 255, 0);
+            assert.equal(c.isTransparent(), true);
+        });
+    });
+});
+
+function assertColor(c, r, g, b, a) {
+    assert.equal(c.r, r);
+    assert.equal(c.g, g);
+    assert.equal(c.b, b);
+    assert.equal(c.a, a);
+}
diff --git a/tests/node/package.js b/tests/node/package.js
new file mode 100644
index 0000000..3e71442
--- /dev/null
+++ b/tests/node/package.js
@@ -0,0 +1,24 @@
+var assert = require('assert');
+var path = require('path');
+var html2canvas = require('../../');
+
+describe("Package", function() {
+    it("should have html2canvas defined", function() {
+        assert.equal(typeof(html2canvas), "function");
+    });
+});
+
+describe.only("requirejs", function() {
+    var requirejs = require('requirejs');
+
+    requirejs.config({
+        baseUrl: path.resolve(__dirname, '../../dist')
+    });
+
+    it("should have html2canvas defined", function(done) {
+        requirejs(['html2canvas'], function(h2c) {
+            assert.equal(typeof(h2c), "function");
+            done();
+        });
+    });
+});
diff --git a/tests/rangetest.html b/tests/rangetest.html
new file mode 100644
index 0000000..bbd3308
--- /dev/null
+++ b/tests/rangetest.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Range tests</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <style>
+      #tests {
+        font-family:Arial;
+      }
+      </style>
+  </head>
+  <body>
+    <div id="tests"></div>
+    <script>
+      var div = document.getElementById("tests"), item, range, textNode, bounds;
+
+      for (var i = 6; i < 72; i++) {
+        item = document.createElement("div");
+        item.style.fontSize = i + "px";
+        item.style.lineHeight = (10 + i * 2) + "px";
+        textNode = document.createTextNode(i);
+        item.appendChild(textNode);
+        div.appendChild(item);
+
+        range = document.createRange();
+        range.setStart(textNode, 0);
+        range.setEnd(textNode, i.toString().length);
+        bounds = range.getBoundingClientRect();
+
+        textNode.nodeValue += " " + bounds.height + " " + bounds.bottom + (i % 3 === 0);
+
+      }
+
+    </script>
+    <script src="test.js"></script>
+  </body>
+</html>
diff --git a/tests/results/.gitignore b/tests/results/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/tests/results/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
\ No newline at end of file
diff --git a/tests/sauceconnect.js b/tests/sauceconnect.js
new file mode 100644
index 0000000..9000689
--- /dev/null
+++ b/tests/sauceconnect.js
@@ -0,0 +1,13 @@
+const sauceConnectLauncher = require('sauce-connect-launcher');
+
+sauceConnectLauncher({
+    username: process.env.SAUCE_USERNAME,
+    accessKey: process.env.SAUCE_ACCESS_KEY,
+    logger: console.log
+}, err => {
+    if (err) {
+        console.error(err.message);
+        return;
+    }
+    console.log('Sauce Connect ready');
+});
diff --git a/tests/selenium.js b/tests/selenium.js
new file mode 100644
index 0000000..cc4440a
--- /dev/null
+++ b/tests/selenium.js
@@ -0,0 +1,114 @@
+(function(){
+    "use strict;";
+    var wd = require('wd'),
+        http = require("http"),
+        https = require("https"),
+        url = require("url"),
+        path = require("path"),
+        base64_arraybuffer = require('base64-arraybuffer'),
+        PNG = require('pngjs').PNG,
+        Promise = require('bluebird'),
+        _ = require('lodash'),
+        humanizeDuration = require("humanize-duration"),
+        fs = require("fs");
+
+    var utils = require('./utils');
+    var colors = utils.colors;
+
+    Promise.promisifyAll(fs);
+
+    var port = 8080;
+
+    function getPixelArray(base64) {
+        return new Promise(function(resolve, reject) {
+            const arraybuffer = base64_arraybuffer.decode(base64);
+            new PNG().parse(arraybuffer, (error, data) => {
+                if (error) {
+                    reject(error);
+                } else {
+                    resolve(data.data);
+                }
+            });
+        });
+    }
+
+    function calculateDifference(h2cPixels, screenPixels) {
+        var len = h2cPixels.length, index = 0, diff = 0;
+        for (; index < len; index++) {
+            if (screenPixels[index] - h2cPixels[index] !== 0) {
+                diff++;
+            }
+        }
+        return (100 - (Math.round((diff/h2cPixels.length) * 10000) / 100));
+    }
+
+    function captureScreenshots(browser) {
+        return function() {
+            return Promise.props({
+                dataUrl: browser.waitForElementByCss(".html2canvas", 15000).then(function(node) {
+                    return browser.execute("return arguments[0].toDataURL('image/png').substring(22)", [node]);
+                }),
+                screenshot: browser.takeScreenshot()
+            });
+        };
+    }
+
+    function analyzeResults(test) {
+        return function(result) {
+            return Promise.all([getPixelArray(result.dataUrl), getPixelArray(result.screenshot)]).spread(calculateDifference).then(function(accuracy) {
+                return {
+                    testCase: test,
+                    accuracy: accuracy,
+                    dataUrl: result.dataUrl,
+                    screenshot: result.screenshot
+                };
+            });
+        };
+    }
+
+    function runTestWithRetries(browser, test, retries) {
+        retries = retries || 0;
+        return runTest(browser, test)
+            .timeout(60000)
+            .catch(Promise.TimeoutError, function() {
+                if (retries < 3) {
+                    console.log(colors.violet, "Retry", (retries + 1), test);
+                    return runTestWithRetries(browser, test, retries + 1);
+                } else {
+                    throw new Error("Couldn't run test after 3 retries");
+                }
+            });
+    }
+
+    function runTest(browser, test) {
+        return Promise.resolve(browser
+            .then(utils.loadTestPage(browser, test, port))
+            .then(captureScreenshots(browser))
+            .then(analyzeResults(test)));
+    }
+
+    exports.tests = function(browsers, singleTest) {
+        var path = "tests/cases";
+        return (singleTest ? Promise.resolve([singleTest]) : utils.getTests(path)).then(function(tests) {
+            return Promise.map(browsers, function(settings) {
+                var browser = utils.initBrowser(settings);
+                var name = [settings.browserName, settings.version, settings.platform].join("-");
+                var count = 0;
+                return Promise.using(browser, function() {
+                    return Promise.map(tests, function(test, index, total) {
+                        console.log(colors.green, "STARTING", "(" + (++count) + "/" + total + ")", name, test, colors.clear);
+                        var start = Date.now();
+                        return runTestWithRetries(browser, test).then(function(result) {
+                            console.log(colors.green, "COMPLETE", humanizeDuration(Date.now() - start), "(" + count + "/" + total + ")", name, result.testCase.substring(path.length), result.accuracy.toFixed(2) + "%", colors.clear);
+                        });
+                    }, {concurrency: 1})
+                        .settle()
+                        .catch(function(error) {
+                            console.log(colors.red, "ERROR", name, error.message);
+                            throw error;
+                        });
+                });
+            }, {concurrency: 3});
+        });
+    };
+})();
diff --git a/tests/test.js b/tests/test.js
new file mode 100644
index 0000000..2312b32
--- /dev/null
+++ b/tests/test.js
@@ -0,0 +1,122 @@
+var h2cSelector, h2cOptions;
+(function(document, window) {
+    function appendScript(src) {
+        document.write('<script type="text/javascript" src="' + src + '.js?' + Math.random() + '"></script>');
+    }
+
+    ['/node_modules/bluebird/js/browser/bluebird', '/tests/assets/jquery-1.6.2', '/dist/html2canvas'].forEach(appendScript);
+
+    if (typeof(noFabric) === "undefined") {
+        appendScript('/dist/html2canvas.svg');
+    }
+
+    window.onload = function() {
+        (function( $ ){
+            $.fn.html2canvas = function(options) {
+                if (options && options.profile && window.console && window.console.profile && window.location.search !== "?selenium2") {
+                    window.console.profile();
+                }
+                var date = new Date(),
+                    $message = null,
+                    timeoutTimer = false,
+                    timer = date.getTime();
+                options = options || {};
+
+                var promise = html2canvas(this, options);
+                promise['catch'](function(err) {
+                    console.log("html2canvas threw an error", err);
+                });
+
+                promise.then(function(canvas) {
+                    var $canvas = $(canvas),
+                        finishTime = new Date();
+
+                    if (options && options.profile && window.console && window.console.profileEnd) {
+                        window.console.profileEnd();
+                    }
+                    $canvas.addClass("html2canvas")
+                        .css({
+                            position: 'absolute',
+                            left: 0,
+                            top: 0
+                        }).appendTo(document.body);
+
+                    if (window.location.search !== "?selenium") {
+                        $canvas.siblings().toggle();
+                        $(window).click(function(){
+                            $canvas.toggle().siblings().toggle();
+                            $(document.documentElement).css('background', $canvas.is(':visible') ? "none" : "");
+                            $(document.body).css('background', $canvas.is(':visible') ? "none" : "");
+                            throwMessage("Canvas Render " + ($canvas.is(':visible') ? "visible" : "hidden"));
+                        });
+                        $(document.documentElement).css('background', $canvas.is(':visible') ? "none" : "");
+                        $(document.body).css('background', $canvas.is(':visible') ? "none" : "");
+                        throwMessage('Screenshot created in '+ ((finishTime.getTime()-timer)) + " ms<br />",4000);
+                    } else {
+                        $canvas.css('display', 'none');
+                    }
+                    // test if canvas is read-able
+                    try {
+                        $canvas[0].toDataURL();
+                    } catch(e) {
+                        if ($canvas[0].nodeName.toLowerCase() === "canvas") {
+                            // TODO, maybe add a bit less offensive way to present this, but still something that can easily be noticed
+                            window.alert("Canvas is tainted, unable to read data");
+                        }
+                    }
+                });
+
+                function throwMessage(msg,duration){
+                    window.clearTimeout(timeoutTimer);
+                    timeoutTimer = window.setTimeout(function(){
+                        $message.fadeOut(function(){
+                            $message.remove();
+                            $message = null;
+                        });
+                    },duration || 2000);
+                    if ($message)
+                        $message.remove();
+                    $message = $('<div />').html(msg).css({
+                        margin:0,
+                        padding:10,
+                        background: "#000",
+                        opacity:0.7,
+                        position:"fixed",
+                        top:10,
+                        right:10,
+                        fontFamily: 'Tahoma',
+                        color:'#fff',
+                        fontSize:12,
+                        borderRadius:12,
+                        width:'auto',
+                        height:'auto',
+                        textAlign:'center',
+                        textDecoration:'none',
+                        display:'none'
+                    }).appendTo(document.body).fadeIn();
+                    console.log(msg);
+                }
+            };
+        })(jQuery);
+
+        h2cSelector = [document.documentElement];
+
+        if (window.setUp) {
+            window.setUp();
+        }
+
+        window.run = function() {
+            $(h2cSelector).html2canvas($.extend({
+                logging: true,
+                profile: true,
+                proxy: "http://localhost:8082",
+                useCORS: false,
+                removeContainer: false
+            }, h2cOptions));
+        };
+
+        if (typeof(dontRun) === "undefined") {
+            setTimeout(window.run, 100);
+        }
+    };
+}(document, window));
diff --git a/tests/utils.js b/tests/utils.js
new file mode 100644
index 0000000..db1e3e5
--- /dev/null
+++ b/tests/utils.js
@@ -0,0 +1,66 @@
+var fs = require('fs');
+var wd = require('wd');
+var path = require('path');
+var Promise = require('bluebird');
+var _ = require('lodash');
+
+Promise.promisifyAll(fs);
+
+var colors = {
+    red: "\x1b[1;31m",
+    blue: "\x1b[1;36m",
+    violet: "\x1b[0;35m",
+    green: "\x1b[0;32m",
+    clear: "\x1b[0m"
+};
+
+function isHtmlFile(filename) {
+    return path.extname(filename) === '.html';
+}
+
+function getTests(path) {
+    return fs.readdirAsync(path).map(function(name) {
+        var filename = path + "/" + name;
+        return fs.statAsync(filename).then(function(stat) {
+            return stat.isDirectory() ? getTests(filename) : filename;
+        });
+    }).then(function(t) {
+        return _.flatten(t).filter(isHtmlFile);
+    });
+}
+
+function initBrowser(settings) {
+    var browser = wd.remote({
+        hostname: 'localhost',
+        port: 4445,
+        user: process.env.SAUCE_USERNAME,
+        pwd: process.env.SAUCE_ACCESS_KEY
+    }, 'promiseChain');
+
+    if (process.env.TRAVIS_JOB_NUMBER) {
+        settings["tunnel-identifier"] = process.env.TRAVIS_JOB_NUMBER;
+        settings["name"] = process.env.TRAVIS_COMMIT.substring(0, 10);
+        settings["build"] = process.env.TRAVIS_BUILD_NUMBER;
+    } else {
+        settings["name"] = "Manual run";
+    }
+
+    return browser.resolve(Promise).init(settings).then(function(b) {
+        return Promise.resolve(b).disposer(function() {
+            return browser.quit();
+        });
+    });
+}
+
+function loadTestPage(browser, test, port) {
+    return function(settings) {
+        return browser.get("http://localhost:" + port + "/" + test + "?selenium").then(function() {
+            return settings;
+        });
+    };
+}
+
+module.exports.colors = colors;
+module.exports.getTests = getTests;
+module.exports.initBrowser = initBrowser;
+module.exports.loadTestPage = loadTestPage;

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/html2canvas.git



More information about the Pkg-javascript-commits mailing list